ObolNetwork / ObolNetwork/obol-stack

release blocker: validate OBOL-token x402 payments for Spark2 sell path

Open
#440 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement release-blocker x402
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.yaml
    • spec.payment has network, payTo, price, but no asset.
    • descriptions say USDC.
  • internal/schemas/payment.go
    • PaymentTerms has no asset field.
    • PriceTable comments and conversion logic assume USDC-style human decimals.
  • cmd/obol/sell.go
    • CLI flag text says USDC everywhere.
    • no --payment-asset flag.
    • buildInferenceServiceOfferSpec() does not emit payment.asset.
  • internal/embed/skills/sell/scripts/monetize.py
    • no get_asset() accessor.
    • _add_pricing_route() writes price, payTo, network, but not asset/token metadata.
  • internal/x402/config.go
    • PricingConfig/RouteRule have no asset/token fields.
    • ResolveChain() returns x402-go ChainConfig values with USDC addresses.
  • internal/x402/verifier.go
    • always calls x402lib.NewUSDCPaymentRequirement(...).
    • this always produces a requirement whose asset is the chain's USDC address.
    • OBOL needs either direct PaymentRequirement construction or a generalized token resolver.
Buy-side gaps

The buy side is closer, but still has USDC assumptions:

  • internal/x402/buyer/config.go
    • already stores asset address in UpstreamConfig, good.
  • internal/x402/buyer/signer.go
    • CanSign() compares req.Asset, good.
    • GetTokens() hardcodes symbol USDC and decimals 6.
  • 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.
Test gaps
  • internal/testutil/eip712_signer.go hardcodes 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.asset to the ServiceOffer CRD.
  • Add PaymentTerms.Asset string to Go schema.
  • Define asset aliases:
    • usdc
    • obol
    • 0x... 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.asset to usdc for backwards compatibility.
2. Seller/verifier path
  • Add asset/token metadata to PricingConfig and RouteRule.
  • Have monetize.py preserve payment.asset into x402-pricing routes.
  • Replace verifier's unconditional NewUSDCPaymentRequirement() with generalized payment requirement construction.
  • Ensure 402 response for asset: obol includes:
    • asset: <OBOL token address>
    • maxAmountRequired converted using OBOL decimals
    • extra.name and extra.version for 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... to obol sell inference and obol sell http.
  • Replace “USDC recipient wallet” language with “payment recipient wallet”.
  • obol sell status should show route asset, not only price/chain/payTo.
  • Document Spark2 OBOL example as the release-path smoke.
4. Buyer path
  • buy.py probe prints asset symbol/address and atomic amount without saying USDC unless asset is USDC.
  • buy.py buy signs 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.asset survives CRD/schema/JSON/YAML roundtrip.
  • Unit test: asset=obol resolves to the expected network token address/domain/decimals.
  • Unit test: verifier emits an OBOL PaymentRequirement with 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 ServiceOffer with payment.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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.