Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
LABS
Guides

How to Structure Transaction Data Correctly

A developer guide to constructing, serializing, and signing raw transaction data for major blockchains. Includes code examples and field-by-field breakdowns.
Chainscore © 2026
introduction
BLOCKCHAIN FUNDAMENTALS

Introduction to Transaction Structure

Understanding the precise format of transaction data is essential for building reliable Web3 applications. This guide explains the core components of a blockchain transaction and how to structure them correctly.

A blockchain transaction is a structured data packet that authorizes a state change on the network. It is the fundamental unit of interaction, whether you're sending ETH, calling a smart contract function, or minting an NFT. Every transaction must be cryptographically signed by the sender's private key to prove ownership and intent. This signature is appended to the transaction data, creating a verifiable proof that cannot be forged.

The structure varies slightly between networks like Ethereum, Solana, and Bitcoin, but core fields are universal. For Ethereum and EVM-compatible chains (e.g., Arbitrum, Polygon), a legacy transaction includes fields like nonce, gasPrice, gasLimit, to, value, data, and signature components (v, r, s). With EIP-1559, the structure evolved to include maxPriorityFeePerGas and maxFeePerGas instead of gasPrice. The data field is particularly important—it's empty for simple value transfers but contains encoded function calls for smart contract interactions.

Constructing a transaction requires precise serialization. For Ethereum, this means Recursive Length Prefix (RLP) encoding for legacy types or the more straightforward transaction type-specific serialization for EIP-1559. A single byte error in this process will result in an invalid transaction. Developers typically rely on libraries like ethers.js, web3.js, or viem to handle this complexity. For example, using viem, you create a transaction object which the client serializes and signs: const tx = await walletClient.sendTransaction({ to: '0x...', value: parseEther('1') }).

Before broadcasting, transactions are often simulated via eth_estimateGas or similar RPC methods to catch errors and predict gas usage. Key validation steps include: checking the nonce is sequential, ensuring the sender's balance covers value + gasLimit * maxFeePerGas, and verifying the to address is valid. For contract calls, the data payload must be ABI-encoded correctly, matching the function signature and expected parameter types.

Understanding this structure is critical for debugging. Common issues like 'out of gas', 'nonce too low', or 'execution reverted' can often be diagnosed by inspecting the raw transaction data. Tools like Etherscan's Transaction Decoder or libraries that decode data fields are invaluable for developers. Mastering transaction construction ensures your dApps interact with the blockchain predictably and securely.

prerequisites
PREREQUISITES

How to Structure Transaction Data Correctly

Understanding the fundamental data structures is essential for building secure and efficient Web3 applications.

A blockchain transaction is a structured data packet that represents a state change. Unlike a simple value transfer, it contains critical fields that the network uses to validate and execute your intent. The core components include the nonce (a unique sequence number for the sender's account), gasPrice or maxFeePerGas (the fee you're willing to pay), gasLimit (the maximum computational work allowed), to (the recipient address, or null for contract creation), value (the amount of native token to send, in wei), data (encoded function calls for smart contract interactions), and chainId (the identifier for the specific blockchain network). Signing this structured object with your private key generates the v, r, s signature values.

The data field is particularly powerful for smart contract interactions. It is a hexadecimal string that encodes the function selector and the arguments you want to pass. The function selector is the first 4 bytes (8 hex characters) of the Keccak-256 hash of the function signature (e.g., transfer(address,uint256)). The arguments are then ABI-encoded and appended. For example, a call to transfer(0xabc..., 100) would have a data field starting with the selector for transfer followed by the padded address and the padded uint256 value. Tools like ethers.js' Interface or web3.js' encodeFunctionCall handle this encoding automatically.

