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