Interact with contracts
To interact with your smart contracts, you'll need to use one of the standard Ethereum libraries like viem
or ethers.js
.
- Viem
- Ethers.js + Typechain
Import smart contract bindings
The contracts library which contains our smart contract presets can also be used to import Typescript ABIs that can be used directly with modern libraries like abitype
, viem
and wagmi
.
contracts
The TypeScript ABIs can be used to interact with deployed preset contracts in a type-safe way.
Read more about interacting with contracts using viem.
To import a Typescript ABI use the following pattern:
import {
ImmutableERC721Abi,
ImmutableERC721MintByIdAbi,
} from '@imtbl/contracts';
Interact with contract
Here is an example script that uses the importable Typescript ABI with viem
to send a mint transaction on a deployed preset contract:
import { getContract, http, createWalletClient, defineChain } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { ImmutableERC721Abi } from '@imtbl/contracts';
const PRIVATE_KEY = '0xYOUR_PRIVATE_KEY'; // should be read from environment variable, should be of type `0x${string}`
const CONTRACT_ADDRESS = '0xYOUR_CONTRACT_ADDRESS'; // should be of type `0x${string}`
const TOKEN_ID_1 = BigInt(1);
const TOKEN_ID_2 = BigInt(2);
const TOKEN_ID_3 = BigInt(3);
const TOKEN_ID_4 = BigInt(4);
const ACCOUNT_ADDRESS_1: `0x${string}` = '0xACCOUNT_ADDRESS_1'; // should be of type `0x${string}`
const ACCOUNT_ADDRESS_2: `0x${string}` = '0xACCOUNT_ADDRESS_2'; // should be of type `0x${string}`
const REQUESTS = [
{
to: ACCOUNT_ADDRESS_1,
tokenIds: [TOKEN_ID_1, TOKEN_ID_2],
},
{
to: ACCOUNT_ADDRESS_2,
tokenIds: [TOKEN_ID_3, TOKEN_ID_4],
},
];
export const batchMintERC721ByID = async (
privateKey: `0x${string}`,
contractAddress: `0x${string}`,
requests: {
to: `0x${string}`;
tokenIds: bigint[];
}[],
): Promise<string> => {
const immutableTestnet = defineChain({
id: 13473,
name: 'imtbl-zkevm-testnet',
nativeCurrency: { name: 'IMX', symbol: 'IMX', decimals: 18 },
rpcUrls: {
default: {
http: ['https://rpc.testnet.immutable.com'],
},
},
});
const walletClient = createWalletClient({
chain: immutableTestnet,
transport: http(),
account: privateKeyToAccount(privateKey),
});
// Bound contract instance
const contract = getContract({
address: contractAddress,
abi: ImmutableERC721Abi,
client: walletClient,
});
// We can use the read function hasRole to check if the intended signer
// has sufficient permissions to mint before we send the transaction
const minterRole = await contract.read.MINTER_ROLE();
const hasMinterRole = await contract.read.hasRole([
minterRole,
walletClient.account.address,
]);
if (!hasMinterRole) {
// Handle scenario without permissions...
console.log('Account doesnt have permissions to mint.');
return Promise.reject(
new Error('Account doesnt have permissions to mint.'),
);
}
const txHash = await contract.write.mintBatch([requests]);
console.log(`txHash: ${txHash}`);
return txHash;
};
batchMintERC721ByID(PRIVATE_KEY, CONTRACT_ADDRESS, REQUESTS);
Generate smart contract bindings
We can generate TypeScript bindings for Ethereum smart contracts based on their ABI files so that we can interact with our contracts in a type-safe way.
In this example, we will use the typechain
plugin, which can be installed as a development dependency using the following command:
npm install --save-dev @typechain/hardhat @typechain/ethers-v5
To set up typechain, import @typechain/hardhat
in your hardhat.config.ts
:
import '@typechain/hardhat';
Now, Typescript typings should be automatically generated each time you compile your contracts, i.e. by running npx hardhat compile
. A /typechain-types
directory will be created in the project directory, with Typescript bindings for each smart contract in the project.
$ npx hardhat clean
$ npx hardhat compile
Generating typings for: 1 artifacts in dir: typechain-types for target: ethers-v5
Successfully generated 6 typings!
Compiled 1 Solidity file successfully
Before running it for the first time you need to run npx hardhat clean
, otherwise TypeChain will think that there is no need to generate any typings. This is because this plugin will attempt to do incremental generation and generate typings only for changed contracts. You should also run hardhat clean
if you change any TypeChain-related config option.
Interact with contract
Here is an example script that uses generated contract bindings to call a simple view function, and also send a transaction to interact with a deployed contract:
import { ethers } from 'hardhat';
import { MyContract } from '../typechain-types';
async function main() {
// get first account from the list of accounts defined in hardhat.config.ts
const [acc1] = await ethers.getSigners();
// get the contract instance
const contract: MyContract = await ethers.getContractAt(
'ImmutableERC721', // name of the contract
'0x123' // deployed contract address
);
// call a simple view function `name` on the contract (no tx required)
const name = await contract.name();
console.log('Name:', name);
// send a tx from acc1, calling the `mint` function on the contract
// with the to address = acc1.address and amount to be minted = 1
const tx = await contract.connect(acc1).mint(acc1.address, 1);
// wait for the tx to be processed, then log the receipt
const receipt = await tx.wait();
console.log('Tx receipt:', receipt);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});