Serialization, the process of converting this structured object into a byte sequence for transmission, follows a strict Recursive Length Prefix (RLP) encoding scheme on networks like Ethereum. RLP is a space-efficient encoding method designed specifically for serializing nested arrays of binary data. The signed transaction object (including the signature) is RLP-encoded to produce the raw transaction hash that gets broadcast. Other chains may use different serialization formats, such as Protocol Buffers or native runtime encoding, making it crucial to use the correct SDK for your target network.

Common pitfalls in transaction structuring lead to failed transactions or lost funds. These include: - Nonce mismatches: Sending a transaction with a nonce that is too high (skips a sequence) or too low (replays an old one). - Insufficient gas: Setting a gasLimit below what the execution requires, causing an 'out of gas' revert. - ChainId errors: Using a transaction signed for Goerli on Mainnet, which will be rejected. - Data encoding errors: Manually constructing an incorrect data payload that calls the wrong function or passes corrupt parameters, leading to unexpected contract behavior.

To structure transactions correctly, always rely on established provider libraries like ethers.js, web3.js, or viem. These libraries abstract the complexities of RLP encoding, ABI encoding, and signing. For instance, with ethers: const tx = await signer.sendTransaction({ to: address, value: ethers.parseEther("1.0"), gasLimit: 21000 });. The library handles nonce management, gas estimation, chain ID inclusion, and proper serialization. For advanced use cases, you can manually craft and sign a transaction object using populateTransaction and signTransaction methods, but the library ensures the underlying structure is valid.

Before broadcasting, it is a critical security practice to simulate the transaction using eth_call (for read-only execution) or eth_estimateGas. This dry-run reveals potential reverts, gas usage, and state changes without spending gas or making permanent changes. Services like Tenderly or OpenZeppelin Defender provide advanced simulation and debugging. Always verify the decoded transaction data on a block explorer like Etherscan before signing with a private key, ensuring the to, value, and data fields match your intent, as this is the final safeguard against malicious or erroneous transactions.

key-concepts
DATA STRUCTURES

Key Transaction Concepts

Understanding the core data structures of blockchain transactions is essential for building secure and efficient applications. This guide covers the fundamental components and their correct formatting.

01

Transaction Anatomy

A blockchain transaction is a structured data object containing authorization and execution logic. Key fields include:

  • Nonce: A sequential number preventing replay attacks.
  • Gas Price & Limit: Fees paid to the network for computation.
  • To: The recipient's address (empty for contract creation).
  • Value: Amount of native token (e.g., ETH) to transfer.
  • Data: Payload for contract calls or arbitrary data.
  • Signature (v, r, s): Cryptographic proof from the sender's private key.
02

EIP-1559 Fee Market

EIP-1559 reformed Ethereum's fee structure. Transactions now specify:

  • Base Fee: A network-calculated, burned fee that adjusts per block.
  • Priority Fee (Tip): An extra incentive for validators.
  • Max Fee: The absolute maximum a user will pay (base fee + tip). This structure provides more predictable gas costs. Always set max fee per gas and max priority fee per gas for EIP-1559 transactions.
04

Serialization & Signing

Before broadcast, a transaction object must be serialized into a raw format and signed. The process is:

  1. Create the unsigned transaction object with all fields.
  2. Serialize it using RLP (Ethereum) or another network-specific encoding.
  3. Create a cryptographic signature (ECDSA) of the serialized hash.
  4. Encode the final signed transaction (RLP + signature) for submission. Libraries handle this, but understanding the flow is critical for debugging.
05

Access Lists (EIP-2930)

EIP-2930 introduced optional access lists to reduce gas costs for subsequent transactions. An access list pre-declares the storage slots and addresses a transaction will access. This saves gas by warming up these slots. Structure it as an array of address-storage keys tuples. While not always optimal, using access lists can reduce costs for complex, predictable state access patterns.

ethereum-transaction-structure
TECHNICAL GUIDE

Ethereum (EIP-1559) Transaction Structure

EIP-1559 fundamentally changed how Ethereum transactions are priced and structured. This guide explains the new transaction fields and how to format them correctly for developers and users.

