Skip to main content

Deploy and call a smart contract using Nil.js

These recipes showcase how to use Nil.js to deploy and call a smart contract.

Create a smart contract

This example uses the standard counter contract:

pragma solidity ^0.8.0;

contract Counter {
uint256 private value;

event ValueChanged(uint256 newValue);

receive() external payable {}

function increment() public {
value += 1;
emit ValueChanged(value);
}

function getValue() public view returns (uint256) {
return value;
}

function verifyExternal(
uint256 hash,
bytes memory authData
) external pure returns (bool) {
return true;
}
}

Import statements

Import the following components from @nilfoundation/niljs:

import {
ExternalMessageEnvelope,
Faucet,
HttpTransport,
LocalECDSAKeySigner,
PublicClient,
WalletV1,
bytesToHex,
convertEthToWei,
externalDeploymentMessage,
generateRandomPrivateKey,
hexToBytes,
waitTillCompleted,
getContract,
} from "@nilfoundation/niljs";

import { encodeFunctionData, type Abi } from "viem";

Deploy the contract

Internal deployment

To create a new wallet and deploy the contract internally:

const SALT = BigInt(Math.floor(Math.random() * 10000));

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

const faucet = new Faucet(client);

const pkey = generateRandomPrivateKey();

const signer = new LocalECDSAKeySigner({
privateKey: pkey,
});

const pubkey = signer.getPublicKey();

const wallet = new WalletV1({
pubkey: pubkey,
client: client,
signer: signer,
shardId: 1,
salt: SALT,
});

const walletAddress = wallet.address;

await faucet.withdrawToWithRetry(walletAddress, convertEthToWei(10));

await wallet.selfDeploy(true);

const { address, hash } = await wallet.deployContract({
bytecode: COUNTER_BYTECODE,
abi: COUNTER_ABI as unknown as Abi,
args: [],
feeCredit: 50_000_000n,
salt: SALT,
shardId: 1,
});

const manufacturerReceipts = await waitTillCompleted(client, hash);

External deployment

To deploy the contract externally:

const SALT = BigInt(Math.floor(Math.random() * 10000));

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

const faucet = new Faucet(client);

const chainId = await client.chainId();

const deploymentMessage = externalDeploymentMessage(
{
salt: SALT,
shard: 1,
bytecode: COUNTER_BYTECODE,
abi: COUNTER_ABI as unknown as Abi,
args: [],
},
chainId,
);

const addr = bytesToHex(deploymentMessage.to);

const faucetHash = await faucet.withdrawToWithRetry(addr, convertEthToWei(0.1));

await waitTillCompleted(client, faucetHash);

const hash = await deploymentMessage.send(client);

const receipts = await waitTillCompleted(client, hash);

Sending a message to the contract

Via the contract factory

Nil.js can utilize a special 'contract factory' to interact with smart contracts in a manner similar to a Hardhat task:

const contract = getContract({
client: client,
abi: COUNTER_ABI as unknown[],
address: COUNTER_ADDRESS,
wallet: wallet,
});

const res = await contract.read.getValue([]);
expect(res).toBe(0n);

const hash = await contract.write.increment([]);
await waitTillCompleted(client, hash);

const res2 = await contract.read.getValue([]);

console.log(res2);

Internal message

To send a new internal message to the increment() function:

const hash = await wallet.sendMessage({
to: COUNTER_ADDRESS,
abi: COUNTER_ABI as unknown as Abi,
feeCredit: 5_000_000n,
functionName: "increment",
});

const receipts = await waitTillCompleted(client, hash);

External message

To send a new external message to the increment() function:

const message = new ExternalMessageEnvelope({
to: hexToBytes(COUNTER_ADDRESS),
isDeploy: false,
chainId,
data: hexToBytes(
encodeFunctionData({
abi: COUNTER_ABI as unknown as Abi,
functionName: "increment",
args: [],
}),
),
authData: new Uint8Array(0),
seqno: await client.getMessageCount(COUNTER_ADDRESS),
});

const encodedMessage = message.encode();

let success = false;
let messageHash: `0x${string}`;

while (!success) {
try {
messageHash = await client.sendRawMessage(bytesToHex(encodedMessage));
success = true;
} catch (error) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}

const receipts = await waitTillCompleted(client, messageHash);