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