The Ethereum Package Manager

The Ethereum Package Manager (ethPM) is a decentralized package manager used to distribute EVM smart contracts and projects. It has similar goals to most package managers found in any given programming language:

  • Easily import and build upon core ideas written by others.
  • Distribute the ideas that you’ve written and/or deployed, making them easily consumable for tooling and the community at large.

At its core, an ethPM package is a JSON object containing the ABI, source code, bytecode, deployment data and any other information that combines together to compose the smart contract idea. The ethPM specification defines a schema to store all of this data in a structured JSON format, enabling quick and efficient transportation of smart contract ideas between tools and frameworks which support the specification.

Brownie supports ethPM, offering the following functionality:

  1. ethPM packages may be used to obtain deployment data, providing easy interaction with existing contracts on the main-net or testnets.
  2. Package source files may be installed within a Brownie project, to be inherited by existing contracts or used as a starting point when building something new.
  3. Packages can be generated from Brownie projects and released on ethPM registries, for simple and verified distribution.

Registry URIs

To obtain an ethPM package, you must know both the package name and the address of the registry where it is available. The simplest way to communicate this information is through a registry URI. Registry URIs adhere to the following format:

erc1319://[CONTRACT_ADDRESS]:[CHAIN_ID]/[PACKAGE_NAME]@[VERSION]

For example, here is a registry URI for the popular OpenZeppelin Math package, served by the Snake Charmers Zeppelin registry:

erc1319://zeppelin.snakecharmers.eth:1/math@1.0.0

Working with ethPM Packages

The brownie ethpm command-line interface is used to add and remove packages to a Brownie project, as well as to generate a package from a project.

Installing a Package

To install an ethPM package within a Brownie project:

$ brownie ethpm install [registry-uri]

This will add all of the package sources files into the project contracts/ folder.

If a package contains a source with an identical filename to one in your project, Brownie raises a FileExistsError unless the contents of the two files are identical, or the overwrite flag is set to True.

Listing Installed Packages

To view a list of currently installed packages within a project:

$ brownie ethpm list
Brownie - Python development framework for Ethereum

Found 2 installed packages:
 ├─access@1.0.0
 └─math@1.0.0

Any packages that are installed from a registry are also saved locally. To view a list of all locally available ethPM packages, and the registries they were downloaded from:

$ brownie ethpm all
Brownie - Python development framework for Ethereum

erc1319://erc20.snakecharmers.eth
 └─dai-dai@1.0.0

erc1319://zeppelin.snakecharmers.eth
 ├─access@1.0.0
 ├─gns@1.0.0
 └─math@1.0.0

Removing a Package

Removing an installed package from a Brownie project will delete any of that package’s sources files, as long as they are not also required by another package.

To remove a package, either delete all of it’s source files or use the following command:

$ brownie ethpm remove [package-name]

Unlinking a Package

You may wish to install a package as a starting point upon which you build your own project, and in doing so make changes to the package sources. This will cause Brownie to flag the package as “modified” and raise warnings when performing certain actions. You can silence these warnings by unlinking the package - deleting Brownie’s record that it is an ethPM package without removing the source files.

To unlink a package:

$ brownie ethpm unlink [package-name]

Creating and Releasing a Package

Brownie allows you to generate an ethPM package from your project and publish it to a registry. Packages generated by Brownie will always include:

  • All contract source files within the project
  • The name, ABI, bytecode and compiler settings for each contract in the project

Depending upon the configuartion, they may also optionally include:

  • Addresses of deployed contracts instances across each network
  • References to other ethPM packages that this package requires

The process of releasing a package is:

  1. Set all required fields within the ethpm-config.yaml configuration file.
  2. Generate the package manifest and verify the contents.
  3. Pin the manifest and sources to IPFS and publish the manifest URI to an ethPM registry.

Important

Ensure that all import statements within your source files use relative file paths (beginning with ./). If you use absolute paths, your package is more likely to have namespace collisions when imported into other projects.

Step 1: Package Configuration Settings

To create a package you must first set all required fields within the ethpm-config.yaml file in the root folder of your project.

Required Settings

The following settings must have a non-null value in order to generate a package.

package_name

The package_name field defines a human readable name for the package. It must begin with a lowercase letter and be comprised of only lowercase letters, numeric characters, dashes and underscores. Package names must not exceed 255 characters in length.

version

The version field defines the version number for the package. All versions should conform to the semver versioning specificaion.

settings.deployment_networks

