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:
- ethPM packages may be used to obtain deployment data, providing easy interaction with existing contracts on the main-net or testnets.
- 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.
- 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:
ethpm://[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:
ethpm://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
ethpm://erc20.snakecharmers.eth
└─dai-dai@1.0.0
ethpm://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:
- Set all required fields within the
ethpm-config.yaml
configuration file.- Generate the package manifest and verify the contents.
- 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’sdeployments
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, orFalse
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 thedeployments
field.
Note that you cannot set
include_dependencies
toFalse
while your package contains dependency source files that have been modified. In this situation you must first unlink the modified packages.- if
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:
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.
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-netaccount
: 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: ethpm://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("ethpm://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="ethpm://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.