The standard way to track extrinsics is:

  .transfer(recipient, 123)
  .signAndSend(sender, ({ status, events }) => {
    if (status.isInBlock) {
      // here we know that our tx is in chain and can check if it succeeded or not

I need to support a case where something can go wrong on client before extrinsic gets in the block, so I'm looking for something like this:

let txHash = await api.tx.balances
  .transfer(recipient, 123)


// later somewhere else in the code, for example after server restart or page refresh

let txHash = load_hash_from_db();

// how to check here if extrinsic got included in block and in which one?

I'm aware that tx hash isn't a unique identifier and block hash + index should be used instead (https://wiki.polkadot.network/docs/build-protocol-info#unique-identifiers-for-extrinsics), but that only works if I wait until transaction gets included, which may be a long time during which things can go wrong.

What's the solution here?

3 Answers 3


You are right transactionHash is not unique, from the Polkadot wiki:

Many infrastructure providers on existing blockchains, e.g. Ethereum, consider a transaction's hash as a unique identifier. In Substrate-based chains like Polkadot, a transaction's hash only serves as a fingerprint of the information within a transaction, and there are times when two transactions with the same hash are both valid.

You can see a real example of 3 extrinsics with the same Hash in this previous StackEchange answer: Why could we find the same transaction hash in 3 different blocks?.

If you can't to see in which block has been included is a bit tricky. I can think on some options you can implement (I'd to see other people ideas in this question):

The first one is to be monitoring Events, extrinsics can trigger events, so depending on your use case this can be enough. And for example is the Transfer event, if you are monitoring the events you can see in which block has been included. Now if you have the the block number the solution is easy: How do I view extrinsic information? or To get the extrinsic data from the extrinsic Hash.

If you want to monitor deposits into an address, keep in mind that several transactions can initiate a balance transfer (such as balances.transferKeepAlive and a utility.batch transaction with a transfer inside of it). Only monitoring balances.transfer transactions will not be sufficient. Make sure that you monitor events in each block for events that contain your addresses of interest. Monitor events instead of transaction names to ensure that you can properly credit deposits.

Another is to batch your transactions, so in that case you improve a bit the waiting time (you don't need to wait each time you are sending a transaction). This was the solution on a very specific use case: Best practices for generating and monitoring transactions.

And finally (this is not 100% accurate) but you can guess when is going to be included, when you store your txHash: store_hash_in_db(txHash) store the block you are right now, and later when you look for this transaction Hash you try to find the one that is closer to this txHash.

  • You said that last solution is not 100% accurate. What scenarios can break it?
    – Mexke
    Commented Jun 26, 2023 at 9:18
  • Check this example where 2 extrinsics has the same hash: wiki.polkadot.network/docs/… It can happens with few blocks difference
    – Alex Bean
    Commented Jun 26, 2023 at 9:58

I use the uptest-cli: $ uptest -w ws:// 200 to subscribe to a chain and display the events that got triggered in each block. Nifty tool to have in one terminal and then you can see what events got triggered in the latest blocks. Public doc: https://uptest-sc.github.io/cli/event_sub.html


Just to build on Alex's answer a little:

The call hash for a transaction is not unique, because accounts can be reaped (if they fall below the existential deposit amount) and then re-created when new funds are transferred to them. When they are recreated, the account nonce (number that increments each time the account does a tx) is reset to 0. But...

If you own the account

If you construct and make a transaction from an account that you own, and save its hash, then the only person who can cause the exact same hash to occur again on chain is yourself (you'd need to get the account reaped and then put funds back into it to reset the nonce and be able to make the same tx with same hash again). So in this case, it's perfectly fine to search for blocks (starting with the latest known block that didn't contain it) for the call hash, and know that it's the call that you submitted if you find it.

If you don't own the account

Even if you don't own the account, if the goal is to check whether a transaction matching the one that you submitted is on chain somewhere, then searching (starting with the latest known block that didn't contain it) for the hash and finding it is proof that the exact tx that you submitted was successful, even if it was not actually submitted by you (eg yours went wrong for some reason, and somebody else submitted the same thing successfully). So perhaps this is good enough; the event you wanted to happen, happened.

A note on mortality

One way to have more control over this is to set a mortality on the transactions you submit. This way, you know exactly how many blocks the transaction will live for, and thus can scan a precise range of blocks in order to locate the transaction before being certain that it will definitely not be included in the future.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.