Skip to main content

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.

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 method:

const hashFunds = await faucet.withdrawToWithRetry(
retailerAddress,
5_000_000n,
);

await waitTillCompleted(client, 1, 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, 1, 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,
1,
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 --network nil
npx hardhat getProducts --manufacturer MANUFACTURER_ADDRESS --network nil

The Hardhat plugin should decode the results of the call and show the new product directly in the terminal.