Skip to content

fix(batch): sign deposits with a finalized blockhash when the 402 names none - #95

Merged
VickyXAI merged 1 commit into
mainfrom
fix/batch-finalized-blockhash
Oct 27, 2026
Merged

VickyXAI merged 1 commit into
mainfrom
fix/batch-finalized-blockhash

Conversation

@VickyXAI

Copy link
Copy Markdown
Contributor

Problem

A batch deposit (open or top-up) is a Solana transaction. In @x402/svm 2.28, resolveBlockhash() takes its blockhash from the 402's extra.recentBlockhash. If the 402 has none, it calls @solana/kit's getLatestBlockhash() at kit's default commitment, which is confirmed. The agent-money-bench session traced the request ("params":[{"commitment":"confirmed"}]), and the new fallback test shows the same thing. PayAI's verifier runs behind the chain tip and rejects a blockhash it hasn't seen yet with invalid_exact_svm_transaction_simulation_failed: "BlockhashNotFound", the same failure the bench saw on @x402/svm exact payments. The SDK then classifies the refused deposit as deposit_refused: the call pays exact and the deposit is re-read from chain. Nothing is lost, but each refusal costs a full exact payment.

The SDK's own exact path (src/x402.ts) already gets a finalized blockhash: web3.js sends no commitment, and the RPC's default is finalized.

Change

  • createPayload() now builds the payment from a copy of the 402. In that copy, batch accepts with no recentBlockhash get a finalized blockhash plus its lastValidBlockHeight, read from the configured RPC (with rpcHeaders) and cached 10 s per RPC URL, like the exact path's BLOCKHASH_TTL_MS.
  • This only happens when the payment will likely carry a deposit: either no usable record exists for the trusted accept's channel (so this is an open), or prepareTopUp just sized a top-up. Authorizations sign no transaction and read nothing.
  • Additive only. A blockhash the 402 names is never replaced. @x402/core's paymentRequirementsMatchAccepted matches the echoed accepted.extra as a superset of the gateway's requirements, and blockrun-sol's batch handler only compares payTo / asset / network (src/lib/batch/payment.ts:260).
  • A failed read leaves the 402 as it is, so the scheme falls back to its own read. The SDK never consumes the injected lastValidBlockHeight itself; in-doubt logic still uses only the SDK's own anchor.

Tests

New, in test/unit/solana-batch.test.ts:

  • An open uses a finalized blockhash, and the echoed accepted.extra carries it.
  • A top-up after a restart uses a finalized blockhash.
  • An authorization reads no blockhash.
  • A blockhash the 402 names is kept, and nothing is read.
  • A failed finalized read falls back to the scheme's own confirmed read.

The first two and the fallback test fail on main; the other two guard behaviour that should not change. npm test: 1074 passed, 12 skipped. Build, lint and typecheck are clean.

Docs: docs/solana-batch.md (RPC section), CLAUDE.md batch section, CHANGELOG (Unreleased). #94 also edits CHANGELOG [Unreleased], so whichever merges second needs a one-line rebase.

…es none

@x402/svm 2.28 resolveBlockhash() uses the 402's extra.recentBlockhash, else
@solana/kit getLatestBlockhash at its default confirmed commitment (traced by
agent-money-bench: params [{commitment: confirmed}]). PayAI's verifier lags
the tip and rejects such a blockhash as BlockhashNotFound, so the deposit is
refused and the call pays exact.

createPayload() now passes the scheme a copy of the 402 whose batch accepts
lacking recentBlockhash carry a finalized one (getLatestBlockhash finalized,
cached 10 s per RPC URL), when the payment will likely carry a deposit (no
usable record for the trusted accept's key, or a top-up prepareTopUp sized).
Additive only; @x402/core matches the echoed accept as a superset; a failed
read leaves the 402 as is. Tests: open, top-up, authorization reads nothing,
402-named blockhash kept, failed read falls back (3 fail on main).
@VickyXAI
VickyXAI force-pushed the fix/batch-finalized-blockhash branch from 5ea3384 to 131925b Compare October 27, 2026 16:11
@VickyXAI
VickyXAI merged commit f0b4579 into main Oct 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant