Send and confirm spans
Each transaction gets a send span when the hash comes back and a confirm span when the receipt does, nested under the tool that sent it and linked to each other.
- execute_toolpay_vendor
- send 845320x2e68…3fa8
- mempoolpending…
- confirm 84532success
- block#48338717
The real cost
On OP-stack chains like Base, blockchain.tx.fee includes the L1 data fee, and blockchain.tx.l1_fee records it on its own. The operator fee has its own attribute, blockchain.tx.operator_fee. Fees come from the sealed receipt, even on Base's flashblocks RPCs. With a metrics SDK, fees and confirmation times become histograms too.
Agent-aware
Transaction spans carry the agent's identity, so you can ask which agent spent what.
gen_ai.agent.name = "treasury-agent"
Reverts you can read
Mined reverts are replayed and decoded, custom errors included.
status = reverted
revert.reason = WithdrawalLimitExceeded(…)
Replaced transactions, handled
Same nonce, new hash? The receipt goes to the transaction that was actually mined.
blockchain.tx.status = replaced
You choose what is recorded
Calldata arguments and error messages are opt-in. Addresses are recorded by default, in lower case, and can be hashed or dropped; x402 keeps only the origin of a paid URL. It never signs or broadcasts.
Payments, too
With x402, the agent signs and a facilitator settles. A payment span records what was paid, to whom and whether it settled. With a reader, the settling transaction gets a confirm span linked to it, and the payment span checks that it actually carries your payment. MPP payments made with mppx get the same spans.
blockchain.payment.status = settled
What the send spent its time on
Signing, RPC and HTTP spans from your other instrumentation nest under the send span, for viem clients with a chain and for CDP. With traceTransport(), each JSON-RPC request becomes a span of its own, and propagateTraceContext() brings an RPC gateway such as eRPC into the same trace.