Skip to content

feat(solana): poll media jobs with a wallet ownership proof, not a payment - #176

Merged
VickyXAI merged 3 commits into
mainfrom
feat/solana-poll-ownership
Oct 11, 2026
Merged

VickyXAI merged 3 commits into
mainfrom
feat/solana-poll-ownership

Conversation

@VickyXAI

Copy link
Copy Markdown
Contributor

On the Solana wallet rail, async media polls now carry the x-poll-* ownership proof that sol.blockrun.ai accepts since rev 00561-ghx (blockrun-sol src/lib/poll-ownership.ts), so a pending job no longer needs a fresh SPL payment signature every 20s.

  • video: pending polls send only the proof. When the job finishes, the gateway answers 402 with body status:"completed"; the client signs one payment from that challenge (pinned to the submit's amount, recipient and fee payer) and re-sends the poll on the existing payment-signed path. Stale-blockhash re-signs, in-flight and give-up handling are unchanged.
  • music / image: these are paid at submit, so polls never sign a payment.
  • fallback: if the gateway refuses the proof (video 400 Invalid job id, audio/image 404 Job not found) or returns any 402 that is not the completion challenge, the client switches once to payment-signed polling and logs it to stderr.
  • unchanged: the Base rail and the account (API key) rail. No version bump (0.54.3 — Solana polls prove the wallet; the decide skill teaches Decisions #172 is the release).

The MCP owns this polling in src/utils/solana-402.ts; it does not go through the @blockrun/llm SDK. Signing uses node:crypto ed25519 over the session key's seed. bs58 (already installed transitively via @blockrun/llm) is now a declared dependency.

Tests: added test/solana-poll-ownership.test.ts (18 tests: the signature verifies under node:crypto over the exact message, pending polls never call the payment signer, completion makes exactly one payment, music and image never pay, fallback). The legacy suite is pinned to pollOwnership:false as the fallback path, and a Base-unchanged test is in video-money-path. Each of the four planted defects turned the suite red. typecheck passes, and npm test passes 1413/1413.

…yment

sol.blockrun.ai (rev 00561-ghx, 2026-10-10) accepts x-poll-wallet /
x-poll-timestamp / x-poll-signature on the video, audio and image poll
routes: an ed25519 signature by the submitting wallet over
"blockrun-poll:v1:<kind>:<jobId>:<unix seconds>". solanaPaidAsyncPost now
polls with that proof instead of re-signing an SPL payment every 20s.

- video: pending polls carry the proof only; the finished job's 402
  (body status "completed") is the challenge for the ONE payment, pinned
  to the submit's amount/recipient/fee payer, then re-sent on the
  existing payment-signed path.
- music/image: paid at submit; polls never sign a payment.
- a refused proof (video 400 "Invalid job id", audio/image 404 "Job not
  found") or a non-completion 402 falls back once to payment-signed
  polling, logged to stderr.
- Base rail and the account rail are unchanged.

Signing is node:crypto ed25519 over the session keypair's seed; bs58
(already installed via @blockrun/llm) is now a declared dependency.
… signer that cannot be built is logged

Node 20.19's --experimental-test-module-mocks loader resolves bs58 through its
CJS build, where the default import is { default: codec }, so bs58.encode was
undefined and 13 poll-ownership tests failed on node 20.19 only. The published
ESM bundle was unaffected (checked on 20.19 and 22). Unwrap either shape.

The createPollSigner failure was swallowed, which surfaced as 10s poll
timeouts instead of the TypeError; it now goes to stderr (message only).
The completion 402 set lastStatus to "completed" before anything settled,
and the poll classifier read that remembered status: a later 2xx with no
status and no receipt (a bodiless 202) returned as the delivered video, so
blockrun_video booked the quote and then failed on the missing URL.
The classifier now decides on each answer's own status; lastStatus is kept
for messages only. Regression test added (red before, green after).

Found by codex review.
@VickyXAI
VickyXAI force-pushed the feat/solana-poll-ownership branch from 2a78522 to 5405392 Compare October 11, 2026 21:58
VickyXAI added a commit that referenced this pull request Oct 11, 2026
…sions

Release notes for #176 (Solana media polls carry a wallet ownership proof
instead of a payment signature; one payment at video completion) join the
decide skill rewrite: POST api.blockrun.ai/v1/decisions (OpenAI Decisions
contract, gpt-6-luna), the only rail, free with a key. Gateway #804 took
Decisions off x402, so the skill names no paid route. The legacy
/v1/decide shape is still accepted there.

Version 0.54.3: 0.54.2 shipped the withdrawal fix (#174).
@VickyXAI
VickyXAI merged commit 0a6e99a into main Oct 11, 2026
3 checks passed
VickyXAI added a commit that referenced this pull request Oct 11, 2026
…sions

Release notes for #176 (Solana media polls carry a wallet ownership proof
instead of a payment signature; one payment at video completion) join the
decide skill rewrite: POST api.blockrun.ai/v1/decisions (OpenAI Decisions
contract, gpt-6-luna), the only rail, free with a key. Gateway #804 took
Decisions off x402, so the skill names no paid route. The legacy
/v1/decide shape is still accepted there.

Version 0.54.3: 0.54.2 shipped the withdrawal fix (#174).
VickyXAI added a commit that referenced this pull request Oct 11, 2026
…sions (#172)

Release notes for #176 (Solana media polls carry a wallet ownership proof
instead of a payment signature; one payment at video completion) join the
decide skill rewrite: POST api.blockrun.ai/v1/decisions (OpenAI Decisions
contract, gpt-6-luna), the only rail, free with a key. Gateway #804 took
Decisions off x402, so the skill names no paid route. The legacy
/v1/decide shape is still accepted there.

Version 0.54.3: 0.54.2 shipped the withdrawal fix (#174).
VickyXAI added a commit that referenced this pull request Oct 12, 2026
…ed out, never "no charge" (#177)

Every finished video now reaches the payment-signed path on its first paid
poll (#176). When that settle times out, sol answers 402 "Payment settlement
outcome unknown" with Retry-After: 90 and holds the job's claim; other polls
get "settlement in progress on a concurrent poll". The helper ignored
Retry-After, spent its 3 re-signs one poll interval apart and threw "no
charge was made" inside the hold, while that payment could still land.

- Held answers (isSettleHeld, classified on THIS answer's body and reason,
  never the remembered reason) honour Retry-After, re-sign with a fresh
  blockhash and do not spend the re-sign budget; the deadline bounds them.
  A later header-less 402 ("Payment verification failed") still takes the
  bounded re-sign path.
- Once a payment's outcome is open, EVERY exit is a BilledJobError billing
  "unknown" — the deadline, a 500 poll answer, an unreadable challenge, a
  funding refusal of the fresh settle — so the tool books the quote and says
  the job MAY have been charged.
- A completion 402 whose challenge cannot be read falls back to
  payment-signed polling instead of aborting the finished job.

Tests: 5 added in test/solana-poll-ownership.test.ts; each is red against
the pre-fix solana-402.ts.
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