Using Ethereum Package Manager

Install other packages with Ethereum Package Manager

Instead of copying-pasting the source code, the abi, the bytecode from other people's smart contract code, we can utilize Ethereum Package Manager (EPM). Because of some design issue in web3.py library, we must use Infura to use EPM. So set Infura API's project ID and project secret in settings.py.



infura_settings = {
    "project_id": "FILL_IT_WITH_PROJECT_ID",
    "project_secret": "FILL_IT_WITH_PROJECT_SECRET",
}

We can install a package from GitHub or EPM registry. Let's install Owned package from GitHub.


(.venv) $ mamba epm --epm_mode install --epm_uri https://api.github.com/repos/ethereum/web3.py/git/blobs/a7232a93f1e9e75d606f6c1da18aa16037e03480
(.venv) $ ls ethpm_packages
owned
(.venv) $ ls ethpm_packages/owned/1.0.1
manifest.json

The installed package is in json format and is stored inside ethpm_packages directory. You can take a look at the manifest.json file if you are curious.

You can also install it from EPM registry. Notice the protocol is ethpm://.


(.venv) $ mamba epm --epm_mode install --epm_uri ethpm://maker.snakecharmers.eth:1/sai@1.0.0
(.venv) $ ls ethpm_packages
owned	sai

You can delete and list the packages also with Mamba CLI. But let's use this sai package in our Ganache. I mean, let's use the code from sai so we can play around with sai code in our blockchain development environment. Create a Python script. You can put it in decentralized_app directory. Name it playing_with_epm.py. Run your Ganache application and make sure you have correct setting in settings.py.


from black_mamba.epm.package_manager import PackageManager

pm = PackageManager()
factory = pm.load("DSToken", "sai")

OWNER_ADDRESS = "0x49DFf23da6518ad602f6a4d261f6A41E7FdF7ec6"
param = {"from": OWNER_ADDRESS}
tx_hash = factory.constructor(b"my_sai").transact(param)
tx_receipt = factory.web3.eth.waitForTransactionReceipt(tx_hash)

contract_address = tx_receipt["contractAddress"]
abi = pm.get_abi("DSToken", "sai")

instance = factory.web3.eth.contract(address=contract_address, abi=abi)
symbol = instance.functions.symbol().call()

print(symbol)

A package like sai has many contracts, like DSToken, DSAuth, etc. We can load the smart contract factory with load method from PackageManager object. Then you can deploy this smart contract into your Ganache. Factory object has constructor method and web3 property. To interact with the deployed smart contract, you need the address and abi. You can get the abi with get_abi method from PackageManager object. Run the script and you would get this output:


b'my_sai\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

What if we want to create a package so later we can publish it to registry? We can do that as well with Mamba. Say, you have a contract HelloWorld.vy in contracts directory which looks like this:


"""
@title A greeting program
@author Arjuna Sky Kok
@notice You can use this contract for greeting
@dev Greetings must be done in a polite way
"""
greeting: bytes[20]

@public
def __init__():
    self.greeting = "Hello World"

@public
def setGreeting(x: bytes[20]):
    """
    @notice Set the greeting
    @dev Set the greeting no less than 20 bytes
    @param x The greeting itself
    """
    self.greeting = x

@public
def greet() -> bytes[20]:
    """
    @notice Return the greeting
    @dev Return the greeting which is 20 bytes
    @return The greeting itself
    """
    return self.greeting

Compile it.


(.venv) $ mamba compile

You would have HelloWorld.json in build/contracts directory. This is the output of your compilation process. It has bytecode, runtime bytecode, compiler's information, etc. We need all of these to create a manifest. Manifest is like a wheel file (.whl) in Python ecosystem or a deb file in Debian/Ubuntu system or a rpm file in Red Hat / Fedora system. Manifest is a json file. It is our smart contract package. To create a manifest file of your HelloWorld.json file, type this command:


