Deploying a contract
This guide lists all possible methods for deploying the tutorial contracts.
Prerequisites
Complete the following tutorials before proceeding.
Internal deployment
An internal deployment of a contract is made by another smart contract (as all wallets are smart contracts, this also applies to wallets).
Internal messages pay for themselves by spending their value
. As a result, there is no need to send funds to a particular address before deploying a contract there. This allows for handling deployment in just one message.
The contract constructor has to be set as payable
to support internal deployment via some developer tools (such as the =nil; CLI),
Via the =nil; CLI
To deploy Contract 1 via the =nil; CLI:
nil wallet deploy path/to/Retailer.bin --abi path/to/Retailer.abi --salt SALT
Take note of the address for Contract 1. To retrieve the wallet public key:
nil wallet info
To deploy Contract 2 with the given public key and the address of the retailer contract:
nil wallet deploy path/to/Manufacturer.bin PUBKEY RETAILER_ADDRESS --abi path/to/Manufacturer.abi --shard-id 2 --salt SALT
This will make sure that Manufacturer
and Retailer
are on different shards.
Via the client library
When submitting contract bytecode as a string to a deployment message, preface it with "0x".
The contract ABI can be copy-pasted from the ./abi
file and then cast into the Abi
type.
To deploy the retailer contract:
const client = new PublicClient({
transport: new HttpTransport({
endpoint: RPC_ENDPOINT,
}),
shardId: 1,
});
const faucet = new Faucet(client);
const signer = new LocalECDSAKeySigner({
privateKey: generateRandomPrivateKey(),
});
const gasPrice = await client.getGasPrice(1);
const pubkey = await signer.getPublicKey();
const wallet = new WalletV1({
pubkey: pubkey,
salt: BigInt(Math.floor(Math.random() * 10000)),
shardId: 1,
client,
signer,
});
const walletAddress = wallet.getAddressHex();
await faucet.withdrawToWithRetry(walletAddress, convertEthToWei(1));
await wallet.selfDeploy(true);
const { address: retailerAddress, hash: retailerDeploymentHash } =
await wallet.deployContract({
bytecode: RETAILER_BYTECODE,
abi: RETAILER_ABI,
args: [],
value: 0n,
feeCredit: 10_000_000n * gasPrice,
salt: BigInt(Math.floor(Math.random() * 10000)),
shardId: 1,
});
const receiptsRetailer = await waitTillCompleted(
client,
1,
retailerDeploymentHash,
);
To deploy the manufacturer contract:
const shardTwoClient = new PublicClient({
transport: new HttpTransport({
endpoint: RPC_ENDPOINT,
}),
shardId: 2,
});
const { address: manufacturerAddress, hash: manufacturerDeploymentHash } =
await wallet.deployContract({
bytecode: MANUFACTURER_BYTECODE,
abi: MANUFACTURER_ABI,
args: [bytesToHex(pubkey), retailerAddress],
value: 0n,
feeCredit: 1000000n * gasPrice,
salt: BigInt(Math.floor(Math.random() * 10000)),
shardId: 2,
});
const manufacturerReceipts = await waitTillCompleted(
client,
1,
manufacturerDeploymentHash,
);
External deployment
An external deployment of a contract is made by a user or another entity outside of the cluster.
As is the case with all external messages, an external deployment must be paid by the contract at the address to which the call is maid. However, before deployment, this address is unoccupied. To proceed with external deployment, an entity must first send funds to the address where the contract will reside.
A new address is always calculated based on the bytecode of the contract constructor to deploy a contract. As a result, only the contract with a specific constructor can occupy its allocated address. This makes it so that funds sent to an address can only be used by the contract intended for this address.
Via the =nil; CLI
First, calulate the address of the retailer contract and send funds to this address:
nil contract address path/to/Retailer.sol --shard-id 1 --salt SALT
nil wallet send-tokens RETAILER_ADDRESS AMOUNT
Then, deploy the retailer contract while providing the same SALT
:
nil contract deploy path/to/Retailer.bin --shard-id 1 SALT
Retrieve the public key assigned to the wallet:
nil wallet info
Calculate the address of the manufacturer contract and send funds to it:
nil contract address path/to/Manufacturer.sol PUBKEY RETAILER_ADDRESS --shard-id 2 --salt SALT --abi path/to/Manufacturer.abi
nil wallet send-tokens MANUFACTURER_ADDRESS AMOUNT
Deploy the manufacturer contract:
nil contract deploy path/to/Manufacturer.bin PUBKEY RETAILER_ADDRESS --salt SALT --shard-id 2 --abi path/to/Manufacturer.abi
Via the client library
When submitting contract bytecode as a string to a deployment message, preface it with "0x".
The contract ABI can be copy-pasted from the ./abi
file and then cast into the Abi
type.
To deploy the retailer contract:
const client = new PublicClient({
transport: new HttpTransport({
endpoint: RPC_ENDPOINT,
}),
shardId: 1,
});
const faucet = new Faucet(client);
const signer = new LocalECDSAKeySigner({
privateKey: generateRandomPrivateKey(),
});
const pubkey = await signer.getPublicKey();
const chainId = await client.chainId();
const deploymentMessageRetailer = externalDeploymentMessage(
{
salt: BigInt(Math.floor(Math.random() * 10000)),
shard: 1,
bytecode: RETAILER_BYTECODE,
},
chainId,
);
const addressRetailer = bytesToHex(deploymentMessageRetailer.to);
const faucetHashRetailer = await faucet.withdrawToWithRetry(
addressRetailer,
convertEthToWei(1),
);
await waitTillCompleted(client, 1, faucetHashRetailer);
const receipts = await deploymentMessageRetailer.send(client);
await waitTillCompleted(client, 1, receipts);
To deploy the manufacturer contract:
const clientTwo = new PublicClient({
transport: new HttpTransport({
endpoint: RPC_ENDPOINT,
}),
shardId: 2,
});
const deploymentMessageManufacturer = externalDeploymentMessage(
{
salt: BigInt(Math.floor(Math.random() * 10000)),
shard: 2,
bytecode: MANUFACTURER_BYTECODE,
abi: MANUFACTURER_ABI,
args: [bytesToHex(pubkey), addressRetailer],
},
chainId,
);
const addressManufacturer = bytesToHex(deploymentMessageManufacturer.to);
const faucetHashManufacturer = await faucet.withdrawToWithRetry(
addressManufacturer,
convertEthToWei(1),
);
await waitTillCompleted(client, 1, faucetHashManufacturer);
const receiptsManufacturer = await deploymentMessageManufacturer.send(client);
await waitTillCompleted(clientTwo, 2, receiptsManufacturer);
Hardhat deployment
The =nil; Hardhat plugin only supports internal deployment.
After learning how to use the =nil; Hardhat plugin, create two module files for the tutorial contracts.
Retailer:
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
module.exports = buildModule("Retailer", (m: any) => {
const retailer = m.contract("Retailer");
return { retailer };
});
Manufacturer:
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
module.exports = buildModule("Manufacturer", (m: any) => {
const pubkey = "{PUBKEY}";
const retailerContractAddress = "{RETAILER_ADDRESS}";
const manufacturer = m.contract("Manufacturer", [pubkey, retailerContractAddress]);
return { manufacturer };
});
PUBKEY
can be accessed by running the nil wallet info
command.
Before deploying the retailer contract, open hardhat.config.ts
and add this parameter to NilHardhatUserConfig
:
shardId: 1
Deploy the retailer:
npx hardhat ignition deploy ./ignition/modules/Retailer.ts --network nil
To deploy the manufacturer on a different shard, go back to hardhat.config.ts
and change NilHardhatUserConfig
:
shardId: 2
Deploy the manufacturer:
npx hardhat ignition deploy ./ignition/modules/Manufacturer.ts --network nil
After the manufacturer is deployed, set shardId
to 1
in hardhat.config.ts
.
Opcode deployment
It is also possible to deploy contracts without having to send an external or an internal message: this is done by directly using the CREATE
and CREATE2
opcodes.
To use CREATE
in Solidity:
import "./Nil.sol";
using Nil for address;
contract Manufacturer {
...
}
contract Creator {
function deployContract() private returns (address deployedAddress) {
bytes memory code = abi.encodePacked(type(NewContract).creationCode, abi.encode(msg.sender));
assembly {
deployedAddress := create(callvalue(), add(code), mload(code))
}
return deployedAddress;
}
}
To use CREATE2 in Solidity:
import "./Nil.sol";
using Nil for address;
contract Manufacturer {
...
}
contract Creator {
function deployContract(uint salt) private returns (address deployedAddress) {
bytes memory code = type(NewContract).creationCode;
assembly {
deployedAddress := create2(callvalue(), add(code, 0x20), mload(code), salt);
}
return deployedAddress;
}
}