Calling methods inside smart contracts
This tutorial outlines how to interact with the tutorial smart contracts given the developer tools provided by =nil;.
Prerequisites
Complete the following tutorials before proceeding.
Via the contract factory
Nil.js
exposes the contract factory, a Hardhat-like tool for interacting with smart contracts.
To order a new product via the contract factory:
const hashFunds = await faucet.withdrawToWithRetry(retailerAddress, 5_000_000n);
await waitTillCompleted(client, hashFunds);
const retailerContract = getContract({
client,
RETAILER_ABI,
address: retailerAddress,
wallet: wallet,
});
const manufacturerContract = getContract({
client,
MANUFACTURER_ABI,
address: manufacturerAddress,
wallet: wallet,
});
const res = await retailerContract.read.orderProduct([manufacturerAddress, "new-product"]);
To attain the result:
const res2 = await manufacturerContract.read.getProducts();
console.log(res2);
Using internal messages
Via the =nil; CLI
To call the orderProduct()
function inside the retailer contract and trigger an async call:
nil wallet send-tokens RETAILER_ADDRESS 5000000
nil wallet send-message RETAILER_ADDRESS orderProduct MANUFACTURER_ADDRESS new-product --abi path/to/Retailer.abi --fee-credit 2000000
Make a readonly call to access the value:
nil contract call-readonly MANUFACTURER_ADDRESS getProducts --abi path/to/Manufacturer.abi
The CLI should output the decoded values automatically.
Via the client library
To send funds to the retailer product and call the orderProduct
function:
const hashFunds = await faucet.withdrawToWithRetry(retailerAddress, 5_000_000n);
await waitTillCompleted(client, hashFunds);
const hashProduct = await wallet.sendMessage({
to: retailerAddress,
data: encodeFunctionData({
abi: RETAILER_ABI,
functionName: "orderProduct",
args: [manufacturerAddress, "another-product"],
}),
feeCredit: 3_000_000n,
});
const productReceipts = await waitTillCompleted(client, hashProduct);
To receive and decode the currently created products:
const resultsCall = await client.call(
{
from: manufacturerAddress,
to: manufacturerAddress,
data: encodeFunctionData({
abi: MANUFACTURER_ABI,
functionName: "getProducts",
args: [],
}),
},
"latest",
);
console.log(
"getProducts",
decodeFunctionResult({
abi: MANUFACTURER_ABI,
functionName: "getProducts",
data: resultsCall,
}),
);
Using external messages
Via the =nil; CLI
The CLI currently does not support sending external messages that modify the internal state of smart contracts or spawn internal messages. As a result, sending an external message to a function that performs an async call to another function will fail.
In the retailer contract, the orderProduct()
function cannot be called externally as it makes an async call to the manufacturer.
Via the client library
To order a new product using the retailer:
const orderMessage = new ExternalMessageEnvelope({
isDeploy: false,
to: hexToBytes(addressRetailer),
chainId,
data: hexToBytes(
encodeFunctionData({
abi: RETAILER_ABI,
functionName: "orderProduct",
args: [addressManufacturer, "new-product"],
}),
),
authData: new Uint8Array(0),
seqno: await client.getMessageCount(addressRetailer),
});
const encodedOrderMessage = orderMessage.encode();
let success = false;
let orderMessageHash;
while (!success) {
try {
orderMessageHash = await client.sendRawMessage(bytesToHex(encodedOrderMessage));
success = true;
} catch (error) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
const orderReceipts = await waitTillCompleted(client, orderMessageHash);
To receive and decode the currently created products:
const resultsCall = await client.call(
{
from: manufacturerAddress,
to: manufacturerAddress,
data: encodeFunctionData({
abi: MANUFACTURER_ABI,
functionName: "getProducts",
args: [],
}),
},
"latest",
);
console.log(
"getProducts",
decodeFunctionResult({
abi: MANUFACTURER_ABI,
functionName: "getProducts",
data: resultsCall,
}),
);
Via the Hardhat plugin
Calling smart contract methods via the Hardhat plugin requires creating Hardhat tasks.
Ordering a product from the manufacturer:
import { task } from "hardhat/config";
task("orderProduct", "Orders a product")
.addParam("retailer", "The address of the Retailer contract")
.addParam("manufacturer", "The address of the Manufacturer contract")
.addParam("product", "The name of the product")
.setAction(async (taskArgs, hre) => {
const Retailer = await hre.ethers.getContractFactory("Retailer");
const retailer = Retailer.attach(taskArgs.retailer);
console.log("Ordering the product...");
const setterTx = await retailer.orderProduct(taskArgs.manufacturer, taskArgs.product);
await setterTx.wait(0);
});
Retrieving the currently ordered products:
import { task } from "hardhat/config";
task("getProducts", "Gets the current products")
.addParam("manufacturer", "The address of the Manufacturer contract")
.setAction(async (taskArgs, hre) => {
const Manufacturer = await hre.ethers.getContractFactory("Manufacturer");
const manufacturer = Manufacturer.attach(taskArgs.manufacturer);
console.log("Getting products..");
const products = await manufacturer.getProducts();
console.log(products);
});
Execute the following commands to run the newly created tasks:
npx hardhat orderProduct --manufacturer MANUFACTURER_ADDRESS --retailer RETAILER_ADDRESS --product PRODUCT_NAME
npx hardhat getProducts --manufacturer MANUFACTURER_ADDRESS
The Hardhat plugin should decode the results of the call and show the new product directly in the terminal.