(.venv) $ mamba epm --epm_mode create
Please answer these questions for package and version!
Package name: helloworld
Version: 1.0.0
Please answer these questions for meta!
Authors (separate them by comma), e.g. Arjuna <arjuna@mamba.black>: Arjuna <arjuna@mamba.black>
Description: Smart contract to say hello world.
Keywords (separate them by comma): vyper, hello, basic
License: MIT
Documentation URL: https://mamba.black/documentation
Repository: https://github.com/arjunaskykok/mamba
Website: https://mamba.black

As you can see, you have to answer some questions about the name, license, description, etc. Your manifest file is saved in ethpm_build/manifest.json.

Before we can publish this manifest file to registry, we need to get the content addressable URI for this file. Content addressable URI is a way to retrieve the information based on content, not the location of the file. Say, we have hello_world.txt which has the content of this string: "Hello world!". We save the hello_world.txt file in something directory. Then the location of the file is something/hello_world.txt. But the content addressable URI makes possible to get the file based on the content which is "Hello World!". We don't store content just like that, but we usually hash it with specific algorithm. There are two ways to do this. We can store it in IPFS or GitHub.

If we stored it in GitHub, we could get the content addressable URI by making request to GitHub API. Say, you put you manifest.json in https://github.com/arjunaskykok/mamba/blob/master/black_mamba/constants.py. Then you curl it by using this command:


$ curl https://api.github.com/repos/arjunaskykok/mamba/contents/black_mamba/constants.py
{
  "name": "constants.py",
  "path": "black_mamba/constants.py",
  "sha": "d0ef0c784aa8311209e3b1dc9a3bdd9b3605dbd0",
  "size": 307,
  "url": "https://api.github.com/repos/arjunaskykok/mamba/contents/black_mamba/constants.py?ref=master",
  "html_url": "https://github.com/arjunaskykok/mamba/blob/master/black_mamba/constants.py",
  "git_url": "https://api.github.com/repos/arjunaskykok/mamba/git/blobs/d0ef0c784aa8311209e3b1dc9a3bdd9b3605dbd0",
  "download_url": "https://raw.githubusercontent.com/arjunaskykok/mamba/master/black_mamba/constants.py",
  "type": "file",
  "content": "ZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCgoKRVRIRVJFVU1fUEFDS0FHRVNf\nRElSID0gImV0aHBtX3BhY2thZ2VzIgpTTUFSVF9DT05UUkFDVF9CVUlMRF9E\nSVIgPSBQYXRoKCIuIikgLyBQYXRoKCJidWlsZCIpIC8gUGF0aCgiY29udHJh\nY3RzIikKU01BUlRfQ09OVFJBQ1RfU09VUkNFX0RJUiA9IFBhdGgoIi4iKSAv\nIFBhdGgoImNvbnRyYWN0cyIpCkVQTV9CVUlMRF9ESVIgPSBQYXRoKCIuIikg\nLyBQYXRoKCJldGhwbV9idWlsZCIpCk1BTklGRVNUX0JVSUxEX0ZJTEUgPSBF\nUE1fQlVJTERfRElSIC8gUGF0aCgibWFuaWZlc3QuanNvbiIpCg==\n",
  "encoding": "base64",
  "_links": {
    "self": "https://api.github.com/repos/arjunaskykok/mamba/contents/black_mamba/constants.py?ref=master",
    "git": "https://api.github.com/repos/arjunaskykok/mamba/git/blobs/d0ef0c784aa8311209e3b1dc9a3bdd9b3605dbd0",
    "html": "https://github.com/arjunaskykok/mamba/blob/master/black_mamba/constants.py"
  }
}

Your content addressable URI of the file in GitHub is the one with key git, which is https://api.github.com/repos/arjunaskykok/mamba/git/blobs/d0ef0c784aa8311209e3b1dc9a3bdd9b3605dbd0. If you opened this URL, you would get this JSON file.


