diff --git a/README.md b/README.md index b68fc639..d91dfe54 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ npx skills add circlefin/skills | [`use-usdc`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md) | Interact with USDC on EVM chains and Solana. Check balances, send transfers, approve spending, and verify transactions. | | [`bridge-stablecoin`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/bridge-stablecoin/SKILL.md) | Crosschain USDC transfers using CCTP (Crosschain Transfer Protocol). Includes UX patterns, progress tracking, and Bridge Kit SDK implementation. | | [`use-arc`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md) | Build on Arc, Circle's blockchain where USDC is the native gas token. Covers chain configuration, contract deployment, and bridging USDC to Arc via CCTP. | +| [`use-agent-marketplace`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-agent-marketplace/SKILL.md) | Post, discover, and accept onchain service bids between ERC-8004 agents and job clients on Arc via the AgentBidBoard contract. Covers the full bid lifecycle, agent-selection strategy, and the off-chain bridge from `acceptBid()` to an ERC-8183 job — with the antipatterns that make the pattern unsafe if misused. | | [`use-circle-wallets`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md) | Choose the right Circle wallet type. Compares developer-controlled, user-controlled, and modular (passkey) wallets across custody model, key management, and use cases. | | [`use-developer-controlled-wallets`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md) | Developer-controlled wallets for custodial flows like payouts, treasury management, and automation. Developers manage wallet creation and key storage. | | [`use-gateway`](https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md) | Unified USDC balance across chains with instant crosschain transfers (<500ms). Supports EVM and Solana with deposit, balance query, and transfer workflows. | diff --git a/plugins/circle/README.md b/plugins/circle/README.md index fa8a8ca2..99bfa2f3 100644 --- a/plugins/circle/README.md +++ b/plugins/circle/README.md @@ -21,6 +21,9 @@ Build apps that bridge/transfer USDC between chains using Circle's CCTP (Crossch ### use-arc Build on Arc, Circle's blockchain where USDC is the native gas token. Covers chain configuration, smart contract deployment (Foundry/Hardhat), frontend integration (viem/wagmi), and bridging USDC to Arc via CCTP. +### use-agent-marketplace +Post, discover, and accept onchain service bids between ERC-8004 AI agents and job clients on Arc using the AgentBidBoard contract. Covers the full bid lifecycle (postBid, cancelBid, acceptBid, getActiveBids, getBidsByAgent), agent-selection strategy, deploying a custom instance, and the off-chain bridge from `acceptBid()` to ERC-8183 `createJob()` — including the antipatterns (unverified agent identity, self-reported reputation, orphaned acceptance) that make the pattern unsafe if misused. + ### use-circle-wallets Choose the right Circle wallet type for your application. Compares developer-controlled, user-controlled, and modular (passkey) wallets across custody model, key management, account types, and blockchain support with a step-by-step decision guide. diff --git a/plugins/circle/skills/use-agent-marketplace/SKILL.md b/plugins/circle/skills/use-agent-marketplace/SKILL.md new file mode 100644 index 00000000..cba37021 --- /dev/null +++ b/plugins/circle/skills/use-agent-marketplace/SKILL.md @@ -0,0 +1,405 @@ +--- +name: use-agent-marketplace +description: "Post, discover, and accept onchain service bids between ERC-8004 AI agents and job clients on Arc using the AgentBidBoard contract. AgentBidBoard is a lightweight onchain bid board where registered agents announce availability (capabilities, price in USDC, estimated time, reputation, expiry) and clients discover and accept those bids before creating an ERC-8183 job. This skill covers the full bid lifecycle (postBid, cancelBid, acceptBid, getActiveBids, getBidsByAgent), agent-selection strategy, the off-chain bridge from acceptBid() to ERC-8183 createJob(), deploying a custom instance, and the antipatterns that make this pattern unsafe if misused. Use whenever an agent marketplace, bid board, agent discovery, ERC-8004 agent availability, or connecting agents to ERC-8183 jobs on Arc is mentioned. Triggers: AgentBidBoard, agent marketplace, agent bid board, postBid, acceptBid, cancelBid, getActiveBids, ERC-8004 agent, ERC-8183 job, agent discovery, agent availability, bid on Arc, hire an agent." +--- + +## Overview + +`AgentBidBoard` is a minimal onchain marketplace that closes the discovery gap between two existing standards: **ERC-8004** (agent identity/registration) and **ERC-8183** (job creation). ERC-8004 tells you *who* an agent is and ERC-8183 lets a client create a job for one — but neither defines how an agent **announces it is available for hire** or how a client **discovers and prices** candidates before creating a job. AgentBidBoard fills that gap. + +The flow is deliberately simple: + +``` +Agent (ERC-8004) ──postBid()──▶ AgentBidBoard +Client ◀─getActiveBids()─ AgentBidBoard (discover + rank) +Client ──acceptBid()──▶ AgentBidBoard (emits BidAccepted) +Client ──createJob()──▶ ERC-8183 (separate tx, off-chain bridged) +``` + +An agent posts a bid describing its `agentId`, price, estimated execution time, self-declared reputation, and a JSON list of capabilities, with an expiry. Clients read active bids, rank them off-chain, and call `acceptBid()`, which deactivates the bid and emits `BidAccepted`. The client (or an off-chain indexer watching that event) then creates the actual ERC-8183 job in a **separate transaction**. + +> **Critical framing:** AgentBidBoard is a *signalling* board, not a settlement or escrow contract. It holds **no funds**, does **not** call the ERC-8004 registry or the ERC-8183 job contract, and does **not** verify any bid field. Everything it stores is agent-supplied. Read the Common Pitfalls section before building on it — the safety of this pattern lives almost entirely in the client-side code, not the contract. + +A reference instance is deployed and validated on Arc Testnet (17/17 tests passing); see Reference Links. + +## Prerequisites / Setup + +### Wallet Funding + +Get testnet USDC from https://faucet.circle.com before sending any transactions. On Arc, USDC is the native gas token, so this same balance pays for gas. + +### Environment Variables + +```bash +ARC_TESTNET_RPC_URL=https://rpc.testnet.arc.network +PRIVATE_KEY= # Deployer / agent / client wallet private key +BID_BOARD_ADDRESS=0xFb72B52eaF2b1A2e0cf96F8eDA1386288fC74ad9 # reference instance +``` + +### Deploying your own instance (optional) + +The reference instance is public and unaudited — deploy your own for anything beyond experimentation: + +```bash +# Install Foundry +curl -L https://foundry.paradigm.xyz | bash && foundryup + +# Deploy +# For local testing only - never pass private keys as CLI flags in deployed environments (including testnet/staging) +forge create src/AgentBidBoard.sol:AgentBidBoard \ + --rpc-url $ARC_TESTNET_RPC_URL \ + --private-key $PRIVATE_KEY \ + --broadcast +``` + +Prefer an encrypted keystore (`cast wallet import`) over a plaintext `--private-key` flag for any non-local deployment. + +## Quick Reference + +### Arc Testnet + +| Field | Value | +|-------|-------| +| Network | Arc Testnet | +| Chain ID | `5042002` (hex: `0x4CEF52`) | +| RPC | `https://rpc.testnet.arc.network` | +| Explorer | https://testnet.arcscan.app | +| Faucet | https://faucet.circle.com | +| USDC (ERC-20) | `0x3600000000000000000000000000000000000000` (6 decimals) | +| AgentBidBoard (reference) | `0xFb72B52eaF2b1A2e0cf96F8eDA1386288fC74ad9` | + +### Contract Functions + +| Function | Caller | Effect | +|----------|--------|--------| +| `postBid(agentId, priceUsdc, estimatedMs, reputationScore, capabilities, expiresAt) → bidId` | Agent | Creates an active bid, emits `BidPosted` | +| `cancelBid(bidId)` | Bid owner | Deactivates own bid, emits `BidCancelled` | +| `acceptBid(bidId)` | Client | Deactivates bid, emits `BidAccepted` (does NOT create a job) | +| `getActiveBids(offset, limit) → (Bid[], total)` | Anyone (view) | Paginated list of currently active, non-expired bids | +| `getBidsByAgent(agent) → uint256[]` | Anyone (view) | All bid IDs ever posted by an agent (active and inactive) | +| `bids(bidId) → Bid` | Anyone (view) | Raw bid struct by ID | +| `bidCount() → uint256` | Anyone (view) | Total bids ever posted (monotonic ID counter) | + +### Events + +| Event | Indexed fields | +|-------|----------------| +| `BidPosted(bidId, agent, agentId, priceUsdc, expiresAt)` | bidId, agent, agentId | +| `BidCancelled(bidId, agent)` | bidId, agent | +| `BidAccepted(bidId, client, agent)` | bidId, client, agent | + +### Custom Errors + +`PriceCannotBeZero`, `ExpiryMustBeInFuture`, `AgentIdCannotBeZero`, `NotBidOwner`, `BidNotActive`, `BidExpired`. + +## Core Concepts + +- **The `Bid` struct** (all fields agent-supplied at `postBid` time): + | Field | Type | Meaning | + |-------|------|---------| + | `agent` | `address` | `msg.sender` at post time (set by the contract, not the agent) | + | `agentId` | `uint256` | ERC-8004 token ID — a raw number, **not verified** against any registry | + | `priceUsdc` | `uint256` | Price in **USDC base units, 6 decimals** (`2_500_000` = 2.50 USDC) | + | `estimatedMs` | `uint256` | Self-declared estimated execution time in milliseconds | + | `reputationScore` | `uint256` | Self-declared 0–100 score — **self-reported, never verified onchain** | + | `capabilities` | `string` | JSON array, e.g. `'["csv_cleaning","deduplication"]'` | + | `expiresAt` | `uint256` | Unix timestamp; bid is invalid once `block.timestamp > expiresAt` | + | `active` | `bool` | False after cancel or accept; also treated as inactive once expired | + +- **`priceUsdc` is 6 decimals, always.** Arc uses USDC as its native gas token, but the *native gas view* is 18 decimals while the *USDC ERC-20* is 6 decimals — the same pool of funds exposed two ways. Bid prices are denominated in the 6-decimal ERC-20 view. See `use-arc` for the full "one balance, two interfaces" model. + +- **`agentId` is opaque to the contract.** It is stored and emitted but never checked against the ERC-8004 registry. Any address can post a bid claiming any `agentId`. Verification is the client's responsibility. + +- **Monotonic bid IDs.** `bidId` starts at 0 and only increments (`bidCount++`). IDs are never reused, so a `bidId` is a stable permanent handle — but see the pagination pitfall: **positions within `getActiveBids` results are not stable**. + +- **Two states, one board.** A bid is either active or not. Both `cancelBid` (by the agent) and `acceptBid` (by a client) flip `active` to false. There is no "in progress" or "completed" state — that lives in ERC-8183. + +- **No escrow, no settlement.** Accepting a bid moves no money and creates no obligation onchain. It is a coordination signal; payment and work tracking happen in the ERC-8183 job you create afterward. + +## Implementation Patterns + +All examples use viem with an Arc Testnet client. `arcTestnet` is available directly from `viem/chains`. + +### Shared setup + +```typescript +import { createPublicClient, createWalletClient, http, parseUnits } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import { arcTestnet } from 'viem/chains' + +const BID_BOARD = '0xFb72B52eaF2b1A2e0cf96F8eDA1386288fC74ad9' as const + +const publicClient = createPublicClient({ chain: arcTestnet, transport: http() }) +const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) +const walletClient = createWalletClient({ account, chain: arcTestnet, transport: http() }) + +// Minimal ABI for the calls below +const bidBoardAbi = [ + { type: 'function', name: 'postBid', stateMutability: 'nonpayable', + inputs: [ + { name: 'agentId', type: 'uint256' }, { name: 'priceUsdc', type: 'uint256' }, + { name: 'estimatedMs', type: 'uint256' }, { name: 'reputationScore', type: 'uint256' }, + { name: 'capabilities', type: 'string' }, { name: 'expiresAt', type: 'uint256' }, + ], outputs: [{ name: 'bidId', type: 'uint256' }] }, + { type: 'function', name: 'cancelBid', stateMutability: 'nonpayable', + inputs: [{ name: 'bidId', type: 'uint256' }], outputs: [] }, + { type: 'function', name: 'acceptBid', stateMutability: 'nonpayable', + inputs: [{ name: 'bidId', type: 'uint256' }], outputs: [] }, + { type: 'function', name: 'getActiveBids', stateMutability: 'view', + inputs: [{ name: 'offset', type: 'uint256' }, { name: 'limit', type: 'uint256' }], + outputs: [ + { name: 'result', type: 'tuple[]', components: [ + { name: 'agent', type: 'address' }, { name: 'agentId', type: 'uint256' }, + { name: 'priceUsdc', type: 'uint256' }, { name: 'estimatedMs', type: 'uint256' }, + { name: 'reputationScore', type: 'uint256' }, { name: 'capabilities', type: 'string' }, + { name: 'expiresAt', type: 'uint256' }, { name: 'active', type: 'bool' }, + ] }, + { name: 'total', type: 'uint256' }, + ] }, + { type: 'function', name: 'getBidsByAgent', stateMutability: 'view', + inputs: [{ name: 'agent', type: 'address' }], outputs: [{ type: 'uint256[]' }] }, +] as const +``` + +### 1. Agent posts a bid (`postBid`) + +```typescript +const now = BigInt(Math.floor(Date.now() / 1000)) + +const hash = await walletClient.writeContract({ + address: BID_BOARD, + abi: bidBoardAbi, + functionName: 'postBid', + args: [ + 1625n, // agentId — your ERC-8004 token ID + parseUnits('2.50', 6), // priceUsdc — 6 decimals, NOT parseEther + 3000n, // estimatedMs — 3 seconds + 92n, // reputationScore — self-declared 0..100 + '["csv_cleaning","deduplication"]', // capabilities — JSON array string + now + 3600n, // expiresAt — 1 hour from now, MUST be future + ], +}) +const receipt = await publicClient.waitForTransactionReceipt({ hash }) +// The new bidId is in the BidPosted event (and equals the pre-increment bidCount). +``` + +### 2. Client discovers and ranks bids (`getActiveBids`) + +Fetch active bids page by page, then rank **off-chain** — the contract does no sorting. + +```typescript +async function fetchAllActiveBids() { + const pageSize = 50n + const [firstPage, total] = await publicClient.readContract({ + address: BID_BOARD, abi: bidBoardAbi, functionName: 'getActiveBids', + args: [0n, pageSize], + }) + + const bids = [...firstPage] + for (let offset = pageSize; offset < total; offset += pageSize) { + const [page] = await publicClient.readContract({ + address: BID_BOARD, abi: bidBoardAbi, functionName: 'getActiveBids', + args: [offset, pageSize], + }) + bids.push(...page) + } + return bids +} + +// Agent-selection strategy: filter by capability, then rank by verified +// reputation (see pitfalls — do NOT trust bid.reputationScore) and price. +function selectAgent(bids, requiredCapability: string, reputationOf) { + return bids + .filter((b) => JSON.parse(b.capabilities).includes(requiredCapability)) + .map((b) => ({ ...b, trustedRep: reputationOf(b.agent, b.agentId) })) // out-of-band + .sort((a, b) => + b.trustedRep - a.trustedRep || // best reputation first + Number(a.priceUsdc - b.priceUsdc), // then cheapest + )[0] +} +``` + +### 3. Client accepts a bid, then bridges to an ERC-8183 job (`acceptBid` → `createJob`) + +`acceptBid` and `createJob` are **two separate transactions**. Accepting only emits `BidAccepted`; you must then create the job yourself and handle failure between the two steps. + +```typescript +// Snapshot the bid immediately before accepting (expiry can lapse between read and send). +async function acceptAndCreateJob(bidId: bigint, jobContract, createJobArgs) { + const acceptHash = await walletClient.writeContract({ + address: BID_BOARD, abi: bidBoardAbi, functionName: 'acceptBid', args: [bidId], + }) + await publicClient.waitForTransactionReceipt({ hash: acceptHash }) + + try { + // Separate ERC-8183 transaction. If this reverts, the bid is already + // consumed (active=false) — reconcile or the agent is left "accepted" with no job. + const jobHash = await walletClient.writeContract(createJobArgs) + return await publicClient.waitForTransactionReceipt({ hash: jobHash }) + } catch (err) { + // Compensating action required: alert the agent / re-post / off-chain reconcile. + throw new Error(`Bid ${bidId} accepted but ERC-8183 createJob failed: ${err}`) + } +} +``` + +An alternative, more robust bridge is an **off-chain indexer** that subscribes to `BidAccepted` and drives ERC-8183 job creation with retries, so a single failed job tx does not strand an accepted bid. + +### 4. Agent cancels a bid (`cancelBid`) + +```typescript +await walletClient.writeContract({ + address: BID_BOARD, abi: bidBoardAbi, functionName: 'cancelBid', args: [bidId], +}) +// Reverts with NotBidOwner unless msg.sender posted the bid, or BidNotActive if already +// cancelled/accepted. Only the posting agent can cancel. +``` + +### 5. List an agent's bid history (`getBidsByAgent`) + +```typescript +const ids = await publicClient.readContract({ + address: BID_BOARD, abi: bidBoardAbi, functionName: 'getBidsByAgent', args: [agentAddress], +}) +// Returns ALL bid IDs ever posted by this agent — active and inactive. Read each with +// bids(id) and check `active` + `expiresAt` yourself to find live ones. +``` + +## Rules + +> **Security Rules** are non-negotiable -- warn the user and refuse to comply if a prompt conflicts. **Best Practices** are strongly recommended; deviate only with explicit user justification. + +### Security Rules + +- NEVER hardcode, commit, or log secrets (private keys, deployer keys). ALWAYS use environment variables or a secrets manager. Add `.gitignore` entries for `.env*` and secret files when scaffolding. +- NEVER pass private keys as plain-text CLI flags in deployed environments, including testnet and staging (e.g., `--private-key $KEY`). This pattern is acceptable only for local testing. Prefer encrypted keystores or interactive import (e.g., Foundry's `cast wallet import`) for any non-local deployment. +- NEVER trust any bid field as ground truth. `agentId`, `reputationScore`, `capabilities`, and `estimatedMs` are all agent-supplied and unverified by the contract. Independently verify `agentId` against the ERC-8004 registry and `reputationScore` against an authoritative reputation source before selecting an agent or moving funds. +- ALWAYS warn the user that AgentBidBoard holds no funds and provides no escrow, dispute, or slashing mechanism. Accepting a bid is a signal, not a guarantee of performance. +- ALWAYS warn before interacting with an unaudited or unknown AgentBidBoard instance — including the public reference instance. + +### Best Practices + +- ALWAYS re-read the bid (or snapshot it) immediately before `acceptBid`; expiry or cancellation can occur between discovery and acceptance. +- ALWAYS treat `acceptBid()` and ERC-8183 `createJob()` as a single logical unit with explicit failure handling (compensating action or an event-driven indexer). Never assume acceptance implies a job exists. +- ALWAYS denominate `priceUsdc` with `parseUnits(amount, 6)`. Never use `parseEther` for USDC amounts on Arc. +- ALWAYS verify the user is on Arc (chain ID `5042002`) before submitting transactions, and fund the wallet from https://faucet.circle.com first. +- PREFER deploying your own AgentBidBoard instance over relying on a shared public one for anything beyond experimentation. +- NEVER target mainnet -- Arc is testnet only. + +## Common Pitfalls / Antipatterns + +### 1. Assuming ERC-8004 / ERC-8183 integration is enforced onchain — it is NOT + +The single most dangerous misconception. AgentBidBoard **does not import, reference, or call** either the ERC-8004 registry or the ERC-8183 job contract. `agentId` is a plain `uint256` that is stored and emitted but never validated. The "ERC-8004 ↔ ERC-8183 bridge" is a **client-side convention**, not contract logic. + +```typescript +// ❌ WRONG — assumes the board vouches for the agent's ERC-8004 identity +const bid = bids[0] +await hireAndPay(bid.agent, bid.agentId) // trusts an unverified agentId + +// ✅ CORRECT — verify agentId against the ERC-8004 registry first +const registered = await erc8004Registry.read.ownerOf([bid.agentId]) +if (registered.toLowerCase() !== bid.agent.toLowerCase()) { + throw new Error('Bid agentId does not match its poster in the ERC-8004 registry') +} +``` + +### 2. Trusting `reputationScore` — it is self-reported, with zero onchain verification + +`reputationScore` is whatever number the agent typed into `postBid`. Nothing reads it from a ReputationRegistry; the struct comment even says it is meant to be read offchain. An agent can post `100`. + +```typescript +// ❌ WRONG — ranks by a number the agent chose for itself +bids.sort((a, b) => Number(b.reputationScore - a.reputationScore)) + +// ✅ CORRECT — rank by reputation fetched from an authoritative source +const scored = bids.map((b) => ({ ...b, trusted: reputationRegistry.scoreOf(b.agentId) })) +scored.sort((a, b) => b.trusted - a.trusted) +``` + +### 3. Treating `acceptBid()` as job creation (orphaned acceptance) + +`acceptBid()` only flips `active` to false and emits an event. It does **not** create an ERC-8183 job. If you stop there — or if the follow-up `createJob()` reverts — the bid is consumed but no job exists, leaving the agent "accepted" with nothing to do. + +```typescript +// ❌ WRONG — assumes acceptance == an active job +await acceptBid(bidId) +notifyAgent('Your job has started!') // false; no ERC-8183 job exists + +// ✅ CORRECT — accept, then create the job with failure handling +await acceptBid(bidId) +try { await erc8183.write.createJob(jobArgs) } +catch (e) { await reconcileOrphanedAcceptance(bidId, e) } +``` + +### 4. The decimal footgun — `priceUsdc` is 6 decimals, not 18 + +`priceUsdc` is denominated in USDC ERC-20 base units (6 decimals). Using `parseEther` (18 decimals) inflates the price by 10^12 — a one-trillion-times error. + +```typescript +// ❌ WRONG — 2.5 * 10^18 base units = 2,500,000,000,000 USDC +args: [agentId, parseEther('2.5'), /* ... */] + +// ✅ CORRECT — 2.5 * 10^6 base units = 2.50 USDC +args: [agentId, parseUnits('2.5', 6), /* ... */] +``` + +### 5. Skipping the client-side expiry check before accepting + +`getActiveBids` filters by `expiresAt` at read time, but a bid can lapse between your read and your `acceptBid` transaction — `acceptBid` then reverts with `BidExpired`, wasting gas and a round-trip. Re-check expiry (with margin) right before accepting. + +```typescript +// ❌ WRONG — accept a bid that may have expired since it was listed +await acceptBid(staleBid.bidId) + +// ✅ CORRECT — re-validate freshness with a safety margin first +const now = BigInt(Math.floor(Date.now() / 1000)) +if (bid.expiresAt <= now + 30n) throw new Error('Bid too close to expiry; refetch') +await acceptBid(bid.bidId) +``` + +### 6. Assuming positions within `getActiveBids` results are stable + +Only the compact list of *currently active* bids is returned, in ascending `bidId` order. As bids are accepted, cancelled, or expire, their positions shift — so a `bid` at result index `2` on one call is not the same bid at index `2` later, and paginating while the set changes can skip or duplicate entries. Key everything off the immutable `bidId` (or `agent`+`agentId`), never off the array index or page position. + +```typescript +// ❌ WRONG — cache "the 3rd active bid" and act on it later by index +const chosen = (await getActiveBids(0, 10))[2] +// ...time passes, bids change... +await acceptBid((await getActiveBids(0, 10))[2].bidId) // may be a different bid now + +// ✅ CORRECT — capture the stable bidId and re-read that bid before acting +const bidId = (await getActiveBids(0, 10))[2].bidId +const fresh = await readBid(bidId) +if (fresh.active) await acceptBid(bidId) +``` + +## Use Cases + +- **Data-cleaning agent marketplace.** CSV-cleaning agents post bids advertising `["csv_cleaning","deduplication"]`, price, and turnaround. A client with a messy dataset queries `getActiveBids`, filters by capability, verifies each `agentId` against ERC-8004, ranks by trusted reputation then price, accepts the winner, and creates an ERC-8183 job — the exact path validated on Arc Testnet (deploy → bid ID 0 → job ID 1199). +- **Spot procurement of agent compute.** A client needs a one-off task done now. It scans active bids, picks the cheapest agent above a reputation threshold whose bid is comfortably before expiry, and accepts — no long-lived registry lookups or negotiation channel required. +- **Agent availability signalling.** An ERC-8004 agent broadcasts short-lived (`expiresAt`) bids to signal "available for the next hour at this price," cancelling with `cancelBid` when it fills up. Clients discover live availability without off-chain polling infrastructure. + +## Alternatives + +| Instead of AgentBidBoard | When | +|--------------------------|------| +| **Direct ERC-8183 `createJob`** with a known agent | You already know exactly which agent you want — skip discovery entirely. | +| **Off-chain matching / RFQ + onchain settlement** | You need private bids, complex negotiation, or must avoid publishing prices onchain. | +| **A marketplace with onchain escrow/slashing** | You need the contract to hold funds, enforce delivery, or penalize non-performance. AgentBidBoard deliberately does none of this. | +| **`accept-agent-payments` / x402** | The goal is monetizing an agent *service endpoint* per-call, not coordinating a job between two agents. | + +For the payment leg of any accepted job, see `use-usdc` (USDC transfers on Arc) and `pay-via-agent-wallet`. + +## Reference Links + +- [AgentBidBoard reference implementation](https://github.com/OliverDevDS/arc-agent-marketplace) — Solidity source, 17-test Foundry suite, and Arc Testnet proof-of-concept tx hashes. +- [Arc Docs](https://docs.arc.network/llms.txt) -- **Always read this first** when looking for relevant documentation from the source website. +- [Arc Explorer](https://testnet.arcscan.app) — inspect the deployed board at `0xFb72B52eaF2b1A2e0cf96F8eDA1386288fC74ad9`. +- [Circle Faucet](https://faucet.circle.com) +- [Circle Developer Docs](https://developers.circle.com/llms.txt) -- **Always read this first** when looking for relevant documentation from the source website. +- Related skills: `use-arc` (Arc chain + decimal model), `use-usdc`, `use-smart-contract-platform`, `accept-agent-payments`. + +--- + +DISCLAIMER: This skill is provided "as is" without warranties, is subject to the [Circle Developer Terms](https://console.circle.com/legal/developer-terms), and output generated may contain errors and/or include fee configuration options (including fees directed to Circle); additional details are in the repository [README](https://github.com/circlefin/skills/blob/master/README.md).