Skip to content

Phase 3.1: Trading-comp verifier + API routes (agent-submit + chainhook + cron) #734

@biwasxyz

Description

@biwasxyz

Sub-issue under #652 — pre-erc-8004 simplification + D1 migration umbrella.

Implements the trading-comp verifier surface on top of the merged Phase 1.2 substrate (swaps, balances, registered_wallets from PR #668, commit dd001e80). Supersedes the stale spec issue #683 — see reconciliation comment for why the original spec drifted from the RFC.

Goal

Build the worker that populates swaps from three converging ingestion sources, and expose three read/write API routes that aibtc-mcp-server's competition tools call. After this lands, agents can:

  1. Submit a Stacks txid as a fast-path hint via the MCP (competition_submit_trade).
  2. Be passively discovered through chainhook (real-time) and nightly cron (catch-up).
  3. Read their current standing and trade history.

Substrate (already merged on main)

  • migrations/005_swaps.sql(txid) PK, INSERT OR IGNORE idempotency, three-source source enum ('agent' | 'cron' | 'chainhook'), 8-value tx_status CHECK matching x402-sponsor-relay's TerminalFailureStatuses + success.
  • migrations/006_balances.sql — per-agent token snapshots (Phase 3.3 cron populates).
  • migrations/007_registered_wallets_view.sql — projection over agents for "is this address a registered AIBTC agent."

This issue does not touch the schema. Any field-name or shape change is a separate migration (e.g. migration 008 for verified_at).

Sub-phases

3.1.a — API routes (read paths first, no verifier needed)

  • GET /api/competition/status?address={stx} — JOIN agentsswaps (verified trade count) ↔ optional balances (current P&L if scored). Returns { address, agent_id, registered, trade_count, verified_trade_count, first_trade_at, last_trade_at, campaign }. Uses registered_wallets view for the membership check.
  • GET /api/competition/trades?address={stx}&limit=&cursor= — paginated query on swaps filtered by sender. 1–200 limit, default 50. Opaque base64 cursor over (burn_block_time, txid).
  • GET /api/competition/trades?docs=1 — self-doc payload matching the pattern from /api/dashboard (feat: trading-comp dashboard with multi-token portfolio + USD totals #651).
  • OpenAPI: add path objects to app/api/openapi.json/route.ts.
  • Discovery wiring: app/llms.txt, app/llms-full.txt, app/.well-known/agent.json entries under levels.

These routes return empty/zero responses until 3.1.b populates swaps. They're testable against fixture rows.

3.1.b — Agent-submit verifier (POST /api/competition/trades)

  • Route handler validates txid format, looks up tx via Hiro (GET /extended/v1/tx/{txid} + /raw).
  • Verify: sender ∈ registered_wallets, tx_status terminal, contract+function ∈ allowlist (see 3.1.d).
  • Parse amounts per-protocol from FT/STX transfer events (Bitflow stableswap/xyk/dlmm have different shapes — see the comp-attribution gist).
  • INSERT OR IGNORE into swaps with source='agent'. First writer wins (cron/chainhook may have already inserted).
  • Response shape per the reconciliation comment — RFC field names (sender, token_in, amount_in, burn_block_time), no queued state in body (return 202 Accepted with { accepted: true } for not-yet-confirmed; agent re-polls via GET /trades), no network parameter.
  • Pending tracker (for txids not yet confirmed when submitted) lives in KV with a 30-min TTL, not D1. Key shape: comp:pending:{txid}. Re-checked by 3.1.b on subsequent submits + by 3.1.c chainhook trigger.

3.1.c — Chainhook ingestion (real-time)

  • Cloudflare Worker route receives chainhook predicate firings on Bitflow/ALEX/Zest contracts (allowlist filtered).
  • Same verify pipeline as 3.1.b, ingestion source 'chainhook'.
  • Idempotency: (txid) PK guarantees no double-insert; chainhook race with agent-submit is harmless.
  • Auth: HMAC signature on chainhook payload (predicate webhook secret in env).

3.1.d — Allowlist storage

  • Recommendation: code constant lib/competition/allowlist.ts holding the (contract_id, function_name) set. Cheap, reviewable in PRs, dashboards can introspect via static import. Promotion to a allowlist_contracts table is a future ask if per-campaign rotation becomes real.
  • Seed list (Bitflow): SPQC38PW542EQJ5M11CR25P7BS1CA6QT4TBXGB3M.stableswap-* (6 pools, swap-x-for-y / swap-y-for-x), SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.xyk-core-v-1-1 (swap-x-for-y / swap-y-for-x), SM1793C4R5PZ4NS4VQ4WMP7SKKYVH8JZEWSZ9HCCR.xyk-swap-helper-v-1-3 (swap-helper-{a,b,c,d,e}), SM1FKXGNZJWSTWDWXQZJNF7B5TV5ZB235JTCXYXKD.dlmm-swap-router-v-1-1 (swap-simple-multi), all 12 cross-DEX router-* contracts at SPQC38….
  • ALEX + Zest allowlists tracked separately (file once contracts firm).

3.1.e — Nightly catch-up cron

  • wrangler scheduled trigger, daily at 02:00 UTC.
  • Walks each registered_wallets address's recent Stacks tx history via Hiro paginated /extended/v1/address/{principal}/transactions.
  • For each tx that hits the allowlist and isn't already in swaps, runs the verify pipeline with source='cron'.
  • Caps per-run cost: max 100 addresses per execution, resumes from cursor in KV.

3.1.f — Provider-address cross-check (audit only)

  • When verifying a Bitflow swap, if the tx args include provider == SP1M8KHCJXB3SBRQRDBCG3J3859AA1CN0AWDHN17B (the AIBTC attribution tag — see aibtc-mcp-server#510), record it in raw_event_json for audit.
  • Not used as a verification primary signal — only ~6 of ~12 Bitflow contracts inject the provider arg, so it's best-effort.

What's intentionally NOT in this phase

  • Scoring (Phase 3.2) — populates scored_value / scored_at on swaps. Separate sub-issue.
  • Balances cron (Phase 3.3) — 5-min balance snapshots into balances. Separate sub-issue. Ships with the 90-day TTL sweep per the RFC decision.
  • Dashboard fold (Phase 3.4) — swaps feat: trading-comp dashboard with multi-token portfolio + USD totals #651's KV cache:dashboard SWR for a SELECT … ORDER BY total_value DESC query against balances. Separate sub-issue.
  • Multi-leg parsing (Zest supply+borrow in one tx) — one row per txid in v1 (the (txid) PK forces it). A swap_legs child table is a future migration if dashboards need it.
  • Auth on submit — txids are self-attesting (the on-chain tx already carries the agent's signature). Rate-limited per IP server-side via the ratelimits binding.

Cross-refs

cc @whoabuddy — you wrote the substrate (PR #668); flagging this so naming/decisions stay consistent with the RFC you shipped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions