This article uses the ethers
library as the underlying implementation, and describes the detailed properties of the Ethereum transaction object built in Javascript. This article assumes that the reader has a certain basic knowledge of Ethereum.
1. What is a ethers
library
The following is an original introduction of its document:
The ethers.js library aims to be a complete and compact library for interacting with the Ethereum Blockchain and its ecosystem. It was originally designed for use with ethers.io and has since expanded into a much more general-purpose library.
The rough meaning is ethers.js
a complete and compact library applied to Ethereum and its ecosystem. It was originally designed to be used on ethers.io and slowly expanded into a multifunctional library.
ethers
The library has the following characteristics:
- The private key is stored on the client, safe and risk-free
- Support import and export wallets in json format (can be used for Geth or Parity)
- Support import and export mnemonic words and hardware wallet, mnemonic words support multiple languages
- Support multiple ABI formats, including ABIv2 and human-readable ABI
- Support multiple ways to connect to Ethereum nodes, such as JSON-RPC, INFURA, Etherscan or MetaMask.
- ENS names are fully supported as the first type of element
- The library is very small (284kb uncompressed, 88kb compressed)
- Complete functions to meet all your needs for Ethereum
- Detailed documentation
- Added a lot of test cases
- TypeScript readable
- MIT certificate (including its dependencies), completely open source
Second, build a trading object
In ethers.js
, an Ethereum transaction object is an ordinary object {}
, which contains the following optional attributes:
- to
- gasLimit
- gasPrice
- nonce
- data
- value
- chainId
The above attributes are all optional, which means they can be omitted, but not all of them, there must be at least one attribute. We create a transaction object in the following way:
// All properties are optional
let transaction = {
nonce: 0,
gasLimit: 21000,
gasPrice: utils.bigNumberify("20000000000"),
to: "0x88a5C2d9919e46F883EB62F7b8Dd9d0CC45bc290",
// ... or supports ENS names
// to: "ricmoo.firefly.eth",
value: utils.parseEther("1.0"),
data: "0x",
// This ensures the transaction cannot be replayed on different networks
chainId: ethers.utils.getNetwork('homestead').chainId
}
Below we explain these attributes through actual transactions on the Kovan testnet.
3. Detailed explanation of transaction object attributes
There are the following code snippets, we will modify or add to this snippet in future transactions. This is a transaction that creates a contract:
let data='0x60....'
let provider = ethers.getDefaultProvider('kovan')
let wallet_new = wallet.connect(provider)
let trans = {
data:inputData
}
wallet_new.sendTransaction(trans).then( tx => {
console.log(tx)
}).catch( err => {
console.log(err)
})
As you can see, our transaction object has only one attribute data
, and its value is the bytecode for creating the contract. Note: The bytecode when creating the contract is not the bytecode compiled by the creating contract, but the bytecode that can be obtained by running the bytecode of the created contract.
Let's take a look at the printed transaction response (Transaction Response):
you can see that in the transaction response object, in addition to to
attributes, other attributes exist. So the attribute mentioned above can be omitted means that it can be omitted when constructing the transaction object. If omitted, the underlying ethers
library will automatically set it up for you. Let's start with this simplest transaction object, increase and explain its properties step by step.
3.1 to
Since the to
attribute is null
, we will start with the to
attribute. to
Represents the address of the callee in the transaction.
A transaction on Ethereum must have an initiator (external account, non-contract account), usually from
. Because the ethers
library we use uses the wallet to sign transactions, so whoever signs is whoever signs from
. The transaction usually has a receiver (both external account and contract account), that is to
. Why is it usual? Because like our previous example, there is no receiver when the contract is created. Although the address of the contract will be to
returned as an attribute after the contract is created , this to
address is empty when it is created . Let's take a look at the screenshot on etherscan to deepen this impression:
you can see that after the transaction is executed, this to
attribute is the address of the new contract. Let me add that the address of the contract is calculated based on the address of the caller and the number of transactions (nonce) that the caller has completed. Therefore, before a contract is actually deployed, the address is set and can be obtained.
To summarize, the to
attribute is the address of the callee in the transaction. Specifically: if you are transferring ETH to an external account, it is the ETH receiving address; if you are calling a contract (transferring ETH to a contract account is also a calling contract), it is the contract address; if you are creating a contract, because there is no callee at this time, Just default it.
3.2 data
Next we talk about the above code uses the property: data
. During the transaction, we can send transaction data along with the transaction. Transaction data can be method calls to the contract, or some meaningless data, which is sometimes called payload
. In the above example, data
the value of the attribute is the bytecode of the contract we created. Let us add an to
attribute to the above transaction object and modify data
the value of the attribute:
let trans = {
data:"0x496c6f7665457468657265756d",
to:"0xDD55634e1027d706a235374e01D69c2D121E1CCb"
}
Here to
is an external account address, which data
is the string of "I love Ethereum" converted into hexadecimal value ( data
must 0x
start with). The response after the transaction is sent is as follows:
Let's look at the result on etherscan:
From the code snippet, we can see that we directly sent a message (string) to an account. At the bottom of InputData, it displays native data by default. Select View Input As UTF-8, and IloveEthereum will be displayed. No spaces are shown here because the tool I used did not encode spaces. Does this function of sending a string to an account look like sending a short message to a mobile phone number? You can even send an article (but with a lot of fees), is Ethereum interesting?
If the data sent is the data when the contract method is called, it usually has a fixed format and cannot be arbitrary data. Examples are as follows:
data:0x07391dd6000000000000000000000000000000000000000000000000000000000000000a
Here, the first 8 bits 07391dd6
of the first 32 bytes are function selectors, and after 32 bytes are the corresponding types of data. Interested readers can read related articles on Ethereum coding for themselves.
Well, to summarize: the data
attribute is the data sent with the call. If the called object is a contract, it is usually the code of the contract calling method; if it is to create a contract, it is the created bytecode; if the called object is an external account, the content of this data is arbitrary (the external account has no code , And will not execute the sent data).
3.3 value
value
The attribute represents the amount of Ether sent with this transaction. Regardless of whether the transaction type is direct ETH transfer (including transfer to the contract and transfer to an external account), creation of a contract (in this case ETH will be used as the initial ETH of the created contract), or contract call (the contract method is payable
), it is faithfully recorded The amount of ETH you sent in the transaction (not including the handling fee, the handling fee is an additional consumption). Let's just add a transaction object value
property, its value is attention wei
units. And usually when we are in a general reference to the Ethernet currency ether
units, need to make use of a conversion.
let trans = {
data:"0x496c6f7665457468657265756d",
value:ethers.utils.parseEther('0.1'),
to:"0xDD55634e1027d706a235374e01D69c2D121E1CCb"
}
value
The value in the code is 0.1 ETH. Let us send this transaction:
In JS, if the number is relatively large, it will exceed the upper limit of js decimal representation (about 10 ** 15), so BigNumber is generally used to interact with Ethereum. You can see that the number of WEI sent is converted into a BigNumber. Let's look at the result of etherscan again:
it is not shown here data
because I did not click Click to see More to expand. As you can see, we did send 0.1 ETH with the transaction.
3.4 gasLimit
andgasPrice
Next we will introduce two attributes related to gas: gasLimit
and gasPrice
. This gasLimit
refers to the maximum gas consumption of this transaction, and gasPrice
refers to the price you are willing to pay for the actual gas consumed. The specific amount of gas consumed is multiplied by gasPrice
the fee you are willing to pay to the miner. After the transaction is executed, the unconsumed gas will be returned to you (the transaction error situation will not be discussed here, in this case sometimes the unconsumed gas will not be refunded).
gasLimit
Usually used to limit a certain transaction can not consume too many resources. Here is a usage scenario: We often use MetaMask to transfer funds directly to external accounts. The gasLimit
default value in MetaMask is 23000, and the minimum cannot be lower than 21000.
Let us look at a specific transfer transaction on etherscan:
As you can see from the above figure, when we do not send any data with the transaction (the data
attribute is empty, if it is not empty, additional gas will be consumed), to an external Account transfer will consume 21,000 gas, which is basically fixed. Therefore, the gasLimit
upper limit of this transaction is also set 21000
, the utilization rate 100%
.
Ours in the picture above gasPrice
is 5 Gwei
. The higher the price you give, the faster the transaction, and of course the higher your handling fee. Usually gasPrice
, when we talk about it, we all use it Gwei
as a unit, but we still have to convert it when we use it wei
. This 5 Gwei
multiplied by the consumed gas 21000
is exactly what is shown in the figure above Transaction Fee
: 0.000105
ETH. When the author writes here, the price of ETH is around $205, so the handling fee for sending it is about 0.15RMB
.
Let us add these two attributes to the transaction object to see if the excess gas is consumed. Our gasLimit is set to 100000
, gasPrice
set to 3 Gwei
, let us resend the transaction:
let trans = {
data:"0x496c6f7665457468657265756d",
value:ethers.utils.parseEther('0.1'),
gasLimit:100000,
gasPrice:ethers.utils.parseUnits("3",'gwei'),
to:"0xDD55634e1027d706a235374e01D69c2D121E1CCb"
}
Here, because ours gasLimit
cannot exceed the decimal limit of JS, the decimal system is used directly 100000
. The transaction response is:
we directly look at the transaction result on etherscan:
because we sent I love Ethereum
this string with the transaction , we consumed 208 more gas. According to the fee we deduct, the unused gas is not included in the cost.
For gasLimit
speaking generally in use ethers
do not need to set the library, let it default on the line. If you want to set it manually, you can use it to estimate first, and then expand it upwards appropriately, such as the following code snippet:
let args = [_address,amount]
let gasLimit = await contract.estimate.transfer(...args)
let step = ethers.utils.bigNumberify(1000)
gasLimit = gasLimit.add(step)
For gasPrice
speaking, generally set to 5 Gwei
or under normal circumstances 6 Gwei
. It can be set to 1.5 Gwei
or when the gas consumes a lot or the network is idle 2 Gwei
. However, the transaction time will be extended in this way, and it may even fail. If you want to trade quickly, set it to 10 Gwei
or 20 Gwei
even higher, but this will cost more. If you have more money, you will be fast, and if you have less money, you will be slow. The reason is that simple. And be careful: too low a fee may cause no miners to pack the transaction, and the transaction will fail. Of course, if you are on the testnet, you can set it higher, because you don't have to really spend money.
3.5 nonce
In the transaction object, it nonce
represents the number of transactions completed by the address. It starts from 0 and is an automatically increasing integer. Usually, we don't need to set it. But in some special cases, it can be set manually. One scenario is when covering transactions. You can manually set the nonce to the nonce value of a transaction that has been sent but not yet completed to overwrite the transaction. Usually the purpose of this is to speed up the transaction (increase gasPrice
) or completely use a new transaction. This is also easy to understand. For example, my transaction No. 122 is to send an ETH to A, but before this transaction is not sent or completed, I made an urgent modification to change this transaction No. 122 to send an ETH to B. At this point, I only need to set the nonce of the new transaction to 122.
If you want to set in the transaction object that you usually use, you need to query the number of transactions you have completed. This number is the nonce value you should use. Use the following code:
let address = "0x02F024e0882B310c6734703AB9066EdD3a10C6e0";
provider.getTransactionCount(address).then((transactionCount) => {
console.log("Total Transactions Ever Sent: " + transactionCount);
});
It is worth noting that: nonce has a special usage, you can specify a future value. For example, if the number of transactions you have currently completed is 2096, then the nonce value for the next transaction should be 2097. At this point, you can also skip 2097 and set it to 2098, so what will happen? At this time, the transaction numbered 2098 is equivalent to a delayed transaction, which will be sent out, but will not be executed, and you will not be able to query it on etherscan. Then we perform a normal transaction with the nonce value set to 2097, and the transaction will be sent and executed. Here comes the important point: in the next block, the transaction with a nonce of 2098 will also be executed (because 2097 has already been executed, it's its turn).
3.6 chainId
chainId
Represents the network ID you want to initiate a transaction. With three main network and test network, for example, the main network (mainnet, but in ethers
the still call homestead
home) is 1, Ropsten
the test network 3, Rinkeby
test network 4, Kovan
the test network to 42. Custom network can be set by yourself, etc.
Generally speaking, there is no need to set this when using the wallet chainId
. Because the wallet login will bind a network, it is the network of your transaction object. But you can also manually set to a specific value to prevent transactions on the wrong network. You can directly use the decimal number value above, or you can use ethers
the sample code in:
chainId: ethers.utils.getNetwork('homestead').chainId
If we are trading on the Kovan testnet, the parameters in the method must be changed kovan
. Let us add chainId
and nonce
to the transaction. And change the value to 0.01ETH to distinguish.
let count = await provider.getTransactionCount(wallet_new.address)
let trans = {
data:"0x496c6f7665457468657265756d",
value:ethers.utils.parseEther('0.01'),
gasLimit:100000,
nonce: count,
chainId:ethers.utils.getNetwork('kovan').chainId,
gasPrice:ethers.utils.parseUnits("3",'gwei'),
to:"0xDD55634e1027d706a235374e01D69c2D121E1CCb"
}
Here is the transaction response:
Because the numbers 2097 and 2098 nonce
are consumed when using future values, the number is now 2099. Let's take a look at the results on etherscan:
you can see that the amount of ETH sent is 0.01 ETH, and the nonce is 2099. Someone may ask why it is not displayed on etherscan chainId
, because etherscan is divided into several sites based on the mainnet and testnet, and each site only displays transactions on its own network. For example, the actual URL of etherscan I visited is:
https://kovan.etherscan.io/tx/0x4db8e6b4096d6c27be341b73af99a8d0477e19ba483248c1fdb6fb431fbb3646
All transactions shown on this site chainId
are 42.
Four, summary
In this article, we give a detailed introduction to the specific properties of the manually created Ethereum transaction object. These attributes can be omitted, but they cannot be omitted (because it is meaningless to omit them all). The most commonly used ones are to
, value
and data
attributes. Note: This is only an attribute that needs to be set when manually creating a transaction object in the code; if you directly use a common wallet (such as MetaMask or Trust wallet), the wallet will have a UI interface to help you set everything up. However, it is necessary to clarify the basis of the implementation. I hope this article can provide a little help to developers on Ethereum.
You are welcome to leave a message to point out errors or suggest improvements.