How to batch transactions with Ternoa SDK 🔗
Tags: ternoasdk, polkadotjs, batchextrinsic, ternoablockchain
Published: 2022-10-25 17:03:41

Batching some transactions will make you stop loosing time ⏱. _If you look to an alternative into creating transactions one by one_ on the [Ternoa blockchain](https://www.ternoa.com/), you are at the right place. Let's imagine a use-case where you want to create a thousand of NFT. Instead of creating them one by one, you should batch them all into only one single call function. _Easy, right_ ? **Lets dive into detail right now!** 🤿

This article is not meant to be a full coverage of how to use Ternoa SDK. Exemples are quite simple (particularly the batched items) as we want to **focus on the flow to handle succeeded or failed batches**. We assume you already know the basic concepts of **blockchain events**, [extrinsics](https://docs.substrate.io/fundamentals/transaction-types/) (some substrate functions, also called here transaction or tx), how to initialize the Ternoa SDK, and **the basic extrinsic lifecycle**: 

- _🔧 Create a transaction_
- _🖊 Sign a transaction_
- _🚀 Send a transaction_

At the end of this article, you will be able to identify which type of batch function might suits to your use-case and how to handle the failed events.

Ternoa SDK version used : v1.3.1-rc0
`npm i ternoa-js@1.3.1-rc0`
This version of the SDK embed some breaking changes: the `submitTxBlocking` now provides the block information alongside events in the result. It also provides the new `forceBatch`and a bunch of fancy useful helpers to check the result of batched transactions. Learn more [here](https://github.com/capsule-corp-ternoa/ternoa-js/releases).

### 👀 What's the difference between a `Batch`, a `BatchAll` and a `ForceBatch` call ?

The first important step is to understand each of the three batchs respective process. If they all aim to group several transactions in one call, the difference is **how they handle one or several failed extrinsic(s)** and the corresponding events returned. Here is a summary: 


|            | Without failed tx|With failed tx(s)|
| :-------------------:|:-------------------|:-----------------------:|
| Batch         | ItemCompleted(n)<br>BatchCompleted<br>ExtrinsicSuccess  | BatchInterrupted<br>ExtrinsicSuccess|
| BatchAll      | ItemCompleted(n)<br>BatchCompleted<br>ExtrinsicSuccess  | ExtrinsicFailed |
| ForceBatch    | ItemCompleted(n)<br>BatchCompleted<br>ExtrinsicSuccess   | itemFailed(n)<br>BatchCompletedWithErrors<br>ExtrinsicSuccess |

_All the code snippets below assume you already have initialized the API in your dApp, on **the network of your choice** (reminder : default is Ternoa Alphanet. Warning with Mainnet.), and have an account filled with test CAPS to use the keyring to sign and submit transactions. In case you don't, claim some test CAPS on the Ternoa Alphanet [Faucet](https://www.ternoa.com/alphanet)._

#### 1 - In case of successful extrinsic : 
Batch/BatchAll/ForceBatch work the same. For each of theses functions, when completing a full batch without any issue, the SDK, through the chain, will provide a `BatchCompleted` event,  meaning that everything went well.

In order to retrieve those specifics events, the ternoa SDK provides some features as the `findEvent()` function or the `findEventOrThrow()` that can be bundled to your batch transaction when the call is over. 

```Javascript

const simpleBatchTxExemple = async () => {
  try {
    ... 
    // we initialized the api:
    const network = "wss://alphanet.ternoa.com"
    await initializeApi(network)
    // we make the keyring accessible in a constant:
    const keyring = await getKeyringFromSeed("your seed")
    // Let's create some transactions to batch :
    // an NFT
    const signableNFTTxHex = await createNftTx("NFT to be batched", 0, undefined, false)
    // and a collection
    const signableCollectionTxHex = await createCollectionTx("Collection to be batched", 1000)
    // here we batchAll the transactions hashs in an array [].
    // we use the batchAll as we want the tx to be reverted in case of fail.
    const signableBatchTx = await batchAllTxHex([signableNFTTxHex, signableCollectionTxHex])
    // we submit the batched extrinsics and sign them with our keyring variable to receive the events details.
    // we can destructure the result to isolate blockInfo and events
    const { blockInfo, events } = await submitTxBlocking(signableBatchTx, WaitUntil.BlockInclusion, keyring)
    // we retrived the required event : here let's say we want the NFTCreatedEvent to access detail of the transaction.
    const eventsDetail = events.findEventOrThrow(NFTCreatedEvent)
    return eventsDetail
  } catch (err) {
    console.log(err)
  }
}
``` 
That's it! Now we know how to batch a transaction, lets see how to handle errors, and which batch function you should use.

#### 2 - In case of failed extrinsic within _a batch_ call (among all the transactions submitted):
- The batch process **stops where it is**, _and keeps the firsts validated transactions_.
- For each successful event the chain provides a specific transaction event (ex: `nft.CollectionCreated`) + an `ItemCompleted` event, until it reachs a failed event. The ternoa SDK then throw a `BatchInterrupted` event with the error detail and the block index of the failed extrinsic.

**Where it gets slightly complicated :** Since Batch Extrinsic is considered as successful, even when interrupted, it does not mean that all transactions are validated. **The chain provides the `ExtrinsicSuccess` event anyway**. 

A good practice would be to monitor `ExtrinsicSuccess` and check if any `BatchInterruptedEvent` have been thrown within the batch transaction to retrieve the detail of the failed extrinsic.

```Javascript 

const batchTxExemple = async () => {
  try {
    ...
    // again, api is already initialized 
    // keyring is also retrieved and available in a variable
    // let's create some transactions to batch:
    // a first collection
    const signableCollection1 = await createCollectionTx("BatchAllCollection1", undefined)
    // as we want to make the batch fail, we create an nft with an error (a non existing collectionId):
    const signableNFT = await createNftTx("create NFT", 0, 20000, false)
    // and a second collection
    const signableCollection2 = await createCollectionTx("BatchAllCollection2", undefined)
    // here we batch the previous transactions hashs in an array []. 
    // we want to retrieve the failed tx and keep the firsts valid extrinsics.
    const signableForceBatchTx = await batchTxHex([
      signableCollection1,
      signableNFT,
      signableCollection2,
    ])
    // we can again destructure the result to isolate blockInfo and events
    const { blockInfo, events } = await 
submitTxBlocking(signableForceBatchTx, WaitUntil.BlockInclusion, keyring)
    
    // at this point, we know the batch will fail, because of the wrong collectionId in createNftTx.
    // let's check and catch the error : 
    // we try to look at a BatchInterruptedEvent
    const interruptedEvent = events.findEvent(BatchInterruptedEvent)
    // if there is one, we return the interrupted event.
    if (interruptedEvent) return interruptedEvent
    return { blockInfo, events }
  } catch (err) {
    console.log(err)
  }
}

// expected output of the error detail is the following: 'No Collection was found with that NFT id.'

```
So what happened here:

- We batched a group of transactions using the batch function. 
- We deliberately made an extrinsic failed (we created an NFT with a non existing collection Id). 
- When we signed and submitted the batch tx, we received the events list describing what happened. 
- As expected, we retrieved an error : a BatchInterruptedEvent  meaning that the firsts transactions (here the collection created) have been validated on the chain and the batch has been interrupted/stopped when trying to create the NFT. 
- Because some transactions are valid, the batch extrinsic is considered as successful even if it has been interrupted. 
- The last transaction (the second collection) has not been executed.

#### 3 - In case of a failed extrinsic within _a batchAll call_ (among all the transactions submitted):
- An `ExtrinsicFailed` event is provided with the error detail of the first failed extrinsic encountered.
- **The chain reverts each validated extrinsics** : Meaning the chain status goes back to where it was before the call. No extrinsic is validated at all.

```Javascript 

const batchAllTxExemple = async () => {
  try {
    ...
    // again, api is already initialized 
    // keyring is also retrieved and available in a variable
    // let's create some transactions to batch using batchAll:
    // a first collection
    const signableCollection1 = await createCollectionTx("BatchAllCollection1", undefined)
    // as we want to make the batch fail, we create again an nft with an error (a non existing collectionId):
    const signableNFT = await createNftTx("create NFT", 0, 20000, false)
    // and a second collection
    const signableCollection2 = await createCollectionTx("BatchAllCollection2", undefined)
    // here we batch the previous transactions hashs in an array [].
    // we want all the tx to be reverted incase of a failed tx.
    const signableForceBatchTx = await batchAllTxHex([
      signableCollection1,
      signableNFT,
      signableCollection2,
    ])
    // we can again destructure the result to isolate blockInfo and events
    const { blockInfo, events } = await submitTxBlocking(signableForceBatchTx, WaitUntil.BlockInclusion, keyring)
    
    // at this point, we know the batch will fail, because of the wrong collectionId in createNftTx.
    // let's check and handle the error : 
    // this time we try to look at an ExtrinsicFailedEvent
    const failedEvent = events.findEvent(ExtrinsicFailedEvent)
    // if there is one, we return the failed event.
    if (failedEvent) return failedEvent
    return { blockInfo, events }
  } catch (err) {
    console.log(err)
  }
}

// expected output of the error detail is the following: 'No Collection was found with that NFT id.'
```

So what happened here:
- We batched a group of transactions using the batchAll function. 
- We deliberately made an extrinsic failed (we created an NFT with a non existing collection Id). 
- When we signed and submitted the batchAll tx, we received the events list describing what happened. 
- As expected, we retrieved an error : an extrinsicFailedEvent meaning that all transactions have been reverted and the batchAll extrinsic has not been validated (considered as not successful).
- The last transaction (the second collection) has not been executed.

#### 4 - In case of a failed extrinsic within _a forceBatch call_ (among all the transactions submitted):
- The batch process will anyway keep running until the last extrinsic provided in the forceBatch parameters.
- For each successful events the chain provides a specific transaction event (ex: nft.nftCreated) + an `ItemCompleted` event, while it provides an `ItemFailed` event with the error detail for each failed extrinsics.
- In case of failed extrinsic, the chain will also provide a `BatchCompletedWithErrors` event.

**Like for the classic batch extrinsic**, the forceBatch extrinsic is considered as successful even with failedItems. **The chain provides the `ExtrinsicSuccess` event at the end**. 

Again the good practice would be to monitor `ExtrinsicSuccess` with the `BatchCompletedWithErrors`event and check if any `ItemFailed` events have been thrown within the forceBatch.

```Javascript 

const forceBatchTxExemple = async () => {
  try {
    ...
    // again, api is already initialized 
    // keyring is also retrieved and available in a variable
    // let's create some transactions to batch using forceBatch:
    // a first collection
    const signableCollection1 = await createCollectionTx("BatchAllCollection1", undefined)
    // as we want to make the batch fail, we create again an nft with an error (a non existing collectionId):
    const signableNFT = await createNftTx("create NFT", 0, 20000, false)
    // and a second collection
    const signableCollection2 = await createCollectionTx("BatchAllCollection2", undefined)
    // here we forceBatch the previous transactions hashs in an array [].
    // we want the process to be ran until the end even in case of failed tx.
    const signableForceBatchTx = await forceBatchAllTxHex([
      signableCollection1,
      signableNFT,
      signableCollection2,
    ])
    // we can again destructure the result to isolate blockInfo and events
    const { blockInfo, events } = await submitTxBlocking(signableForceBatchTx, WaitUntil.BlockInclusion, keyring)
    
    // at this point, we know the batch will fail, because of the wrong collectionId in createNftTx.
    // let's check and handle the error : 
    // this time we try to look at an BatchCompletedWithErrorsEvent
    const isBatchIncomplete = events.findEvent(BatchCompletedWithErrorsEvent)
    if (isBatchIncomplete) {
    // if the forceBatch is incomplete, we retrieve the list of failed events in an array of event []
    const failedEvents = events.findEvents(ItemFailedEvent)
    return failedEvents
    }
    return { blockInfo, events }
  } catch (err) {
    console.log(err)
  }
}

// expected output is an array of failed events with their respective error details. Here only one event: the NFT created
```

So what happened here:
- We batched a group of transactions using the forceBatch function. 
- We deliberately made an extrinsic failed (we created an NFT with a non existing collection Id). 
- When we signed and submitted the forceBatch tx, we received the events list describing what happened. 
- As expected, we retrieved an error : an BatchCompletedWithErrorsEvent meaning that the valid transactions are kept while the failed ones are not validated. 
- for the first time, the last transaction (the second collection) has been executed and is validated (no issue is retrieved).

### 💎 Improve checking batch results

Since the v1.3.1-rc0 version, the Ternoa SDK provides the related **utils** to check a batch call status and result. According to the type of batch call you need to execute, you will find the corresponding util function : 
- checkBatch for a 'standard' batch call
- checkForceBatch for a forceBatch call
- checkBatchAll for a batchAll call

As an exemple, checkForceBatch will provide in case of batch without errors, the list of successful events, and an isBatchCompleteWithoutErrors boolean (true). If any errors occurs it will provide the same isBatchCompleteWithoutErrors boolean (false), the list of successful events and failedItems, and an isTxSuccess boolean (true).

```Javascript 

const forceBatchTxExemple = async () => {
  try {
    ...
    // using the exact same code as before
    const { blockInfo, events } = await submitTxBlocking(signableForceBatchTx, WaitUntil.BlockInclusion, keyring)
    // here we use the checkForceBatch to see if any errors happened.
    const isForceBatchSuccedeed = await checkForceBatch(events)
    // do your logic with the results provided by isForceBatchSuccedeed.
   return what you need.
    ...
  } catch (err) {
    console.log(err)
  }
}
```

### 🔥 Conclusion 

The general best practice is to always use the batchAll transaction. It provide more consistency to avoid loosing any transactions on the way. But depending on the use-case, you can go with the batch function that suit you the best, and in case of failed transaction, you now know how to monitor them 💪.

🙌 Congrats folks, you are now batch friendly !
You know how to make the difference between a batch, a batchAll and a forceBatch call. You know which type of event to monitor in the result of your function, and which one to query to retrieve the good information. <br>_Have fun_ 🦦 !