The deployment_networks field is a list of networks that should be included in the package’s deployments field. The name of each network must correspond to that of a network listed in the project configuration file.

In order for a deployment to be included:

  • Persistence must be enabled for that network
  • The bytecode of the deployed contract must be identical to the bytecode generated from the source code currently present in the project’s contracts/ folder

You can use a wildcard * to include deployments on all networks, or False to not include any deployments.

settings.include_dependencies

The include_dependencies field is a boolean to indicate how package dependencies should be handled.

  • if True, Brownie will generate a standalone package without any listed dependencies.
  • if False, Brownie will list all package dependencies within the manifest, and only include as much data about them as is required by the deployments field.

Note that you cannot set include_dependencies to False while your package contains dependency source files that have been modified. In this situation you must first unlink the modified packages.

Optional Settings

meta

The meta field, and all it’s subfields, provides metadata about the package. This data is not integral for package installation, but may be important or convenient to provide.

Any fields that are left blank will be omitted. You can also add additional fields, they will be included within the package.

Example Configuration

Here is an example configuration for ethpm-config.yaml:

# required fields
package_name: nftoken
version: 1.0.1
settings:
  deployment_networks:
    - mainnet
include_dependencies: false

# optional fields
meta:
  description: A non-fungible implementation of the ERC20 standard, allowing scalable NFT transfers with fixed gas costs.
  authors:
    - Ben Hauser
    - Gabriel Shapiro
  license: MIT
  keywords:
    - ERC20
    - ERC721
    - NFT
  links:
    repository: https://github.com/iamdefinitelyahuman/nftoken

Step 2: Creating the Manifest

Once you have set the required fields in the configuration file, you can create a manifest with the following command:

$ brownie ethpm create

The manifest is saved locally as manifest.json in the project root folder. Note that this saved copy is not tightly packed and so does not strictly adhere the ethPM specification. This is not the final copy to be pinned to IPFS, rather it is a human-readable version that you can use to verify it’s contents before releasing.

Once you have confirmed that the included fields are consistent with what you would like to publish, you are ready to release.

Step 3: Releasing the Package

There are two steps in releasing a package:

  1. Pinning the manifest and related sources to IPFS.

    Brownie uses Infura’s public IPFS gateway to interact with IPFS. Note that pinning files to IPFS can be a very slow proess. If you receive a timeout error, simply repeat the request. Files that have been successfully pinned will not need to be re-pinned.

  2. Calling the release function of an ethPM registry with details of the package.

    Brownie broadcasts this transaction on the “mainnet” network as defined in the project configuration file. The account that you send the transaction from must be approved to call release in the registry, otherwise it will fail. Depending on your use case you may wish to run your own registry, or include your files within an existing one. See the ethPM documentation for more information.

To release a package:

$ brownie ethpm release [registry] [account]

You must include the following arguments:

  • registry: the address of an ethPM registry on the main-net
  • account: the address that the transaction is sent from. It can be given as an alias to a local account, or as a hex string if the address is unlocked within the connected node.

Once the package is successfully released, Brownie provides you with a registry URI that you can share with others so they can easily access your package:

$ brownie ethpm release erc20.snakecharmers.eth registry_owner
Brownie - Python development framework for Ethereum

Generating manifest and pinning assets to IPFS...
Pinning "NFToken.sol"...
Pinning "NFMintable.sol"...
Pinning manifest...

Releasing nftoken@1.0.1 on "erc20.snakecharmers.eth"...
Enter the password for this account: *****

SUCCESS: nftoken@1.0.1 has been released!

URI: erc1319://erc20.snakecharmers.eth:1/nftoken@1.0.1

Interacting with Package Deployments

You can load an entire package as a Project object, which includes Contract instances for any contracts deployed on the currently active network:

>>> from brownie.project import from_ethpm
>>> maker = from_ethpm("erc1319://erc20.snakecharmers.eth:1/dai-dai@1.0.0")
>>> maker
<TempProject object 'dai-dai'>
>>> maker.dict()
{
    'DSToken': [<DSToken Contract object '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359'>]
}

Or, create a Contract object to interact with a deployed instance of a specific contract within a package:

>>> from brownie import network, Contract
>>> network.connect('mainnet')
>>> ds = Contract("DSToken", manifest_uri="erc1319://erc20.snakecharmers.eth:1/dai-dai@1.0.0")
>>> ds
<DSToken Contract object '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359'>

If the package does not include deployment information for the currently active network, a ContractNotFound exception is raised.