Skip to main content

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.

info

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

tip

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.

info

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

tip

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

Deployment type

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 };
});
tip

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;
}
}