feat: hosted payment mode — pay for storage via gateway credits - #163
Draft
Nic-dorman wants to merge 18 commits into
Draft
feat: hosted payment mode — pay for storage via gateway credits#163Nic-dorman wants to merge 18 commits into
Nic-dorman wants to merge 18 commits into
Conversation
… gateway payment_mode=hosted (V2-929 PoC): a payer interface at the upload worker's evmSigner seam, a HostedPayer that POSTs the batch to the gateway's /pay and maps its 202 unconfirmed onto evm.ErrConfirmationTimeout (so classifyFailure preserves instead of re-paying), and payment_mode/payment_gateway_url config. Local-signing default path unchanged. Merkle unsupported in hosted mode (V2-934). Proven e2e on a local devnet + the dev1 rig. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hosted mode now prepares with include_signed_quotes (antd >= 0.13.0 via go.mod replace to the branch antd-go) and relays the opaque signed_quotes[] in /pay so the gateway can verify the batch offline before paying. The payer seam gains a signedQuotes parameter; localPayer adapts *evm.Signer unchanged (migrate.EvmPayer untouched). A gateway "rejected" answer is permanent — the worker abandons rather than retrying. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gateway credits (V2-1086)
…ot these wallets (V2-1086)
…lance alarm, human insufficient-credits message (V2-930)
…s treat the gateway as payment-ready (V2-929)
A nil Go map marshals to JSON null; antd requires tx_hashes to be an empty object when prepare reported no payments (chunks already stored). Caught by the V2-931 suite's dedup case — bites local mode too.
…2-1097) Admin-scoped server-side relays (checkout / sync / summary) keep the tenant API key out of the browser; BillingView owns credits, the exact- credit confirmation before Stripe, and the top-up history; the nav swaps Wallets→Billing in hosted mode (Wallets redirects); the insufficient- credits upload error deep-links admins to Billing and tells users to ask their administrator; Transactions accepts ?type= deep-links.
…, IDR #18) The counter speaks USD, the ledger stays ANT: shared BigInt money util (exact rational rate, ≈-labelled nearest-cent display), Billing goes fiat-primary (credit tile, confirm dialog 'receive $25.00 of storage credit', history, sync toast) with exact ANT in tooltips; upload cost rows, hosted transaction rows, and analytics spend tiles convert when a rate is present; the 402 refusal arrives from the gateway in fiat via total_usd_cents (ANT only as fallback). wallet-status carries the gateway rate, cached a minute server-side. Local mode untouched.
A gateway killed with our POST in flight EOFs the connection before the 202+payment_key arrives, failing the upload while the payment itself survives (debited, broadcast, even paid). The batch is content-addressed idempotent gateway-side, so resending can never double-pay: retry the transport (6 attempts, 5s apart — outlasts a restart) and land on the replay path. Found by V2-931 suite case I on an instamining devnet.
…(V2-1097) Billing's history now answers 'where did this credit come from' for every funding path: card rows carry their exact locked USD, invoice/grant rows convert at the rate (≈, ANT fallback) and carry their note.
… (V2-1098)
The gateway now charges a per-batch network fee with every paid batch, so
the tenant's credits drop by total + fee. The customer's books must say so:
- HostedPayer parses fee_amount/total_debited/fee_usd_cents and stashes
them per payment_key (PaymentCost, pop-on-read — one payer serves
concurrent uploads) from both the sync /pay and poll paths
- the worker records the GROSS debit as the upload cost and the hosted
transaction amount — balance_after math stays exact to the atto — and
stamps the fee on the upload (migration 017 uploads.gateway_fee_atto)
- the 402 message names both parts, fiat first ("including a $X.YY
network fee"), ANT fallback, still no crypto leak in fiat mode
- upload detail shows the fee row (fiat via the V2-1100 money util),
marked included in the actual cost
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gas_fee ceiling The gateway charges GROSS — batch total + a per-batch network fee (V2-1098) — but every pre-upload surface still quoted net, understating by one fee per batch while the actual debit and the gateway's 402 threshold are gross. Now the estimate and the ceiling speak the same basis as the charge: - HostedPayer relays fee_per_batch_atto from GET /account (AccountInfo grows a third value; absent field from older gateways tolerated as empty) and caches it a minute via FeePerBatch — exact big.Int, last-known on gateway outage, zero when unknown; never an error - wallet-status carries gateway_fee_per_batch_atto beside the rate (one shared 60s pricing cache, the V2-1100 relay pattern); the admin billing summary relays it too - /uploads/quote adds gateway_fee_per_batch_atto, estimated_batch_count and estimated_total_with_fee_atto in hosted mode — one upload settles as exactly one gateway batch, so the estimate adds one fee (full dedup at prepare time can still drop it: it's an estimate, and says so) - the max_gas_fee ceiling compares grossUploadCost — net quote + one fee when payments exist — so a configured ceiling refuses BEFORE spending exactly when the gateway would charge past it; last_quoted_cost and the backoff logs record the same gross figure; the hosted payer is now ensured before the ceiling (the later ensure block is a no-op) - web: upload card names the per-upload fee up front (fiat via the money util, ANT fallback, exact atto in the tooltip); the detail drawer's Estimated row displays gross via grossEstimateAtto (BigInt, no floats); Last quoted stays as recorded — it is already gross Local mode untouched: nil fee keeps the V2-431 net basis everywhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The client half of the storage-capacity estimator (V2-1106 Option 3): - HostedPayer.AccountDetails relays the gateway's est_cost_per_gb_atto and its opaque methodology basis alongside balance/rate/fee; AccountInfo stays as a thin wrapper. Absence (older gateway, thin paid history) reads as empty fields, never an error. - wallet-status carries gateway_est_cost_per_gb_atto + _basis via the 60s gatewayPricingCache; /admin/billing relays est_cost_per_gb_atto + basis from the live AccountDetails call it already makes for the balance. - Billing view renders '≈ N GB remaining at current prices' under the balance: N = balance ÷ cost-per-GB, one decimal, FLOORED, exact BigInt — hidden entirely while the gateway has no estimate. The cursor-help tooltip is built from the gateway's basis object (median chunk price × chunks/GB + network fee, sample size + window, the dedup/market-drift caveat) — no methodology numbers hardcoded client-side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
busy_timeout and synchronous are connection-level SQLite pragmas; the one-shot Exec after Open covered a single pooled connection, leaving the rest with busy_timeout=0. First bite: V2-1112 coalescing settles several payments in one tx, so their uploads finalize in the same instant — one of three concurrent record saves failed SQLITE_BUSY (and the recovery requeue hit the same lock). Move both pragmas into the DSN, where modernc.org/sqlite applies them to every connection, alongside the existing foreign_keys param; journal_mode=WAL stays as the one-shot Exec (database-persistent, and doubles as the CANTOPEN probe). Regression tests: the pragma is asserted on several simultaneously held pool connections, and 8 concurrent write transactions must all commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Summary
Adds
payment_mode=hosted: Indelible delegates upload payment to the Autonomi Pay gateway (WithAutonomi/paygate, private) instead of signing locally. Quote batches POST to the gateway's/pay; prepaid tenant credits are debited server-side with verification, idempotency, and async settlement handled there. No wallet is needed on the Indelible host.What's in the branch (17 commits):
payerseam at the evmSigner boundary +HostedPayer— transport-retrying/pay, async poll loop on 202, 402 mapped to a human top-up message with fiat amountspayment_mode+ gateway payment key), hosted transaction rows with gateway credit balancesinclude_signed_quotes) so the gateway verifies ML-DSA quotes on its own probe antd before spending/admin/billingin-app Stripe top-ups via admin-scoped relay (tenant key never in the browser), credit history, fiat-primary display across hosted surfacesmax_gas_feeceiling (V2-1113) and "≈ N GB remaining at current prices" with methodology tooltip (V2-1114)Local wallet mode is untouched — every change is hosted-mode-gated.
Draft status — not mergeable yet
go.modcarriesreplace github.com/WithAutonomi/ant-sdk/antd-go => ../ant-sdk-854/antd-go: the signed-quote prepare options ride ant-sdk#242 (open) and need a tagged antd-go release (antd 0.13.0 line) before the replace can drop — CI will fail on this until then.Testing
npm run build(vue-tsc + vite) cleanLinear
Tracker: V2-916 · principal tickets: V2-923, V2-926, V2-929, V2-930, V2-1086, V2-1097, V2-1098, V2-1100, V2-1113, V2-1114
🤖 Generated with Claude Code