Developer Fundamentals

-- Write Transactions III

Learn how to sign, send, and track VeChainThor transactions, including building signed objects, submitting them to the network, and monitoring results using the SDK.

Sign, Send & Track

Once a transaction is built, it needs to be signed by an entity that will execute all the code. This also makes the origin verifiable.

It is a four-step process:

  1. Get Signer

  2. Sign Transaction

  3. Build Signed Transaction Object

  4. Send & Track Transaction

Let's look at each step in more detail.

1. Get Signer

const wallet = new ProviderInternalBaseWallet(
  [{ privateKey, address: senderAddress }]
);

const provider = new VeChainProvider(
  // Thor client used by the provider
  thorClient,

  // Internal wallet used by the provider (needed to call the getSigner() method)
  wallet,

  // Enable fee delegation
  false
);

const signer = await provider.getSigner(senderAddress);

2. Sign Transaction

And using the signer to sign the transaction:

const rawSignedTx = await signer.signTransaction(tx, privateKey);

3. Build Signed Transaction Object

signTransaction returns the fully signed transaction that can already be published using a POST request to the /transactions endpoint of a VeChain node:

await fetch(`${nodeUrl}/transactions`, {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    raw: rawSignedTx,
  }),
})

For submission by SDK, the raw hex string needs to be restored into a transaction object:

const signedTx = TransactionHandler.decode(
  Buffer.from(rawSignedTx.slice(2), 'hex'),
  true
);

4. Send Transaction

The signed transaction can be published to the network using sendTransaction, which will post the data to the connected node:

const sendTransactionResult = await thor.transactions.sendTransaction(signedTx);

Wait for Results

sendTransaction returns a transaction ID that can be used to track the status of the newly published transaction. waitForTransaction will resolve with the full receipt as soon as the result is available:

const txReceipt = await thor.transactions.waitForTransaction(
  sendTransactionResult.id
);

The snippet below brings it all together and illustrates how you can build a transaction, estimate how much it costs, and sign it to execute the transaction onchain.

The snippet includes:

  • Necessary packages and imports

  • Build a transaction

  • Encoded function calls

  • Calculate how much gas the transaction costs

  • Sign the transaction with fee delegation (covered in the next lesson)

Try It Yourself

  • If you're using your own mnemonic, make sure to fund your wallet with VTHO

  • If you're using a one-time wallet, the script will fail as we don't have a funded wallet. Move to the Fee Delegation lesson to understand how to delegate gas fees and complete the full script.

import { ThorClient, ProviderInternalBaseWallet, VeChainProvider } from '@vechain/sdk-network';
import { Clause, ABIFunction, Secp256k1, Address, Transaction, HexUInt } from '@vechain/sdk-core';

const thor = ThorClient.at('https://testnet.vechain.org/');

//Prepare Wallet or add your own wallet
const privateKey = await Secp256k1.generatePrivateKey();
const senderAddress = Address.ofPrivateKey(privateKey);
console.log("Address: 0x" + senderAddress.digits)

// Clauses
const clauses = [
    Clause.callFunction(
        '0x8384738c995d49c5b692560ae688fc8b51af1059',
        new ABIFunction({
            name: 'increment',
            inputs: [],
            outputs: [],
            constant: false,
            payable: false,
            type: 'function',
        })
    ),
];

//Calculate Gas
const gasResult = await thor.transactions.estimateGas(clauses);

//Build Transaction.
// Fee delegation is set to "false" by default
const tx = await thor.transactions.buildTransactionBody(clauses, gasResult.totalGas);

// Sign Transaction. Needs 4 steps
//Step 1: Get signer
const wallet = new ProviderInternalBaseWallet(
  [{ privateKey: privateKey, address: senderAddress }]
);

const provider = new VeChainProvider( thor, wallet,
  // Enable fee delegation
 false
);

const signer = await provider.getSigner(0);

// Step 2: Sign transaction
const rawSignedTx = await signer.signTransaction(tx, privateKey);

// Step 3: Build Signed Transaction Object
const signedTx = Transaction.decode(
  HexUInt.of(rawSignedTx).bytes,
  true
);

// Step 4: Send Transaction
const sendTransactionResult = await thor.transactions.sendTransaction(signedTx);
console.log(sendTransactionResult)

// Wait for results
const txReceipt = await thor.transactions.waitForTransaction(sendTransactionResult.id);
console.log(txReceipt);