Prior to EIP-1559, Ethereum used a simple first-price auction model where users specified a single gasPrice. This led to unpredictable fees and network congestion. The London hard fork in August 2021 introduced EIP-1559, a new transaction pricing mechanism that includes a base fee burned by the protocol and a priority fee (tip) for miners/validators. This creates a more predictable fee market and introduces deflationary pressure via the base fee burn.

An EIP-1559 transaction, also known as a Type 2 transaction, has a distinct structure defined by new fields in its RLP encoding. The key components are:

  • chainId: The EIP-155 chain ID for replay protection.
  • nonce: The sender's transaction sequence number.
  • maxPriorityFeePerGas (tip): The maximum fee per gas you are willing to pay to the validator, in wei.
  • maxFeePerGas: The maximum total fee per gas you are willing to pay (base fee + tip).
  • gasLimit: The maximum gas units the transaction can consume.
  • to: The recipient's address (empty for contract creation).
  • value: The amount of Ether to send, in wei.
  • data: The input data (bytecode for deployment, or encoded function call).
  • accessList: An optional list of addresses and storage keys the transaction plans to access (EIP-2930).
  • Signature fields (v, r, s).

The actual fee you pay per gas unit is calculated as min(baseFee + maxPriorityFeePerGas, maxFeePerGas). The baseFeePerGas is set by the protocol based on block fullness from the previous block and is burned. Your maxPriorityFeePerGas is the incentive for the validator to include your transaction. Your wallet or application should estimate the next block's base fee and set maxFeePerGas to at least (estimated base fee + maxPriorityFeePerGas) to ensure the transaction is valid.

Here is an example of constructing an EIP-1559 transaction object in JavaScript using the ethers library:

javascript
const transaction = {
    type: 2, // Indicates EIP-1559
    chainId: 1, // Mainnet
    nonce: await signer.getNonce(),
    maxPriorityFeePerGas: ethers.parseUnits('1.5', 'gwei'),
    maxFeePerGas: ethers.parseUnits('30', 'gwei'),
    gasLimit: 21000, // Standard transfer
    to: '0xRecipientAddress',
    value: ethers.parseEther('0.1'),
    data: '0x', // Empty for simple ETH transfer
};
const signedTx = await signer.signTransaction(transaction);

This structure is then RLP-encoded for submission to the network.

When interacting with smart contracts, the data field becomes critical. It contains the encoded function signature and arguments. For example, calling a transfer(address,uint256) function requires ABI-encoded data. Incorrect encoding here is a common source of failed transactions. Always use libraries like ethers or web3.js for encoding, and estimate gasLimit using eth_estimateGas RPC calls, as contract interactions vary widely in complexity.

Best practices for structuring transactions include: using reliable providers like Alchemy or Infura for base fee estimation, setting a maxPriorityFeePerGas based on current network demand (sites like Etherscan provide metrics), and always validating that maxFeePerGas comfortably exceeds the sum of the estimated base fee and your tip. Understanding this structure is essential for building robust dApps, gas-efficient smart contracts, and user-friendly wallets in the post-London upgrade ecosystem.

solana-transaction-structure
DEVELOPER GUIDE

Solana Transaction Structure

A Solana transaction is a cryptographically signed instruction that modifies the state of the blockchain. This guide explains its core components and how to construct one correctly.

A Solana transaction is composed of three essential parts: a list of recent blockhash, an array of signatures, and a Message. The blockhash references a recent slot, ensuring the transaction is processed within a short time window (typically 150 blocks). The signatures array is populated after signing, with each signature corresponding to a required signer from the message's account list. The real logic and data reside within the Message structure, which is serialized for transmission.

The Message is the most critical component. It contains a header, an array of account addresses, the recent blockhash, and a list of instructions. The header specifies how many accounts are required for signing and how many are read-only. The account addresses list includes every account the transaction will read from or write to, ordered with signers first, then writable accounts, then read-only accounts. This ordering is crucial for correct serialization.

