feat: sell and buy services in multiple currencies + networks#655
Open
OisinKyne wants to merge 5 commits into
Open
feat: sell and buy services in multiple currencies + networks#655OisinKyne wants to merge 5 commits into
OisinKyne wants to merge 5 commits into
Conversation
…t making multi-service agents
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.
Problem to be solved
I want to sell services in USDC alongside their OBOL offerings, particularly to be able to sell on Base where x402scan is but OBOL is not. This PR allows service offers to list multiple payment options. It also allows people to sell in tokens other than USDC and OBOL though not fully supported best in class on the stack. This grants some flexibility to the stack users who want to sell something in their utility token, but don't want to fork and build an entire parallel Obol Stack to do it. Future updates can make the support for alternative tokens more best in class.
Summary
Lets a single service (agent or HTTP) be sold in multiple currencies/networks at once � advertised as one x402 endpoint whose 402 response lists every accepted payment, with the buyer choosing which to pay. Previously each
ServiceOffercarried exactly one payment, so offering "10 OBOL on Ethereum or 1 USDC on Base" meant hand-rolling N separate offers (N URLs, N storefront cards, duplicated registration). Now it's one offer, one URL, one card, one command � end to end: create -> CRD -> verifier -> catalog -> OpenAPI -> storefront -> buyer selection.This branch also tightens the surrounding seller UX: a self-contained
skill.md, consistentnetworkterminology, multi-payment-awaresell status, and a replace-confirm guard.What changed
Schema (backward-compatible)
ServiceOffer.spec.payments[](canonical multi-payment) added alongside the existing singularspec.payment(kept as the always-set primary =payments[0]).EffectivePayments()normalizes both, so every existing CR, thestack upresume replay, andstack importkeep working untouched.spec.listing{weight, category}for storefront ordering/grouping (replaces the bespokedemospecial-casing � demo is now an ordinary category).maxTimeoutSeconds(different chains, different block times).Verifier / data plane (
internal/x402) � the protocol layer was already multi-payment (402acceptsis an array;findMatchingRequirementV1+/verify+/settleact on whichever requirement the buyer matched). This wires it up:RouteRulecarries all options,matchPaidRouteFullemits onePaymentRequirementsper option, and metrics attribute revenue to the actual chain/asset paid (newOnPaymentMatchedhook). No change to settlement correctness.Seller CLI (
obol sell agent|http) � repeatable--acceptflag:token=<symbol>resolves the registry asset;asset=0x...is an escape hatch for any ERC-20 on a supported chain. Plus--weight/--category. ERC-8004 registration uses the first option's network.sell update --acceptreplaces the set.On-chain asset autofill � for raw
asset=0x..., missingdecimals/symbol/EIP-712 domain are read best-effort from the chain (decimals()/symbol()/EIP-5267eip712Domain());transferdefaults to Permit2; errors-to-specify if unresolvable. Registry/USDC options make zero RPC calls (no new cluster dependency for the common path).Agent factory (
factory.py) � mirrors--accept+ in-pod eRPC autofill,--weight/--category. Sub-agent wallet creation flipped to opt-in, and--pay-tonow defaults to the master Hermes wallet so a sub-agent needn't provision its own signer just to sell. Closes the handoff pain points:--descriptiondecoupled from--register,statusauto-discovers all offers, skill resolution searches both layouts.Buyer skill (
buy.py) �pay/pay-agent/buyaccept--token/--network/--payment-optionto choose among advertised options (auto-selects when there's one; prompts on a TTY; errors with the list otherwise).--token/--networkalso guard against paying the wrong asset.Catalog, OpenAPI & storefront
/api/services.jsonentries gainpayments[](flat fields still mirror the primary)./openapi.jsonx-payment-infokeepspriceas the primary for single-price indexers and addsaccepts[](one{mode,currency,amount,network-as-CAIP-2}per option) for multi-currency offers, so indexers can surface the cheapest.ServiceCardrenders all options with a payment selector that re-targets the buy snippets, plus copyable per-service anchor links and weight-based ordering.skill.mdrewritten to be self-contained for any LLM � added a "How to pay (x402)" section (the full 402 -> sign ->X-PAYMENT-> 200 loop, EIP-3009 vs Permit2, gasless), pointers to the machine-readable/openapi.json(Swagger) and/api/services.json, a multi-payment "Pay with" column that includes the network, and per-option detail (price, network, CAIP-2, payTo, token contract, decimals, transfer scheme) plus a concrete call hint. Copy-paste prompts on the 402 page and storefront no longer send agents to the broadobol.org/llms.txt� they point at this operator's own<tunnel>/skill.md+/openapi.json(same origin as the endpoint, one fetch to learn to pay).Consistent
networkterminology � the spec field, 402 wire,buy.py, andobol networkall say network; only the sell CLI said--chain. Flipped all sell flags to primary--networkwith--chainretained as a back-compat alias (verified: existingcmd.String("chain")reads still resolve). Examples/hints updated to--network.sell statusmulti-payment � lists every accepted option, and resolves the chain-default USDC asset so offers without an explicit asset showUSDC (0x833...)/3 USDC per requestinstead of(not set)/3 per request.Replace-confirm guard �
obol sell agent(andsell demo) can resolve to the same offer name+path; a second create previouslykubectl apply-ed over the first silently. On a TTY it now warns and prompts before replacing; non-interactive callers (resume/flows/JSON) keep the idempotent apply.Operational note (CRD re-apply required)
The ServiceOffer CRD gains
spec.payments/spec.listing. Strict decoding rejects offers using them until the updated CRD is applied � re-runobol stack up(recreate the cluster if a running one doesn't refresh). Additive change; no data migration.Testing
EffectivePaymentsfallback, verifier multi-accept + settle-the-chosen-option,--acceptparser (registry/raw/dedup/errors), on-chain autofill merge, catalogpayments[], OpenAPIaccepts[],sell statusmulti-payment + default-USDC resolution, the--network/--chainalias, CRD field presence.factory.pyparser + EIP-5267 decode,buy.py_select_payment(token/network/index/single + error paths).tsc --noEmitclean.go test ./...green; both skillspy_compileclean./skill.mdrenders the new format and/openapi.jsonresolves.Out of scope (intentional)
obol sell inferencestays USDC/OBOL-curated (no arbitrary multi-currency on the standalone gateway).eip155:Nchains beyond the supported set (raw assets are limited to supported chains).sell agentflags (planned as a follow-up branch).obol buy inferenceoption-selection (catalog already exposespayments[]to build on).