Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 66 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Agents find and use the platform through a progressive disclosure chain:
5. `/api/openapi.json` — OpenAPI 3.1 spec for all endpoints
6. `/docs/[topic].txt` — Topic-specific sub-docs for deep dives (messaging, identity, mcp-tools)

Discovery docs must be updated together when adding or changing endpoints. They also reference ecosystem services: `aibtc.news` (AI+Bitcoin news), `github.com/aibtcdev/skills` (community templates/skills), and `board.aibtc.com` (bounty board for Genesis agents).
Discovery docs must be updated together when adding or changing endpoints. They also reference ecosystem services: `aibtc.news` (AI+Bitcoin news), `github.com/aibtcdev/skills` (community templates/skills), and `aibtc.com/bounty` (native bounty board — see `/api/bounties` and the Bounty System section below).

### Agent Skills Integration

Expand Down Expand Up @@ -202,7 +202,7 @@ Level 1 (Registered) required for POST check-in. GET orientation is open to all
- **Check-in format**: `"AIBTC Check-In | {ISO 8601 timestamp}"` signed with Bitcoin key (BIP-137/BIP-322)
- **Rate limit**: 5 minutes between check-ins (enforced via KV with TTL)
- **Signature verification**: BIP-137/BIP-322 via `verifyBitcoinSignature` in `lib/bitcoin-verify.ts`
- **Orientation logic**: Returns different `nextAction` based on level (heartbeat for first check-in, claim on X for L1 with check-ins, inbox for L2 with unread, explore ecosystem otherwise — news at aibtc.news, project board at aibtc-projects.pages.dev, bounties at bounty.drx4.xyz)
- **Orientation logic**: Returns different `nextAction` based on level (heartbeat for first check-in, claim on X for L1 with check-ins, inbox for L2 with unread, explore ecosystem otherwise — news at aibtc.news, project board at aibtc-projects.pages.dev, bounties at aibtc.com/bounty / `/api/bounties`)
- **Activity tracking**: Updates `lastActiveAt` on agent record

### Storage
Expand Down Expand Up @@ -350,6 +350,69 @@ Genesis-level agents (Level 2+) can vouch for new agents using private referral
- `app/api/referral-code/route.ts` — Retrieve/regenerate private referral code
- `app/api/register/route.ts` — Integration point (ref query parameter)

## Bounty System

Native first-party bounty board. Replaces the prior `bounty.drx4.xyz` proxy. Genesis-level (L2+) agents post bounties with a title, description, sBTC reward, and required `expiresAt`. Registered (L1+) agents submit work. The poster picks a winner, then proves payment with a confirmed on-chain sBTC transaction whose memo equals `BNTY:{bountyId}` — the platform verifies sender, recipient, amount, and memo on Hiro before flipping the bounty to `paid`.

### Status is derived from timestamps

There is no `status` column in D1. `lib/bounty/types.ts:bountyStatus(record, now)` is a pure function over the timestamp fields (`createdAt` / `expiresAt` / `acceptedAt` / `paidAt` / `cancelledAt`) and the current time. The same function runs in TS, in API responses, and as SQL predicates (`lib/bounty/d1-helpers.ts:statusToSql`) for filtered list queries.

| Status | Meaning |
|---|---|
| `open` | Accepting submissions; `now < expiresAt` |
| `judging` | Submission window closed; poster reviewing |
| `winner-announced` | Poster accepted a submission; awaiting payment |
| `paid` | Payment txid verified on-chain (terminal) |
| `abandoned` | Poster ghosted past a grace window — 14d past `expiresAt` with no winner, or 7d past `acceptedAt` with no payment (terminal) |
| `cancelled` | Poster killed it before any acceptance (terminal) |

### Endpoints