Each instruction within the message defines a single program call. It contains the program ID, an array of account metadata (indexes pointing to the main account addresses list), and a data byte array. The data is an instruction discriminator followed by serialized arguments specific to the program. For example, a token transfer instruction would include the discriminator for transfer and the amount as a u64. You can inspect raw transactions using the Solana Explorer.

Constructing a transaction programmatically typically involves using the @solana/web3.js library. You first build the instructions, then create a MessageV0 with those instructions and the required accounts. Finally, you create a VersionedTransaction from that message. For a simple system transfer, the code looks like this:

javascript
const transferInstruction = SystemProgram.transfer({
  fromPubkey: fromKeypair.publicKey,
  toPubkey: toPubkey,
  lamports: 1000000,
});
const message = MessageV0.compile({
  payerKey: fromKeypair.publicKey,
  instructions: [transferInstruction],
  recentBlockhash: blockhash,
});
const transaction = new VersionedTransaction(message);
transaction.sign([fromKeypair]);

Common errors include incorrect account ordering, missing required signers, using an expired blockhash, or exceeding the 1232-byte serialization limit. Transactions are serialized using a compact encoding; the total size of the signatures, message header, account addresses, blockhash, and all instructions must fit within this limit. For complex transactions, you may need to use Address Lookup Tables (ALTs) to reference more accounts without bloating the transaction size, as ALT accounts are stored on-chain and referenced by index.

Understanding this structure is key for debugging failed transactions and building efficient applications. Always verify the account ordering matches the program's expectations, ensure you are using a fresh blockhash, and consider transaction size early in your design. Tools like the simulateTransaction RPC method can help catch structural issues before broadcasting. For the definitive specification, refer to the Solana Transaction Format documentation.

bitcoin-transaction-structure
DEEP DIVE

Bitcoin Transaction Structure

A Bitcoin transaction is a structured data object that defines the transfer of value. This guide explains its core components, from inputs and outputs to scripts and signatures.

At its core, a Bitcoin transaction is a data structure that transfers ownership of satoshis. Every transaction has a globally unique identifier called a Transaction ID (TXID), which is the double SHA-256 hash of the transaction data. A transaction does not directly reference account balances; instead, it spends specific, previously unspent transaction outputs (UTXOs) and creates new ones. This UTXO model is fundamental to Bitcoin's design, treating coins not as account balances but as discrete pieces of verifiable data.

A transaction is composed of two primary sections: inputs and outputs. The vin (vector input) array lists the UTXOs being spent. Each input must provide a cryptographic signature (via a script) that proves authorization to spend the referenced output. The vout (vector output) array defines where the value is being sent. Each output specifies an amount in satoshis and a locking script (scriptPubKey) that sets the conditions required to spend it in the future, such as providing a specific public key hash.

The most critical data within an input is the scriptSig (or witness for SegWit transactions), which contains the unlocking script satisfying the conditions of the UTXO's scriptPubKey. For a standard P2PKH payment, this includes a digital signature and the corresponding public key. The transaction also includes a version number (currently 1 or 2 for taproot), a locktime field to delay transaction validity, and the total transaction size. All this data is serialized into a specific byte format before being hashed for signing and broadcast.

Here is a simplified JSON representation of a transaction's structure:

json
{
  "version": 2,
  "locktime": 0,
  "vin": [
    {
      "txid": "previous_txid_here",
      "vout": 0,
      "scriptSig": {"asm": "", "hex": ""},
      "witness": ["signature", "pubkey"],
      "sequence": 4294967295
    }
  ],
  "vout": [
    {
      "value": 0.0005,
      "scriptPubKey": {
        "asm": "OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG",
        "hex": "76a914...88ac"
      }
    }
  ]
}

