Sending multiple transactions in one batch using Tezos RPC

Disclaimer: This is a complicated combination of multiple RPC calls that results in a custom operation. Use it wisely and at your own risk.

So what do you need to do in order to perform operation through Tezos RPC? First of all you need to understand how to send 1 single transaction using RPC and then you will see how to do multiple transactions in one batch. Basically, it takes few steps: get dependencies, forge operation, sign bytes, preapply operation, decode signature and inject operation.

Let’s go through this steps in details.

Get dependencies

Before forging and signing any operation, you need to get dependencies — some values that you need for the next steps.
First one called “branch” — it’s a hash of the current head:
./tezos-client rpc get http://127.0.0.1:8732/chains/main/blocks/head/hash

Returns: <branch_hash>

Second one called “counter” — it’s counter for your source address:
./tezos-client rpc get http://127.0.0.1:8732/chains/main/blocks/head/context/contracts/<source_pkh>/counter

Returns: <counter>

Third one called “protocol” — you need a hash of the current protocol. You can simply find it on TzScan explorer http://tzscan.io/protocols or get it with:

./tezos-client rpc get http://127.0.0.1:8732/protocols (current protocol would be the last one from what RPC returned)

Returns: <protocol_hash>

Forge operation

In this step, you should forge the operation to get <operation_bytes>:
curl -X POST \
 http://127.0.0.1:8732/chains/main/blocks/head/helpers/forge/operations \
 -H ‘Cache-Control: no-cache’ \
 -H ‘Content-Type: application/json’ \
 -d ‘{
 “contents”: [
 {
 “kind”: “transaction”,
 “amount”: “10”,
 “source”: “<source_pkh>”,
 “destination”: “<destination_pkh>”,
 “storage_limit”: “0”,
 “gas_limit”: “127”,
 “fee”: “0”,
 “counter”: “<counter+1>”
 }
 ],
 “branch”: “<branch_hash>”
}’

Returns:<operation_bytes>

Sign bytes

Then you should sign forged operation with your secret key. Use <operation_bytes> from the first step and don’t forget to add 0x03 in prefix:

./tezos-client sign bytes 0x03<operation_bytes> for <source_alias>

Returns:<edsig_signature> in format edsig…

Preapply operation

Once forged and signed, your operation should be prevalidated using <edsig_signature> from the previous step:
curl -X POST \
 http://127.0.0.1:8732/chains/main/blocks/head/helpers/preapply/operations \
 -H ‘Cache-Control: no-cache’ \
 -H ‘Content-Type: application/json’ \
 -d ‘[{
 “protocol”: “<protocol_hash>”,
 “branch”: “<branch_hash>”,
 “contents”: ‘[
 {
 “kind”: “transaction”,
 “amount”: “10”,
 “source”: “<source_pkh>”,
 “destination”: “<destination_pkh>”,
 “storage_limit”: “0”,
 “gas_limit”: “127”,
 “fee”: “0”,
 “counter”: “<counter+1>”
 }
 ],
 “signature”: “<edsig_siganture>”
}]’

Returns: data about preapplied transaction like [{“contents”:[{“kind”:”transaction”,”source”:”tz1... — you won't use this, so nevermind.

Decode signature to hexademical format

Next you need to get <signed_operation_bytes> in order to inject signed operation:
<signed_operation_bytes> = <operation_bytes><decoded_edsig_signature>

So you need to decode <edsig_signature> from Base58 format to hexademical.
I used this website to do so: https://incoherency.co.uk/base58/
Note that you will receive prefix<decoded_edsig_signature>suffix so you need to kill those prefix and suffix in order to obtain <decoded_edsig_signature>
Prefix value is ussualy 09f5cd8612 — it’s the first 10 hexademical symbols. Suffix is just the last 8 hex symbols. So imagine that you will receive:
09f5cd8612<decoded_edsig_signature>8_last_hex_symbols

So after that you need to get <signed_operation_bytes> by sticking <operation_bytes> part to <decoded_edsig_signature> part.

Returns: <signed_operation_bytes>

Inject operation

In the final step you should inject signed operation with:
curl -X POST \
 http://127.0.0.1:8732/injection/operation \
 -H ‘Cache-Control: no-cache’ \
 -H ‘Content-Type: application/json’ \
 -d ‘“<signed_operation_bytes>”’

Returns: <operation_hash>

Congratulations if you got an <operation_hash> after injecting an operation! This means that your operation injected in the block and you can check it on TzScan: tzscan.io/<operation_hash>

Batch transactions

You are still curious how to include few transaction in one operation? Just add an extra array with data in “context”. Example of how to forge 2 transactions:

curl -X POST \
 http://127.0.0.1:8732/chains/main/blocks/head/helpers/forge/operations \
 -H ‘Cache-Control: no-cache’ \
 -H ‘Content-Type: application/json’ \
 -d ‘{
 “contents”: [
 {
 “kind”: “transaction”,
 “amount”: “10”,
 “source”: “<source_pkh>”,
 “destination”: “<destination_pkh>”,
 “storage_limit”: “0”,
 “gas_limit”: “127”,
 “fee”: “0”,
 “counter”: “<counter+1>”
 },

{
 “kind”: “transaction”,
 “amount”: “20”,
 “source”: “<source_pkh>”,
 “destination”: “<destination_pkh>”,
 “storage_limit”: “0”,
 “gas_limit”: “127”,
 “fee”: “0”,
 “counter”: “<counter+2>”
 }
 ],
 “branch”: “<branch_hash>”
}’

Use the same “contents” on the preapplying step.

Note:

  1. <counter> should be increased by one for each transaction in operation. For example, if current<counter> is 1000, then for the first transaction in the list you should specify “counter”:”1001", for the second transaction “counter”:”1002" and so on. This is security measure that enable to not run a transaction a second time.
  2. <branch_hash> is a hash of the current head in the moment when you start this process. But don’t worry that this hash actually keeps changing every minute with new blocks created — <branch_hash> should be valid at least for the ~100 blocks. It means that you have some time to complete operation.
  3. “amount” field specified in μꜩ. So 1000000 equals to 1 XTZ.
This guide was forged, signed and injected by Bake’n’Rolls! Big shout to “Baking” Community, especially to Klassare from the Kukai wallet and Marco for providing insights!