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