Note: In SegWit, the witness data is segregated and not part of the traditional transaction hash, reducing fees and enabling scaling solutions like the Lightning Network.

Understanding this structure is essential for developers building wallets, exploring the blockchain with tools like Bitcoin Core RPC, or writing smart contracts for layers like RGB or Mint. Correctly serializing transaction data is necessary for creating valid signatures. Errors in the construction—such as mismatched hashes, invalid script formats, or incorrect fee calculation—will result in the network rejecting the transaction.

DATA STRUCTURES

Transaction Field Comparison

Comparison of common transaction data formats used in EVM and Solana development.

Field / PropertyEVM (EIP-1559)Solana (Versioned Tx)Cosmos SDK (StdTx)

Transaction Type

Legacy, Type 2 (EIP-1559)

VersionedTransaction

StdTx

Gas/Compute Unit Field

gasLimit

computeUnitLimit

gas

Fee Model

baseFee + priorityFee (maxFeePerGas)

prioritization fee (microLamports)

gas * gasPrice

Chain ID Field

chainId

recentBlockhash

chain_id

Signature Format

rsv

ed25519 signature array

secp256k1 signatures

Nonce Required

Default Serialization

RLP

Binary (borsh)

Amino (deprecated), Protobuf

Max Data Size

~128KB (block gas limit)

1232 Bytes (tx size limit)

~100KB (mempool limit)

serialization-methods
DATA INTEGRITY

Serialization and Hashing Methods

How to structure and secure transaction data for blockchain execution. Correct serialization ensures nodes can process your data, while proper hashing guarantees its immutability.

01

RLP (Recursive Length Prefix)

The legacy serialization format used by Ethereum for encoding transactions and state. It's a simple, deterministic method for encoding nested arrays of binary data.

  • Purpose: Serialize data for transaction signing and block construction.
  • Key Property: The same input always produces the same byte sequence.
  • Example: Ethereum's original transaction format (EIP-155) uses RLP.

Note: Newer standards like SSZ are replacing RLP for consensus-layer data.

02

SSZ (Simple Serialize)

The modern serialization and Merkleization standard for Ethereum 2.0. It's designed for efficient Merkle tree generation and predictable hashing.

  • Core Feature: Combines serialization with Merkle tree hashing in one algorithm.
  • Use Case: Encodes Beacon Chain blocks, attestations, and consensus objects.
  • Deterministic: Like RLP, but with fixed offsets for fast verification.

SSZ is central to Ethereum's proof-of-stake security and light client protocols.

03

Transaction Hash (tx hash / txid)

A unique cryptographic fingerprint for a transaction, generated by hashing its serialized data. This is the primary identifier broadcast to the network.

  • Calculation: keccak256(serialized_signed_transaction) on Ethereum.
  • Immutability: Changing a single byte in the input creates a completely different hash.
  • Verification: Nodes re-hash received transactions to validate integrity.

Common Pitfall: The hash is of the signed transaction, not just the payload.

04

Signing Hash (Message Hash)

The digest that is actually signed by a private key. It's derived from the serialized transaction data, often with a chain-specific prefix.

  • Ethereum (EIP-155): keccak256(\x19Ethereum Signed Message:\n32 + keccak256(rlp_encoded_tx_data))
  • Bitcoin: Double SHA-256 of the serialized transaction.
  • Critical Step: Signing the wrong hash (e.g., raw data vs. prefixed hash) will produce an invalid signature.

Always use the protocol's specified signing scheme.

05

ABI Encoding

The standard for encoding structured function calls and data for Ethereum smart contracts. It defines how to serialize parameters for the EVM.

  • Function Selector: First 4 bytes of keccak256(function_signature).
  • Parameter Packing: Pads arguments to 32-byte words.
  • Tools: Use libraries like ethers.utils.defaultAbiCoder or web3.eth.abi.

Incorrect ABI encoding is a leading cause of failed contract interactions.

06

