Build a Transaction Object

How to Build An Unsigned Transaction

In this quick tutorial, we'll see how to build an unsigned transaction object for testing purposes, using the web3 library.

Let's assume that we're calling transfer() on the USDT ETH mainnet contract. The WALLET_ADDRESS field is completely arbitrary and set only for demonstration purposes.

const USDT_ABI = [{
    "name": "transfer",
    "inputs": [{
        "name": "_to",
        "type": "address"
    }, {
        "name": "_value",
        "type": "uint256"
    }],
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function"
}];

const USDT_CONTRACT_ADDRESS = "0xdAC17F958D2ee523a2206206994597C13D831ec7";

const WALLET_ADDRESS = "0x9B1054d24dC31a54739B6d8950af5a7dbAa56815";

async function getCurrentGasPrice() {
    return await web3.eth.getGasPrice();
}

// Function to build an ERC-20 transfer. Takes recipient and amount as parameters
async function buildTransferTransaction(transferRecipient, amount) {
    const contract = new web3.eth.Contract(USDT_ABI, USDT_CONTRACT_ADDRESS);
    const data = contract.methods.transfer(transferRecipient, amount).encodeABI();
  
    const gasPrice = await getCurrentGasPrice();

    const transaction = {
        from: WALLET_ADDRESS,
        to: USDT_CONTRACT_ADDRESS,
        data: data,
        value: '0x0', // No native tokens being sent
        gas: web3.utils.toHex(200000), // Typical gas limit for an ERC-20 transfer
        gasPrice: web3.utils.toHex(gasPrice),
        type: "0x0" // Legacy transaction type, set to 0x2 for EIP-1559 transactions
    };

    return transaction;
}

In the code above, we have:

  • Defined an ABI for the contract we're calling
  • Defined a function that can build a transaction object for an ERC-20 token transfer

In practice, your wallet application will likely be receiving these web3 transaction objects from the dApps that your users interact with.