it's time to deploy.
Niacin is a tool for smart contract deployment, aka chain ops.
- a standard deployment format - addresses, ABI's, deploy metadata.
delegatecall
proxies for upgradeable contracts, that are implemented safely and securely.- onchain dependency linking with
MixinResolver
, with a resolution cache (much cheaper than a beacon). - centralized address registry in the
AddressProvider
. - a drop-in CLI for upgradeable contracts - just
niacin deploy xyz
. - useful tools:
- vendoring - one command to import contracts from Etherscan, and generate *.sol types for them.
- NPM package generator - generate an NPM package for your subgraphs and frontends.
- deployment docs generator - generate a website for your deployments, including an interactive UI.
- well-designed:
- automatically keeps track of git metadata, so you can revert easily
- automatically tracks the deploy block, so you don't have to copy-paste that into the subgraph
1. Install the CLI.
npm i -g niacin-cli
2. Install the contracts.
forge install liamzebedee/niacin-contracts
3. Start playing.
niacin init
forge build
niacin deploy YourContractA YourContractB
niacin status
Niacin is oriented around the manifest
file, which describes all the information about a deployment to an EVM chain.
You can operate on different chains by switching the manifest you are using with the --manifest
argument.
Niacin does safety checks on chain ID, so you can't overwrite your deployments by accident.
Here's an example:
export PRIVATE_KEY="0x"
# Deploy multichain.
mkdir deployments/
RPC_URL="" niacin deploy -a --manifest deployments/local.json
RPC_URL="https://polygon‑rpc.com" niacin deploy -a --manifest deployments/polygon.json --gas-estimator polygon
RPC_URL="https://arb1.arbitrum.io/rpc" niacin deploy -a --manifest deployments/arbitrum.json
# Generate index.js API's.
niacin generate-npm-pkg --manifest deployments/local.json > deployments/local.js
niacin generate-npm-pkg --manifest deployments/polygon.json > deployments/polygon.js
niacin generate-npm-pkg --manifest deployments/arbitrum.json > deployments/arbitrum.js
# Interact with contracts.
niacin call --manifest deployments/local.json
niacin call --manifest deployments/polygon.json
niacin call --manifest deployments/arbitrum.json
# Get deployments status for each chain.
niacin status --manifest deployments/local.json
niacin status --manifest deployments/polygon.json
niacin status --manifest deployments/arbitrum.json
Say you have two contracts, a ProtocolManager
and a FeePool
, and you want to get the address of the FeePool contract.
// FeePool.sol
contract FeePool {
// ...
}
Niacin automatically links your contract dependencies together. Just import the MixinResolver
as so:
// ProtocolManager.sol
import {MixinResolver} from "niacin-contracts/mixins/MixinResolver.sol";
contract ProtocolManager is
MixinResolver
{
function getDependencies() public override pure returns (bytes32[] memory addresses) {
bytes32[] memory requiredAddresses = new bytes32[](1);
requiredAddresses[0] = bytes32("FeePool");
return requiredAddresses;
}
function feePool() internal view returns (FeePool) {
return FeePool(requireAddress(bytes32("FeePool")));
}
function swap() external {
FeePool pool = feePool();
// do whatever you want.
}
}
niacin generate-npm-pkg --manifest manifest.json > index.js
You can use this in your JS code like so:
const deployments = require('./index.js')
const ethers = require('ethers')
const provider = new ethers.providers.JsonRpcProvider()
const ProtocolManager = new ethers.Contract(
deployments.ProtocolManager.address,
deployments.ProtocolManager.abi,
provider
)
You can also automatically use Niacin from the CLI:
# Show your deployments
niacin call
# Call a specific contract
niacin call ProtocolManager
BUG: Note that for now, arguments which are hexadecimal strings (addresses, bytes) must be enclosed with quotes and without the 0x
prefix. e.g. 0x12312313
-> "12312313"
.
Import third-party contracts from Etherscan EVM chains:
niacin add-vendor --name Curve3Pool --fetch-from-etherscan https://optimistic.etherscan.io/address/0x1337BedC9D22ecbe766dF105c9623922A27963EC
niacin generate-sol-interface --name Curve3Pool > src/vendor/Curve3Pool.sol
niacin generate-npm-pkg > index.js
Not yet integrated: autogenerated contract UI's.
Due to how upgradeable contracts are implemented, you cannot use constructors. Instead, Niacin provides a simple initialize
which can be called from a postdeploy script.
Import the MixinInitializable
and write an initialize
function with the initializer
modifier:
// ProtocolManager.sol
import {MixinResolver} from "niacin-contracts/mixins/MixinResolver.sol";
import {MixinInitializable} from "niacin-contracts/mixins/MixinInitializable.sol";
contract ProtocolManager is
MixinResolver,
MixinInitializable
{
function initialize()
public
initializer
{
// ...
}
function getDependencies() public override pure returns (bytes32[] memory addresses) {
bytes32[] memory requiredAddresses = new bytes32[](1);
requiredAddresses[0] = bytes32("FeePool");
return requiredAddresses;
}
function feePool() internal view returns (FeePool) {
return FeePool(requireAddress(bytes32("FeePool")));
}
function swap() external {
FeePool pool = feePool();
// do whatever you want.
}
}
I'm still working on the UX of initializers.
For now, you can write a simple postdeploy
script which will run after invoking niacin deploy
for a deployment.
Create a new script postdeploy.js
and include this code:
module.exports = async function (niacin) {
const {
ProtocolManager
} = niacin.contracts
await niacin.initialize({
contract: ProtocolManager,
args: []
})
}
Open the .niacinrc.js
and paste the following:
module.exports = {
version: "0.1.0",
ignore: [],
scripts: {
initialize: require('./deploy/initialize')
}
}
Your initializer will now run as part of niacin deploy
.