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 {
ExternalTransactionEnvelope,
HttpTransport,
PublicClient,
bytesToHex,
externalDeploymentTransaction,
generateSmartAccount,
getContract,
hexToBytes,
topUp,
waitTillCompleted,
} from "@nilfoundation/niljs";

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

Deploy the contract

Internal deployment

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

const { address, hash } = await smartAccount.deployContract({
bytecode: COUNTER_BYTECODE,
abi: COUNTER_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 chainId = await client.chainId();

const deploymentTransaction = externalDeploymentTransaction(
{
salt: SALT,
shard: 1,
bytecode: COUNTER_BYTECODE,
abi: COUNTER_ABI,
args: [],
},
chainId,
);

const addr = bytesToHex(deploymentTransaction.to);

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

const hash = await deploymentTransaction.send(client);

const receipts = await waitTillCompleted(client, hash);

Sending a transaction 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,
smartAccount: smartAccount,
});

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 transaction

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

const hash = await smartAccount.sendTransaction({
to: COUNTER_ADDRESS,
abi: COUNTER_ABI,
functionName: "increment",
});

const receipts = await waitTillCompleted(client, hash);

External transaction

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

const transaction = new ExternalTransactionEnvelope({
to: hexToBytes(COUNTER_ADDRESS),
isDeploy: false,
chainId,
data: hexToBytes(
encodeFunctionData({
abi: COUNTER_ABI,
functionName: "increment",
args: [],
}),
),
authData: new Uint8Array(0),
seqno: await client.getTransactionCount(COUNTER_ADDRESS),
});

const encodedTransaction = transaction.encode();

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

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

const receipts = await waitTillCompleted(client, transactionHash);