Bracnh with test case
(https://github.com/fystack/multichain-indexer/tree/indexing-gnosis-internal-tx) bracnh for the tests case continue from here
Problem
The EVM indexer cannot index ETH transfers made through Gnosis Safe (multisig) wallets. The outer execTransaction call has value=0 and the actual ETH moves as an internal transaction inside the Safe contract.
Example transaction: 0x7c98ff7c910b025736b11d2f70db001d5c2ec25df6de9fb65193963f6059b1f9
- 0.1 ETH transferred from Safe (
0x84ba2321d46814fb1aa69a7b71882efea50f700c) to 0xc26dC13d057824342D5480b153f288bd1C5e3e9d
- Indexer sees
value=0 on the outer tx and skips it entirely
Why Not debug_traceTransaction
- Expensive and slow RPC call
- Most providers don't support it by default or charge premium
- Not scalable for high-throughput indexing
Proposed Approach
Decode the execTransaction input data directly + verify execution via receipt events. No new RPC methods needed.
How It Works
The Safe's execTransaction (method ID 0x6a761202) encodes transfer details in its input:
execTransaction(address to, uint256 value, bytes data, uint8 operation, ...)
| Offset |
Parameter |
Description |
0x00 |
to |
Transfer recipient |
0x20 |
value |
ETH amount in wei |
0x40 |
data |
Calldata (empty = pure ETH transfer) |
0x60 |
operation |
0 = Call, 1 = DelegateCall |
Receipt events confirm whether execution succeeded:
ExecutionSuccess: 0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e
ExecutionFailure: 0x23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23
Detection Flow
Is input sig == 0x6a761202?
+-- NO --> existing logic (unchanged)
+-- YES --> fetch receipt (batched with existing receipts)
Has ExecutionSuccess event?
+-- NO --> skip (execution failed)
+-- YES --> decode input params
operation == 0 (Call)?
+-- YES, data empty, value > 0 --> emit native_transfer
+-- YES, data non-empty --> ERC20 logs handled by existing parseERC20Logs
+-- NO (DelegateCall) --> skip (out of scope)
RPC Cost Impact
No new RPC methods or round-trips. The only change is a few extra tx hashes added to the existing BatchGetTransactionReceipts batch call.
| Mode |
Extra RPC round-trips |
Extra receipts per block |
| With pubkeyStore (production) |
0 |
~0-2 (only for Safe txs targeting monitored addresses) |
| Without pubkeyStore |
0 |
~1-5 (batched with existing receipt fetches) |
All input decoding and event checking is local byte parsing — no RPC calls.
Files to Change
1. internal/rpc/evm/tx.go — Constants
Add Safe-specific constants:
SAFE_EXEC_TRANSACTION_SIG = "0x6a761202"
SAFE_EXECUTION_SUCCESS_TOPIC = "0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e"
2. internal/rpc/evm/tx.go — NeedReceipt()
Add 0x6a761202 to the list of method sigs that require receipt fetching.
3. internal/rpc/evm/tx.go — New parseGnosisSafeTransfer() method
Decode execTransaction input to extract to, value, data, operation:
- If
operation == 0 (Call) AND data is empty AND value > 0 — native ETH transfer
FromAddress = Safe contract address (tx.To on the outer tx)
ToAddress = decoded to parameter
Amount = decoded value parameter
- Verify receipt contains
ExecutionSuccess event (not ExecutionFailure)
4. internal/rpc/evm/tx.go — ExtractTransfers()
Add Safe handling before existing native/ERC20 logic:
if input starts with 0x6a761202 && receipt has ExecutionSuccess:
extract Safe internal transfer
5. internal/indexer/evm.go — extractReceiptTxHashes()
When pubkeyStore != nil, also match Safe txs where the decoded recipient (to param from input) is a monitored address. This is pure byte parsing — no RPC cost.
6. internal/indexer/evm_test.go — Update test assertions
Change TestParseGnosisSafeETHTransfer to verify the transfer IS found:
- Type:
native_transfer
- From:
0x84ba2321d46814fb1aa69a7b71882efea50f700c (Safe contract)
- To:
0xc26dC13d057824342D5480b153f288bd1C5e3e9d
- Amount:
100000000000000000 (0.1 ETH in wei)
Out of Scope (future work)
multiSend batched operations (0x8d80ff0a via DelegateCall) — requires decoding tightly-packed sub-transactions
- DelegateCall operations (
operation == 1) — different execution semantics
- Other multisig contracts (non-Safe) — different ABIs
Acceptance Criteria
Automated Tests
Manual Verification
Verify the indexer correctly picks up Safe execTransaction internal ETH transfers on the following networks by finding a real Safe tx on each block explorer (search for method ID 0x6a761202), running the indexer against that block, and confirming the transfer appears:
For each, confirm:
- Outer tx has
value=0 and input starts with 0x6a761202
- Receipt contains
ExecutionSuccess event
- Indexer emits a
native_transfer with correct from (Safe contract), to, and amount
Non-Regression
Out of Scope (future work)
multiSend batched operations (0x8d80ff0a via DelegateCall) — requires decoding tightly-packed sub-transactions
- DelegateCall operations (
operation == 1) — different execution semantics
- Other multisig contracts (non-Safe) — different ABIs
Bracnh with test case
(https://github.com/fystack/multichain-indexer/tree/indexing-gnosis-internal-tx) bracnh for the tests case continue from here
Problem
The EVM indexer cannot index ETH transfers made through Gnosis Safe (multisig) wallets. The outer
execTransactioncall hasvalue=0and the actual ETH moves as an internal transaction inside the Safe contract.Example transaction: 0x7c98ff7c910b025736b11d2f70db001d5c2ec25df6de9fb65193963f6059b1f9
0x84ba2321d46814fb1aa69a7b71882efea50f700c) to0xc26dC13d057824342D5480b153f288bd1C5e3e9dvalue=0on the outer tx and skips it entirelyWhy Not
debug_traceTransactionProposed Approach
Decode the
execTransactioninput data directly + verify execution via receipt events. No new RPC methods needed.How It Works
The Safe's
execTransaction(method ID0x6a761202) encodes transfer details in its input:0x00to0x20value0x40data0x60operationReceipt events confirm whether execution succeeded:
ExecutionSuccess:0x442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556eExecutionFailure:0x23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d23Detection Flow
RPC Cost Impact
No new RPC methods or round-trips. The only change is a few extra tx hashes added to the existing
BatchGetTransactionReceiptsbatch call.All input decoding and event checking is local byte parsing — no RPC calls.
Files to Change
1.
internal/rpc/evm/tx.go— ConstantsAdd Safe-specific constants:
2.
internal/rpc/evm/tx.go—NeedReceipt()Add
0x6a761202to the list of method sigs that require receipt fetching.3.
internal/rpc/evm/tx.go— NewparseGnosisSafeTransfer()methodDecode
execTransactioninput to extractto,value,data,operation:operation == 0(Call) ANDdatais empty ANDvalue > 0— native ETH transferFromAddress= Safe contract address (tx.Toon the outer tx)ToAddress= decodedtoparameterAmount= decodedvalueparameterExecutionSuccessevent (notExecutionFailure)4.
internal/rpc/evm/tx.go—ExtractTransfers()Add Safe handling before existing native/ERC20 logic:
5.
internal/indexer/evm.go—extractReceiptTxHashes()When
pubkeyStore != nil, also match Safe txs where the decoded recipient (toparam from input) is a monitored address. This is pure byte parsing — no RPC cost.6.
internal/indexer/evm_test.go— Update test assertionsChange
TestParseGnosisSafeETHTransferto verify the transfer IS found:native_transfer0x84ba2321d46814fb1aa69a7b71882efea50f700c(Safe contract)0xc26dC13d057824342D5480b153f288bd1C5e3e9d100000000000000000(0.1 ETH in wei)Out of Scope (future work)
multiSendbatched operations (0x8d80ff0avia DelegateCall) — requires decoding tightly-packed sub-transactionsoperation == 1) — different execution semanticsAcceptance Criteria
Automated Tests
TestParseGnosisSafeETHTransferininternal/indexer/evm_test.gopasses — verifies the Ethereum mainnet tx 0x7c98ff... is indexed as anative_transferwith:0x84ba2321d46814fb1aa69a7b71882efea50f700c(Safe contract)0xc26dC13d057824342D5480b153f288bd1C5e3e9d100000000000000000(0.1 ETH)go test ./internal/rpc/evm/ ./internal/indexer/ -v— all existing tests still pass (no regressions)parseGnosisSafeTransfer()covering:ExecutionFailurein receipt (notExecutionSuccess)Manual Verification
Verify the indexer correctly picks up Safe
execTransactioninternal ETH transfers on the following networks by finding a real Safe tx on each block explorer (search for method ID0x6a761202), running the indexer against that block, and confirming the transfer appears:For each, confirm:
value=0and input starts with0x6a761202ExecutionSuccesseventnative_transferwith correct from (Safe contract), to, and amountNon-Regression
NeedReceipt()change does not cause receipt fetching for unrelated contract callsOut of Scope (future work)
multiSendbatched operations (0x8d80ff0avia DelegateCall) — requires decoding tightly-packed sub-transactionsoperation == 1) — different execution semantics