Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
319987ec3e | ||
|
|
2a7ce0e72a | ||
|
|
8ba64af29f | ||
|
|
17af9f812f | ||
|
|
d001901c8c | ||
|
|
91951dc825 | ||
|
|
8277f5a62a | ||
|
|
e615e51fbf | ||
|
|
99422c1c7c | ||
|
|
e8a0144b7a | ||
|
|
f9d09645e7 | ||
|
|
91fff1449d | ||
|
|
c5bca7767e | ||
|
|
79c5bf6bcb | ||
|
|
5456c35924 | ||
|
|
7a12216cfb |
@@ -21,38 +21,40 @@ _subsection: Accounts Methods @<Provider--account-methods>
|
||||
_property: provider.getBalance(address [ , blockTag = latest ]) => Promise<[[BigNumber]]> @<Provider-getBalance> @SRC<providers/base-provider>
|
||||
Returns the balance of //address// as of the //blockTag// block height.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_result:
|
||||
await provider.getBalance("ricmoo.eth");
|
||||
//_log:
|
||||
|
||||
_property: provider.getCode(address [ , blockTag = latest ]) => Promise<string<[[DataHexString]]>> @<Provider-getCode> @SRC<providers/base-provider>
|
||||
Returns the contract code of //address// as of the //blockTag// block height. If there is
|
||||
no contract currently deployed, the result is ``0x``.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_result:
|
||||
await provider.getCode("registrar.firefly.eth");
|
||||
//_log:
|
||||
|
||||
_property: provider.getStorageAt(addr, pos [ , blockTag = latest ]) => Promise<string<[[DataHexString]]>> @<Provider-getStorageAt> @SRC<providers/base-provider>
|
||||
Returns the ``Bytes32`` value of the position //pos// at address //addr//, as of the //blockTag//.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_result:
|
||||
await provider.getStorageAt("registrar.firefly.eth", 0)
|
||||
//_log:
|
||||
|
||||
_property: provider.getTransactionCount(address [ , blockTag = latest ]) => Promise<number> @<Provider-getTransactionCount> @SRC<providers/base-provider>
|
||||
Returns the number of transactions //address// has ever **sent**, as of //blockTag//.
|
||||
This value is required to be the nonce for the next transaction from //address//
|
||||
sent to the network.
|
||||
|
||||
_code: Account Examples @lang<javascript>
|
||||
_code: @lang<javascript>
|
||||
|
||||
// Get the balance for an account...
|
||||
//_result:
|
||||
await provider.getBalance("ricmoo.firefly.eth");
|
||||
//_log:
|
||||
|
||||
// Get the code for a contract...
|
||||
//_result:
|
||||
await provider.getCode("registrar.firefly.eth");
|
||||
//_log:
|
||||
|
||||
// Get the storage value at position 0...
|
||||
//_result:
|
||||
await provider.getStorageAt("registrar.firefly.eth", 0)
|
||||
//_log:
|
||||
|
||||
// Get transaction count of an account...
|
||||
//_result:
|
||||
await provider.getTransactionCount("ricmoo.firefly.eth");
|
||||
await provider.getTransactionCount("ricmoo.eth");
|
||||
//_log:
|
||||
|
||||
|
||||
@@ -62,16 +64,18 @@ _property: provider.getBlock(block) => Promise<[[providers-Block]]> @<Provider-
|
||||
Get the //block// from the network, where the ``result.transactions`` is a list
|
||||
of transaction hashes.
|
||||
|
||||
_property: provider.getBlockWithTransactions(block) => Promise<[[providers-BlockWithTransactions]]> @<Provider-getBlockWithTransactions> @SRC<providers/base-provider>
|
||||
Get the //block// from the network, where the ``result.transactions`` is
|
||||
an Array of [[providers-TransactionResponse]] objects.
|
||||
|
||||
_code: Block Examples @lang<javascript>
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_result:
|
||||
await provider.getBlock(100004)
|
||||
//_log:
|
||||
|
||||
_property: provider.getBlockWithTransactions(block) => Promise<[[providers-BlockWithTransactions]]> @<Provider-getBlockWithTransactions> @SRC<providers/base-provider>
|
||||
Get the //block// from the network, where the ``result.transactions`` is
|
||||
an Array of [[providers-TransactionResponse]] objects.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_result:
|
||||
await provider.getBlockWithTransactions(100004)
|
||||
//_log:
|
||||
@@ -96,26 +100,38 @@ _property: provider.getResolver(name) => Promise<[[EnsResolver]]>
|
||||
Returns an EnsResolver instance which can be used to further inquire
|
||||
about specific entries for an ENS name.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_hide: provider = ethers.getDefaultProvider();
|
||||
|
||||
// See below (Resolver) for examples of using this object
|
||||
const resolver = await provider.getResolver("ricmoo.eth");
|
||||
|
||||
//_hide: _page.resolver = resolver;
|
||||
|
||||
_property: provider.lookupAddress(address) => Promise<string> @<Provider-lookupAddress> @SRC<providers/base-provider>
|
||||
Performs a reverse lookup of the //address// in ENS using the
|
||||
//Reverse Registrar//. If the name does not exist, or the
|
||||
forward lookup does not match, ``null`` is returned.
|
||||
|
||||
An ENS name requries additional configuration to setup a reverse
|
||||
record, they are not automatically set up.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_result:
|
||||
await provider.lookupAddress("0x5555763613a12D8F3e73be831DFf8598089d3dCa");
|
||||
//_log:
|
||||
|
||||
_property: provider.resolveName(name) => Promise<string<[Address](address)>> @<Provider-ResolveName> @SRC<providers/base-provider>
|
||||
Looks up the address of //name//. If the name is not owned, or
|
||||
does not have a //Resolver// configured, or the //Resolver// does
|
||||
not have an address configured, ``null`` is returned.
|
||||
|
||||
_code: ENS Examples @lang<javascript>
|
||||
_code: @lang<javascript>
|
||||
|
||||
// Reverse lookup of an ENS by address...
|
||||
//_result:
|
||||
await provider.lookupAddress("0x6fC21092DA55B392b045eD78F4732bff3C580e2c");
|
||||
//_log:
|
||||
|
||||
// Lookup an address of an ENS name...
|
||||
//_result:
|
||||
await provider.resolveName("ricmoo.firefly.eth");
|
||||
await provider.resolveName("ricmoo.eth");
|
||||
//_log:
|
||||
|
||||
|
||||
@@ -132,12 +148,51 @@ Returns a Promise which resolves to the [[link-eip-2304]] multicoin address
|
||||
stored for the //coinType//. By default an Ethereum [[address]]
|
||||
(``coinType = 60``) will be returned.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_hide: const resolver = _page.resolver;
|
||||
|
||||
// By default, looks up the Ethereum address
|
||||
// (this will match provider.resolveName)
|
||||
//_result:
|
||||
await resolver.getAddress();
|
||||
//_log:
|
||||
|
||||
// Specify the coinType for other coin addresses (0 = Bitcoin)
|
||||
//_result:
|
||||
await resolver.getAddress(0);
|
||||
//_log:
|
||||
|
||||
_property: resolver.getContentHash() => Promise<string>
|
||||
Returns a Promise which resolves to any stored [[link-eip-1577]] content hash.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_hide: const resolver = _page.resolver;
|
||||
|
||||
//_result:
|
||||
await resolver.getContentHash();
|
||||
//_log:
|
||||
|
||||
_property: resolver.getText(key) => Promise<string>
|
||||
Returns a Promise which resolves to any stored [[link-eip-634]] text entry for //key//.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_hide: const resolver = _page.resolver;
|
||||
|
||||
//_result:
|
||||
await resolver.getText("email");
|
||||
//_log:
|
||||
|
||||
//_result:
|
||||
await resolver.getText("url");
|
||||
//_log:
|
||||
|
||||
//_result:
|
||||
await resolver.getText("com.twitter");
|
||||
//_log:
|
||||
|
||||
_subsection: Logs Methods @<Provider--log-methods>
|
||||
|
||||
_property: provider.getLogs(filter) => Promise<Array<[[providers-Log]]>> @<Provider-getLogs> @SRC<providers/base-provider>
|
||||
@@ -153,12 +208,38 @@ _subsection: Network Status Methods @<Provider--network-methods>
|
||||
_property: provider.getNetwork() => Promise<[[providers-Network]]> @<Provider-getNetwork> @SRC<providers/base-provider:method.BaseProvider.getNetwork>
|
||||
Returns the [[providers-Network]] this Provider is connected to.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_result:
|
||||
await provider.getNetwork()
|
||||
//_hide: _ = utils.shallowCopy(_);
|
||||
//_hide: delete _._defaultProvider;
|
||||
//_log:
|
||||
|
||||
_property: provider.getBlockNumber() => Promise<number> @<Provider-getBlockNumber> @SRC<providers/base-provider>
|
||||
Returns the block number (or height) of the most recently mined block.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_result:
|
||||
await provider.getBlockNumber()
|
||||
//_log:
|
||||
|
||||
_property: provider.getGasPrice() => Promise<[[BigNumber]]> @<Provider-getGasPrice> @SRC<providers/base-provider>
|
||||
Returns a //best guess// of the [[gas-price]] to use in a transaction.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
// The gas price (in wei)...
|
||||
gasPrice = await provider.getGasPrice()
|
||||
//_log: gasPrice
|
||||
|
||||
// ...often this gas price is easier to understand or
|
||||
// display to the user in gwei
|
||||
//_result:
|
||||
utils.formatUnits(gasPrice, "gwei")
|
||||
//_log:
|
||||
|
||||
_property: provider.ready => Promise<[[providers-Network]]> @<Provider-ready> @src<providers/base-provider>
|
||||
Returns a Promise which will stall until the network has heen established,
|
||||
ignoring errors due to the target node not being active yet.
|
||||
@@ -166,29 +247,6 @@ ignoring errors due to the target node not being active yet.
|
||||
This can be used for testing or attaching scripts to wait until the node is
|
||||
up and running smoothly.
|
||||
|
||||
_code: Network Status Examples @lang<javascript>
|
||||
|
||||
// The network information
|
||||
network = await provider.getNetwork()
|
||||
//_hide: network = utils.shallowCopy(network);
|
||||
//_hide: delete network._defaultProvider;
|
||||
//_log: network
|
||||
|
||||
// The current block number
|
||||
//_result:
|
||||
await provider.getBlockNumber()
|
||||
//_log:
|
||||
|
||||
// Get the current suggested gas price (in wei)...
|
||||
gasPrice = await provider.getGasPrice()
|
||||
//_log: gasPrice
|
||||
|
||||
// ...often this gas price is easier to understand or
|
||||
// display to the user in gwei (giga-wei, or 1e9 wei)
|
||||
//_result:
|
||||
utils.formatUnits(gasPrice, "gwei")
|
||||
//_log:
|
||||
|
||||
|
||||
_subsection: Transactions Methods @<Provider--transaction-methods>
|
||||
|
||||
@@ -197,6 +255,18 @@ Returns the result of executing the //transaction//, using //call//. A call
|
||||
does not require any ether, but cannot change any state. This is useful
|
||||
for calling getters on Contracts.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_result:
|
||||
await provider.call({
|
||||
// ENS public resovler address
|
||||
to: "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41",
|
||||
|
||||
// `function addr(namehash("ricmoo.eth")) view returns (address)`
|
||||
data: "0x3b3b57debf074faa138b72c65adbdcfb329847e4f2c04bde7f7dd7fcad5a52d2f395a558"
|
||||
});
|
||||
//_log:
|
||||
|
||||
_property: provider.estimateGas(transaction) => Promise<[[BigNumber]]> @<Provider-estimateGas> @SRC<providers/base-provider>
|
||||
Returns an estimate of the amount of gas that would be required to submit //transaction//
|
||||
to the network.
|
||||
@@ -205,6 +275,23 @@ An estimate may not be accurate since there could be another transaction
|
||||
on the network that was not accounted for, but after being mined affected
|
||||
relevant state.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_hide: const parseEther = ethers.utils.parseEther;
|
||||
|
||||
//_result:
|
||||
await provider.estimateGas({
|
||||
// Wrapped ETH address
|
||||
to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
|
||||
// `function deposit() payable`
|
||||
data: "0xd0e30db0",
|
||||
|
||||
// 1 ether
|
||||
value: parseEther("1.0")
|
||||
});
|
||||
//_log:
|
||||
|
||||
_property: provider.getTransaction(hash) => Promise<[[providers-TransactionResponse]]> @<Provider-getTransaction> @src<providers/base-provider>
|
||||
Returns the transaction with //hash// or null if the transaction is unknown.
|
||||
|
||||
@@ -213,6 +300,12 @@ pool. Various backends may have more restrictive transaction pool access (e.g.
|
||||
if the gas price is too low or the transaction was only recently sent and not
|
||||
yet indexed) in which case this method may also return null.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_result:
|
||||
await provider.getTransaction("0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0");
|
||||
//_log:
|
||||
|
||||
_property: provider.getTransactionReceipt(hash) => Promise<[[providers-TransactionReceipt]]> @<Provider-getTransactionReceipt> @src<providers/base-provider>
|
||||
Returns the transaction receipt for //hash// or null if the transaction
|
||||
has not been mined.
|
||||
@@ -220,11 +313,31 @@ has not been mined.
|
||||
To stall until the transaction has been mined, consider the ``waitForTransaction``
|
||||
method below.
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_result:
|
||||
await provider.getTransactionReceipt("0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0");
|
||||
//_log:
|
||||
|
||||
_property: provider.sendTransaction(transaction) => Promise<[[providers-TransactionResponse]]> @<Provider-sendTransaction> @SRC<providers/base-provider>
|
||||
Submits //transaction// to the network to be mined. The //transaction// **must** be signed,
|
||||
and be valid (i.e. the nonce is correct and the account has sufficient balance to pay
|
||||
for the transaction).
|
||||
|
||||
_code: @lang<javascript>
|
||||
|
||||
//_hide: const provider = localProvider;
|
||||
//_hide: const wallet = new ethers.Wallet(ethers.utils.id("HelloWorld"), provider);
|
||||
//_hide: const fundTx = await localSigner.sendTransaction({
|
||||
//_hide: to: wallet.address,
|
||||
//_hide: value: ethers.utils.parseEther("2.0")
|
||||
//_hide: });
|
||||
//_hide: await fundTx.wait();
|
||||
|
||||
//_result:
|
||||
await provider.sendTransaction("0xf86e808502540be400825208948ba1f109551bd432803012645ac136ddd64dba72880de0b6b3a764000080820a96a0f0c5bcb11e5a16ab116c60a0e5837ae98ec36e7f217740076572e8183002edd2a01ed1b4411c2840b9793e8be5415a554507f1ea320069be6dcecabd7b9097dbd4");
|
||||
//_log:
|
||||
|
||||
_property: provider.waitForTransaction(hash [ , confirms = 1 [ , timeout ] ]) => Promise<[TxReceipt](providers-TransactionReceipt)> @<Provider-waitForTransaction> @SRC<providers/base-provider>
|
||||
Returns a Promise which will not resolve until //transactionHash// is mined.
|
||||
|
||||
|
||||
@@ -74,6 +74,12 @@ _property: signer.signMessage(message) => Promise<string<[RawSignature](signatur
|
||||
This returns a Promise which resolves to the [[signature-raw]]
|
||||
of //message//.
|
||||
|
||||
A signed message is prefixd with ``"\\x19Ethereum signed message:\\n"`` and
|
||||
the length of the message, using the [hashMessage](utils-hashMessage)
|
||||
method, so that it is [EIP-191](link-eip-191) compliant. If recovering
|
||||
the address in Solidity, this prefix will be required to create a matching
|
||||
hash.
|
||||
|
||||
Sub-classes **must** implement this, however they may throw if signing a
|
||||
message is not supported, such as in a Contract-based Wallet or
|
||||
Meta-Transaction-based Wallet.
|
||||
|
||||
@@ -47,14 +47,29 @@ are not required for the ABI and used old by Solidity's semantic checking
|
||||
system, such as input parameter data location like ``"calldata"``
|
||||
and ``"memory"``. As such, they can be safely dropped in the ABI as well.
|
||||
|
||||
_code: A simple Human-Readable ABI @lang<javascript>
|
||||
_code: Human-Readable ABI Example @lang<javascript>
|
||||
|
||||
const humanReadableAbi = [
|
||||
|
||||
// Simple types
|
||||
"constructor(string symbol, string name)",
|
||||
"function transferFrom(address from, address to, uint value)",
|
||||
"function balanceOf(address owner) view returns (uint balance)",
|
||||
"event Transfer(address indexed from, address indexed to, address value)",
|
||||
"error InsufficientBalance(account owner, uint balance)"
|
||||
"error InsufficientBalance(account owner, uint balance)",
|
||||
|
||||
// Some examples with the struct type, we use the tuple keyword:
|
||||
// (note: the tuple keyword is optional, simply using additional
|
||||
// parentheses accomplishes the same thing)
|
||||
// struct Person {
|
||||
// string name;
|
||||
// uint16 age;
|
||||
// }
|
||||
"function addPerson(tuple(string name, uint16 age) person)",
|
||||
"function addPeople(tuple(string name, uint16 age)[] person)",
|
||||
"function getPerson(uint id) view returns (tuple(string name, uint16 age))",
|
||||
|
||||
"event PersonAdded(uint indexed id, tuple(string name, uint16 age) person)"
|
||||
];
|
||||
|
||||
//_hide: _page.humanReadableAbi = humanReadableAbi;
|
||||
@@ -128,6 +143,75 @@ const jsonAbi = `[
|
||||
{ "type": "account", "name": "owner"},
|
||||
{ "type": "uint256", "name": "balance"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "addPerson",
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"inputs": [
|
||||
{
|
||||
"type": "tuple",
|
||||
"name": "person",
|
||||
"components": [
|
||||
{ "type": "string", "name": "name" },
|
||||
{ "type": "uint16", "name": "age" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "addPeople",
|
||||
"constant": false,
|
||||
"payable": false,
|
||||
"inputs": [
|
||||
{
|
||||
"type": "tuple[]",
|
||||
"name": "person",
|
||||
"components": [
|
||||
{ "type": "string", "name": "name" },
|
||||
{ "type": "uint16", "name": "age" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"name": "getPerson",
|
||||
"constant": true,
|
||||
"stateMutability": "view",
|
||||
"payable": false,
|
||||
"inputs": [
|
||||
{ "type": "uint256", "name": "id" }
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"type": "tuple",
|
||||
"components": [
|
||||
{ "type": "string", "name": "name" },
|
||||
{ "type": "uint16", "name": "age" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "event",
|
||||
"anonymous": false,
|
||||
"name": "PersonAdded",
|
||||
"inputs": [
|
||||
{ "type": "uint256", "name": "id", "indexed": true },
|
||||
{
|
||||
"type": "tuple",
|
||||
"name": "person",
|
||||
"components": [
|
||||
{ "type": "string", "name": "name", "indexed": false },
|
||||
{ "type": "uint16", "name": "age", "indexed": false }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]`;
|
||||
|
||||
@@ -156,7 +240,7 @@ and errors are available. It is also highly recommend to strip out
|
||||
unused parts of the ABI (such as admin methods) to further reduce
|
||||
code size.
|
||||
|
||||
_code: Converting to Human-Readable ABI @lang<javascript>
|
||||
_code: Converting to Full Human-Readable ABI @lang<javascript>
|
||||
|
||||
//_hide: const Interface = ethers.utils.Interface;
|
||||
//_hide: const FormatTypes = ethers.utils.FormatTypes;
|
||||
@@ -169,9 +253,16 @@ const iface = new Interface(jsonAbi);
|
||||
iface.format(FormatTypes.full);
|
||||
//_log:
|
||||
|
||||
_code: Converting to Minimal Human-Readable ABI @lang<javascript>
|
||||
|
||||
//_hide: const Interface = ethers.utils.Interface;
|
||||
//_hide: const FormatTypes = ethers.utils.FormatTypes;
|
||||
//_hide: jsonAbi = _page.jsonAbi;
|
||||
|
||||
// Using the "minimal" format will save a small amount of
|
||||
// space, but is generally not worth it as named properties
|
||||
// on Results will not be available
|
||||
const iface = new Interface(jsonAbi);
|
||||
//_result:
|
||||
iface.format(FormatTypes.minimal);
|
||||
//_log:
|
||||
|
||||
@@ -52,21 +52,21 @@ _heading: Output Formats @<fragments--output-formats> @SRC<abi/fragments:FormatT
|
||||
Each [[Fragment]] and [[ParamType]] may be output using its ``format``
|
||||
method.
|
||||
|
||||
_property: ethers.utils.FragmentTypes.full => string
|
||||
_property: ethers.utils.FormatTypes.full => string
|
||||
This is a full human-readable string, including all parameter names, any
|
||||
optional modifiers (e.g. ``indexed``, ``public``, etc) and white-space
|
||||
to aid in human readability.
|
||||
|
||||
_property: ethers.utils.FragmentTypes.minimal => string
|
||||
_property: ethers.utils.FormatTypes.minimal => string
|
||||
This is similar to ``full``, except with no unnecessary whitespace or parameter
|
||||
names. This is useful for storing a minimal string which can still fully
|
||||
reconstruct the original Fragment using [Fragment . from](Fragment-from).
|
||||
|
||||
_property: ethers.utils.FragmentTypes.json => string
|
||||
_property: ethers.utils.FormatTypes.json => string
|
||||
This returns a JavaScript Object which is safe to call ``JSON.stringify``
|
||||
on to create a JSON string.
|
||||
|
||||
_property: ethers.utils.FragmentTypes.sighash => string
|
||||
_property: ethers.utils.FormatTypes.sighash => string
|
||||
This is a minimal output format, which is used by Solidity when computing a
|
||||
signature hash or an event topic hash.
|
||||
|
||||
@@ -74,10 +74,14 @@ _warning: Note
|
||||
The ``sighash`` format is **insufficient** to re-create the original [[Fragment]],
|
||||
since it discards modifiers such as indexed, anonymous, stateMutability, etc.
|
||||
|
||||
It is only useful for computing the selector for a Fragment, and cannot
|
||||
be used to format an Interface.
|
||||
|
||||
|
||||
_subsection: Fragment @<Fragment> @SRC<abi/fragments:class.Fragment>
|
||||
An ABI is a collection of **Fragments**, where each fragment specifies:
|
||||
|
||||
- An [Error](ErrorFragment)
|
||||
- An [Event](EventFragment)
|
||||
- A [Function](FunctionFragment)
|
||||
- A [Constructor](ConstructorFragment)
|
||||
|
||||
@@ -33,12 +33,28 @@ _code: Creating an Interface instance @lang<javascript>
|
||||
// This interface is used for the below examples
|
||||
|
||||
const iface = new Interface([
|
||||
// Constructor
|
||||
"constructor(string symbol, string name)",
|
||||
|
||||
// State mutating method
|
||||
"function transferFrom(address from, address to, uint amount)",
|
||||
"function balanceOf(address owner) view returns (uint)",
|
||||
|
||||
// State mutating method, which is payable
|
||||
"function mint(uint amount) payable",
|
||||
|
||||
// Constant method (i.e. "view" or "pure")
|
||||
"function balanceOf(address owner) view returns (uint)",
|
||||
|
||||
// An Event
|
||||
"event Transfer(address indexed from, address indexed to, uint256 amount)",
|
||||
"error AccountLocked(address owner, uint256 balance)"
|
||||
|
||||
// A Custom Solidity Error
|
||||
"error AccountLocked(address owner, uint256 balance)",
|
||||
|
||||
// Examples with structured types
|
||||
"function addUser(tuple(string name, address addr) user) returns (uint id)",
|
||||
"function addUsers(tuple(string name, address addr)[] user) returns (uint[] id)",
|
||||
"function getUser(uint id) view returns (tuple(string name, address addr) user)"
|
||||
]);
|
||||
|
||||
//_hide: _page.iface = iface;
|
||||
@@ -263,6 +279,25 @@ iface.encodeFunctionData("transferFrom", [
|
||||
])
|
||||
//_log:
|
||||
|
||||
// Encoding structured data (using positional Array)
|
||||
user = [
|
||||
"Richard Moore",
|
||||
"0x8ba1f109551bD432803012645Ac136ddd64DBA72"
|
||||
];
|
||||
//_result:
|
||||
iface.encodeFunctionData("addUser", [ user ]);
|
||||
//_log:
|
||||
|
||||
// Encoding structured data, using objects. Only available
|
||||
// if paramters are named.
|
||||
user = {
|
||||
name: "Richard Moore",
|
||||
addr: "0x8ba1f109551bD432803012645Ac136ddd64DBA72"
|
||||
};
|
||||
//_result:
|
||||
iface.encodeFunctionData("addUser", [ user ]);
|
||||
//_log:
|
||||
|
||||
_property: interface.encodeFunctionResult(fragment [ , values ]) => string<[[DataHexString]]> @SRC<abi/interface>
|
||||
Returns the encoded result, which would normally be the response from a call for
|
||||
//fragment// (see [[Interface--specifying-fragments]]) for the given //values//.
|
||||
@@ -342,18 +377,37 @@ _code: @lang<javascript>
|
||||
//_hide: const iface = _page.iface;
|
||||
|
||||
// Decoding result data (e.g. from an eth_call)
|
||||
const resultData = "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000";
|
||||
resultData = "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000";
|
||||
//_result:
|
||||
iface.decodeFunctionResult("balanceOf", resultData)
|
||||
//_log:
|
||||
|
||||
// Decoding result data which was caused by a revert
|
||||
// Throws a CALL_EXCEPTION, with extra details
|
||||
const errorData = "0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000";
|
||||
errorData = "0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000";
|
||||
//_throws:
|
||||
iface.decodeFunctionResult("balanceOf", errorData)
|
||||
//_log:
|
||||
|
||||
// Decoding structured data returns a Result object, which
|
||||
// will include all values positionally and if the ABI
|
||||
// included names, values will additionally be available
|
||||
// by their name.
|
||||
resultData = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000000000000000000000000000000000000000000d52696368617264204d6f6f726500000000000000000000000000000000000000";
|
||||
//_result:
|
||||
result = iface.decodeFunctionResult("getUser", resultData);
|
||||
//_log:
|
||||
|
||||
// Access positionally:
|
||||
// The 0th output parameter, the 0th proerty of the structure
|
||||
//_result:
|
||||
result[0][0];
|
||||
//_log:
|
||||
|
||||
// Access by name: (only avilable because parameters were named)
|
||||
//_result:
|
||||
result.user.name
|
||||
//_log:
|
||||
|
||||
_subsection: Parsing @<Interface--parsing>
|
||||
|
||||
|
||||
@@ -179,12 +179,20 @@ function codeContextify(context) {
|
||||
sorted: true,
|
||||
});
|
||||
}
|
||||
|
||||
context._startup = function() {
|
||||
console.log("Startup");
|
||||
}
|
||||
|
||||
context._shutdown = function() {
|
||||
console.log("Shutdown");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
title: "ethers",
|
||||
subtitle: "v5.2",
|
||||
subtitle: "v5.3",
|
||||
description: "Documentation for ethers, a complete, tiny and simple Ethereum library.",
|
||||
logo: "logo.svg",
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ apikey asc endblock startblock
|
||||
alchemyapi Cloudflare Etherscan INFURA IPFS MetaMask Nodesmith
|
||||
Trezor ledgerhq axic bitcoinjs browserify easyseed ethereumjs
|
||||
goerli homestead kotti kovan mainnet morden mordor rinkeby
|
||||
ropsten testnet lb
|
||||
ropsten testnet lb maticmum
|
||||
|
||||
// Demo words
|
||||
args foo eth foo foobar ll localhost passwd ricmoo tx xxx yna
|
||||
|
||||
@@ -73,7 +73,7 @@ function _getUrl(href, options) {
|
||||
options = {};
|
||||
}
|
||||
// @TODO: Once we drop support for node 8, we can pass the href
|
||||
// firectly into request and skip adding the components
|
||||
// directly into request and skip adding the components
|
||||
// to this request object
|
||||
const url = url_1.parse(href);
|
||||
const request = {
|
||||
|
||||
@@ -66,7 +66,7 @@ apikey asc endblock startblock
|
||||
alchemyapi Cloudflare Etherscan INFURA IPFS MetaMask Nodesmith
|
||||
Trezor ledgerhq axic bitcoinjs browserify easyseed ethereumjs
|
||||
goerli homestead kotti kovan mainnet morden mordor rinkeby
|
||||
ropsten testnet lb
|
||||
ropsten testnet lb maticmum
|
||||
|
||||
// Demo words
|
||||
args foo eth foo foobar ll localhost passwd ricmoo tx xxx yna
|
||||
|
||||
@@ -79,7 +79,7 @@ async function _getUrl(href: string, options?: Options): Promise<GetUrlResponse>
|
||||
if (options == null) { options = { }; }
|
||||
|
||||
// @TODO: Once we drop support for node 8, we can pass the href
|
||||
// firectly into request and skip adding the components
|
||||
// directly into request and skip adding the components
|
||||
// to this request object
|
||||
const url = parse(href);
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"aes-js": "3.0.0",
|
||||
"aws-sdk": "2.137.0",
|
||||
"diff": "4.0.1",
|
||||
"flatworm": "0.0.2-beta.5",
|
||||
"flatworm": "0.0.2-beta.6",
|
||||
"jison": "0.4.18",
|
||||
"karma": "6.3.2",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
|
||||
@@ -11,6 +11,7 @@ export interface JsonFragmentType {
|
||||
readonly name?: string;
|
||||
readonly indexed?: boolean;
|
||||
readonly type?: string;
|
||||
readonly internalType?: any; // @TODO: in v6 reduce type
|
||||
readonly components?: ReadonlyArray<JsonFragmentType>;
|
||||
}
|
||||
|
||||
@@ -30,7 +31,6 @@ export interface JsonFragment {
|
||||
readonly gas?: string;
|
||||
};
|
||||
|
||||
|
||||
const _constructorGuard = { };
|
||||
|
||||
// AST Node parser state
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
|
||||
import { BytesLike, isHexString } from "@ethersproject/bytes";
|
||||
import { Network } from "@ethersproject/networks";
|
||||
import { Deferrable, Description, defineReadOnly } from "@ethersproject/properties";
|
||||
import { Deferrable, Description, defineReadOnly, resolveProperties } from "@ethersproject/properties";
|
||||
import { AccessListish, Transaction } from "@ethersproject/transactions";
|
||||
import { OnceBlockable } from "@ethersproject/web";
|
||||
|
||||
@@ -29,6 +29,9 @@ export type TransactionRequest = {
|
||||
|
||||
type?: number;
|
||||
accessList?: AccessListish;
|
||||
|
||||
maxPriorityFeePerGas?: BigNumberish;
|
||||
maxFeePerGas?: BigNumberish;
|
||||
}
|
||||
|
||||
export interface TransactionResponse extends Transaction {
|
||||
@@ -67,6 +70,8 @@ interface _Block {
|
||||
|
||||
miner: string;
|
||||
extraData: string;
|
||||
|
||||
baseFeePerGas?: null | BigNumber;
|
||||
}
|
||||
|
||||
export interface Block extends _Block {
|
||||
@@ -109,9 +114,16 @@ export interface TransactionReceipt {
|
||||
confirmations: number,
|
||||
cumulativeGasUsed: BigNumber,
|
||||
byzantium: boolean,
|
||||
type: number;
|
||||
status?: number
|
||||
};
|
||||
|
||||
export interface FeeData {
|
||||
maxFeePerGas: null | BigNumber;
|
||||
maxPriorityFeePerGas: null | BigNumber;
|
||||
gasPrice: null | BigNumber;
|
||||
}
|
||||
|
||||
export interface EventFilter {
|
||||
address?: string;
|
||||
topics?: Array<string | Array<string> | null>;
|
||||
@@ -206,7 +218,6 @@ export type Listener = (...args: Array<any>) => void;
|
||||
|
||||
///////////////////////////////
|
||||
// Exported Abstracts
|
||||
|
||||
export abstract class Provider implements OnceBlockable {
|
||||
|
||||
// Network
|
||||
@@ -215,6 +226,28 @@ export abstract class Provider implements OnceBlockable {
|
||||
// Latest State
|
||||
abstract getBlockNumber(): Promise<number>;
|
||||
abstract getGasPrice(): Promise<BigNumber>;
|
||||
async getFeeData(): Promise<FeeData> {
|
||||
const { block, gasPrice } = await resolveProperties({
|
||||
block: this.getBlock("latest"),
|
||||
gasPrice: this.getGasPrice().catch((error) => {
|
||||
// @TODO: Why is this now failing on Calaveras?
|
||||
//console.log(error);
|
||||
return null;
|
||||
})
|
||||
});
|
||||
|
||||
let maxFeePerGas = null, maxPriorityFeePerGas = null;
|
||||
|
||||
if (block && block.baseFeePerGas) {
|
||||
// We may want to compute this more accurately in the future,
|
||||
// using the formula "check if the base fee is correct".
|
||||
// See: https://eips.ethereum.org/EIPS/eip-1559
|
||||
maxPriorityFeePerGas = BigNumber.from("1000000000");
|
||||
maxFeePerGas = block.baseFeePerGas.mul(2).add(maxPriorityFeePerGas);
|
||||
}
|
||||
|
||||
return { maxFeePerGas, maxPriorityFeePerGas, gasPrice };
|
||||
}
|
||||
|
||||
// Account
|
||||
abstract getBalance(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<BigNumber>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
import { BlockTag, Provider, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
|
||||
import { BlockTag, FeeData, Provider, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
|
||||
import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
|
||||
import { Bytes, BytesLike } from "@ethersproject/bytes";
|
||||
import { Deferrable, defineReadOnly, resolveProperties, shallowCopy } from "@ethersproject/properties";
|
||||
@@ -10,7 +10,7 @@ import { version } from "./_version";
|
||||
const logger = new Logger(version);
|
||||
|
||||
const allowedTransactionKeys: Array<string> = [
|
||||
"accessList", "chainId", "data", "from", "gasLimit", "gasPrice", "nonce", "to", "type", "value"
|
||||
"accessList", "chainId", "data", "from", "gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "to", "type", "value"
|
||||
];
|
||||
|
||||
const forwardErrors = [
|
||||
@@ -139,6 +139,12 @@ export abstract class Signer {
|
||||
return await this.provider.getGasPrice();
|
||||
}
|
||||
|
||||
async getFeeData(): Promise<FeeData> {
|
||||
this._checkProvider("getFeeData");
|
||||
return await this.provider.getFeeData();
|
||||
}
|
||||
|
||||
|
||||
async resolveName(name: string): Promise<string> {
|
||||
this._checkProvider("resolveName");
|
||||
return await this.provider.resolveName(name);
|
||||
@@ -146,7 +152,6 @@ export abstract class Signer {
|
||||
|
||||
|
||||
|
||||
|
||||
// Checks a transaction does not contain invalid keys and if
|
||||
// no "from" is provided, populates it.
|
||||
// - does NOT require a provider
|
||||
@@ -167,6 +172,7 @@ export abstract class Signer {
|
||||
|
||||
if (tx.from == null) {
|
||||
tx.from = this.getAddress();
|
||||
|
||||
} else {
|
||||
// Make sure any provided address matches this signer
|
||||
tx.from = Promise.all([
|
||||
@@ -187,6 +193,9 @@ export abstract class Signer {
|
||||
// this Signer. Should be used by sendTransaction but NOT by signTransaction.
|
||||
// By default called from: (overriding these prevents it)
|
||||
// - sendTransaction
|
||||
//
|
||||
// Notes:
|
||||
// - We allow gasPrice for EIP-1559 as long as it matches maxFeePerGas
|
||||
async populateTransaction(transaction: Deferrable<TransactionRequest>): Promise<TransactionRequest> {
|
||||
|
||||
const tx: Deferrable<TransactionRequest> = await resolveProperties(this.checkTransaction(transaction))
|
||||
@@ -201,7 +210,85 @@ export abstract class Signer {
|
||||
return address;
|
||||
});
|
||||
}
|
||||
if (tx.gasPrice == null) { tx.gasPrice = this.getGasPrice(); }
|
||||
|
||||
// Do not allow mixing pre-eip-1559 and eip-1559 proerties
|
||||
const hasEip1559 = (tx.maxFeePerGas != null || tx.maxPriorityFeePerGas != null);
|
||||
if (tx.gasPrice != null && (tx.type === 2 || hasEip1559)) {
|
||||
logger.throwArgumentError("eip-1559 transaction do not support gasPrice", "transaction", transaction);
|
||||
} else if ((tx.type === 0 || tx.type === 1) && hasEip1559) {
|
||||
logger.throwArgumentError("pre-eip-1559 transaction do not support maxFeePerGas/maxPriorityFeePerGas", "transaction", transaction);
|
||||
}
|
||||
|
||||
if ((tx.type === 2 || tx.type == null) && (tx.maxFeePerGas != null && tx.maxPriorityFeePerGas != null)) {
|
||||
// Fully-formed EIP-1559 transaction (skip getFeeData)
|
||||
tx.type = 2;
|
||||
|
||||
} else if (tx.type === 0 || tx.type === 1) {
|
||||
// Explicit Legacy or EIP-2930 transaction
|
||||
|
||||
// Populate missing gasPrice
|
||||
if (tx.gasPrice == null) { tx.gasPrice = this.getGasPrice(); }
|
||||
|
||||
} else {
|
||||
|
||||
// We need to get fee data to determine things
|
||||
const feeData = await this.getFeeData();
|
||||
|
||||
if (tx.type == null) {
|
||||
// We need to auto-detect the intended type of this transaction...
|
||||
|
||||
if (feeData.maxFeePerGas != null && feeData.maxPriorityFeePerGas != null) {
|
||||
// The network supports EIP-1559!
|
||||
|
||||
// Upgrade transaction from null to eip-1559
|
||||
tx.type = 2;
|
||||
|
||||
if (tx.gasPrice != null) {
|
||||
// Using legacy gasPrice property on an eip-1559 network,
|
||||
// so use gasPrice as both fee properties
|
||||
const gasPrice = tx.gasPrice;
|
||||
delete tx.gasPrice;
|
||||
tx.maxFeePerGas = gasPrice;
|
||||
tx.maxPriorityFeePerGas = gasPrice;
|
||||
|
||||
} else {
|
||||
// Populate missing fee data
|
||||
if (tx.maxFeePerGas == null) { tx.maxFeePerGas = feeData.maxFeePerGas; }
|
||||
if (tx.maxPriorityFeePerGas == null) { tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas; }
|
||||
}
|
||||
|
||||
} else if (feeData.gasPrice != null) {
|
||||
// Network doesn't support EIP-1559...
|
||||
|
||||
// ...but they are trying to use EIP-1559 properties
|
||||
if (hasEip1559) {
|
||||
logger.throwError("network does not support EIP-1559", Logger.errors.UNSUPPORTED_OPERATION, {
|
||||
operation: "populateTransaction"
|
||||
});
|
||||
}
|
||||
|
||||
// Populate missing fee data
|
||||
if (tx.gasPrice == null) { tx.gasPrice = feeData.gasPrice; }
|
||||
|
||||
// Explicitly set untyped transaction to legacy
|
||||
tx.type = 0;
|
||||
|
||||
} else {
|
||||
// getFeeData has failed us.
|
||||
logger.throwError("failed to get consistent fee data", Logger.errors.UNSUPPORTED_OPERATION, {
|
||||
operation: "signer.getFeeData"
|
||||
});
|
||||
}
|
||||
|
||||
} else if (tx.type === 2) {
|
||||
// Explicitly using EIP-1559
|
||||
|
||||
// Populate missing fee data
|
||||
if (tx.maxFeePerGas == null) { tx.maxFeePerGas = feeData.maxFeePerGas; }
|
||||
if (tx.maxPriorityFeePerGas == null) { tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas; }
|
||||
}
|
||||
}
|
||||
|
||||
if (tx.nonce == null) { tx.nonce = this.getTransactionCount("pending"); }
|
||||
|
||||
if (tx.gasLimit == null) {
|
||||
|
||||
@@ -17,6 +17,8 @@ const logger = new Logger(version);
|
||||
export interface Overrides {
|
||||
gasLimit?: BigNumberish | Promise<BigNumberish>;
|
||||
gasPrice?: BigNumberish | Promise<BigNumberish>;
|
||||
maxFeePerGas?: BigNumberish | Promise<BigNumberish>;
|
||||
maxPriorityFeePerGas?: BigNumberish | Promise<BigNumberish>;
|
||||
nonce?: BigNumberish | Promise<BigNumberish>;
|
||||
type?: number;
|
||||
accessList?: AccessListish;
|
||||
@@ -50,6 +52,9 @@ export interface PopulatedTransaction {
|
||||
|
||||
type?: number;
|
||||
accessList?: AccessList;
|
||||
|
||||
maxFeePerGas?: BigNumber;
|
||||
maxPriorityFeePerGas?: BigNumber;
|
||||
};
|
||||
|
||||
export type EventFilter = {
|
||||
@@ -101,6 +106,7 @@ export interface ContractTransaction extends TransactionResponse {
|
||||
const allowedTransactionKeys: { [ key: string ]: boolean } = {
|
||||
chainId: true, data: true, from: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true,
|
||||
type: true, accessList: true,
|
||||
maxFeePerGas: true, maxPriorityFeePerGas: true
|
||||
}
|
||||
|
||||
async function resolveName(resolver: Signer | Provider, nameOrPromise: string | Promise<string>): Promise<string> {
|
||||
@@ -216,6 +222,8 @@ async function populateTransaction(contract: Contract, fragment: FunctionFragmen
|
||||
if (ro.nonce != null) { tx.nonce = BigNumber.from(ro.nonce).toNumber(); }
|
||||
if (ro.gasLimit != null) { tx.gasLimit = BigNumber.from(ro.gasLimit); }
|
||||
if (ro.gasPrice != null) { tx.gasPrice = BigNumber.from(ro.gasPrice); }
|
||||
if (ro.maxFeePerGas != null) { tx.maxFeePerGas = BigNumber.from(ro.maxFeePerGas); }
|
||||
if (ro.maxPriorityFeePerGas != null) { tx.maxPriorityFeePerGas = BigNumber.from(ro.maxPriorityFeePerGas); }
|
||||
if (ro.from != null) { tx.from = ro.from; }
|
||||
|
||||
if (ro.type != null) { tx.type = ro.type; }
|
||||
@@ -259,6 +267,9 @@ async function populateTransaction(contract: Contract, fragment: FunctionFragmen
|
||||
delete overrides.type;
|
||||
delete overrides.accessList;
|
||||
|
||||
delete overrides.maxFeePerGas;
|
||||
delete overrides.maxPriorityFeePerGas;
|
||||
|
||||
// Make sure there are no stray overrides, which may indicate a
|
||||
// typo or using an unsupported key.
|
||||
const leftovers = Object.keys(overrides).filter((key) => ((<any>overrides)[key] != null));
|
||||
|
||||
@@ -17,7 +17,7 @@ import { checkProperties, deepCopy, defineReadOnly, getStatic, resolveProperties
|
||||
import * as RLP from "@ethersproject/rlp";
|
||||
import { computePublicKey, recoverPublicKey, SigningKey } from "@ethersproject/signing-key";
|
||||
import { formatBytes32String, nameprep, parseBytes32String, _toEscapedUtf8String, toUtf8Bytes, toUtf8CodePoints, toUtf8String, Utf8ErrorFuncs } from "@ethersproject/strings";
|
||||
import { accessListify, computeAddress, parse as parseTransaction, recoverAddress, serialize as serializeTransaction } from "@ethersproject/transactions";
|
||||
import { accessListify, computeAddress, parse as parseTransaction, recoverAddress, serialize as serializeTransaction, TransactionTypes } from "@ethersproject/transactions";
|
||||
import { commify, formatEther, parseEther, formatUnits, parseUnits } from "@ethersproject/units";
|
||||
import { verifyMessage, verifyTypedData } from "@ethersproject/wallet";
|
||||
import { _fetchData, fetchJson, poll } from "@ethersproject/web";
|
||||
@@ -153,6 +153,7 @@ export {
|
||||
accessListify,
|
||||
parseTransaction,
|
||||
serializeTransaction,
|
||||
TransactionTypes,
|
||||
|
||||
getJsonWalletAddress,
|
||||
|
||||
|
||||
@@ -250,19 +250,23 @@ export class Resolver implements EnsResolver {
|
||||
}
|
||||
|
||||
async _fetchBytes(selector: string, parameters?: string): Promise<string> {
|
||||
|
||||
// keccak256("addr(bytes32,uint256)")
|
||||
const transaction = {
|
||||
to: this.address,
|
||||
data: hexConcat([ selector, namehash(this.name), (parameters || "0x") ])
|
||||
};
|
||||
|
||||
const result = await this.provider.call(transaction);
|
||||
if (result === "0x") { return null; }
|
||||
try {
|
||||
const result = await this.provider.call(transaction);
|
||||
if (result === "0x") { return null; }
|
||||
|
||||
const offset = BigNumber.from(hexDataSlice(result, 0, 32)).toNumber();
|
||||
const length = BigNumber.from(hexDataSlice(result, offset, offset + 32)).toNumber();
|
||||
return hexDataSlice(result, offset + 32, offset + 32 + length);
|
||||
const offset = BigNumber.from(hexDataSlice(result, 0, 32)).toNumber();
|
||||
const length = BigNumber.from(hexDataSlice(result, offset, offset + 32)).toNumber();
|
||||
return hexDataSlice(result, offset + 32, offset + 32 + length);
|
||||
} catch (error) {
|
||||
if (error.code === Logger.errors.CALL_EXCEPTION) { return null; }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
_getAddress(coinType: number, hexBytes: string): string {
|
||||
@@ -332,17 +336,22 @@ export class Resolver implements EnsResolver {
|
||||
|
||||
// If Ethereum, use the standard `addr(bytes32)`
|
||||
if (coinType === 60) {
|
||||
// keccak256("addr(bytes32)")
|
||||
const transaction = {
|
||||
to: this.address,
|
||||
data: ("0x3b3b57de" + namehash(this.name).substring(2))
|
||||
};
|
||||
const hexBytes = await this.provider.call(transaction);
|
||||
try {
|
||||
// keccak256("addr(bytes32)")
|
||||
const transaction = {
|
||||
to: this.address,
|
||||
data: ("0x3b3b57de" + namehash(this.name).substring(2))
|
||||
};
|
||||
const hexBytes = await this.provider.call(transaction);
|
||||
|
||||
// No address
|
||||
if (hexBytes === "0x" || hexBytes === HashZero) { return null; }
|
||||
// No address
|
||||
if (hexBytes === "0x" || hexBytes === HashZero) { return null; }
|
||||
|
||||
return this.provider.formatter.callAddress(hexBytes);
|
||||
return this.provider.formatter.callAddress(hexBytes);
|
||||
} catch (error) {
|
||||
if (error.code === Logger.errors.CALL_EXCEPTION) { return null; }
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// keccak256("addr(bytes32,uint256")
|
||||
@@ -1499,9 +1508,14 @@ export class BaseProvider extends Provider implements EnsProvider {
|
||||
|
||||
|
||||
async getResolver(name: string): Promise<Resolver> {
|
||||
const address = await this._getResolver(name);
|
||||
if (address == null) { return null; }
|
||||
return new Resolver(this, address, name);
|
||||
try {
|
||||
const address = await this._getResolver(name);
|
||||
if (address == null) { return null; }
|
||||
return new Resolver(this, address, name);
|
||||
} catch (error) {
|
||||
if (error.code === Logger.errors.CALL_EXCEPTION) { return null; }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async _getResolver(name: string): Promise<string> {
|
||||
@@ -1523,7 +1537,12 @@ export class BaseProvider extends Provider implements EnsProvider {
|
||||
data: ("0x0178b8bf" + namehash(name).substring(2))
|
||||
};
|
||||
|
||||
return this.formatter.callAddress(await this.call(transaction));
|
||||
try {
|
||||
return this.formatter.callAddress(await this.call(transaction));
|
||||
} catch (error) {
|
||||
if (error.code === Logger.errors.CALL_EXCEPTION) { return null; }
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async resolveName(name: string | Promise<string>): Promise<string> {
|
||||
|
||||
@@ -98,8 +98,18 @@ function checkError(method: string, error: any, transaction: any): any {
|
||||
// incompatibility; maybe for v6 consider forwarding reverts as errors
|
||||
if (method === "call" && error.code === Logger.errors.SERVER_ERROR) {
|
||||
const e = error.error;
|
||||
if (e && e.message.match("reverted") && isHexString(e.data)) {
|
||||
return e.data;
|
||||
|
||||
// Etherscan keeps changing their string
|
||||
if (e && (e.message.match(/reverted/i) || e.message.match(/VM execution error/i))) {
|
||||
// Etherscan prefixes the data like "Reverted 0x1234"
|
||||
let data = e.data;
|
||||
if (data) { data = "0x" + data.replace(/^.*0x/i, ""); }
|
||||
|
||||
if (isHexString(data)) { return data; }
|
||||
|
||||
logger.throwError("missing revert data in call exception", Logger.errors.CALL_EXCEPTION, {
|
||||
error, data: "0x"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,13 +45,14 @@ export class Formatter {
|
||||
const hash = this.hash.bind(this);
|
||||
const hex = this.hex.bind(this);
|
||||
const number = this.number.bind(this);
|
||||
const type = this.type.bind(this);
|
||||
|
||||
const strictData = (v: any) => { return this.data(v, true); };
|
||||
|
||||
formats.transaction = {
|
||||
hash: hash,
|
||||
|
||||
type: Formatter.allowNull(number, null),
|
||||
type: type,
|
||||
accessList: Formatter.allowNull(this.accessList.bind(this), null),
|
||||
|
||||
blockHash: Formatter.allowNull(hash, null),
|
||||
@@ -62,7 +63,12 @@ export class Formatter {
|
||||
|
||||
from: address,
|
||||
|
||||
gasPrice: bigNumber,
|
||||
// either (gasPrice) or (maxPriorityFeePerGas + maxFeePerGas)
|
||||
// must be set
|
||||
gasPrice: Formatter.allowNull(bigNumber),
|
||||
maxPriorityFeePerGas: Formatter.allowNull(bigNumber),
|
||||
maxFeePerGas: Formatter.allowNull(bigNumber),
|
||||
|
||||
gasLimit: bigNumber,
|
||||
to: Formatter.allowNull(address, null),
|
||||
value: bigNumber,
|
||||
@@ -83,6 +89,8 @@ export class Formatter {
|
||||
nonce: Formatter.allowNull(number),
|
||||
gasLimit: Formatter.allowNull(bigNumber),
|
||||
gasPrice: Formatter.allowNull(bigNumber),
|
||||
maxPriorityFeePerGas: Formatter.allowNull(bigNumber),
|
||||
maxFeePerGas: Formatter.allowNull(bigNumber),
|
||||
to: Formatter.allowNull(address),
|
||||
value: Formatter.allowNull(bigNumber),
|
||||
data: Formatter.allowNull(strictData),
|
||||
@@ -116,7 +124,8 @@ export class Formatter {
|
||||
blockNumber: number,
|
||||
confirmations: Formatter.allowNull(number, null),
|
||||
cumulativeGasUsed: bigNumber,
|
||||
status: Formatter.allowNull(number)
|
||||
status: Formatter.allowNull(number),
|
||||
type: type
|
||||
};
|
||||
|
||||
formats.block = {
|
||||
@@ -135,6 +144,8 @@ export class Formatter {
|
||||
extraData: data,
|
||||
|
||||
transactions: Formatter.allowNull(Formatter.arrayOf(hash)),
|
||||
|
||||
baseFeePerGas: Formatter.allowNull(bigNumber)
|
||||
};
|
||||
|
||||
formats.blockWithTransactions = shallowCopy(formats.block);
|
||||
@@ -178,6 +189,11 @@ export class Formatter {
|
||||
return BigNumber.from(number).toNumber();
|
||||
}
|
||||
|
||||
type(number: any): number {
|
||||
if (number === "0x" || number == null) { return 0; }
|
||||
return BigNumber.from(number).toNumber();
|
||||
}
|
||||
|
||||
// Strict! Used on input.
|
||||
bigNumber(value: any): BigNumber {
|
||||
return BigNumber.from(value);
|
||||
@@ -323,6 +339,12 @@ export class Formatter {
|
||||
|
||||
const result: TransactionResponse = Formatter.check(this.formats.transaction, transaction);
|
||||
|
||||
if (result.type === 2) {
|
||||
if (result.gasPrice == null) {
|
||||
result.gasPrice = result.maxFeePerGas;
|
||||
}
|
||||
}
|
||||
|
||||
if (transaction.chainId != null) {
|
||||
let chainId = transaction.chainId;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
Block,
|
||||
BlockTag,
|
||||
EventType,
|
||||
FeeData,
|
||||
Filter,
|
||||
Log,
|
||||
Listener,
|
||||
@@ -150,6 +151,7 @@ export {
|
||||
Block,
|
||||
BlockTag,
|
||||
EventType,
|
||||
FeeData,
|
||||
Filter,
|
||||
Log,
|
||||
Listener,
|
||||
|
||||
@@ -30,6 +30,10 @@ function checkError(method: string, error: any, params: any): any {
|
||||
if (e && e.message.match("reverted") && isHexString(e.data)) {
|
||||
return e.data;
|
||||
}
|
||||
|
||||
logger.throwError("missing revert data in call exception", Logger.errors.CALL_EXCEPTION, {
|
||||
error, data: "0x"
|
||||
});
|
||||
}
|
||||
|
||||
let message = error.message;
|
||||
@@ -600,7 +604,7 @@ export class JsonRpcProvider extends BaseProvider {
|
||||
const result: { [key: string]: string | AccessList } = {};
|
||||
|
||||
// Some nodes (INFURA ropsten; INFURA mainnet is fine) do not like leading zeros.
|
||||
["gasLimit", "gasPrice", "type", "nonce", "value"].forEach(function(key) {
|
||||
["gasLimit", "gasPrice", "type", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "value"].forEach(function(key) {
|
||||
if ((<any>transaction)[key] == null) { return; }
|
||||
const value = hexValue((<any>transaction)[key]);
|
||||
if (key === "gasLimit") { key = "gas"; }
|
||||
|
||||
@@ -23,6 +23,12 @@ export type AccessListish = AccessList |
|
||||
Array<[ string, Array<string> ]> |
|
||||
Record<string, Array<string>>;
|
||||
|
||||
export enum TransactionTypes {
|
||||
legacy = 0,
|
||||
eip2930 = 1,
|
||||
eip1559 = 2,
|
||||
};
|
||||
|
||||
export type UnsignedTransaction = {
|
||||
to?: string;
|
||||
nonce?: number;
|
||||
@@ -36,7 +42,13 @@ export type UnsignedTransaction = {
|
||||
|
||||
// Typed-Transaction features
|
||||
type?: number | null;
|
||||
|
||||
// EIP-2930; Type 1 & EIP-1559; Type 2
|
||||
accessList?: AccessListish;
|
||||
|
||||
// EIP-1559; Type 2
|
||||
maxPriorityFeePerGas?: BigNumberish;
|
||||
maxFeePerGas?: BigNumberish;
|
||||
}
|
||||
|
||||
export interface Transaction {
|
||||
@@ -60,8 +72,12 @@ export interface Transaction {
|
||||
// Typed-Transaction features
|
||||
type?: number | null;
|
||||
|
||||
// EIP-2930; Type 1
|
||||
// EIP-2930; Type 1 & EIP-1559; Type 2
|
||||
accessList?: AccessList;
|
||||
|
||||
// EIP-1559; Type 2
|
||||
maxPriorityFeePerGas?: BigNumber;
|
||||
maxFeePerGas?: BigNumber;
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
@@ -87,7 +103,7 @@ const transactionFields = [
|
||||
];
|
||||
|
||||
const allowedTransactionKeys: { [ key: string ]: boolean } = {
|
||||
chainId: true, data: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true
|
||||
chainId: true, data: true, gasLimit: true, gasPrice:true, nonce: true, to: true, type: true, value: true
|
||||
}
|
||||
|
||||
export function computeAddress(key: BytesLike | string): string {
|
||||
@@ -147,6 +163,42 @@ function formatAccessList(value: AccessListish): Array<[ string, Array<string> ]
|
||||
return accessListify(value).map((set) => [ set.address, set.storageKeys ]);
|
||||
}
|
||||
|
||||
function _serializeEip1559(transaction: UnsignedTransaction, signature?: SignatureLike): string {
|
||||
// If there is an explicit gasPrice, make sure it matches the
|
||||
// EIP-1559 fees; otherwise they may not understand what they
|
||||
// think they are setting in terms of fee.
|
||||
if (transaction.gasPrice != null) {
|
||||
const gasPrice = BigNumber.from(transaction.gasPrice);
|
||||
const maxFeePerGas = BigNumber.from(transaction.maxFeePerGas || 0);
|
||||
if (!gasPrice.eq(maxFeePerGas)) {
|
||||
logger.throwArgumentError("mismatch EIP-1559 gasPrice != maxFeePerGas", "tx", {
|
||||
gasPrice, maxFeePerGas
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const fields: any = [
|
||||
formatNumber(transaction.chainId || 0, "chainId"),
|
||||
formatNumber(transaction.nonce || 0, "nonce"),
|
||||
formatNumber(transaction.maxPriorityFeePerGas || 0, "maxPriorityFeePerGas"),
|
||||
formatNumber(transaction.maxFeePerGas || 0, "maxFeePerGas"),
|
||||
formatNumber(transaction.gasLimit || 0, "gasLimit"),
|
||||
((transaction.to != null) ? getAddress(transaction.to): "0x"),
|
||||
formatNumber(transaction.value || 0, "value"),
|
||||
(transaction.data || "0x"),
|
||||
(formatAccessList(transaction.accessList || []))
|
||||
];
|
||||
|
||||
if (signature) {
|
||||
const sig = splitSignature(signature);
|
||||
fields.push(formatNumber(sig.recoveryParam, "recoveryParam"));
|
||||
fields.push(stripZeros(sig.r));
|
||||
fields.push(stripZeros(sig.s));
|
||||
}
|
||||
|
||||
return hexConcat([ "0x02", RLP.encode(fields)]);
|
||||
}
|
||||
|
||||
function _serializeEip2930(transaction: UnsignedTransaction, signature?: SignatureLike): string {
|
||||
const fields: any = [
|
||||
formatNumber(transaction.chainId || 0, "chainId"),
|
||||
@@ -252,7 +304,7 @@ function _serialize(transaction: UnsignedTransaction, signature?: SignatureLike)
|
||||
|
||||
export function serialize(transaction: UnsignedTransaction, signature?: SignatureLike): string {
|
||||
// Legacy and EIP-155 Transactions
|
||||
if (transaction.type == null) {
|
||||
if (transaction.type == null || transaction.type === 0) {
|
||||
if (transaction.accessList != null) {
|
||||
logger.throwArgumentError("untyped transactions do not support accessList; include type: 1", "transaction", transaction);
|
||||
}
|
||||
@@ -263,6 +315,8 @@ export function serialize(transaction: UnsignedTransaction, signature?: Signatur
|
||||
switch (transaction.type) {
|
||||
case 1:
|
||||
return _serializeEip2930(transaction, signature);
|
||||
case 2:
|
||||
return _serializeEip1559(transaction, signature);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -273,6 +327,59 @@ export function serialize(transaction: UnsignedTransaction, signature?: Signatur
|
||||
});
|
||||
}
|
||||
|
||||
function _parseEipSignature(tx: Transaction, fields: Array<string>, serialize: (tx: UnsignedTransaction) => string): void {
|
||||
try {
|
||||
const recid = handleNumber(fields[0]).toNumber();
|
||||
if (recid !== 0 && recid !== 1) { throw new Error("bad recid"); }
|
||||
tx.v = recid;
|
||||
} catch (error) {
|
||||
logger.throwArgumentError("invalid v for transaction type: 1", "v", fields[0]);
|
||||
}
|
||||
|
||||
tx.r = hexZeroPad(fields[1], 32);
|
||||
tx.s = hexZeroPad(fields[2], 32);
|
||||
|
||||
try {
|
||||
const digest = keccak256(serialize(tx));
|
||||
tx.from = recoverAddress(digest, { r: tx.r, s: tx.s, recoveryParam: tx.v });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
function _parseEip1559(payload: Uint8Array): Transaction {
|
||||
const transaction = RLP.decode(payload.slice(1));
|
||||
|
||||
if (transaction.length !== 9 && transaction.length !== 12) {
|
||||
logger.throwArgumentError("invalid component count for transaction type: 2", "payload", hexlify(payload));
|
||||
}
|
||||
|
||||
const maxPriorityFeePerGas = handleNumber(transaction[2]);
|
||||
const maxFeePerGas = handleNumber(transaction[3]);
|
||||
const tx: Transaction = {
|
||||
type: 2,
|
||||
chainId: handleNumber(transaction[0]).toNumber(),
|
||||
nonce: handleNumber(transaction[1]).toNumber(),
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas,
|
||||
maxFeePerGas: maxFeePerGas,
|
||||
gasPrice: maxFeePerGas,
|
||||
gasLimit: handleNumber(transaction[4]),
|
||||
to: handleAddress(transaction[5]),
|
||||
value: handleNumber(transaction[6]),
|
||||
data: transaction[7],
|
||||
accessList: accessListify(transaction[8]),
|
||||
};
|
||||
|
||||
// Unsigned EIP-1559 Transaction
|
||||
if (transaction.length === 9) { return tx; }
|
||||
|
||||
tx.hash = keccak256(payload);
|
||||
|
||||
_parseEipSignature(tx, transaction.slice(9), _serializeEip1559);
|
||||
|
||||
return tx;
|
||||
}
|
||||
|
||||
function _parseEip2930(payload: Uint8Array): Transaction {
|
||||
const transaction = RLP.decode(payload.slice(1));
|
||||
|
||||
@@ -289,31 +396,16 @@ function _parseEip2930(payload: Uint8Array): Transaction {
|
||||
to: handleAddress(transaction[4]),
|
||||
value: handleNumber(transaction[5]),
|
||||
data: transaction[6],
|
||||
accessList: accessListify(transaction[7]),
|
||||
accessList: accessListify(transaction[7])
|
||||
};
|
||||
|
||||
// Unsigned EIP-2930 Transaction
|
||||
if (transaction.length === 8) { return tx; }
|
||||
|
||||
try {
|
||||
const recid = handleNumber(transaction[8]).toNumber();
|
||||
if (recid !== 0 && recid !== 1) { throw new Error("bad recid"); }
|
||||
tx.v = recid;
|
||||
} catch (error) {
|
||||
logger.throwArgumentError("invalid v for transaction type: 1", "v", transaction[8]);
|
||||
}
|
||||
|
||||
tx.r = hexZeroPad(transaction[9], 32);
|
||||
tx.s = hexZeroPad(transaction[10], 32);
|
||||
|
||||
try {
|
||||
const digest = keccak256(_serializeEip2930(tx));
|
||||
tx.from = recoverAddress(digest, { r: tx.r, s: tx.s, recoveryParam: tx.v });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
tx.hash = keccak256(payload);
|
||||
|
||||
_parseEipSignature(tx, transaction.slice(8), _serializeEip2930);
|
||||
|
||||
return tx;
|
||||
}
|
||||
|
||||
@@ -397,6 +489,8 @@ export function parse(rawTransaction: BytesLike): Transaction {
|
||||
switch (payload[0]) {
|
||||
case 1:
|
||||
return _parseEip2930(payload);
|
||||
case 2:
|
||||
return _parseEip1559(payload);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user