Repository navigation
Served API spec offers only the networks a buyer can pay on; externalDocs; Bazaar output schema - #187
Merged
Merged
Conversation
The hero's proof panel gets a second figure, styled like "Total paid to agents": "Registered agents" with the live count from GET /v1/status (agents.total), "All time · On the public registry", and a link to the agent directory. Like the paid total it prerenders as a dash and fills in after hydration; an unreachable API shows a dash, never 0. The homepage already read /v1/status for its live-work section; both now share one request (lib/registryStatus.ts, reused for a minute). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Review on #156: registryStatus (lib/registryStatus.ts) now has tests for two callers sharing one request, reuse for a minute then a fresh fetch, and a failed request not being kept so the next caller retries. The web package gets a vitest test script (same vitest as the other packages), so `npm test` runs it in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Circle agent wallets are smart-contract accounts on Base that stay counterfactual until their first transaction, so their signatures come ERC-6492-wrapped. The wallet bind used to refuse those with undeployed_smart_wallet; it now checks them. - api: an ERC-6492 signature is verified with the reference ValidateSigOffchain bytecode run as a deployless eth_call (no `to`), which deploys the wallet in the call, then asks it per ERC-1271. A 0x01 result binds (recorded as signer_kind erc1271); anything else or a revert is bad_signature; an unreachable RPC is rpc_unavailable. The bytecode is pinned by sha256 in the tests. wallet_proof.signature accepts up to 8192 bytes, since a 6492 wrapper carries the factory calldata. - sdk 0.9.6: `wallet set` without a key also prints the Circle CLI command that signs the exact message (`circle wallet sign message 0x<hex> --hex --address … --chain BASE`), and --json carries it as circle_sign_command. - skill 1.3.7 (min CLI 0.9.6), SPEC, OpenAPI, READMEs, CHANGELOG. Checked live on Base mainnet against an undeployed smart account: its 6492 signature binds; a changed message, another wallet's claim and a flipped signature byte are refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
…yed" Two review findings on the Circle wallet change: - The service descriptor's walletProof.smartWallets still said "deployed wallets on Base", contradicting the skill. It now says a wallet not deployed yet signs per ERC-6492; the .well-known copies are regenerated. - Whether `circle wallet sign message` signs from a counterfactual wallet is not verified. If it answers "Wallet not deployed", Circle's documented fix is a zero-amount transfer to itself, after which the wallet is deployed and binds per ERC-1271. `wallet set` now prints that command next to the sign command (circle_deploy_command in --json), and the skill, SDK README and CHANGELOG say when to run it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9 # Conflicts: # CHANGELOG.md # package-lock.json # packages/sdk/package.json # packages/web/public/llms-full.txt
A real Circle agent wallet bind showed three problems:
- Circle's CLI signs only from a deployed wallet ("This wallet isn't
deployed on-chain yet"). `wallet set` now prints the deploy step first
for Circle wallets; skill 1.3.8 §3 and the SDK README say so. Once
deployed, the wallet binds per ERC-1271.
- `circle wallet transfer … --token usdc` fails with 404 "Cannot find
target token": --token takes a contract address and defaults to USDC.
circle_deploy_command no longer passes it.
- The API checked smart-wallet signatures against mainnet.base.org alone,
which answered 429 from Cloudflare's shared egress and failed the bind
with 503. It now tries BASE_RPC_URL (one URL or a comma-separated
list), then mainnet.base.org, base-rpc.publicnode.com and base.drpc.org
(same operators on Sepolia), each checked live for eth_getCode, eth_call
and the deployless ERC-6492 call. A 429, 5xx, timeout or unsupported
method moves on; a revert is the wallet's answer and stops there.
Per-attempt timeout 5s.
basedagents 0.10.1, skill 1.3.8 (min CLI 0.10.1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Review finding: with up to four endpoints, 5 s per attempt and two calls (eth_getCode, eth_call), one proof could spend 40 s on RPC, past the SDK's 30 s request timeout, so a bind that succeeded on a late endpoint could be abandoned by the client. All RPC attempts for one proof now share a 15 s deadline (each attempt gets min(5 s, what's left)), and the endpoint that answered eth_getCode goes first for eth_call. A test with every endpoint hanging shows the check returning rpc_unavailable at 15 s after three attempts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Review finding on 787079e: with a shared 15 s deadline tried one endpoint at a time, three nodes that each hang for 5 s use the whole budget, so a healthy fourth is never tried, and a late eth_getCode can leave no time for eth_call. Either way a valid bind answers rpc_unavailable. Each call now starts on the first endpoint and also goes to the next one on any failure at once, or after 1.5 s of silence (a hedged request). The first answer wins and the others are cancelled; a revert still wins as the wallet's answer. Each call has its own 9 s budget, so a proof (at most two calls) settles within 18 s, inside the SDK's 30 s. The endpoint that answered first goes first for the next call. Tests: a hanging configured node is passed over after 1.5 s and the bind succeeds on the next; with every node hanging, all four are tried and the call gives up at 9 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Review finding on d9c1f1e: with hedged first-answer-wins, a public node that hasn't seen a wallet deployed seconds ago can answer eth_getCode "0x" before a slower, up-to-date node, and the bind is refused as bad_signature. That is exactly the Circle flow: deploy, then bind. Lag only ever hides a yes, so the verdict is now asymmetric: a yes from any node wins at once, while a no (wrong answer, no code, a revert) counts only when every endpoint has answered, or the budget ran out, and none said yes. Failures still move on at once and 1.5 s of silence still hedges to the next node. The separate eth_getCode is gone: eth_call to isValidSignature on an address with no code answers empty, which is a no, so one call (9 s budget) does the whole check. Tests: a slow configured node with the wallet beats three public nodes answering "no code" (would fail before); a revert on one node loses to a yes on another; a no needs all three public nodes. Live on Base mainnet: the ERC-6492 vector still binds, a flipped byte, an address with no code and a contract without isValidSignature are bad_signature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Review findings on 21f611d (both security): 1. Without eth_getCode, the prefix check on isValidSignature's result accepted the identity precompile (0x…04): it echoes the call data, which starts with 0x1626ba7e, so an agent could "verify" it. 2. "A yes from any node wins" let a lagging node approve with a signer the wallet has since removed. Lag runs both ways, so neither a lagging yes nor a lagging no can be trusted; the fix removes the lag instead: - Every smart-wallet signature, deployed (ERC-1271) or not (ERC-6492), is checked by the ERC-6492 reference validator in one deployless eth_call. It answers exactly 0x01 only for a valid signature: it asks isValidSignature when the address has code and recovers the signer when it has none. With no `to`, no precompile or other contract can answer in the wallet's place. - The call is pinned to the highest head block the nodes report (eth_blockNumber to all at once, answers within 1.5 s of the first count). Every node that has that block gives the same answer, so the first answer wins; one that lags errors and is skipped. - Head lookup 3 s, check 9 s: inside the SDK's 30 s. Checked live on Base mainnet on all three public nodes, with a state override standing in for a deployed wallet: a wallet answering the ERC-1271 magic -> 0x01, one answering 0xffffffff -> 0x00, precompile 0x04 -> 0x00, a block the node lacks -> error. End to end through verifyBindProof: the ERC-6492 vector binds; a flipped byte, 0x…04, an address with no code and USDC are bad_signature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Review finding on 66137fc: if the node that reported the freshest block fails the eth_call (say it's rate limiting us) while the healthy nodes are a block behind, every node fails at the pinned block and a valid bind answers 503. Falling back to an older block would bring back stale answers, so instead: when every endpoint has failed at the pinned block, the round starts over at the same block after 2 s (one Base block), by which time the nodes behind have it. Rounds repeat within the 9 s budget. Test: mainnet.base.org reports 0x12 and answers 429; the other nodes report 0x11 and say "block not found: 0x12" on the first round. The bind succeeds on the second round, and every call is pinned to 0x12. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9 # Conflicts: # CHANGELOG.md
The first real Circle wallet bind hit `zsh: command not found: basedagents`: the `Then run:` line printed a bare `basedagents wallet set …`, but run through npx (the way the docs run it) nothing is installed. The same was true of every command the CLI prints to run next: tasks claim/deliver/payment/fund/watch/accept/dispute hints, register, init, validate and id. cliCommand() (cli/invocation.ts) answers `npx basedagents@latest` when the CLI was started through npx (npm sets npm_command=exec and runs the package from its _npx cache) and `basedagents` when it's installed. The 24 run-this hints, and the --json `next` field, use it; title banners and help text keep the bare name. Tests: cliCommand for npx (both signals, POSIX and Windows cache paths) and installed runs; `wallet set --json` under npx gives an npx `next`. The wallet and tasks tests pin npm_command so they pass however vitest is launched. Checked by running the packed 0.10.2 tarball through a real npx: `Then run: npx basedagents@latest wallet set …`. basedagents 0.10.2 (also publishes #163's SDK attribution, merged without a version bump). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9 # Conflicts: # CHANGELOG.md
packages/mcp/dist is committed so the server runs from a checkout (bin/basedagents-mcp.mjs imports ../dist/index.js). #163 changed the MCP source without rebuilding, so a checkout ran the pre-attribution server. Regenerated with `npm run build --workspace=packages/mcp`, and added the new dist/attribution.{js,d.ts} and their maps with `git add -f`: the new index.js imports ./attribution.js and dist/ is gitignored. npm publishes were never affected (prepublishOnly rebuilds). Checked from a `git archive` of the committed tree: the stdio server initializes as basedagents 0.7.2 and lists 26 tools; the archived index.js is byte-identical to a fresh build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
A buyer without an agent key or account can now post a task: the x402 escrow deposit is the authentication, and the paying wallet is the poster. - POST /v1/x402/tasks takes a buyer-chosen bounty_usdc (at least the minimum); /usd-1, /usd-5 and /usd-20 take a fixed price. An empty POST is a price check (402, nothing written); the 402 carries a Bazaar discovery block. GET /v1/x402/tasks lists the prices. - The poster is the payer the facilitator verified (creator_kind 'wallet', creator_wallet), never a request field. - Manage by the token returned once (only its sha256 is stored) or by a signature from the paying wallet over a one-time action message (X-Wallet-Message + X-Wallet-Signature; the 401 gives the message and the Circle sign command). Accept, revision, dispute and cancel run the same gates as the owner routes. - Binding the wallet as an agent's payout wallet moves its tasks to that agent in the same batch; the wallet keeps access. GET /v1/tasks?creator=0x… lists a wallet's tasks. - Migration 0051 rebuilds tasks (creator_kind 'wallet', creator_wallet, manage_token_hash) and adds wallet_action_nonces. - OpenAPI, /.well-known/x402, /docs, the service descriptor, SPEC, README and CHANGELOG document it; the MCP server shows a wallet poster. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
… branch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
…on footers, tier minimum - The poster's private reads (GET /v1/x402/tasks/:id and /submission) send Cache-Control: no-store, and the cache default counts X-Wallet-Signature as a credential, so a wallet-signed read is never marked public. - Web (task detail, board card) and console (Explore) show a wallet poster as "a wallet · 0x1234…abcd" instead of "an agent"; the creator types gain kind 'wallet' and `wallet`. - The signed action message's last line now says what the action does with the escrowed bounty: accept releases it, cancel refunds it, revision and dispute leave it held. The parser requires the footer of the named action. - A tier priced under the live minimum bounty is refused before any quote (400 bounty_below_minimum, no 402) and left out of both price lists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
…idance, contact)
Circle's agent-readiness check found no paid endpoint declared in
openapi.json. The four wallet-only hire operations now carry x-payment-info
(fixed 1/5/20 USDC on the tiers; dynamic 0.10–1000 on the custom endpoint;
protocols [{x402: {}}]). info.x-guidance replaces the non-standard
info.guidance with a short agent brief, and info.contact gains
hello@basedagents.ai. Tests pin the priced set, the tier amounts against the
route's prices, and route/spec parity for /v1/x402/tasks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
The cancel route refuses submitted work until it has been disputed; the guidance listed cancel as a direct option on a delivery, which would earn an agent a 409. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
GET /openapi.json now passes the spec through openApiForEnv: the custom hire endpoint's price.min is the deployment's minimum, and a tier priced under it is left out (the route refuses it before any 402). With the default minimum the file is served unchanged. x-guidance points to GET /v1/x402/tasks for the live prices instead of pinning 0.10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
…adata branch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
… any chain Circle's readiness check asks a paid endpoint to accept 2+ networks. USDC bounties can now be paid on Polygon PoS (eip155:137, native USDC) through the same CDP facilitator and the same escrow. - The wallet-only hire 402 offers the same price on every accepted network (production: Base first, then Polygon); payTo is the escrow wallet, the same address on each. The payer signs one offer and the task's bounty network is the one they paid on: held, released and refunded there, nothing bridged. A body `network` restricts the offer. Agents and owners may post bounty.network eip155:137. - payoutWallet (payments/accept.ts) decides where a deliverer is paid on a bounty's network: a wallet bound there, or a verified plain-key wallet's same address on any EVM chain. It runs at claim (409 wallet_network_mismatch) and again at every payout (escrow release deferred with payee_wallet_wrong_network; sign-at-accept 409), so switching wallets after claiming can't route a payout to an address that isn't the agent's. - Claim bonds are deposited on Base and now always paid back on Base (to a Base wallet or a plain key's address); before, a withdrawal followed the payout wallet's network, which would have drawn on Polygon escrow funds. - Polygon USDC's EIP-712 domain (USD Coin, 2) was read on-chain and pinned; POLYGON_RPC_URL plus public nodes check smart-wallet signatures; the paid feed links Polygonscan; the descriptor lists payments.networks; the Circle sign command uses --chain MATIC. - SDK 0.10.3: BOUNTY_NETWORKS includes eip155:137; Circle command for MATIC. - OpenAPI enums and copy, SPEC, README, CHANGELOG; tests for the multi-network 402, Polygon deposits and payouts, the plain-key rule, the deferred release, and Base-only bond payouts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
…ion; review fixes
- GET /v1/x402/tasks/usd-{1,5,20} answers the same 402 quote as an empty POST.
It used to fall through to GET /:id and 404, so `circle services inspect`
(GET by default) showed the service as unavailable. A GET carrying a payment
is refused (405, Allow: POST) and the payment is not used.
- PaymentPayloadV2 keeps `extensions`: a client echoes the 402's bazaar block
into its payment and a facilitator catalogs the service from it at settle.
The parser dropped it, so it never reached CDP. Forwarded on verify and on
settle (from the stored header).
- Greptile: the price list's OpenAPI response documents `networks` (array)
instead of `network`; the settled-feed tests cover a Polygon settlement
(Polygonscan link, counted in the totals) and explorerTxUrl for 137.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
…wallet USDC bounties can be paid on Arc (eip155:5042), Circle's stablecoin chain, alongside Base and Polygon. CDP doesn't settle on Arc, so paymentProviderFor now routes by network (NetworkRouter): Base and Polygon to CDP, Arc to ArcFacilitator, which splits by who signed the transfer. - Deposits (buyer -> escrow wallet) verify and settle through Circle's Facilitator Service (circle-facilitator.ts, Bearer CIRCLE_API_KEY), with a payment-identifier from the authorization nonce. Circle's pending answer has no tx hash: SettleOutcome 'pending' now allows that, and settle.ts keeps a hash an earlier attempt saw. invalid_transaction_state = nonce used. - Payouts and refunds (escrow wallet -> agent/buyer) never go to Circle, which would bind the payee's wallet to our Circle account. The escrow wallet broadcasts its own transferWithAuthorization (arc.ts relay): checks authorizationState first (a used one resolves to the tx in its AuthorizationUsed log), one escrow-wallet tx in flight at a time, simulates, pays 2x base fee + tip (>= Arc's 20 gwei floor), checks the USDC float for transfer + gas, waits 6 s for the receipt, else pending with the hash. - EIP-1559 signing and RLP live in a leaf module (evm.ts) with the key parser; house-wallet re-exports, so there is no import cycle. The signer matches eth-account byte for byte (vectors pinned in evm.test.ts). - New Arc deposits are offered only while CIRCLE_API_KEY is set (fundableBountyNetworks); existing Arc tasks stay visible and payable. Arc bounties are escrow-only (400 escrow_required on escrow: false). - USDC on Arc: 0x3600...0000, EIP-712 domain USDC/2 read on-chain; the offer's extra carries assetTransferMethod: eip3009 for Circle. - Payout wallets bind on Arc (ARC_RPC_URL + Arc's public nodes); the paid feed and task page link explorer.arc.io; Circle CLI chain ARC; SDK 0.10.4. - Docs: SPEC (Arc section, custody, env, network table), OpenAPI enums and copy, SECURITY, API README, deploy runbook step 8, CHANGELOG. Checked live on Arc mainnet with a throwaway key: the escrow code's authorization passes USDC's signature check (reverts only on balance), a flipped signature byte fails it, and the node decodes the signed tx. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
…d old logs Review fixes (Greptile on #183): - P1 double payment: the relay called an authorization expired 10 s early by its own clock, before looking at a transfer still in flight; settle.ts then put the task back to funded and the cron could sign a second transfer while the first one could still land. Expiry is now the chain's call only: once a block at or past validBefore exists and the authorization is unused AT that block (eth_call pinned to it, so a lagging node errors instead of answering from older state), it can never be used, and only then is the leg expired. A simulation's "expired" waits for the chain the same way. - P1 concurrent payouts sharing a nonce: every round now starts from the chain (used? expired?), waits up to 4 s for any escrow-wallet transaction in flight to land, and sends on the next nonce; a send that loses the nonce to a concurrent payout, or whose nonce goes to another transaction while it is watched, starts over in the same call. A revert after a clean simulation is retried later with backoff instead of being resent at once. - P2 late log lookups: the AuthorizationUsed search now finds the validity window's blocks by timestamp (interpolation search) instead of estimating back from the head, so a retry a day later still finds the transaction. - P2 fake node: FakeArcNode now keeps a mempool (pending vs latest nonce), changes authorization state only when a transaction is mined (unused and unexpired at that block), refuses a taken nonce like a node does, gives every block a timestamp, honours getLogs ranges (5,000-block cap) and errors on a pinned read it can't serve. Tests: the in-flight payout regression through the real settle machine (it fails with the old expiry check), expiry by chain clock vs ours, a dropped transfer expiring only at the pinned block, waiting for and racing another payout (two Workers land on consecutive nonces), a lost nonce, and finding the log a day later in two queries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9 # Conflicts: # CHANGELOG.md
… verdict Review fix (Greptile on #183, P1): on a leg's first attempt settle.ts's row says nothing was ever broadcast, so it reads "nonce already used" or a terminal verdict as "this authorization never moved money" and lets the cron sign a new one. If the relay's own transfer landed in that same call but its receipt and its AuthorizationUsed log couldn't be read (watch() saw the nonce move on and called it lost; the next round found the authorization used with no log), the relay answered nonce_already_used and the bounty could be paid twice. The relay now remembers the transaction it sent (set before the broadcast, since a node may hold it whatever the call answers). After that, a used authorization with no readable transaction is `pending` with that hash, and a simulation's terminal verdict waits for the next attempt, where settle.ts knows a broadcast happened. The pinned-block expiry stays: unused at a block past validBefore can never be used. Tests (all three fail without the fix): the relay answers pending with its hash when receipts and logs are unreadable, and the reuse answer only before any send; a terminal simulation verdict after a send waits; and through the real settle machine, such a payout stays releasing (the sweep doesn't re-sign) and resolves to the transfer it recorded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
…hout a hash Review fix (Greptile on #183, P2): after a send in the same call, keepOpen answered a used authorization whose transaction couldn't be read with `pending` carrying the hash this call last sent. That hash may never have landed: e.g. an earlier attempt's transfer lands just as the retry broadcasts (other fees, so another hash), the node refuses the retry with "nonce too low", and logs can't be read. settle.ts then replaced the recorded hash (the transfer that did land) with the refused one, and a later reuse answer settled the payout with a link to a transaction that never existed on-chain. Now the call tracks only whether it broadcast, and answers `pending` with no hash when the using transaction is unknown, so whatever settle.ts recorded stays; the next attempt reads the log and records the real one. Tests: the race above (fails on the previous relay); the unreadable-receipt case now expects a hashless pending; through the settle machine the payout resolves to the transfer that landed once the chain can be read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
…l over Review fix (Greptile on #183, P2): with the previous change, a payout that landed on its first attempt while receipts and logs were unreadable came back as pending with no hash; if logs were still unreadable on the next retry, that call (having sent nothing) gave the reuse answer and settle.ts settled the payout with no transaction hash, permanently: no explorer link, and out of the paid feed and totals. findAuthorizationTx now tells "absent" (every block of the window was read, no log) from "unreadable" (a node refused a query, a block couldn't be read, or the window needed more queries than allowed). Unreadable answers retry-later, so the leg stays open until its transaction can be read (the cron's 24 h unknown-outcome cap still hands a stuck row to a human); only absent gives the reuse answer. eth_getLogs now moves to the next endpoint when a node refuses the query, instead of taking the refusal as final. Tests (the three below fail on the previous relay): a landing with unreadable receipts and logs stays open over two attempts and settles with its transaction once logs are readable; a refused log query is answered by the next endpoint; through the settle machine the payout stays releasing through a second retry with logs still unreadable, then releases with the transfer that landed. The keepOpen race now uses a window that reads clean of the log (FakeArcNode.hideLogs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
…litator CDP's Bazaar lists a service from the `resource` and `extensions.bazaar` that a payment echoes from the 402. Circle's CLI (`circle services pay`, @circle-fin/cli 1.2.0) builds its payment from the signed transfer, `accepted` and `resource` only, so the first paid hire settled with no Bazaar block and the escrow wallet's listing stayed empty. The payload we verify, store and settle now carries the 402's `resource` and the `bazaar` block the endpoint declared, whatever the payer echoed (payloadForFacilitator). A payer can't keep a hire endpoint unlisted or list it under another URL or description; their other extensions pass through. The sign-at-accept transfer and claim bonds get their 402's resource the same way, and a `bazaar` block a payer adds there is dropped. decodeForFacilitator refuses a payment our declaration would push past the 16 KB header cap, so settle can always decode what was stored. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
…Docs; Bazaar output schema Circle's marketplace reviewers and buying agents read /openapi.json. Since the Arc release it told buyers they could pay "on Base, Polygon or Arc", but production offers Arc only once CIRCLE_API_KEY is set, and the hire request schemas listed Base Sepolia and Arc in their `network` enum, which production refuses with a 400. - openApiForEnv serves the hire endpoints with only the networks a deposit can be paid on here (fundableBountyNetworks): the request `network` enums, the price list's example, and the network wording in info.x-guidance and the hire operations. Production without the Circle key names Base and Polygon; with it, Arc too; never a testnet. - externalDocs links the runbook (https://basedagents.ai/skill.md), the docs link Circle's readiness check scores. - The 402's Bazaar block declares its response envelope (schema.properties.output) as the reference declareDiscoveryExtension does. It passes the reference validators; CDP's validator accepted the hire endpoints before the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
|
🔎 Console preview: https://92735c36.basedagents-console.pages.dev (Passkey ceremonies only work on app.basedagents.ai — previews are for UI review.) |
|
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.
Circle's marketplace reviewers, its readiness check and buying agents all read
/openapi.json. Since #183 it told buyers they could pay "on Base, Polygon or Arc". Production offers Arc only onceCIRCLE_API_KEYis set, and that key isn't set. The hire request schemas also listed Base Sepolia and Arc in theirnetworkenum, which production refuses with400 bounty_network_not_allowed. Circle'sservices inspectbuilds probe bodies from that schema, so some probes would pick a network we refuse.Changes
The spec is served with the networks offered here.
openApiForEnvalready adjusted prices to the live minimum. It now also narrows the hire endpoints tofundableBountyNetworks(env):networkenum ofX402TierHireRequestandX402HireRequest;networksexample of the price list;info.x-guidanceand the hire operations' descriptions, in the file's three phrasings ("Base, Polygon or Arc", "Base eip155:8453, Polygon eip155:137 or Arc eip155:5042", "Base, then Polygon, then Arc").What production serves:
A testnet is never served in production. Off production, Base Sepolia stays. Other operations keep their wording, for example
/v1/tasks, whose text already qualifies Arc. The file itself is unchanged.externalDocs. The spec links the runbook,https://basedagents.ai/skill.md. Circle's readiness page gives a docs link 5 points (M("docsUrl", a.docsUrl, 5, …)in its score bundle). Our spec had none, and we scored 95/100.The Bazaar block declares its output.
extensions.bazaar.schema.properties.outputis now{type: object, properties: {type: string, example: object}, required: [type]}, as the referencedeclareDiscoveryExtensionemits it. Circle's catalog copies some sellers' response schema from this block.Checked
POST https://api.cdp.coinbase.com/platform/v2/x402/validatewithmethod: POSTreturnsvalid: trueandsimulation.outcome: acceptedfor all four hire URLs on the current production. CDP documents it as making no payment and indexing nothing. So Bazaar listing: forward our own resource and Bazaar block to the facilitator #184's forwarded Bazaar block is not at risk ofinvalid_bazaar_extension.outputschema added passes the referencevalidateDiscoveryExtension,validateDiscoveryExtensionSpec,validateAndExtractandextractDiscoveryInfo(@x402/extensions2.28.0). A brokenoutput.exampleis rejected, so the schema is enforced.Tests
openapi.test.ts:eip155:5042inx-guidanceor the hire operations, and[8453, 137]enums and example;[8453, 137, 5042];/v1/tasksand the file are untouched;externalDocs.urlis set.x402-tasks.test.ts: the 402 declares theoutputschema.After merge
/openapi.jsonnames only Base and Polygon.Docs
SPEC (
GET /openapi.jsonand the price-check bullet) and CHANGELOG.🤖 Generated with Claude Code
https://claude.ai/code/session_01VX2Nm8D11UusvYsuAtrcY9
Generated by Claude Code