元类(Meta-Classes)的概念有些令人困惑,因此我们将介绍一个简短的例子。
元类是在运行时(run-time)定义的类。合约是由应用程序二进制接口(ABI)指定的,它描述了它所拥有的方法和事件。 这些描述是合约(Contract)对象处于run-time时传入的,它创建一个新的Class,在run-time时添加了ABI中定义的所有方法。
大多数情况下,您需要与之交互的任何合约都已经部署到区块链上,但对于本例需要先部署合约。
new ethers.ContractFactory( abi , bytecode , signer ) 创建一个新的ContractFactory,它可以将合约部署到区块链。
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
// '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC'
await contract.deployTransaction.wait();
// {
// blockHash: '0xe9b244958d066490c46a826a9733c1a43210316777185353b3ecbc2ec362ea87',
// blockNumber: 22,
// byzantium: true,
// confirmations: 1,
// contractAddress: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// cumulativeGasUsed: { BigNumber: "250842" },
// effectiveGasPrice: { BigNumber: "1553830469" },
// events: [
// {
// address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// blockHash: '0xe9b244958d066490c46a826a9733c1a43210316777185353b3ecbc2ec362ea87',
// blockNumber: 22,
// data: '0x0000000000000000000000000000000000000000000000056bc75e2d63100000',
// getBlock: [Function],
// getTransaction: [Function],
// getTransactionReceipt: [Function],
// logIndex: 0,
// removeListener: [Function],
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000000000000000000000000000000000000000000000',
// '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759'
// ],
// transactionHash: '0x03164a2299c8334178d5a2c731149910d5c21db0309bfbc8aae89a34c8069ad4',
// transactionIndex: 0
// }
// ],
// from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// gasUsed: { BigNumber: "250842" },
// logs: [
// {
// address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// blockHash: '0xe9b244958d066490c46a826a9733c1a43210316777185353b3ecbc2ec362ea87',
// blockNumber: 22,
// data: '0x0000000000000000000000000000000000000000000000056bc75e2d63100000',
// logIndex: 0,
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000000000000000000000000000000000000000000000',
// '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759'
// ],
// transactionHash: '0x03164a2299c8334178d5a2c731149910d5c21db0309bfbc8aae89a34c8069ad4',
// transactionIndex: 0
// }
// ],
// logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000080000000000000000000000000000000000000000020000000000000000000800000000000000000002000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000002000000000000000000000000000000000000000000000000000020000000800008000000000000000000000000000000200000000000000000000000',
// status: 1,
// to: null,
// transactionHash: '0x03164a2299c8334178d5a2c731149910d5c21db0309bfbc8aae89a34c8069ad4',
// transactionIndex: 0,
// type: 2
// }
new ethers.Contract( address , abi , providerOrSigner ) 创建一个Contract的新实例,是通过在区块链上指定这个合约的地址、以及它的abi(用于填充类的方法)和providerOrSigner来连接到的。
如果给定了Provider,那么合约只有只读访问权限,而Signer则提供了对状态操作方法的访问权限。
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 = "0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC";
const erc20 = new ethers.Contract(address, abi, provider);
const erc20_rw = new ethers.Contract(address, abi, signer);
合约被创建的 地址address (或 ENS name)
如果Contract对象是ContractFactory部署的得到的,在这里的返回的结果就是用于部署合约的交易。
如果一个provider被提供给构造函数,这条语句的结果就是provider; 如果提供了的Provider是一个signer,结果就是signer。
如果是一个signer提供给构造函数,结果就是signer。
返回一个附在新地址的合约的新实例。 如果网络上有多个类似或相同的合约副本,并且您希望与它们中的每一个进行交互,那么这是非常有用的。
返回合约的新实例,但需要连接provider或者Signer。
通过传入一个Provider,这将返回一个低级的合约,它只有只读访问(即常量调用)。
通过传入一个Signer。这将返回一个代表该signer的合约。
erc20.deployed( ) ⇒ Promise< Contract >
Contract.isIndexed( value ) ⇒ boolean
关于使用事件的例子请参考Meta-Class Filters。
erc20.queryFilter( event [ , fromBlockOrBlockHash [ , toBlock ] ) ⇒ Promise< Array< Event > > erc20.listenerCount( [ event ] ) ⇒ number 返回订阅该event的监听器数量。如果没有提供event,则返回所有事件的总数。
erc20.listeners( event ) ⇒ Array< Listener > erc20.off( event , listener ) ⇒ this erc20.on( event , listener ) ⇒ this 监听event事件,当事件发生时,会调用listener函数。
erc20.once( event , listener ) ⇒ this 监听event事件,当事件发生时,仅调用一次listener函数。
erc20.removeAllListeners( [ event ] ) ⇒ this 取消所有订阅event事件的监听器。如果未提供event事件,则取消订阅所有事件的监听。
Meta-Class Methods
(added at Runtime)
因为合约是元类,这里面可用的方法取决于传入合约的ABI。
erc20.decimals( [ overrides ] ) ⇒ Promise< number > 返回此ERC-20 token所使用的小数位数。在前端界面,当从用户获取输入时,可以使用parseUnits转化后传入合约; 从合约获得token后可以通过[formatUnits](utils-formatunits]转化后再显示给用户。
await erc20.decimals();
// 18
erc20.balanceOf( owner [ , overrides ] ) ⇒ Promise< 大数(BigNumber) > 返回持有这个ERC-20 token的持有者(owner)的余额。
await erc20.balanceOf(signer.getAddress())
// { BigNumber: "100000000000000000000" }
erc20.symbol( [ overrides ] ) ⇒ Promise< string > await erc20.symbol();
// 'MyToken'
从当前的signer将数量为amount的tokens转给接收者target。 在交易处于写入操作时,返回值(布尔类型)是得不到的。 如果需要这个值,则需要其他方法(如事件)。链上合约调用transfer
函数可以得到这个结果。
formatUnits(await erc20_rw.balanceOf(signer.getAddress()));
// '100.0'
tx = await erc20_rw.transfer("ricmoo.eth", parseUnits("1.23"));
// {
// accessList: [],
// chainId: 31337,
// confirmations: 0,
// data: '0xa9059cbb0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca0000000000000000000000000000000000000000000000001111d67bb1bb0000',
// from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// gasLimit: { BigNumber: "51558" },
// gasPrice: null,
// hash: '0xceb49c9db1d35d18db9b3c56d285e691f006d62529dec397eec004337206e600',
// maxFeePerGas: { BigNumber: "1607660938" },
// maxPriorityFeePerGas: { BigNumber: "1500000000" },
// nonce: 2,
// r: '0x33284b92f1197507d7bd48aee7ccf4f7386527e72b6bf3fe23245026cf0a7eac',
// s: '0x64eb0ca43292a98695f702ed0d59ec1d97618905c3366ac1d4ff50841f4cd0fb',
// to: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// type: 2,
// v: 1,
// value: { BigNumber: "0" },
// wait: [Function]
// }
await tx.wait();
// {
// blockHash: '0x95ac5d8669e6c73c1784296c0ad09c3ccf3e06055c78361056d971c6498171f1',
// blockNumber: 23,
// byzantium: true,
// confirmations: 1,
// contractAddress: null,
// cumulativeGasUsed: { BigNumber: "51558" },
// effectiveGasPrice: { BigNumber: "1547214185" },
// events: [
// {
// address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// args: [
// '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// '0x5555763613a12D8F3e73be831DFf8598089d3dCa',
// { BigNumber: "1230000000000000000" },
// amount: { BigNumber: "1230000000000000000" },
// from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// to: '0x5555763613a12D8F3e73be831DFf8598089d3dCa'
// ],
// blockHash: '0x95ac5d8669e6c73c1784296c0ad09c3ccf3e06055c78361056d971c6498171f1',
// blockNumber: 23,
// data: '0x0000000000000000000000000000000000000000000000001111d67bb1bb0000',
// decode: [Function],
// event: 'Transfer',
// eventSignature: 'Transfer(address,address,uint256)',
// getBlock: [Function],
// getTransaction: [Function],
// getTransactionReceipt: [Function],
// logIndex: 0,
// removeListener: [Function],
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759',
// '0x0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca'
// ],
// transactionHash: '0xceb49c9db1d35d18db9b3c56d285e691f006d62529dec397eec004337206e600',
// transactionIndex: 0
// }
// ],
// from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// gasUsed: { BigNumber: "51558" },
// logs: [
// {
// address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// blockHash: '0x95ac5d8669e6c73c1784296c0ad09c3ccf3e06055c78361056d971c6498171f1',
// blockNumber: 23,
// data: '0x0000000000000000000000000000000000000000000000001111d67bb1bb0000',
// logIndex: 0,
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759',
// '0x0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca'
// ],
// transactionHash: '0xceb49c9db1d35d18db9b3c56d285e691f006d62529dec397eec004337206e600',
// transactionIndex: 0
// }
// ],
// logsBloom: '0x00000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000080000000000000000000000000000000000000000000000000000000000000000000000000000000002000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000002000000000000000000000000000000000000000000000000000000000000800008000001000000000200000000000000200000000000000000000000',
// status: 1,
// to: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// transactionHash: '0xceb49c9db1d35d18db9b3c56d285e691f006d62529dec397eec004337206e600',
// 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 > 执行一次从当前signer向target地址转移amount数量的token的演练,而不实际签名或发送交易。
这可以用于检查真实的转账前,交易是否能成功。
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: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// 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: '0x3744A6A188e46e3f2A10f918126d13F73E3C58D9',
// to: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC'
// }
// }
erc20.estimateGas.transfer( target , amount [ , overrides ] ) ⇒ Promise< 大数(BigNumber) > 返回估计的“将amount数量的tokens发送target地址”所需的多少gas值。
await erc20_rw.estimateGas.transfer("ricmoo.eth", parseUnits("1.23"));
// { BigNumber: "34458" }
await erc20_rw.populateTransaction.transfer("ricmoo.eth", parseUnits("1.23"));
// {
// data: '0xa9059cbb0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca0000000000000000000000000000000000000000000000001111d67bb1bb0000',
// from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// to: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC'
// }
关于估算和静态调用的注意事项
当你执行一个静态调用时,以太坊会尽可能地考虑当前状态。 在许多情况下,这可能会提供假阳性和假阴性。 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)
因为合约是元类,这里面可用的方法取决于传入合约的ABI。
erc20.filters.Transfer( [ fromAddress [ , toAddress ] ] ) ⇒ Filter 返回一个新的Filter用于查询或者去subscribe/unsubscribe to events。
如果fromAddress 是空的或者没有填写,将匹配任何发送人的地址。 如果 toAddress是空的或者没有填写,将匹配任何接受人的地址。
query filter *from* events
filterFrom = erc20.filters.Transfer(signer.address);
// {
// address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759'
// ]
// }
logsFrom = await erc20.queryFilter(filterFrom, -10, "latest");
// [
// {
// address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// args: [
// '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// '0x5555763613a12D8F3e73be831DFf8598089d3dCa',
// { BigNumber: "1230000000000000000" },
// amount: { BigNumber: "1230000000000000000" },
// from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// to: '0x5555763613a12D8F3e73be831DFf8598089d3dCa'
// ],
// blockHash: '0x95ac5d8669e6c73c1784296c0ad09c3ccf3e06055c78361056d971c6498171f1',
// blockNumber: 23,
// data: '0x0000000000000000000000000000000000000000000000001111d67bb1bb0000',
// decode: [Function],
// event: 'Transfer',
// eventSignature: 'Transfer(address,address,uint256)',
// getBlock: [Function],
// getTransaction: [Function],
// getTransactionReceipt: [Function],
// logIndex: 0,
// removeListener: [Function],
// removed: false,
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759',
// '0x0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca'
// ],
// transactionHash: '0xceb49c9db1d35d18db9b3c56d285e691f006d62529dec397eec004337206e600',
// transactionIndex: 0
// }
// ]
logsFrom[0].args
// [
// '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// '0x5555763613a12D8F3e73be831DFf8598089d3dCa',
// { BigNumber: "1230000000000000000" },
// amount: { BigNumber: "1230000000000000000" },
// from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// to: '0x5555763613a12D8F3e73be831DFf8598089d3dCa'
// ]
query filter with *to* events
filterTo = erc20.filters.Transfer(null, signer.address);
// {
// address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// null,
// '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759'
// ]
// }
logsTo = await erc20.queryFilter(filterTo, -10, "latest");
// [
// {
// address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC',
// args: [
// '0x0000000000000000000000000000000000000000',
// '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// { BigNumber: "100000000000000000000" },
// amount: { BigNumber: "100000000000000000000" },
// from: '0x0000000000000000000000000000000000000000',
// to: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759'
// ],
// blockHash: '0xe9b244958d066490c46a826a9733c1a43210316777185353b3ecbc2ec362ea87',
// blockNumber: 22,
// data: '0x0000000000000000000000000000000000000000000000056bc75e2d63100000',
// decode: [Function],
// event: 'Transfer',
// eventSignature: 'Transfer(address,address,uint256)',
// getBlock: [Function],
// getTransaction: [Function],
// getTransactionReceipt: [Function],
// logIndex: 0,
// removeListener: [Function],
// removed: false,
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000000000000000000000000000000000000000000000',
// '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759'
// ],
// transactionHash: '0x03164a2299c8334178d5a2c731149910d5c21db0309bfbc8aae89a34c8069ad4',
// transactionIndex: 0
// }
// ]
logsTo[0].args
// [
// '0x0000000000000000000000000000000000000000',
// '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// { BigNumber: "100000000000000000000" },
// amount: { BigNumber: "100000000000000000000" },
// from: '0x0000000000000000000000000000000000000000',
// to: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759'
// ]
erc20.on(filterFrom, (from, to, amount, event) => {
});
erc20.on(filterTo, (from, to, amount, event) => {
});
erc20.on("Transfer", (from, to, amount, event) => {
});