Transaction Value and Data
The main “payload” of a transaction is contained in two fields: value and data. Transactions can have both value and data, only value, only data, or neither value nor data. All four combinations are valid.
A transaction with only value is a payment. A transaction with only data is an invocation. A transaction with both value and data is both a payment and an invocation. A transaction with neither value nor data—well that’s probably just a waste of gas! But it is still possible.
Let’s try all of these combinations. First we’ll set the source and destination addresses from our wallet, just to make the demo easier to read:
src = web3.eth.accounts[0];
dst = web3.eth.accounts[1];
Our first transaction contains only a value (payment), and no data payload:
web3.eth.sendTransaction({from: src, to: dst, \
value: web3.utils.toWei(0.01, "ether"), data: ""});
Our wallet shows a confirmation screen indicating the value to send, as shown in Parity wallet showing a transaction with value, but no data.
Figure 1. Parity wallet showing a transaction with value, but no data
The next example specifies both a value and a data payload:
web3.eth.sendTransaction({from: src, to: dst, \
value: web3.utils.toWei(0.01, "ether"), data: "0x1234"});
Our wallet shows a confirmation screen indicating the value to send as well as the data payload, as shown in Parity wallet showing a transaction with value and data.
Figure 2. Parity wallet showing a transaction with value and data
The next transaction includes a data payload but specifies a value of zero:
web3.eth.sendTransaction({from: src, to: dst, value: 0, data: "0x1234"});
Our wallet shows a confirmation screen indicating the zero value and the data payload, as shown in Parity wallet showing a transaction with no value, only data.
Figure 3. Parity wallet showing a transaction with no value, only data
Finally, the last transaction includes neither a value to send nor a data payload:
web3.eth.sendTransaction({from: src, to: dst, value: 0, data: ""}));
Our wallet shows a confirmation screen indicating zero value, as shown in Parity wallet showing a transaction with no value, and no data.
Figure 4. Parity wallet showing a transaction with no value, and no data
Transmitting Value to EOAs and Contracts
When you construct an Ethereum transaction that contains a value, it is the equivalent of a payment. Such transactions behave differently depending on whether the destination address is a contract or not.
For EOA addresses, or rather for any address that isn’t flagged as a contract on the blockchain, Ethereum will record a state change, adding the value you sent to the balance of the address. If the address has not been seen before, it will be added to the client’s internal representation of the state and its balance initialized to the value of your payment.
If the destination address (to) is a contract, then the EVM will execute the contract and will attempt to call the function named in the data payload of your transaction. If there is no data in your transaction, the EVM will call a fallback function and, if that function is payable, will execute it to determine what to do next. If there is no code in fallback function, then the effect of the transaction will be to increase the balance of the contract, exactly like a payment to a wallet. If there is no fallback function or non-payable fallback function, then transaction will be reverted.
A contract can reject incoming payments by throwing an exception immediately when a function is called, or as determined by conditions coded in a function. If the function terminates successfully (without an exception), then the contract’s state is updated to reflect an increase in the contract’s ether balance.
Transmitting a Data Payload to an EOA or Contract
When your transaction contains data, it is most likely addressed to a contract address. That doesn’t mean you cannot send a data payload to an EOA—that is completely valid in the Ethereum protocol. However, in that case, the interpretation of the data is up to the wallet you use to access the EOA. It is ignored by the Ethereum protocol. Most wallets also ignore any data received in a transaction to an EOA they control. In the future, it is possible that standards may emerge that allow wallets to interpret data the way contracts do, thereby allowing transactions to invoke functions running inside user wallets. The critical difference is that any interpretation of the data payload by an EOA is not subject to Ethereum’s consensus rules, unlike a contract execution.
For now, let’s assume your transaction is delivering data to a contract address. In that case, the data will be interpreted by the EVM as a contract invocation. Most contracts use this data more specifically as a function invocation, calling the named function and passing any encoded arguments to the function.
The data payload sent to an ABI-compatible contract (which you can assume all contracts are) is a hex-serialized encoding of:
A function selector
The first 4 bytes of the Keccak-256 hash of the function’s prototype. This allows the contract to unambiguously identify which function you wish to invoke.
The function arguments
The function’s arguments, encoded according to the rules for the various elementary types defined in the ABI specification.
In [solidity_faucet_example], we defined a function for withdrawals:
function withdraw(uint withdraw_amount) public {
The prototype of a function is defined as the string containing the name of the function, followed by the data types of each of its arguments, enclosed in parentheses and separated by commas. The function name here is withdraw and it takes a single argument that is a uint (which is an alias for uint256), so the prototype of withdraw would be:
withdraw(uint256)
Let’s calculate the Keccak-256 hash of this string:
> web3.utils.sha3("withdraw(uint256)");
'0x2e1a7d4d13322e7b96f9a57413e1525c250fb7a9021cf91d1540d5b69f16a49f'
The first 4 bytes of the hash are 0x2e1a7d4d. That’s our “function selector” value, which will tell the contract which function we want to call.
Next, let’s calculate a value to pass as the argument withdraw_amount. We want to withdraw 0.01 ether. Let’s encode that to a hex-serialized big-endian unsigned 256-bit integer, denominated in wei:
> withdraw_amount = web3.utils.toWei(0.01, "ether");
'10000000000000000'
> withdraw_amount_hex = web3.utils.toHex(withdraw_amount);
'0x2386f26fc10000'
Now, we add the function selector to the amount (padded to 32 bytes):
2e1a7d4d000000000000000000000000000000000000000000000000002386f26fc10000
That’s the data payload for our transaction, invoking the withdraw function and requesting 0.01 ether as the withdraw_amount.