Update buy/sell skill to be more than just inference#403
Conversation
x402 Direct HTTP Purchase — Friction ReportAn agent attempted to buy a Part 1: Fixes & Small CleanupsThese are concrete, low-effort changes that would eliminate the most common 1.1 — USDC addresses missing from common-contracts.mdFile: The common-contracts reference only lists mainnet tokens. The USDC address for An agent asked to "check your USDC balance on base-sepolia" will load Fix: Add a 1.2 — EIP-712 domain name confusion: "USDC" vs "USD Coin"The 402 response from the verifier returns But the correct EIP-712 domain name for signing on Base Sepolia USDC is The Fix (choose one or both):
1.3 — Signature recovery normalization (v=0/1 → v=27/28)The remote-signer sometimes returns signatures with recovery value v=0 or v=1 An agent constructing a payment manually will get a signature with v=0 or v=1, Fix: Document this in the ethereum-local-wallet SKILL.md or in the
1.4 — Probe always appends /v1/chat/completions (inference assumption)
The probe happens to work because the x402 verifier gate triggers before the Fix: Either:
1.5 — buy command requires --model (unusable for HTTP services)The An agent told to "buy demo-hello" cannot use This is the single biggest gap. See Part 2 for the overhaul suggestion. 1.6 — HTTP method not documented per serviceThe skill.md service catalog doesn't mention which HTTP methods each service Fix: Add a 1.7 — The term "facilitator" is used but never defined with a URIThe buy-inference SKILL.md, x402-buyer-api.md, and x402-pricing.md all mention An agent reading "client pays via facilitator" will search for a facilitator Fix: Add a one-liner to the buy-inference SKILL.md:
Part 2: Significant Overhaul IdeasThese are larger changes that would make x402 HTTP purchases seamless for any 2.1 — Add a
|
| # | Failure | Root cause | Where to fix |
|---|---|---|---|
| 1 | Can't find USDC address for base-sepolia | Only in buy.py source code | common-contracts.md |
| 2 | Signs with wrong EIP-712 domain name | 402 extra.name is misleading | Probe output + docs |
| 3 | Signature rejected (v=0/1 vs v=27/28) | Undocumented remote-signer quirk | wallet skill docs |
| 4 | Uses POST instead of GET for HTTP service | No method info in catalog | skill.md generator |
| 5 | Can't use buy command for HTTP services |
--model required, pipeline is inference-only | New pay command |
| 6 | Thinks it needs ETH for gas | Nowhere says payments are gasless | SKILL.md section |
| 7 | Searches for facilitator URI to call | "facilitator" mentioned but never explained | SKILL.md clarification |
| 8 | Appends /v1/chat/completions to HTTP service URL | Probe hardcodes inference path | Probe --type flag |
Schema enforcement follow-upI added a follow-up commit on this PR to make the Beforeflowchart LR
A["ServiceOffer CRD OpenAPI"] --> B["controller render.go"]
C["internal/schemas input structs"] --> B
B --> D["/api/services.json"]
D -.-> E["buyers / agents"]
Fixflowchart LR
A["ServiceOffer CRD OpenAPI"] --> B["controller"]
C["internal/schemas ServiceCatalogEntry"] --> B
C --> D["embedded service-catalog.schema.json"]
B --> E["/api/services.json"]
E --> F["schema validation in renderer tests"]
E --> G["buyers / agents"]
Remaining boundaryThis does not turn Validated locally: go test ./internal/schemas ./internal/serviceoffercontroller -count=1
git diff --check |
QA report: fresh isolated worktreesWhat changed:
Why it matters:
Risk level: medium Commit under test: Base branch: PR #403 head branch Scope
ValidationCI checks:
Unit tests: Integration tests: Flow tests:
Release smoke: Live Chain EvidenceNetwork: Base Sepolia ( RPC/provider: Facilitator: public x402 facilitator, base-sepolia exact support confirmed in preflight Contracts and tokens:
Wallet roles:
Balances:
Transaction receipts:
Runtime EvidenceQA environment:
Images:
Kubernetes / stack:
Model and routing:
Artifacts and logs:
Demo readiness:
Review NotesKnown gaps:
Follow-ups:
Reviewer focus:
|
Additional QA: explicit
|
| Area | Commands exercised | Result |
|---|---|---|
| Build/version | go build ./cmd/obol, obol version |
PASS |
| Stack lifecycle | obol stack init, obol stack up, obol kubectl get nodes, obol stack down |
PASS |
| Network lifecycle | obol network list, obol network status, obol network add base-sepolia --count 1, obol network status |
PASS |
| Network remove | obol network remove base-sepolia immediately after add |
FAIL: eRPC restart rate-limit; CLI says to wait because restart was already triggered within the past second |
| Negative validation | obol network add base-sepolia --endpoint not-a-valid-url |
Correctly rejected invalid URL; harness counted non-zero as FAIL but CLI behavior is expected |
| Agent lifecycle | obol agent init, obol agent list, obol agent wallet list obol-agent, obol agent auth obol-agent |
PASS |
| Seller pricing | obol sell pricing --wallet ... --chain base-sepolia, obol sell status |
PASS |
| ServiceOffer lifecycle | obol sell http ... --no-register, obol sell list, obol sell status, obol sell test, obol sell update, obol sell stop, obol sell delete |
PASS for create/list/status/test/update/stop/delete |
| Updated-price visibility | obol sell status <name> after obol sell update --per-request 0.002 |
CLI returned Ready conditions but did not show the new price in the status output; verification failed looking for price visibility |
| Tunnel lifecycle | obol tunnel status |
PASS |
| Tunnel logs | obol tunnel logs --tail 20 |
FAIL: --tail is not a supported flag; QA harness command was wrong or CLI lacks expected tail support |
| Delete verification | obol kubectl get serviceoffer <name> after delete |
Correctly returned NotFound; harness counted non-zero as FAIL but deletion behavior is expected |
| Purge cleanup | obol stack purge --force after down |
FAIL/timeout at sudo-owned data removal; cluster containers/config were removed, but data cleanup needed sudo credentials |
Summary from the raw lifecycle harness:
31 commands/checks total
25 harness PASS
6 harness FAIL
Interpretation:
- Real CLI lifecycle coverage is much stronger now: stack, network add/status, agent setup/auth/wallet, sell pricing, ServiceOffer create/test/update/stop/delete, tunnel status, and stack down all ran through
obolcommands. - Two recorded harness failures are expected non-zero outcomes and should be fixed in the QA harness, not product behavior: invalid endpoint rejection and post-delete NotFound verification.
- Three items are useful follow-ups:
- Add a wait/retry around
obol network removeafternetwork add, or make the CLI tolerate immediate remove after add without surfacing the eRPC restart rate-limit. - Make
obol sell status <name>display enough pricing detail aftersell updatefor CLI-only QA to verify the effective price without falling back to raw CRD inspection. - Decide whether
obol tunnel logsshould support--tail, or update QA docs/scripts to call the supported syntax.
- Add a wait/retry around
- Cleanup remains a QA-host concern:
stack purge --forcecan time out when runtime data is sudo-owned and sudo credentials are not cached. The cluster itself was deleted, and no PR-specific k3d cluster was left behind.
This does not change the earlier release-smoke conclusion: flow-11-dual-stack is useful and passed with receipts, but the full QA fleet is still not release-green.
Full
|
| Flow | Result | Notes |
|---|---|---|
flow-01-prerequisites |
PASS | Docker, Ollama, CLI, k3d, Python deps, remote-signer chart 0.3.2 |
flow-02-stack-init-up |
PASS | Stack init/up, pods healthy, frontend, eRPC, Base Sepolia routing, monitoring |
flow-03-inference |
PASS | Host Ollama, in-cluster Ollama, LiteLLM, tool-call passthrough |
flow-04-agent |
FAIL | Hermes agent init mostly passed, but agent inference, hello response, gateway health, and dashboard deeplink failed |
flow-05-network |
PASS | network list/status/add/remove and invalid endpoint rejection |
flow-06-sell-setup |
PASS | x402 components, pricing, ServiceOffer, HTTPRoute |
flow-07-sell-verify |
PASS | tunnel, local/tunnel 402, verifier metrics/logs, ServiceOffer Ready |
flow-08-buy |
FAIL | paid inference succeeded, but settlement assertions failed: no expected USDC Transfer found and seller balance unchanged |
flow-09-lifecycle |
PASS | sell list/status/stop/delete and resource cleanup |
flow-10-anvil-facilitator |
FAIL | fork-local facilitator binary missing; not required for live Base Sepolia |
flow-11-dual-stack |
PASS | USDC dual-stack seller/buyer passed with ERC-8004 registration, PurchaseRequest, paid inference, settlement receipt, balance deltas |
flow-12-obol-payment |
FAIL | expects deployment/openclaw -n openclaw-obol-agent; current default stack path deployed Hermes, so OpenClaw rollout was not present |
flow-13-dual-stack-obol |
PASS/SKIPPED | script exits pass after skipping because no local X402_FACILITATOR_BIN / X402_RS_DIR; should probably report SKIPPED explicitly |
flow-14-live-obol-base-sepolia |
PASS | live OBOL Base Sepolia seller/buyer passed end-to-end using deployed facilitator |
Baseline/stateful flow summary:
flow-01 PASS
flow-02 PASS
flow-03 PASS
flow-04 FAIL
flow-05 PASS
flow-06 PASS
flow-07 PASS
flow-10 FAIL
flow-12 FAIL
flow-08 FAIL
flow-09 PASS
FAILED_COUNT=4
Independent dual-stack summary:
flow-11 PASS
flow-14 PASS
flow-13 PASS/SKIPPED
FAILED_COUNT=0
Live Chain Evidence
Network: Base Sepolia (84532)
Deployed facilitator: https://x402.gcp.obol.tech
OBOL token:
| Field | Value |
|---|---|
| Address | 0x54AE82bc871a4E3E8E2FE1173Cb864B8563D44D4 |
| Name | Obol Network |
| Symbol | OBOL |
| Decimals | 18 |
| EIP-712 domain separator | 0xc21da3ed0501015df2d9efb304b2abbdabeb86398c8fc729d491740a061e9b25 |
Wallet roles:
| Role | Address | Source |
|---|---|---|
| Alice / seller / register | 0xC0De030F6C37f490594F93fB99e2756703c4297E |
.env signer |
| Bob / buyer / payer | 0x57b0eF875DeB5A37301F1640E469a2129Da9490E |
deterministic derived buyer wallet |
USDC dual-stack receipts (flow-11):
| Purpose | Tx hash | Status |
|---|---|---|
| ERC-8004 registration | 0x4c54519599498eaef85bcb7b24cd132ccf505eb65bc245992b811e0e3b1ff329 |
archived |
| Metadata | 0x968c58d524cd3072f178f710795ee49e486467a2ff576ed617c90c4e98cf04c8 |
archived |
| Settlement | 0xdb4a1fbb3e0efc7bac6eaf81f3b2560a02dcc4bf2a4311523b78fd155c745db6 |
amount verified |
USDC balance deltas (flow-11):
| Token | Address | Before | After | Delta |
|---|---|---|---|---|
| USDC | Alice | 8,923,000 micro-USDC |
8,924,000 micro-USDC |
+1,000 |
| USDC | Bob signer | 4,995,000 micro-USDC |
4,994,000 micro-USDC |
-1,000 |
OBOL live receipts (flow-14):
| Purpose | Tx hash | Status |
|---|---|---|
| ERC-8004 registration | 0x0736fd3c09d463be828e99d47552a3b79b08cc9e36cb98e4e0e2d665871acb52 |
archived |
| Metadata | 0xa1c8e0f21bb828c958d67538e60ad222a242bdfbf56f90289fc12d24fff7a892 |
archived |
| Settlement | 0x15775ee9a327a985a37476337688313d2993dc301b3501a9cf6e573de0fadb92 |
amount verified |
OBOL balance deltas (flow-14):
| Token | Address | Before | After | Expected delta | Actual delta |
|---|---|---|---|---|---|
| OBOL | Alice | 0 wei |
1000000000000000 wei |
+1000000000000000 |
+1000000000000000 |
| OBOL | Bob signer | 999999006000000000000000 wei |
999999005000000000000000 wei |
-1000000000000000 |
-1000000000000000 |
flow-14 also verified:
- Bob signer wallet equals the deterministic derived buyer address.
- Bob signer OBOL balance is visible through Bob eRPC before purchase.
- Alice OBOL ServiceOffer reached Ready=True.
- Bob PurchaseRequest reached Ready=True.
- Buyer sidecar had
alice-obolauths withremaining=5before paid inference. - Paid inference returned HTTP 200 via
paid/qwen3.5:9b.
Assessment
Useful and release-relevant:
flow-11-dual-stackpassed and proves the USDC seller/buyer path.flow-14-live-obol-base-sepoliapassed and proves live OBOL seller/buyer payment through the deployed facilitator.- The schema/atomic-units changes remain useful; the live OBOL path used atomic OBOL wei values and verified exact settlement deltas.
Still not full-fleet green:
flow-04-agenthas Hermes HTTP/gateway failures.flow-08-buystill has a single-stack settlement assertion failure despite paid inference success.flow-12-obol-paymentappears stale or mismatched with the current default Hermes runtime because it expects OpenClaw to be deployed.flow-13should not be reported as PASS when it skips due missing fork-local facilitator; it should be explicit SKIPPED.- Fork/local facilitator flows should stay optional and separately named; live Base Sepolia should remain the default OBOL QA path.
QA update: resilient remote flow runCommit under remote test: ValidationCI checks:
Local checks on Flow tests:
Flow 13 FindingsThe local facilitator container path did not regress: The run then hit the expected non-green condition we wanted to stop masking: That confirms the previous false positive is gone. The fork flow no longer records “buy command issued” when the agent explicitly refuses to buy. Cleanup note: this failure exposed that early exits could leave scoped stacks behind, so Live Chain Evidence For Flow 14Network: Base Sepolia Wallet roles:
Balances:
Transaction receipts:
Runtime evidence:
Follow-up
|
Summary
I did a first buy with hermes. Opus 4.7 had to work hard to find addresses, find bugs in remote signer it wasn't working around, etc.
I took the feedback and passed it to this pass.
Why it matters:
Biggest change is to remote-signer. I should have done a minor version rather than a patch but blame claude :)
Scope
x402 Buy/Sell Improvements — Change Summary
What we shipped
Fixed at the source rather than papered over in callers.
{0x00, 0x01}. Affects /sign/.../message, /sign/.../typed-data, and /sign/.../hash. /sign/.../transaction
is unaffected (RLP-encoded type-2 txs use y-parity in the body, never go through sig_to_bytes). https://github.com/ObolNetwork/remote-signer/pull/9
full ecrecover round-trip test that signs a known prehash and recovers back to the keystore address.
Catches both wrong-byte-suffix and wrong-parity-bit regressions. 15/15 pass.
v0.3.0helm-charts#277the canonical-v contract.
at the source.
re-introducing it on a v0.2.1+ signer would double-add 27 and corrupt every payment.
The biggest functional gap from the friction report: buy is inference-shaped (requires --model, builds a
PurchaseRequest, publishes paid/ through the sidecar) and unusable for type:http services like
demo-hello.
pre-signs one EIP-3009 (or Permit2) auth, attaches X-PAYMENT, sends the request, prints the response body
and any X-PAYMENT-RESPONSE settlement metadata. Stateless. No CR, no sidecar. Max loss = price of one
request.
probe no longer hard-appends /v1/chat/completions and POSTs a chat body when the target is an HTTP
service.
signing domain (extra.eip712Domain, "USDC") so agents stop signing with the wrong domain.
The skill now covers both flows (inference budget via buy, one-shot HTTP via pay). Mechanical rename
across CLAUDE.md, README, embed_skills_test.go, four cross-referencing SKILL.md files, openclaw
integration tests, three flow scripts.
Already existed; agents now don't have to parse markdown to construct a payment.
chainId, priceUnit (perRequest/perMTok/perHour), priceAtomicUnits (atomic units of asset), and a full asset
block: {address, symbol, decimals, transferMethod, eip712Domain {name, version}}.
explicit OBOL passes through unchanged with transferMethod: "permit2" and 18-decimal price math.
TestBuildServiceCatalogJSON_ExplicitOBOLToken lock in the schema agents depend on.
internal/schemas/service_catalog.gonow owns the reusable catalog wire structs and embedsservice-catalog.schema.json; renderer tests compile the schema and validate representative empty, HTTP, per-MTok, USDC-default, and explicit OBOL catalogs against it.needed; facilitator settles on-chain), new Facilitator section (server-side, agents never call it; lists
the three networks eip155:1/8453/84532 covered by https://x402.gcp.obol.tech), new Pitfalls section
(EIP-712 domain confusion, pay-vs-buy, prefer /api/services.json over markdown).
Added facilitator chain-coverage table. Pointer to /api/services.json as the preferred machine-readable
surface.
USDC sections with EIP-712 signing pitfall callout. OBOL kept on Ethereum mainnet only per current
deployment.
Optional next steps
These are the items we deliberately scoped out — none of them block this PR set.
signature normalization, and X-PAYMENT envelope code from buy.py into a reusable module, so buy, pay, and
any future client share one tested implementation. Easier to do now that pay exists and we know the
boundary.
spec.upstream.method on ServiceOffer so HTTP services can advertise GET vs POST. Surfaces in the skill.md
table and /api/services.json. Real fix for the "demo-hello rejects POST" friction.
internal/x402/tokens.go and the chain default in defaultUSDCForNetwork (which would generalize to a
per-token resolver).
parameter) that returns y-parity (0/1) per EIP-7702 spec, not v=27/28. Don't reroute through sig_to_bytes.
Worth a one-line note in the remote-signer README so a future maintainer doesn't accidentally extend the
canonical path.
legacy txs for a non-1559 chain, that's a new endpoint with its own v = 35 + 2*chain_id + parity encoding.
EIP3009Name: "USD Coin" but the empirically-correct signing domain on Base Sepolia USDC is "USDC". buy.py
field if buy.py/catalog are now the source of truth.
that's the real proof that v=27/28 settles end-to-end through the facilitator.
want a one-off agent to re-run the live smoke + check services.json schema once the
helm-charts/remote-signer PRs merge, that's a reasonable /schedule candidate.