Skip to content

feat(solana): opt-in batch-settlement through @blockrun/llm's SolanaBatchPayer - #446

Merged
VickyXAI merged 6 commits into
mainfrom
feat/solana-batch-settlement
Oct 12, 2026
Merged

VickyXAI merged 6 commits into
mainfrom
feat/solana-batch-settlement

Conversation

@VickyXAI

@VickyXAI VickyXAI commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Opt-in Solana x402 batch-settlement for chat calls. One deposit opens a channel with BlockRun; after that, each call carries an authorization for its ceiling and the gateway charges what the call actually cost. It is off by default; turn it on with CLAWROUTER_SOLANA_BATCH=1 or startProxy({ solanaBatch }).

This replaces #435 (SkinGem). ClawRouter no longer implements the protocol itself: it drives @blockrun/llm's SolanaBatchPayer (3.23.1), the same payer SolanaLLMClient ships. That payer never replaces an in-doubt payment, locks the channel file across processes, cools down after a 429, and parks a wallet on exact after a refused deposit. BlockRun's operator key is pinned as the only trusted operator.

Commits

  1. chore(deps): @x402/* 2.21 → 2.28 and @blockrun/llm 3.10 → 3.23.1.
    • 2.28's default SDK spend controls ($1 per payment, default assets only) are switched off, which keeps the old behaviour.
    • An override points svm's @solana/program-client-core at kit 8.4. svm only uses getAccountMetaFactory, whose signature is identical, and this keeps a single @solana/* family in the tree.
    • viem is pinned to ~2.56.9.
  2. feat(solana): the integration.
    • Spend rules. The payer's client gets ClawRouter's own hooks: the strict cap, SpendControl and the signing recorders. Batch payments are checked at their ceiling like exact ones, and only if the user set limits. The session cap then books the receipt's charge, not the ceiling.
    • Routing. Non-streaming chat completions on Solana go to the payer:
      • paid → the response is returned;
      • fallback → exact, against the freshest challenge;
      • served or failed → returned unpaid;
      • in doubt → thrown and never paid again.
    • Anthropic SDK. @anthropic-ai/sdk (lazy-imported by @blockrun/llm and unused here) stays external, which saves 0.65MB and removes child_process from dist.
  3. Three rounds of Codex review fixes:
    • In doubt is final. "Payment in doubt" is a hard stop on every retry path: the per-model timeout, explicit-pin and rate-limit retries, and the global deadline.
    • Receipts. Only a parsed receipt charge settles the ceiling down; without one, the ceiling stays booked.
    • Zero charges. A receipt that states a zero charge stays at zero.
    • Unpaid 429 retries. When the payer re-signs after a 429 that charged nothing, the earlier signature is voided first.
    • Balance preflight. It defers to the channel, which holds escrow the wallet balance does not show.
    • Notifications. onPayment fires for batch charges.
    • Replay budget. Bounded at 20s.

Tests (1197 pass; typecheck, lint, prettier and dist smoke are clean):

  • payment-preauth.batch.test.ts: every pay() outcome, receipt parsing (missing, malformed or zero), and an abort after the send counting as in doubt.
  • proxy.solana-batch.test.ts: runs end to end through startProxy with the real payer and the real @x402/svm scheme against a local gateway and RPC. It covers:
    • the strict cap counting the charge, not the ceiling;
    • no receipt keeping the ceiling;
    • in doubt never repaid, for both explicit and routed models;
    • the low liquid-balance preflight;
    • the global deadline while a payment is in flight.
  • Mutation-checked. Each of these changes makes a test fail:
    • booking the ceiling instead of the charge;
    • removing the in-doubt chain stop;
    • restoring the balance downgrade;
    • putting the old order back around the global deadline.

Review

Summary by CodeRabbit

  • New Features

    • Added optional Solana batch payments for eligible, non-streaming wallet-based chat requests. The feature is off by default and supports configurable deposit limits, including a default cap of $1, and programmatic setup.
    • Added safeguards against duplicate payments when an outcome is uncertain, plus session accounting based on confirmed charges.
  • Bug Fixes

    • Improved payment-limit enforcement and handling of payment challenges and failures. Exact-payment fallback occurs only when the gateway confirms that no batch payment was charged.
  • Documentation

    • Documented Solana batch settings, payment behavior, configuration options, and automatic closure of idle deposits after three days.

1bcMax added 5 commits October 11, 2026 21:02
Prepares Solana batch-settlement through @blockrun/llm's SolanaBatchPayer,
which needs @x402/core and @x402/svm ~2.28.

- @x402/core 2.28 turns on its own spend controls by default ($1 per payment,
  default assets only). ClawRouter enforces its own limits and BlockRun prices
  some calls above $1, so the proxy and doctor clients disable them,
  keeping pre-2.28 behavior exactly. Test clients are configured the same way.
- @x402/svm 2.28 declares @solana/program-client-core ^6.1.0, which nests a
  second @solana/signers + transactions (6.10). An override points it at
  kit's 8.4.0 copy: svm uses only getAccountMetaFactory, whose signature is
  identical in 6.10 and 8.4. One @solana/* family stays in the tree.
- viem pinned ~2.56.9: @blockrun/llm needs >=2.56.3, and the newest 2.57.x
  is days old.

Lockfile re-resolved from the previous one with npm 11; the only changes are
these packages, ox (viem's) and @anthropic-ai/sdk (@blockrun/llm's).
…atchPayer

Solana chat calls can now be paid with x402 batch-settlement: one deposit
opens a channel with BlockRun, each call carries an authorization for its
ceiling, and the gateway charges what the call actually cost. Off by default;
CLAWROUTER_SOLANA_BATCH=1 or startProxy({ solanaBatch }) turns it on.

ClawRouter does not implement the protocol. It drives the same payer
SolanaLLMClient ships (in-doubt payments never replaced, cross-process channel
lock, 429 cooldown, refused deposits park the wallet on exact), with BlockRun's
operator key pinned as the only trusted operator.

- Spend rules: the payer's client gets ClawRouter's own hooks via
  configureClient (strict-cap check, SpendControl, signing recorders), so
  batch payments are checked at their per-call ceiling exactly like exact
  ones, and only when the user configured limits. The session cap then books
  what each call was charged, not the ceiling: signatures a pay() proved not
  charged are voided, the charged one is settled down to the receipt amount.
- payment-preauth routes non-streaming chat completions on the Solana rail to
  the payer: paid -> returned; fallback -> exact against the freshest
  challenge; served/failed -> returned unpaid; a throw (payment in doubt) ->
  rethrown, never paid again.
- proxy: BatchPaymentUnresolvedError stops the model chain (payment_in_doubt)
  instead of trying the next model, which would sign a new payment.
- tsup: @anthropic-ai/sdk (lazy-imported by @blockrun/llm, unused here) stays
  external: -0.65MB and no child_process in dist.

Tests: routing unit tests for every pay() outcome, and an end-to-end test
through startProxy with the real payer and @x402/svm scheme against a local
gateway and RPC. Mutation-checked: booking the ceiling instead of the charge,
and dropping the in-doubt chain stop, each fail them.

Supersedes #435 (SkinGem), which started this.
…keeps the ceiling; notify onPayment

Codex review of the batch integration:

- [P1] payment_in_doubt is now a flag the fallback loop checks before the
  per-model timeout branch and after both internal retries (explicit-pin and
  rate-limit), so none of them can pay another model for the same request.
  A non-payer failure after a batch send (an abort, a transport error) is
  wrapped as PaymentInDoubtError and treated the same way.
- [P1] A paid batch response without a receipt makes the payer report 0. That
  proves nothing, so the session cap keeps the signed ceiling instead of
  settling it down to zero.
- [P2] Within one pay(), a re-sign follows a proven-unpaid 429: the earlier
  signatures are voided before the cap checks the new one.
- [P2] onPayment fires for a confirmed batch charge, with the observer guarded
  as on the exact path.
…zero charges stay zero; preflight defers to the channel

Codex re-review:

- [P1] A receipt header is not proof of the charge: the payer reports 0 for a
  malformed receipt or one naming no amount. The charge is now parsed from the
  receipt itself (extra.chargedAmount, else amount, canonical integers only);
  anything else keeps the signed ceiling.
- [P2] recordSuccess no longer books the local estimate after any signature,
  so a receipt that settled a call to zero stays zero.
- [P2] With batch on, chat calls skip the liquid-balance preflight: right after
  a deposit the wallet can look empty while the channel holds the funds. A
  call with no escrow falls to exact, and a payment failure still takes the
  free-model fallback.
… budget

Codex review round 3 (gate passed, two P2s):

- A batch payment in doubt is handled before the global-deadline check, so
  the caller gets payment_in_doubt (and the dedup cache keeps it) instead of
  a generic timeout that drops the dedup entry and lets an identical retry
  pay again. A client that left still gets the disconnect cleanup.
- The payer's in-doubt replay budget is 20s instead of the SDK's 5 min: the
  proxy request has a deadline, and once it is aborted each replay reuses the
  aborted signal, so a long budget only kept the channel busy.
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: BlockRunAI/ClawRouter/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9a0318d4-4470-41d3-b857-aa5404323bc3

📥 Commits

Reviewing files that changed from the base of the PR and between 415490b and 756d072.


📒 Files selected for processing (1)
  • docs/configuration.md

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.



📝 Walkthrough

Walkthrough

The proxy adds opt-in Solana batch settlement for eligible wallet-mode chat requests. It records confirmed charges, distinguishes unresolved payments, and stops retries when payment status is unknown. Configuration, dependency updates, and tests support the new settlement path.

Changes

Solana Batch Settlement

Layer / File(s) Summary
Batch configuration and payer setup
src/config.ts, src/solana-batch.ts, src/proxy.ts, package.json, docs/configuration.md
The environment setting and ProxyOptions configure batch settlement. The payer resolves wallet and settlement settings. Documentation describes eligibility and configuration. Dependency ranges and an override are updated.
Eligible request settlement
src/payment-preauth.ts, src/payment-preauth.batch.test.ts, src/payment-preauth.test.ts
The pre-auth route attempts batch settlement for eligible non-streaming chat requests. It uses valid receipt amounts, returns served responses, and uses a fresh challenge for exact-payment fallback. Tests cover outcomes, errors, receipts, and excluded endpoints.
Proxy accounting and unresolved-payment handling
src/session-spend.ts, src/session-spend.test.ts, src/proxy.ts
The proxy records settled charges and tracks signed payments. Unknown payment outcomes stop model fallback and retry paths.
Settlement tests and policy-client configuration
src/proxy.solana-batch.test.ts, src/commands/policy.test.ts, src/doctor.ts, src/polymarket/spend-policy.test.ts, src/spend-control.test.ts
Tests cover spending limits, missing receipts, low liquid balance, and in-doubt payments. Policy clients disable SDK spend controls before registering policy hooks.
Externalize Anthropic SDK
tsup.config.ts
The build configuration adds @anthropic-ai/sdk to the external list.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant createPayFetchWithPreAuth
  participant Gateway
  participant SolanaBatchPayer
  Client->>createPayFetchWithPreAuth: Send eligible chat request
  createPayFetchWithPreAuth->>Gateway: Fetch unpaid request
  Gateway->>createPayFetchWithPreAuth: Return payment challenge
  createPayFetchWithPreAuth->>SolanaBatchPayer: Attempt batch payment
  SolanaBatchPayer->>Gateway: Send batch authorization
  Gateway->>SolanaBatchPayer: Return settlement response and receipt
  SolanaBatchPayer->>createPayFetchWithPreAuth: Return batch outcome
  createPayFetchWithPreAuth->>Client: Return response or continue exact-payment fallback
Loading

Merge Risk: ⚪ Minimal · up to 756d0

The reviewed documentation describes the opt-in settlement behavior and idle closure without an established contradiction; no actionable merge risk is identified in this change.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 14 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: opt-in Solana batch settlement through @blockrun/llm's SolanaBatchPayer.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

Full details: Docstring Coverage

Explanation

Docstring coverage is 47.37% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 19 functions across 14 files. (1 skipped: 1 unsupported.)


  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…nused escrow

sol.blockrun.ai's 402 advertises batchPolicy.idleCloseSeconds = 259200: an
idle channel is closed at its settled watermark and the rest returns to the
wallet. The doc said only a manual closeBatchChannel() would return it.
@VickyXAI
VickyXAI merged commit 465a49f into main Oct 12, 2026
5 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