ethers.js/docs.wrm/api/contract/example.wrm

403 lines
15 KiB
Plaintext
Raw Normal View History

2020-02-25 22:57:11 +03:00
_section: Example: ERC-20 Contract
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
2022-05-14 08:22:57 +03:00
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.
_subsection: Deploying a Contract
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.
_property: new ethers.ContractFactory(abi, bytecode, signer)
Create a new [[ContractFactory]] which can deploy a contract to the
blockchain.
_code: @lang<javascript>
//_hide: const signer = localSigner;
//_hide: const parseUnits = utils.parseUnits;
const bytecode = "0x608060405234801561001057600080fd5b506040516103bc3803806103bc83398101604081905261002f9161007c565b60405181815233906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a333600090815260208190526040902055610094565b60006020828403121561008d578081fd5b5051919050565b610319806100a36000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063313ce5671461005157806370a082311461006557806395d89b411461009c578063a9059cbb146100c5575b600080fd5b604051601281526020015b60405180910390f35b61008e610073366004610201565b6001600160a01b031660009081526020819052604090205490565b60405190815260200161005c565b604080518082018252600781526626bcaa37b5b2b760c91b6020820152905161005c919061024b565b6100d86100d3366004610222565b6100e8565b604051901515815260200161005c565b3360009081526020819052604081205482111561014b5760405162461bcd60e51b815260206004820152601a60248201527f696e73756666696369656e7420746f6b656e2062616c616e6365000000000000604482015260640160405180910390fd5b336000908152602081905260408120805484929061016a9084906102b6565b90915550506001600160a01b0383166000908152602081905260408120805484929061019790849061029e565b90915550506040518281526001600160a01b0384169033907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a350600192915050565b80356001600160a01b03811681146101fc57600080fd5b919050565b600060208284031215610212578081fd5b61021b826101e5565b9392505050565b60008060408385031215610234578081fd5b61023d836101e5565b946020939093013593505050565b6000602080835283518082850152825b818110156102775785810183015185820160400152820161025b565b818111156102885783604083870101525b50601f01601f1916929092016040019392505050565b600082198211156102b1576102b16102cd565b500190565b6000828210156102c8576102c86102cd565b500390565b634e487b7160e01b600052601160045260246000fdfea2646970667358221220d80384ce584e101c5b92e4ee9b7871262285070dbcd2d71f99601f0f4fcecd2364736f6c63430008040033";
// A Human-Readable ABI; we only need to specify relevant fragments,
// in the case of deployment this means the constructor
const abi = [
"constructor(uint totalSupply)"
];
const factory = new ethers.ContractFactory(abi, bytecode, signer)
// Deploy, setting total supply to 100 tokens (assigned to the deployer)
const contract = await factory.deploy(parseUnits("100"));
// The contract is not currentl live on the network yet, however
// its address is ready for us
//_result:
contract.address
//_log:
//_hide: _page.address = contract.address;
// Wait until the contract has been deployed before interacting
// with it; returns the receipt for the deployemnt transaction
//_result:
await contract.deployTransaction.wait();
//_log:
2020-02-25 22:57:11 +03:00
_subsection: Connecting to a Contract
_heading: ERC20Contract @INHERIT<[[Contract]]>
_property: 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.
_code: @lang<javascript>
//_hide: const provider = localProvider;
//_hide: const signer = localSigner;
// A Human-Readable ABI; for interacting with the contract, we
// must include any fragment we wish to use
const abi = [
// Read-Only Functions
"function balanceOf(address owner) view returns (uint256)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)",
// Authenticated Functions
"function transfer(address to, uint amount) returns (bool)",
// Events
"event Transfer(address indexed from, address indexed to, uint amount)"
];
// This can be an address or an ENS name
//_hide: const address = _page.address;
//_verbatim: `const address = ${ JSON.stringify(address) };`
// Read-Only; By connecting to a Provider, allows:
// - Any constant function
// - Querying Filters
// - Populating Unsigned Transactions for non-constant methods
// - Estimating Gas for non-constant (as an anonymous sender)
// - Static Calling non-constant methods (as anonymous sender)
const erc20 = new ethers.Contract(address, abi, provider);
//_hide: _page.erc20 = erc20;
// Read-Write; By connecting to a Signer, allows:
// - Everything from Read-Only (except as Signer, not anonymous)
// - Sending transactions for non-constant functions
const erc20_rw = new ethers.Contract(address, abi, signer);
//_hide: _page.erc20_rw = erc20_rw;
2020-02-25 22:57:11 +03:00
2020-05-08 10:24:40 +03:00
_subsection: Properties @NOTE<(inheritted from [[Contract]])>
2020-02-25 22:57:11 +03:00
_property: erc20.address => string<[[address]]>
This is the address (or ENS name) the contract was constructed with.
2020-05-08 10:24:40 +03:00
_property: erc20.resolvedAddress => string<[[address]]>
2020-02-25 22:57:11 +03:00
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.
2020-05-08 10:24:40 +03:00
_property: erc20.deployTransaction => [[providers-TransactionResponse]]
2020-02-25 22:57:11 +03:00
If the **Contract** object is the result of a ContractFactory deployment,
this is the transaction which was used to deploy the contract.
2020-05-08 10:24:40 +03:00
_property: erc20.interface => [[Interface]]
This is the ABI as an [[Interface]].
2020-02-25 22:57:11 +03:00
2020-05-08 10:24:40 +03:00
_property: erc20.provider => [[Provider]]
2020-02-25 22:57:11 +03:00
If a provider was provided to the constructor, this is that provider. If
2020-05-08 10:24:40 +03:00
a signer was provided that had a [[Provider]], this is that provider.
2020-02-25 22:57:11 +03:00
2020-05-08 10:24:40 +03:00
_property: erc20.signer => [[Signer]]
2020-02-25 22:57:11 +03:00
If a signer was provided to the constructor, this is that signer.
2020-05-08 10:24:40 +03:00
_subsection: Methods @NOTE<(inheritted from [[Contract]])>
2020-02-25 22:57:11 +03:00
2020-05-08 10:24:40 +03:00
_property: erc20.attach(addressOrName) => [[Contract]]
2020-02-25 22:57:11 +03:00
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.
2020-05-08 10:24:40 +03:00
_property: erc20.connect(providerOrSigner) => [[Contract]]
2020-02-25 22:57:11 +03:00
Returns a new instance of the Contract, but connected to
//providerOrSigner//.
2020-05-08 10:24:40 +03:00
By passing in a [[Provider]], this will return a downgraded
2020-02-25 22:57:11 +03:00
**Contract** which only has read-only access (i.e. constant calls).
By passing in a [[Signer]]. this will return a **Contract** which
2020-02-25 22:57:11 +03:00
will act on behalf of that signer.
_property: erc20.deployed() => Promise<Contract>
_property: Contract.isIndexed(value) => boolean
2020-05-08 10:24:40 +03:00
_subsection: Events @NOTE<(inheritted from [[Contract]])> @<erc20-events>
2020-02-25 22:57:11 +03:00
See [Meta-Class Filters](erc20-meta-events) for examples using events.
2020-02-25 22:57:11 +03:00
_property: erc20.queryFilter(event [ , fromBlockOrBlockHash [ , toBlock ]) => Promise<Array<Event>> @<erc20-queryfilter>
Return Events that match the //event//.
_property: 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.
_property: erc20.listeners(event) => Array<Listener>
Return a list of listeners that are subscribed to //event//.
_property: erc20.off(event, listener) => this
Unsubscribe //listener// to //event//.
_property: erc20.on(event, listener) => this
Subscribe to //event// calling //listener// when the event occurs.
_property: erc20.once(event, listener) => this
Subscribe once to //event// calling //listener// when the event
occurs.
_property: erc20.removeAllListeners([ event ]) => this
Unsubscribe all listeners for //event//. If no event is provided,
all events are unsubscribed.
_subsection: Meta-Class Methods @NOTE<(added at Runtime)> @<erc20-meta-methods>
2020-02-25 22:57:11 +03:00
Since the Contract is a Meta-Class, the methods available here depend
on the ABI which was passed into the **Contract**.
_property: erc20.decimals([ overrides ]) => Promise<number>
Returns the number of decimal places used by this ERC-20 token. This can be
2020-05-08 10:24:40 +03:00
used with [parseUnits](utils-parseUnits) when taking input from the user or
2020-02-25 22:57:11 +03:00
[formatUnits](utils-formatunits] when displaying the token amounts in the UI.
_code: @lang<javascript>
//_hide: const erc20 = _page.erc20;
//_result:
await erc20.decimals();
//_log:
2021-04-11 02:58:20 +03:00
_property: erc20.balanceOf(owner [, overrides ]) => Promise<[[BigNumber]]>
2020-02-25 22:57:11 +03:00
Returns the balance of //owner// for this ERC-20 token.
_code: @lang<javascript>
//_hide: const signer = localSigner;
//_hide: const erc20 = _page.erc20;
//_result:
await erc20.balanceOf(signer.getAddress())
//_log:
2020-02-25 22:57:11 +03:00
_property: erc20.symbol([ overrides ]) => Promise<string>
Returns the symbol of the token.
_code: @lang<javascript>
//_hide: const erc20 = _page.erc20;
//_result:
await erc20.symbol();
//_log:
2020-05-08 10:24:40 +03:00
_property: erc20_rw.transfer(target, amount [, overrides ]) => Promise<[[providers-TransactionResponse]]>
2020-02-25 22:57:11 +03:00
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.
_code: @lang<javascript>
//_hide: const signer = localSigner;
//_hide: const erc20_rw = _page.erc20_rw;
//_hide: const parseUnits = utils.parseUnits;
//_hide: const formatUnits = utils.formatUnits;
// Before...
//_result:
formatUnits(await erc20_rw.balanceOf(signer.getAddress()));
//_log:
// Transfer 1.23 tokens to the ENS name "ricmoo.eth"
//_result:
tx = await erc20_rw.transfer("ricmoo.eth", parseUnits("1.23"));
//_log:
// Wait for the transaction to be mined...
//_result:
await tx.wait();
//_log:
// After!
//_result:
formatUnits(await erc20_rw.balanceOf(signer.getAddress()));
//_log:
//_result:
formatUnits(await erc20_rw.balanceOf("ricmoo.eth"));
//_log:
2020-02-25 22:57:11 +03:00
_property: 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.
_code: @lang<javascript>
//_hide: const erc20_rw = _page.erc20_rw;
//_hide: const randomWallet = ethers.Wallet.createRandom().connect(erc20_rw.provider);
//_hide: const parseUnits = utils.parseUnits;
// The signer has enough tokens to send, so true is returned
//_result:
await erc20_rw.callStatic.transfer("ricmoo.eth", parseUnits("1.23"));
//_log:
// A random address does not have enough tokens to
// send, in which case the contract throws an error
erc20_random = erc20_rw.connect(randomWallet);
//_throws:
await erc20_random.callStatic.transfer("ricmoo.eth", parseUnits("1.23"));
//_log:
2020-05-08 10:24:40 +03:00
_property: erc20.estimateGas.transfer(target, amount [, overrides ]) => Promise<[[BigNumber]]>
2020-02-25 22:57:11 +03:00
Returns an estimate for how many units of gas would be required
to transfer //amount// tokens to //target//.
_code: @lang<javascript>
//_hide: const erc20_rw = _page.erc20_rw;
//_hide: const parseUnits = utils.parseUnits;
//_result:
await erc20_rw.estimateGas.transfer("ricmoo.eth", parseUnits("1.23"));
//_log:
2020-05-08 10:24:40 +03:00
_property: erc20.populateTransaction.transfer(target, amount [, overrides ]) => Promise<[UnsignedTx](UnsignedTransaction)>
Returns an [[UnsignedTransaction]] which could be signed and submitted
2020-02-25 22:57:11 +03:00
to the network to transaction //amount// tokens to //target//.
_code: @lang<javascript>
//_hide: const erc20_rw = _page.erc20_rw;
//_hide: const parseUnits = utils.parseUnits;
//_result:
await erc20_rw.populateTransaction.transfer("ricmoo.eth", parseUnits("1.23"));
//_log:
2020-02-25 22:57:11 +03:00
_note: 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.
_subsection: Meta-Class Filters @NOTE<(added at Runtime)> @<erc20-meta-events>
2020-02-25 22:57:11 +03:00
Since the Contract is a Meta-Class, the methods available here depend
on the ABI which was passed into the **Contract**.
2020-08-20 23:51:41 +03:00
_property: erc20.filters.Transfer([ fromAddress [ , toAddress ] ]) => Filter
2020-02-25 22:57:11 +03:00
Returns a new Filter which can be used to [query](erc20-queryfilter) or
to [subscribe/unsubscribe to events](erc20-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.
_code: query filter *from* events @lang<javascript>
//_hide: const signer = localSigner;
//_hide: const erc20 = _page.erc20;
//_result:
filterFrom = erc20.filters.Transfer(signer.address);
//_log:
// Search for transfers *from* me in the last 10 blocks
//_result:
logsFrom = await erc20.queryFilter(filterFrom, -10, "latest");
//_log:
// Note that the args providees the details of the event, each
// parameters is available positionally, and since our ABI
// included parameter names also by name
//_result:
logsFrom[0].args
//_log:
//_hide: _page.filterFrom = filterFrom;
_code: query filter with *to* events @lang<javascript>
//_hide: const signer = localSigner;
//_hide: const erc20 = _page.erc20;
//_result:
filterTo = erc20.filters.Transfer(null, signer.address);
//_log:
// Search for transfers *to* me in the last 10 blocks
// Note: the contract transferred totalSupply tokens to us
// when it was deployed in its constructor
//_result:
logsTo = await erc20.queryFilter(filterTo, -10, "latest");
//_log:
// Note that the args providees the details of the event, each
// parameters is available positionally, and since our ABI
// included parameter names also by name
//_result:
logsTo[0].args
//_log:
//_hide: _page.filterTo = filterTo;
_code: listen for events @lang<javascript>
//_hide: const erc20 = _page.erc20;
//_hide: const filterFrom = _page.filterFrom;
//_hide: const filterTo = _page.filterTo;
// Listen to incoming events from signer:
erc20.on(filterFrom, (from, to, amount, event) => {
// The `from` will always be the signer address
});
// Listen to incoming events to signer:
erc20.on(filterTo, (from, to, amount, event) => {
// The `to` will always be the signer address
});
// Listen to all Transfer events:
erc20.on("Transfer", (from, to, amount, event) => {
// ...
});
//_hide: erc20.removeAllListeners();