{
  "sha": "d0ef0c784aa8311209e3b1dc9a3bdd9b3605dbd0",
  "node_id": "MDQ6QmxvYjE5MDU3NTczMjpkMGVmMGM3ODRhYTgzMTEyMDllM2IxZGM5YTNiZGQ5YjM2MDVkYmQw",
  "size": 307,
  "url": "https://api.github.com/repos/arjunaskykok/mamba/git/blobs/d0ef0c784aa8311209e3b1dc9a3bdd9b3605dbd0",
  "content": "ZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCgoKRVRIRVJFVU1fUEFDS0FHRVNf\nRElSID0gImV0aHBtX3BhY2thZ2VzIgpTTUFSVF9DT05UUkFDVF9CVUlMRF9E\nSVIgPSBQYXRoKCIuIikgLyBQYXRoKCJidWlsZCIpIC8gUGF0aCgiY29udHJh\nY3RzIikKU01BUlRfQ09OVFJBQ1RfU09VUkNFX0RJUiA9IFBhdGgoIi4iKSAv\nIFBhdGgoImNvbnRyYWN0cyIpCkVQTV9CVUlMRF9ESVIgPSBQYXRoKCIuIikg\nLyBQYXRoKCJldGhwbV9idWlsZCIpCk1BTklGRVNUX0JVSUxEX0ZJTEUgPSBF\nUE1fQlVJTERfRElSIC8gUGF0aCgibWFuaWZlc3QuanNvbiIpCg==\n",
  "encoding": "base64"
}

But in this example, we would use IPFS instead of GitHub. Download IPFS from the release page. But you must use 0.4.x version. The 0.5.x version has a problem with ipfshttpclient library. You can use 0.4.22 version. Install it. Make sure the ipfs binary is in $PATH. Run these commands in separate terminal.


$ ipfs init
initializing IPFS node at /Users/arjuna/.ipfs
generating 2048-bit RSA keypair...done
peer identity: QmSG13mnjyuJuKzAonxWiu4TVabmkYqQGyahMZqMEjA6nF
to get started, enter:

	ipfs cat /ipfs/QmS4ustL54uo8FzR9455qaxZwuMiUhyvMcX9Ba8nUH4uVv/readme

$ ipfs daemon
Initializing daemon...
go-ipfs version: 0.4.22-
Repo version: 7
System version: amd64/darwin
Golang version: go1.12.7
Swarm listening on /ip4/127.0.0.1/tcp/4001
Swarm listening on /ip4/192.168.5.13/tcp/4001
Swarm listening on /ip6/::1/tcp/4001
Swarm listening on /ip6/fdaa:bbb8:c821:0:18:7ca0:7221:4377/tcp/4001
Swarm listening on /ip6/fdaa:bbb8:c821:0:3598:7e82:c005:2e2e/tcp/4001
Swarm listening on /p2p-circuit
Swarm announcing /ip4/127.0.0.1/tcp/4001
Swarm announcing /ip4/192.168.5.13/tcp/4001
Swarm announcing /ip6/::1/tcp/4001
Swarm announcing /ip6/fdaa:bbb8:c821:0:18:7ca0:7221:4377/tcp/4001
Swarm announcing /ip6/fdaa:bbb8:c821:0:3598:7e82:c005:2e2e/tcp/4001
API server listening on /ip4/127.0.0.1/tcp/5001
WebUI: http://127.0.0.1:5001/webui
Gateway (readonly) server listening on /ip4/127.0.0.1/tcp/8080
Daemon is ready

Then you can pin the manifest to IPFS.


(.venv) $ mamba epm --epm_mode pin
Pinned asset URI is QmcnrSkZtzdyQ376zbk6QghhuxSobG5DBcDA29F33rGsRk.

Your content addressable URI is QmcnrSkZtzdyQ376zbk6QghhuxSobG5DBcDA29F33rGsRk. Your manifest.json is saved in IPFS node now and can be found with this URI.

This documentation will be updated with more content, such as:

  • dealing with smart contract which lives in production; say you want to interact with real SAI smart contract
  • creating a registry
  • releasing a package manifest to registry
  • updating content to epm v3

Stay tuned!