Transactions

Build Transaction

Every change on the Blockchain requires a transaction. A transaction wraps function calls in the form of clauses. Each clause sends instructions to an address that are encoded as hex string.

To send a transaction you will need to do multiple steps:

  1. Have Private Key at hand that will sign the transaction

  2. Encode the function calls into data calls

  3. Calculate how much gas the transaction will cost

  4. Build a transaction object with all the previous information

  5. Sign the transaction

  6. Send the transaction to the network

Collecting Function Calls in Clauses

The instructions for executing a function on the blockchain needs to be encoded in a certain way. There are different functions to help create the right format, one is the clauseBuilder that will in this example call increment() on the given address:

const clauses = [
  clauseBuilder.functionInteraction(
    '0x8384738c995d49c5b692560ae688fc8b51af1059',
    'increment()'
  ),
];

A clause can also send VET in the same action. Check the type definition to also learn more about the internals.

Calculate Gas

While reading on the blockchain is free, writing is requires so called gas fees. Gas is paid in VTHO, the secondary token on VeChain, which is generated by holding VET too.

To calculate the right amount of gas for your transaction, you can use estimateGas.

const gasResult = await thor.gas.estimateGas(clauses);

If you expect your contracts to have different results based on the sender, you can also pass in the sender address as optional second parameter.

Build Transaction

Once you have instructions + costs, you'll wrap them together into a transaction object with buildTransactionBody.

const txBody = await thor.transactions.buildTransactionBody(
  clauses,
  gasResult.totalGas
);

There are several options that can optionally be passed as third argument to enable fee delegation, dependency on other transactions, priority and an expiration. You will learn more about them in other sections.

Sign Transaction

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, of getting a signer first:

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);

Sign Transaction

And using the signer to sign the transaction:

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

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
);

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
);

Example Project

Last updated