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 smart accounts are smart contracts, this also applies to smart accounts).

Internal transactions 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 transaction.

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 smart-account deploy path/to/Retailer.bin --abi path/to/Retailer.abi --salt SALT 

Take note of the address for Contract 1. To retrieve the smart account public key:

nil smart-account info

To deploy Contract 2 with the given public key and the address of the retailer contract:

nil smart-account 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 transaction, 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 smartAccount = await generateSmartAccount({
shardId: 1,
rpcEndpoint: RPC_ENDPOINT,
faucetEndpoint: FAUCET_ENDPOINT,
});

const { address: retailerAddress, hash: retailerDeploymentHash } =
await smartAccount.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, retailerDeploymentHash);

To deploy the manufacturer contract:


const { address: manufacturerAddress, hash: manufacturerDeploymentHash } =
await smartAccount.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, 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 transactions, 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.bin --shard-id 1 --salt SALT 
nil smart-account 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 SALT 

Retrieve the public key assigned to the smart account:

nil smart-account info

Calculate the address of the manufacturer contract and send funds to it:

nil contract address path/to/Manufacturer.bin PUBKEY RETAILER_ADDRESS --shard-id 2 --salt SALT  --abi path/to/Manufacturer.abi
nil smart-account 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 transaction, 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 pubkey = getPublicKey(generateRandomPrivateKey());

const chainId = await client.chainId();

const deploymentTransactionRetailer = externalDeploymentTransaction(
{
salt: BigInt(Math.floor(Math.random() * 10000)),
shard: 1,
bytecode: RETAILER_BYTECODE,
},
chainId,
);

const addressRetailer = bytesToHex(deploymentTransactionRetailer.to);

await topUp({
address: addressRetailer,
faucetEndpoint: FAUCET_ENDPOINT,
rpcEndpoint: RPC_ENDPOINT,
});

const receipts = await deploymentTransactionRetailer.send(client);

await waitTillCompleted(client, receipts);

To deploy the manufacturer contract:

const clientTwo = new PublicClient({
transport: new HttpTransport({
endpoint: RPC_ENDPOINT,
}),
shardId: 2,
});

const gasPrice = await client.getGasPrice(2);

const deploymentTransactionManufacturer = externalDeploymentTransaction(
{
salt: BigInt(Math.floor(Math.random() * 10000)),
shard: 2,
bytecode: MANUFACTURER_BYTECODE,
abi: MANUFACTURER_ABI,
args: [bytesToHex(pubkey), addressRetailer],
feeCredit: 1_000_000n * gasPrice,
},
chainId,
);

const addressManufacturer = bytesToHex(deploymentTransactionManufacturer.to);

await topUp({
address: addressManufacturer,
faucetEndpoint: FAUCET_ENDPOINT,
rpcEndpoint: RPC_ENDPOINT,
});

const receiptsManufacturer = await deploymentTransactionManufacturer.send(client);

await waitTillCompleted(clientTwo, receiptsManufacturer);

Opcode deployment

It is also possible to deploy contracts without having to send an external or an internal transaction: 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;
}
}