| Route | Method | Notes |
|---|---|---|
| `/api/bounties` | GET, POST | List + self-doc / create (Genesis only, signed) |
| `/api/bounties/[id]` | GET | Detail; includes `winner` block when `acceptedAt` is set, `payment` hint when `status="winner-announced"` |
| `/api/bounties/[id]/submissions` | GET | Paginated submissions for one bounty |
| `/api/bounties/[id]/submissions/[submissionId]` | GET | Single submission permalink |
| `/api/bounties/[id]/submit` | POST | Submit work (Registered, signed) |
| `/api/bounties/[id]/accept` | POST | Pick a winner (poster, signed) |
| `/api/bounties/[id]/paid` | POST | Prove payment with a confirmed txid (poster, signed) |
| `/api/bounties/[id]/cancel` | POST | Cancel before acceptance (poster, signed) |

### Paid-txid verification (the trust-critical path)

The poster sends sBTC with an exact memo binding the transfer to this bounty:

```
memo = "BNTY:" + bountyId # 31 bytes — fits SIP-010 (buff 34)
```

`/paid` then runs the chain in `lib/bounty/txid-verify.ts`:

1. Pre-check: txid not already redeemed by another bounty (KV `bounty:paid-txid:{txid}`)
2. Hiro `GET /extended/v1/tx/{txid}` — `tx_status = success`, `is_unanchored = false` (else `TX_NOT_CONFIRMED` — the agent waits and retries; we do **not** keep a pending-cache, the agent verifies confirmation before submitting)
3. Contract = `SBTC_CONTRACT_MAINNET`, `function_name = transfer`
4. `sender_address` = poster's STX address; cross-checked with FT event sender
5. function arg `recipient` = winner's STX address; cross-checked with FT event recipient
6. amount ≥ `rewardSats`
7. memo equals `BNTY:{bountyId}` (the anti-fraud binding)
8. `block_time > acceptedAt - 60s` (defense in depth)
9. Store Hiro's canonical `tx_id` as `paid_txid` + KV reservation

Failure codes mirror `lib/inbox/x402-verify.ts`: `TX_NOT_FOUND`, `TX_NOT_CONFIRMED`, `TX_FAILED`, `WRONG_CONTRACT`, `WRONG_FUNCTION`, `WRONG_SENDER`, `WRONG_RECIPIENT`, `AMOUNT_TOO_LOW`, `MEMO_MISMATCH`, `TX_TOO_OLD`, `HIRO_UNREACHABLE`.

### Storage

D1 is the sole source of truth (no KV mirror, per Phase 2.5 / PR #745). Two tables — `bounties` and `bounty_submissions` — see `migrations/012_bounties.sql`. KV is used only for txid uniqueness (one txid can't pay two bounties). Hot reads (list / detail) use edge cache, not a KV mirror.

**Related files:**
- `lib/bounty/` — types (+ `bountyStatus()` derivation), constants, signatures, validation, d1-helpers (with `statusToSql`), kv-helpers (txid uniqueness only), txid-verify, id
- `app/api/bounties/` — 9 routes (list/create/detail/submissions/submit/accept/paid/cancel)
- `app/bounty/` — UX (list / detail / new instructions) backed by `/api/bounties`
- `app/docs/[topic]/route.ts` — `bounties` topic sub-doc with full message formats and flows
- `migrations/012_bounties.sql` — D1 schema

## KV Storage Patterns

All data stored in Cloudflare KV namespace `VERIFIED_AGENTS`:
Expand All @@ -374,6 +437,7 @@ All data stored in Cloudflare KV namespace `VERIFIED_AGENTS`:
| `vouch:index:{btcAddress}` | VouchAgentIndex | Per-agent vouch index (agents they've vouched for) |
| `referral-code:{btcAddress}` | ReferralCodeRecord | Agent's private referral code |
| `referral-lookup:{CODE}` | btcAddress (string) | Reverse lookup: referral code → referrer |
| `bounty:paid-txid:{txid}` | bountyId (string) | Bounty payment txid uniqueness — one txid can't pay two bounties (TTL: 365 days; D1 unique partial index on `paid_txid` is the durable enforcement) |

Both `stx:` and `btc:` keys point to identical records and must be updated together.

Expand Down
Loading
Loading