The concept of Meta-Classes is somewhat confusing, so we will go over a short example.
A meta-class is a class which is defined at run-time. A Contract is specified by an Application Binary Interface (ABI), which describes the methods and events it has. This description is passed to the Contract object at run-time, and it creates a new Class, adding all the methods defined in the ABI at run-time.
Most often, any contract you will need to interact with will already be deployed to the blockchain, but for this example will will first deploy the contract.
new ethers.ContractFactory( abi , bytecode , signer ) Create a new ContractFactory which can deploy a contract to the blockchain.
const bytecode = "0x608060405234801561001057600080fd5b506040516103bc3803806103bc83398101604081905261002f9161007c565b60405181815233906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a333600090815260208190526040902055610094565b60006020828403121561008d578081fd5b5051919050565b610319806100a36000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063313ce5671461005157806370a082311461006557806395d89b411461009c578063a9059cbb146100c5575b600080fd5b604051601281526020015b60405180910390f35b61008e610073366004610201565b6001600160a01b031660009081526020819052604090205490565b60405190815260200161005c565b604080518082018252600781526626bcaa37b5b2b760c91b6020820152905161005c919061024b565b6100d86100d3366004610222565b6100e8565b604051901515815260200161005c565b3360009081526020819052604081205482111561014b5760405162461bcd60e51b815260206004820152601a60248201527f696e73756666696369656e7420746f6b656e2062616c616e6365000000000000604482015260640160405180910390fd5b336000908152602081905260408120805484929061016a9084906102b6565b90915550506001600160a01b0383166000908152602081905260408120805484929061019790849061029e565b90915550506040518281526001600160a01b0384169033907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a350600192915050565b80356001600160a01b03811681146101fc57600080fd5b919050565b600060208284031215610212578081fd5b61021b826101e5565b9392505050565b60008060408385031215610234578081fd5b61023d836101e5565b946020939093013593505050565b6000602080835283518082850152825b818110156102775785810183015185820160400152820161025b565b818111156102885783604083870101525b50601f01601f1916929092016040019392505050565b600082198211156102b1576102b16102cd565b500190565b6000828210156102c8576102c86102cd565b500390565b634e487b7160e01b600052601160045260246000fdfea2646970667358221220d80384ce584e101c5b92e4ee9b7871262285070dbcd2d71f99601f0f4fcecd2364736f6c63430008040033";
const abi = [
"constructor(uint totalSupply)"
];
const factory = new ethers.ContractFactory(abi, bytecode, signer)
const contract = await factory.deploy(parseUnits("100"));
contract.address
// '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674'
await contract.deployTransaction.wait();
// {
// blockHash: '0xe0628e513348591aaf653ff88ac043d9da2a5755cf12060be5532b2ffea4eab3',
// blockNumber: 60329,
// byzantium: true,
// confirmations: 1,
// contractAddress: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// cumulativeGasUsed: { BigNumber: "250842" },
// effectiveGasPrice: { BigNumber: "1500000007" },
// events: [
// {
// address: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// blockHash: '0xe0628e513348591aaf653ff88ac043d9da2a5755cf12060be5532b2ffea4eab3',
// blockNumber: 60329,
// data: '0x0000000000000000000000000000000000000000000000056bc75e2d63100000',
// getBlock: [Function (anonymous)],
// getTransaction: [Function (anonymous)],
// getTransactionReceipt: [Function (anonymous)],
// logIndex: 0,
// removeListener: [Function (anonymous)],
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000000000000000000000000000000000000000000000',
// '0x00000000000000000000000046e0726ef145d92dea66d38797cf51901701926e'
// ],
// transactionHash: '0xa3b258b2a091ef197ccc7ec269e3b4b3eaa421041c5a6db31ee751ebc403bccb',
// transactionIndex: 0
// }
// ],
// from: '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// gasUsed: { BigNumber: "250842" },
// logs: [
// {
// address: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// blockHash: '0xe0628e513348591aaf653ff88ac043d9da2a5755cf12060be5532b2ffea4eab3',
// blockNumber: 60329,
// data: '0x0000000000000000000000000000000000000000000000056bc75e2d63100000',
// logIndex: 0,
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000000000000000000000000000000000000000000000',
// '0x00000000000000000000000046e0726ef145d92dea66d38797cf51901701926e'
// ],
// transactionHash: '0xa3b258b2a091ef197ccc7ec269e3b4b3eaa421041c5a6db31ee751ebc403bccb',
// transactionIndex: 0
// }
// ],
// logsBloom: '0x00000000000000000000000000000004000000000000000000000000000040000000000000000000000000000000000000000000000000000000020000000000000000000000000000000008000000000000001000000000000000000000000000000000020000000000000000000800000000000000000000000010000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010002000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000',
// status: 1,
// to: null,
// transactionHash: '0xa3b258b2a091ef197ccc7ec269e3b4b3eaa421041c5a6db31ee751ebc403bccb',
// transactionIndex: 0,
// type: 2
// }
new ethers.Contract( address , abi , providerOrSigner ) Creating a new instance of a Contract connects to an existing contract by specifying its address on the blockchain, its abi (used to populate the class' methods) a providerOrSigner.
If a Provider is given, the contract has only read-only access, while a Signer offers access to state manipulating methods.
const abi = [
"function balanceOf(address owner) view returns (uint256)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)",
"function transfer(address to, uint amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint amount)"
];
const address = "0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674";
const erc20 = new ethers.Contract(address, abi, provider);
const erc20_rw = new ethers.Contract(address, abi, signer);
This is the address (or ENS name) the contract was constructed with.
erc20.resolvedAddress ⇒ string< Address > This is a promise that will resolve to the address the Contract object is attached to. If an Address was provided to the constructor, it will be equal to this; if an ENS name was provided, this will be the resolved address.
If the Contract object is the result of a ContractFactory deployment, this is the transaction which was used to deploy the contract.
If a provider was provided to the constructor, this is that provider. If a signer was provided that had a Provider, this is that provider.
If a signer was provided to the constructor, this is that signer.
erc20.attach( addressOrName ) ⇒ Contract Returns a new instance of the Contract attached to a new address. This is useful if there are multiple similar or identical copies of a Contract on the network and you wish to interact with each of them.
erc20.connect( providerOrSigner ) ⇒ Contract Returns a new instance of the Contract, but connected to providerOrSigner.
By passing in a Provider, this will return a downgraded Contract which only has read-only access (i.e. constant calls).
By passing in a Signer. this will return a Contract which will act on behalf of that signer.
erc20.deployed( ) ⇒ Promise< Contract >
Contract.isIndexed( value ) ⇒ boolean
See Meta-Class Filters for examples using events.
erc20.queryFilter( event [ , fromBlockOrBlockHash [ , toBlock ] ) ⇒ Promise< Array< Event > > Return Events that match the event.
erc20.listenerCount( [ event ] ) ⇒ number Return the number of listeners that are subscribed to event. If no event is provided, returns the total count of all events.
erc20.listeners( event ) ⇒ Array< Listener > Return a list of listeners that are subscribed to event.
erc20.off( event , listener ) ⇒ this Unsubscribe listener to event.
erc20.on( event , listener ) ⇒ this Subscribe to event calling listener when the event occurs.
erc20.once( event , listener ) ⇒ this Subscribe once to event calling listener when the event occurs.
erc20.removeAllListeners( [ event ] ) ⇒ this Unsubscribe all listeners for event. If no event is provided, all events are unsubscribed.
Meta-Class Methods
(added at Runtime)
Since the Contract is a Meta-Class, the methods available here depend on the ABI which was passed into the Contract.
erc20.decimals( [ overrides ] ) ⇒ Promise< number > Returns the number of decimal places used by this ERC-20 token. This can be used with parseUnits when taking input from the user or [formatUnits](utils-formatunits] when displaying the token amounts in the UI.
await erc20.decimals();
// 18
erc20.balanceOf( owner [ , overrides ] ) ⇒ Promise< BigNumber > Returns the balance of owner for this ERC-20 token.
await erc20.balanceOf(signer.getAddress())
// { BigNumber: "100000000000000000000" }
erc20.symbol( [ overrides ] ) ⇒ Promise< string > Returns the symbol of the token.
await erc20.symbol();
// 'MyToken'
Transfers amount tokens to target from the current signer. The return value (a boolean) is inaccessible during a write operation using a transaction. Other techniques (such as events) are required if this value is required. On-chain contracts calling the transfer
function have access to this result, which is why it is possible.
formatUnits(await erc20_rw.balanceOf(signer.getAddress()));
// '100.0'
tx = await erc20_rw.transfer("ricmoo.eth", parseUnits("1.23"));
// {
// accessList: [],
// chainId: 123456,
// confirmations: 0,
// data: '0xa9059cbb0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca0000000000000000000000000000000000000000000000001111d67bb1bb0000',
// from: '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// gasLimit: { BigNumber: "51558" },
// gasPrice: null,
// hash: '0xafdc1f7ec14e2e05a39826c134c9157e0011fc784aef623df3fdf9b9d29f3ac8',
// maxFeePerGas: { BigNumber: "1500000014" },
// maxPriorityFeePerGas: { BigNumber: "1500000000" },
// nonce: 2,
// r: '0xd5f1784f0eeb12ef38eb38f1040232f61cc52e017b53ef8685a7a07e47f3144b',
// s: '0x5a63103e4b49eae8d372f474857a58612c53fa996b331b9c98611df14191f529',
// to: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// type: 2,
// v: 1,
// value: { BigNumber: "0" },
// wait: [Function (anonymous)]
// }
await tx.wait();
// {
// blockHash: '0x9340a9c7efafbb4ad9a6ebda62e3311ec5fc40ddcc733632938a988c8c62b885',
// blockNumber: 60330,
// byzantium: true,
// confirmations: 1,
// contractAddress: null,
// cumulativeGasUsed: { BigNumber: "51558" },
// effectiveGasPrice: { BigNumber: "1500000007" },
// events: [
// {
// address: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// args: [
// '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// '0x5555763613a12D8F3e73be831DFf8598089d3dCa',
// { BigNumber: "1230000000000000000" },
// amount: { BigNumber: "1230000000000000000" },
// from: '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// to: '0x5555763613a12D8F3e73be831DFf8598089d3dCa'
// ],
// blockHash: '0x9340a9c7efafbb4ad9a6ebda62e3311ec5fc40ddcc733632938a988c8c62b885',
// blockNumber: 60330,
// data: '0x0000000000000000000000000000000000000000000000001111d67bb1bb0000',
// decode: [Function (anonymous)],
// event: 'Transfer',
// eventSignature: 'Transfer(address,address,uint256)',
// getBlock: [Function (anonymous)],
// getTransaction: [Function (anonymous)],
// getTransactionReceipt: [Function (anonymous)],
// logIndex: 0,
// removeListener: [Function (anonymous)],
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x00000000000000000000000046e0726ef145d92dea66d38797cf51901701926e',
// '0x0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca'
// ],
// transactionHash: '0xafdc1f7ec14e2e05a39826c134c9157e0011fc784aef623df3fdf9b9d29f3ac8',
// transactionIndex: 0
// }
// ],
// from: '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// gasUsed: { BigNumber: "51558" },
// logs: [
// {
// address: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// blockHash: '0x9340a9c7efafbb4ad9a6ebda62e3311ec5fc40ddcc733632938a988c8c62b885',
// blockNumber: 60330,
// data: '0x0000000000000000000000000000000000000000000000001111d67bb1bb0000',
// logIndex: 0,
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x00000000000000000000000046e0726ef145d92dea66d38797cf51901701926e',
// '0x0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca'
// ],
// transactionHash: '0xafdc1f7ec14e2e05a39826c134c9157e0011fc784aef623df3fdf9b9d29f3ac8',
// transactionIndex: 0
// }
// ],
// logsBloom: '0x00000000000000000800000000000004000000000000000000000000000040000000000000000000000000000000000000000000000000000000020000000000000000000000000000000008000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010002000000000000000000000000000000000000000000000000000000000000000000000001000000000200000000000000000000000000000000000000',
// status: 1,
// to: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// transactionHash: '0xafdc1f7ec14e2e05a39826c134c9157e0011fc784aef623df3fdf9b9d29f3ac8',
// transactionIndex: 0,
// type: 2
// }
formatUnits(await erc20_rw.balanceOf(signer.getAddress()));
// '98.77'
formatUnits(await erc20_rw.balanceOf("ricmoo.eth"));
// '1.23'
erc20.callStatic.transfer( target , amount [ , overrides ] ) ⇒ Promise< boolean > Performs a dry-run of transferring amount tokens to target from the current signer, without actually signing or sending a transaction.
This can be used to preflight check that a transaction will be successful.
await erc20_rw.callStatic.transfer("ricmoo.eth", parseUnits("1.23"));
// true
erc20_random = erc20_rw.connect(randomWallet);
await erc20_random.callStatic.transfer("ricmoo.eth", parseUnits("1.23"));
// [Error: call revert exception; VM Exception while processing transaction: reverted with reason string "insufficient token balance" [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ]] {
// address: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// args: [
// 'ricmoo.eth',
// { BigNumber: "1230000000000000000" }
// ],
// code: 'CALL_EXCEPTION',
// data: '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a696e73756666696369656e7420746f6b656e2062616c616e6365000000000000',
// errorArgs: [
// 'insufficient token balance'
// ],
// errorName: 'Error',
// errorSignature: 'Error(string)',
// method: 'transfer(address,uint256)',
// reason: 'insufficient token balance',
// transaction: {
// data: '0xa9059cbb0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca0000000000000000000000000000000000000000000000001111d67bb1bb0000',
// from: '0x8b6b9C2CE3468B30Fde7Ca10e54aC5F0bA2456e0',
// to: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674'
// }
// }
erc20.estimateGas.transfer( target , amount [ , overrides ] ) ⇒ Promise< BigNumber > Returns an estimate for how many units of gas would be required to transfer amount tokens to target.
await erc20_rw.estimateGas.transfer("ricmoo.eth", parseUnits("1.23"));
// { BigNumber: "34458" }
erc20.populateTransaction.transfer( target , amount [ , overrides ] ) ⇒ Promise< UnsignedTx > Returns an UnsignedTransaction which could be signed and submitted to the network to transaction amount tokens to target.
await erc20_rw.populateTransaction.transfer("ricmoo.eth", parseUnits("1.23"));
// {
// data: '0xa9059cbb0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca0000000000000000000000000000000000000000000000001111d67bb1bb0000',
// from: '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// to: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674'
// }
Note on Estimating and Static Calling
When you perform a static call, the current state is taken into account as best as Ethereum can determine. There are many cases where this can provide false positives and false negatives. The eventually consistent model of the blockchain also means there are certain consistency modes that cannot be known until an actual transaction is attempted.
Meta-Class Filters
(added at Runtime)
Since the Contract is a Meta-Class, the methods available here depend on the ABI which was passed into the Contract.
erc20.filters.Transfer( [ fromAddress [ , toAddress ] ] ) ⇒ Filter Returns a new Filter which can be used to query or to subscribe/unsubscribe to events.
If fromAddress is null or not provided, then any from address matches. If toAddress is null or not provided, then any to address matches.
query filter *from* events
filterFrom = erc20.filters.Transfer(signer.address);
// {
// address: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x00000000000000000000000046e0726ef145d92dea66d38797cf51901701926e'
// ]
// }
logsFrom = await erc20.queryFilter(filterFrom, -10, "latest");
// [
// {
// address: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// args: [
// '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// '0x5555763613a12D8F3e73be831DFf8598089d3dCa',
// { BigNumber: "1230000000000000000" },
// amount: { BigNumber: "1230000000000000000" },
// from: '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// to: '0x5555763613a12D8F3e73be831DFf8598089d3dCa'
// ],
// blockHash: '0x9340a9c7efafbb4ad9a6ebda62e3311ec5fc40ddcc733632938a988c8c62b885',
// blockNumber: 60330,
// data: '0x0000000000000000000000000000000000000000000000001111d67bb1bb0000',
// decode: [Function (anonymous)],
// event: 'Transfer',
// eventSignature: 'Transfer(address,address,uint256)',
// getBlock: [Function (anonymous)],
// getTransaction: [Function (anonymous)],
// getTransactionReceipt: [Function (anonymous)],
// logIndex: 0,
// removeListener: [Function (anonymous)],
// removed: false,
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x00000000000000000000000046e0726ef145d92dea66d38797cf51901701926e',
// '0x0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca'
// ],
// transactionHash: '0xafdc1f7ec14e2e05a39826c134c9157e0011fc784aef623df3fdf9b9d29f3ac8',
// transactionIndex: 0
// }
// ]
logsFrom[0].args
// [
// '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// '0x5555763613a12D8F3e73be831DFf8598089d3dCa',
// { BigNumber: "1230000000000000000" },
// amount: { BigNumber: "1230000000000000000" },
// from: '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// to: '0x5555763613a12D8F3e73be831DFf8598089d3dCa'
// ]
query filter with *to* events
filterTo = erc20.filters.Transfer(null, signer.address);
// {
// address: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// null,
// '0x00000000000000000000000046e0726ef145d92dea66d38797cf51901701926e'
// ]
// }
logsTo = await erc20.queryFilter(filterTo, -10, "latest");
// [
// {
// address: '0x70ff5c5B1Ad0533eAA5489e0D5Ea01485d530674',
// args: [
// '0x0000000000000000000000000000000000000000',
// '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// { BigNumber: "100000000000000000000" },
// amount: { BigNumber: "100000000000000000000" },
// from: '0x0000000000000000000000000000000000000000',
// to: '0x46E0726Ef145d92DEA66D38797CF51901701926e'
// ],
// blockHash: '0xe0628e513348591aaf653ff88ac043d9da2a5755cf12060be5532b2ffea4eab3',
// blockNumber: 60329,
// data: '0x0000000000000000000000000000000000000000000000056bc75e2d63100000',
// decode: [Function (anonymous)],
// event: 'Transfer',
// eventSignature: 'Transfer(address,address,uint256)',
// getBlock: [Function (anonymous)],
// getTransaction: [Function (anonymous)],
// getTransactionReceipt: [Function (anonymous)],
// logIndex: 0,
// removeListener: [Function (anonymous)],
// removed: false,
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000000000000000000000000000000000000000000000',
// '0x00000000000000000000000046e0726ef145d92dea66d38797cf51901701926e'
// ],
// transactionHash: '0xa3b258b2a091ef197ccc7ec269e3b4b3eaa421041c5a6db31ee751ebc403bccb',
// transactionIndex: 0
// }
// ]
logsTo[0].args
// [
// '0x0000000000000000000000000000000000000000',
// '0x46E0726Ef145d92DEA66D38797CF51901701926e',
// { BigNumber: "100000000000000000000" },
// amount: { BigNumber: "100000000000000000000" },
// from: '0x0000000000000000000000000000000000000000',
// to: '0x46E0726Ef145d92DEA66D38797CF51901701926e'
// ]
erc20.on(filterFrom, (from, to, amount, event) => {
});
erc20.on(filterTo, (from, to, amount, event) => {
});
erc20.on("Transfer", (from, to, amount, event) => {
});