Repository navigation
feat(solana): poll media jobs with a wallet ownership proof, not a payment - #83
Merged
Merged
Conversation
added 5 commits
October 10, 2026 19:06
…yment A Solana payment dies with its blockhash in ~60-90s, so polling a multi-minute video meant signing a fresh payment on almost every poll. sol.blockrun.ai (rev 00561-ghx) accepts an ed25519 proof instead: x-poll-wallet / x-poll-timestamp / x-poll-signature over "blockrun-poll:v1:<video|audio|image>:<job id>:<unix seconds>". - SolanaLLMClient / AsyncSolanaLLMClient sign that proof fresh per poll with the wallet key they already hold (solders Keypair; no new dependency). - Video: pending polls carry only the proof. The finished-but-unpaid poll answers 402 + challenge + status "completed"; one payment is signed from THAT challenge (terms pinned to the submit's) and the GET re-sent. A paid poll that 402s (concurrent claim, stale blockhash) goes back to proof polling, which answers already_settled or a fresh challenge, bounded by 1 + max_resigns payments. - Images and music were paid at POST: their polls never sign a payment. A batch-billed image 402 falls back to the payment path. - A "no such job" refusal of the proof falls back once, per job, to the existing payment-signature polling, logged on blockrun_llm.solana_client. - Slow music() tracks on the wallet rail are now polled at all: the 202 stub used to be returned and failed MusicResponse validation after the track was paid for. - Account rail and Base unchanged; a signer without a keypair keeps the payment path.
…ideo - Spend limits: image/image_edit/video/video_from_content check max_cost_per_call and max_session_cost before signing the submit payment (from the challenge's Solana exact accepts; the signed amount before sending when the challenge has none), and before signing a video's completion payment. - A paid completion poll lost in transit resumes ownership polling (the settlement record answers) instead of abandoning the clip; Retry-After on a paid 402 is honoured; exhaustion, timeouts and errors after the payment went out say it may still settle and are tagged settled. - Under ownership polling only a 200 delivers: the completion 402's status "completed" no longer turns a 5xx/HTML answer into the result. 503 on an ownership/paid poll is retried. - A clip settled by an earlier poll/call is logged and cached at zero cost. - A completion payment signed at the deadline is still sent; none is signed after it. - _last_raw_payment is per thread / asyncio task and reset before the cache check, so concurrent music calls never poll or pay against another call's challenge. - Any exception escaping a paid-at-POST job's poll is tagged settled (covers PaymentError on the music fallback).
…58 package _create_signer decoded a 32-byte seed with `import base58`, which is not a dependency of blockrun-llm[solana]; on a clean install (CI 3.11) every seed-only key was refused as invalid. Decode it with solders (a 32-byte base58 string parses as a Pubkey) instead. Caught by test_real_wallet_key_gives_the_payer_keypair, red on CI before this.
Also de-flakes test_budget_runs_out_after_the_completion_payment (a 0.3s budget raced the first completion sign under async).
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the sol.blockrun.ai poll-ownership contract (live since rev 00561-ghx; server source
blockrun-sol/src/lib/poll-ownership.ts).What changes (Solana wallet rail only)
x-poll-wallet/x-poll-timestamp/x-poll-signature: an ed25519 signature by the submitting wallet overblockrun-poll:v1:<video|audio|image>:<job id>:<unix seconds>, signed fresh per poll. The job id is the URL-decoded last segment ofpoll_url. Uses the soldersKeypairbehind the existing x402 signer, so there's no new dependency.status:"completed". The client signs one payment from that challenge (terms pinned to the submit's) and re-sends the GET. If the paid poll gets a 402 (concurrent claim or stale blockhash), the client goes back to proof polling, which returnsalready_settledor a fresh challenge. Payments are capped at 1 +max_resigns.Invalid job id, audio/image 404Job not found), the client falls back once, for that job, to the existing payment-signature polling. This is logged as a warning onblockrun_llm.solana_client.music()tracks on the Solana wallet rail are now polled. Before this, the 202 stub was returned as the result and failedMusicResponsevalidation after the track had been paid for.jobs.py,music.py,video.py,image.py), and any signer without a keypair.The poll loop moved from
_request_image_with_paymentinto_poll_media_job(sync and async) so music can share it. The legacy branch is the old code, kept verbatim.Tests
tests/unit/test_solana_poll_ownership.py(34 tests). Signatures are checked with an independent pure-Python RFC 8032 verifier and base58 decoder. The fake gateways enforce the contract.black --check .,ruff check .andpytest tests/unitall pass (1152 passed). No new mypy errors in the touched files. No version bump, no publish, and the paid API was not called.