ObolNetwork / ObolNetwork/obol-stack
release blocker: validate OBOL-token x402 payments for Spark2 sell path
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 11
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
Release target
Next release horizon: sell the Spark2 external vLLM/OpenAI-compatible inference endpoint with OBOL token as the x402 settlement asset.
This is the payment-surface blocker for #439. Keep scope minimal:
- external Spark2 inference endpoint, not an in-cluster GPU workload
- one
ServiceOffer/route for the Spark2 endpoint - one settlement asset: OBOL token
- no multi-asset
accepts[]negotiation yet - no KEDA/HPA/GPU scheduling work here; that remains #430
Desired seller UX
Example shape:
obol sell inference spark2-qwen36 \
--model qwen36-fast \
--runtime openai \
--upstream http://host.k3d.internal:8000 \
--chain base-sepolia \
--payment-asset obol \
--wallet 0xSellerRecipient \
--price 10 \
--max-inflight 16
CRD shape:
spec:
type: inference
payment:
network: base-sepolia
asset: obol
payTo: "0xSellerRecipient"
scheme: exact
price:
perRequest: "10"
asset: obol should resolve to the configured OBOL token for the selected network, including address, decimals, symbol, and EIP-3009 domain metadata required to sign/verify TransferWithAuthorization.
Current surface validation
A quick code audit shows OBOL-token settlement is not release-ready yet. The current sell path is still USDC-shaped in several places.
Sell-side gaps
internal/embed/infrastructure/base/templates/serviceoffer-crd.yamlspec.paymenthasnetwork,payTo,price, but noasset.- descriptions say USDC.
internal/schemas/payment.goPaymentTermshas no asset field.PriceTablecomments and conversion logic assume USDC-style human decimals.
cmd/obol/sell.go- CLI flag text says USDC everywhere.
- no
--payment-assetflag. buildInferenceServiceOfferSpec()does not emitpayment.asset.
internal/embed/skills/sell/scripts/monetize.py- no
get_asset()accessor. _add_pricing_route()writesprice,payTo,network, but notasset/token metadata.
- no
internal/x402/config.goPricingConfig/RouteRulehave no asset/token fields.ResolveChain()returns x402-goChainConfigvalues with USDC addresses.
internal/x402/verifier.go- always calls
x402lib.NewUSDCPaymentRequirement(...). - this always produces a requirement whose
assetis the chain's USDC address. - OBOL needs either direct
PaymentRequirementconstruction or a generalized token resolver.
- always calls
Buy-side gaps
The buy side is closer, but still has USDC assumptions:
internal/x402/buyer/config.go- already stores
assetaddress inUpstreamConfig, good.
- already stores
internal/x402/buyer/signer.goCanSign()comparesreq.Asset, good.GetTokens()hardcodes symbolUSDCand decimals6.
internal/embed/skills/buy-inference/scripts/buy.py- has
USDC_CONTRACTS,USDC_DOMAIN_NAME,USDC_DOMAIN_VERSION. _presign_auths()signs typed data using USDC domain metadata even if the 402 requirement asset is OBOL.balance,probe,status, and budget output are USDC-worded/micro-unit-worded.- it currently ignores
paymentRequirement.extra.name/version, which is where the verifier should publish token EIP-3009 domain metadata.
- has
Test gaps
internal/testutil/eip712_signer.gohardcodes USDC contract/domain.- x402 integration tests and mock facilitator fixtures assume USDC/base-sepolia.
- Need a dedicated OBOL-token fixture path, ideally using an ERC-3009-compatible test OBOL token on Anvil and/or official base-sepolia OBOL from #406.
Implementation checklist
1. Token/asset model
- Add
spec.payment.assetto the ServiceOffer CRD. - Add
PaymentTerms.Asset stringto Go schema. - Define asset aliases:
usdcobol0x...explicit token address, optional later
- Add a token resolver, e.g.
ResolvePaymentAsset(network, asset)returning:- network ID
- token address
- symbol
- decimals
- EIP-3009 domain name
- EIP-3009 domain version
- Default
payment.assettousdcfor backwards compatibility.
2. Seller/verifier path
- Add
asset/token metadata toPricingConfigandRouteRule. - Have
monetize.pypreservepayment.assetintox402-pricingroutes. - Replace verifier's unconditional
NewUSDCPaymentRequirement()with generalized payment requirement construction. - Ensure 402 response for
asset: obolincludes:asset: <OBOL token address>maxAmountRequiredconverted using OBOL decimalsextra.nameandextra.versionfor the OBOL EIP-3009 domain
- Update metrics/status labels or output to include asset symbol/address where useful.
3. CLI/status/docs
- Add
--payment-asset usdc|obol|0x...toobol sell inferenceandobol sell http. - Replace “USDC recipient wallet” language with “payment recipient wallet”.
-
obol sell statusshould show route asset, not only price/chain/payTo. - Document Spark2 OBOL example as the release-path smoke.
4. Buyer path
-
buy.py probeprints asset symbol/address and atomic amount without saying USDC unless asset is USDC. -
buy.py buysigns typed data using the payment requirement's asset +extra.name/version, not hardcoded USDC constants. - Balance checks use the requirement asset address and decimals.
- Buyer ConfigMap preserves asset symbol/decimals/domain metadata, not just asset address.
-
PreSignedSigner.GetTokens()returns the configured token symbol/decimals instead of hardcoded USDC/6.
5. Validation tests
- Unit test:
payment.assetsurvives CRD/schema/JSON/YAML roundtrip. - Unit test:
asset=obolresolves to the expected network token address/domain/decimals. - Unit test: verifier emits an OBOL
PaymentRequirementwith the OBOL asset, amount, and domain metadata. - Unit test: buyer pre-signs authorization against the OBOL verifying contract/domain.
- Integration test: mock seller returns OBOL 402; buyer sidecar signs/pays and request succeeds.
- E2E release smoke: Spark2 endpoint exposed through
ServiceOfferwithpayment.asset=obol; unpaid request gets OBOL 402; paid request reaches Spark2.
External dependency / open question
If we use base-sepolia for the release smoke, #406 is a blocker/adjacent dependency: we need an official test OBOL token + faucet or a documented temporary OBOL-compatible ERC-3009 token for e2e testing.
Also confirm the production OBOL token/payment token supports the exact EIP-3009 transferWithAuthorization flow required by x402. If OBOL does not support ERC-3009, we need a facilitator/scheme/token wrapper decision before this can ship.
Links
- Blocks release path for #439
- Related GPU/in-stack inference split: #430
- Testnet OBOL/faucet dependency: #406
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with internal/schemas/payment.go, internal/x402/config.go, and internal/x402/verifier.go to trace payment asset data from the ServiceOffer CRD through seller verification. Then inspect the buyer scripts and existing x402 integration fixtures, including internal/testutil/eip712_signer.go. Done means the listed round-trip, resolver, buyer, integration, and Spark2 OBOL smoke validations pass, subject to the #406 token dependency.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kubernetes, python
- Domain
- api, backend-api-design, payments, testing-qa
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 25/100