Compare commits

...

16 Commits

Author SHA1 Message Date
Richard Moore
319987ec3e Fixed EIP-1559 from address calculation bug (#1610). 2021-06-24 00:46:53 -04:00
Richard Moore
2a7ce0e72a merged master including transaction type 0 legacy constant (#1610). 2021-06-24 00:02:50 -04:00
Richard Moore
8ba64af29f admin: updated Flatworm version 2021-06-23 23:42:00 -04:00
Richard Moore
17af9f812f docs: fixed typos and updated examples 2021-06-23 23:40:50 -04:00
Richard Moore
d001901c8c Added type to TransactionResponse and TrnsactionReceipt (#1687). 2021-06-23 23:39:57 -04:00
Richard Moore
91951dc825 Trap CALL_EXCEPTION errors when resolving ENS entries (#1690). 2021-06-21 23:25:37 -04:00
Richard Moore
8277f5a62a Fixed transaction serialization with explicit null type (#1628). 2021-06-21 21:21:35 -04:00
Richard Moore
e615e51fbf admin: updated spell check dictionary 2021-06-21 21:15:39 -04:00
Richard Moore
99422c1c7c admin: fixed typo in docs (#1686). 2021-06-21 21:15:01 -04:00
Richard Moore
e8a0144b7a Fix issue with loading JSON ABI with internalType property (#728). 2021-06-21 21:12:25 -04:00
Richard Moore
f9d09645e7 docs: Added info on signMessage (#1343). 2021-06-18 16:17:26 -04:00
Richard Moore
91fff1449d Better baseFee calculation (#1610). 2021-06-14 23:42:11 -04:00
Richard Moore
c5bca7767e Refactored eip-1559 logic (#1610). 2021-06-14 22:24:14 -04:00
Richard Moore
79c5bf6bcb docs: added more examples 2021-06-11 17:13:46 -04:00
Richard Moore
5456c35924 Added EIP-1559 overrides to contracts (#1610). 2021-06-10 17:42:20 -04:00
Richard Moore
7a12216cfb Initial EIP-1559 support (#1610). 2021-05-30 17:47:04 -04:00
22 changed files with 684 additions and 125 deletions

View File

@@ -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.

View File

@@ -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.

View File

@@ -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:

View File

@@ -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&thinsp;.&thinsp;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)

View File

@@ -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>

View File

@@ -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",

View File

@@ -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

View File

@@ -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 = {

View File

@@ -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

View File

@@ -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);

View File

@@ -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",

View File

@@ -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

View File

@@ -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>;

View File

@@ -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) {

View File

@@ -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));

View File

@@ -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,

View File

@@ -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> {

View File

@@ -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"
});
}
}

View File

@@ -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;

View File

@@ -4,6 +4,7 @@ import {
Block,
BlockTag,
EventType,
FeeData,
Filter,
Log,
Listener,
@@ -150,6 +151,7 @@ export {
Block,
BlockTag,
EventType,
FeeData,
Filter,
Log,
Listener,

View File

@@ -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"; }

View File

@@ -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;
}