Match Substrate events to the extrinsic that emitted them

A block contains one ordered event stream for many extrinsics. An explorer must associate events by their execution phase before showing a user what one transaction did.

Read the block and event storage together

With a connected polkadot-js ApiPromise, obtain one finalized hash. The example selects the third extrinsic using zero-based index 2. Choose an index present in the fetched block, or locate it by matching the signed extrinsic hash.

const head = await api.rpc.chain.getFinalizedHead()
const block = await api.rpc.chain.getBlock(head)
const records = await (await api.at(head)).query.system.events()
const index = 2
const related = records.filter(({phase}) =>
  phase.isApplyExtrinsic && phase.asApplyExtrinsic.eq(index))
console.log(block.block.extrinsics[index]?.hash.toHex(),
  related.map(({event}) => event.section + "." + event.method))

Retain the event’s original position

ApplyExtrinsic(2) identifies records associated with that extrinsic, including events from nested calls. Initialization and finalization events have separate phases and belong outside that group. If building permanent event IDs, use the block hash and original index in the full events array before filtering.

Multiple transfer events from one extrinsic remain distinct even when their amounts coincide. Persist each event rather than collapsing all rows sharing the same transaction hash.

Sources