Canonical Serialization

The practice of ensuring there is one and only one valid byte representation for a given piece of data. This is fundamental to consensus.

  • Requirement: All nodes must serialize identically to reach the same hash.
  • Implementation: Strict rules on field order, integer encoding, and optional fields.
  • Consequences: Non-canonical serialization can cause chain forks.

Protocols like Bitcoin have hard-forked (BIP66) to enforce stricter canonical signing.

signing-and-broadcasting
GUIDE

Signing and Broadcasting

A technical guide to constructing, signing, and submitting transactions on EVM-compatible blockchains.

A blockchain transaction is a structured data object that initiates a state change. On Ethereum and other EVM chains, this structure is defined by the Transaction type, which includes core fields like nonce, gasPrice, gasLimit, to, value, data, and chainId. The nonce is a sequential counter that prevents replay attacks, while chainId specifies the target network (e.g., 1 for Ethereum Mainnet). The data field is crucial for contract interactions, containing the encoded function call and its arguments. Correctly serializing this data into the proper RLP (Recursive Length Prefix) format is the first step before signing.

Signing is the cryptographic process of authorizing the transaction with your private key. The serialized transaction data is hashed using Keccak-256 to create a digest. This digest is then signed with an Elliptic Curve Digital Signature Algorithm (ECDSA) using the secp256k1 curve. The result is the v, r, and s signature values, which are appended to the original transaction to create a signed transaction. This signature proves you own the address derived from the corresponding public key without revealing the private key itself. Libraries like ethers.js and web3.js abstract this process, but understanding it is key for advanced use cases like meta-transactions or signing offline.

The final step is broadcasting the signed transaction to the network. This involves sending the raw, signed transaction bytes to a node's RPC endpoint (e.g., using eth_sendRawTransaction). The node validates the signature and propagates the transaction to peers. It then enters the mempool, where it awaits inclusion in a block by a validator. You must ensure your gasPrice (or maxFeePerGas/maxPriorityFeePerGas for EIP-1559) is competitive for timely execution. Always verify the transaction hash returned by the RPC call, as it is your proof of submission and can be used to track the transaction on a block explorer like Etherscan.

TRANSACTION DATA

Common Mistakes and Debugging

Incorrect transaction data is a leading cause of failed smart contract interactions. This guide addresses frequent developer errors in structuring `data` fields, `value` handling, and ABI encoding.

An 'execution reverted' error means your transaction's logic triggered a require(), revert(), or assert() statement in the smart contract. This is often due to incorrect input data.

Common causes include:

  • Incorrect function signature: Using transfer() instead of transferFrom() for ERC-20 approvals.
  • Wrong parameter types: Sending a string where the contract expects a uint256.
  • Insufficient validation: Not checking if a user has enough token balance before a transfer.

Debugging steps:

  1. Use Tenderly or OpenChain's transaction debugger to step through the failed call.
  2. Check the contract's source code on Etherscan to understand the revert condition.
  3. Simulate the transaction locally using Hardhat or Foundry's eth_call before broadcasting.
TRANSACTION DATA

Frequently Asked Questions

Common developer questions about structuring, signing, and submitting blockchain transactions.

A raw transaction is an unsigned object containing the transaction's core data, such as the to address, value, data, gasLimit, and nonce. It's the payload that needs to be cryptographically signed.

A signed transaction is the raw transaction object plus the signature components: v, r, and s. This signature is generated using the sender's private key. The signed transaction is serialized into a hex string (RLP-encoded for Ethereum) and can be broadcast to the network.

Example Raw Tx (Ethereum):

json
{
  "nonce": "0x9",
  "gasPrice": "0x4a817c800",
  "gasLimit": "0x5208",
  "to": "0x3535353535353535353535353535353535353535",
  "value": "0xde0b6b3a7640000",
  "data": "0x"
}
How to Structure Transaction Data Correctly | ChainScore Guides