Internal transactions
Every transaction object that you get through onTx()
or onBlock
has two methods that will give you access to internal transactions: .children()
and .flattenChildren()
.
children()
returns an array of all the calls performed by the current transaction, but not their sub-calls.flattenChildren()
will give you an iterator that traverses ALL internal transactions.
Both .children()
and .flattenChildren()
methods will also return internal static calls (which are calls to view
functions in Solidity) and delegate calls (call to actual implementations when you have a proxy, for instance).
They will also return reverted internal-transactions.
If you're not interested in those, both methods allow you to filter them out by passing an optional argument.
Traversing the internal transaction tree
A way to explore internal transactions can be by traversing the transaction tree yourself.
A good example being worth a thousand words:
onTx(topTx => {
traverse(topTx, []);
function traverse(tx: TxBase, path: string[]) {
// do what you want to do.
// for instance, here, we're signaling all calls to Arbitrum's USDC,
// and the chain of calls that lead to that call.
if (tx.to === '0xaf88d065e77c8cc2239327c5edb3a432268e5831') {
emit({
path,
usdcContractInput: tx.input(),
})
}
// traverse children
for (const sub of tx.children()) {
traverse(tx, [...path, tx.to]);
}
}
})
Flatten all internal transactions
If you dont need to infer something from the call tree, then an easier way to get internal transactions is using flattenChildren()
, like so:
onTx(topTx => {
for (const tx of topTx.flattenChildren()) {
// do what you want to do.
// for instance, here, we're signaling all calls to Arbitrum's USDC,
if (tx.to === '0xaf88d065e77c8cc2239327c5edb3a432268e5831') {
emit({
usdcContractInput: tx.input(),
})
}
}
})
Filter internal transactions
When using .flattenChildren()
, you can of course filter internal transactions that interest you manually, but when there are a lot, this might be a bit cost inneficient (remember that you pay for execution time!).
To do that more efficiently, you can pass an optional TxFilter
to .flattenChildren()
(see runtime types), which might be much more efficient depending on your use case.
Several examples:
// all internal calls and static calls to Arbitrum USDC contract
tx.flattenChildren('0xaf88d065e77c8cc2239327c5edb3a432268e5831')
// the same, but only calls, and also includes internal transactions that have reverted
tx.flattenChildren({
contract: '0xaf88d065e77c8cc2239327c5edb3a432268e5831',
onlyOp: 'CALL',
includeReverts: true,
})
// ... with this defined in the main body (not in your handler)
// (see ABI & events parsing section)
const { Transfer } = abi<TransferAbi>`event Transfer(address indexed from, address indexed to, uint256 value);`;
// then this fill give you all transactions that has emitted a transfer.
// (nb, you could give an array of events, or directly a 0x string representing the event topic0)
tx.flattenChildren({
emittedLog: Transfer,
})