You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
Submit a Stacks txid as a fast-path hint via the MCP (competition_submit_trade).
Be passively discovered through chainhook (real-time) and nightly cron (catch-up).
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 agents ↔ swaps (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).
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.
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.
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.
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_walletsfrom PR #668, commitdd001e80). Supersedes the stale spec issue #683 — see reconciliation comment for why the original spec drifted from the RFC.Goal
Build the worker that populates
swapsfrom three converging ingestion sources, and expose three read/write API routes thataibtc-mcp-server's competition tools call. After this lands, agents can:competition_submit_trade).Substrate (already merged on
main)migrations/005_swaps.sql—(txid)PK,INSERT OR IGNOREidempotency, three-sourcesourceenum ('agent' | 'cron' | 'chainhook'), 8-valuetx_statusCHECK matching x402-sponsor-relay'sTerminalFailureStatuses+ success.migrations/006_balances.sql— per-agent token snapshots (Phase 3.3 cron populates).migrations/007_registered_wallets_view.sql— projection overagentsfor "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}— JOINagents↔swaps(verified trade count) ↔ optionalbalances(current P&L if scored). Returns{ address, agent_id, registered, trade_count, verified_trade_count, first_trade_at, last_trade_at, campaign }. Usesregistered_walletsview for the membership check.GET /api/competition/trades?address={stx}&limit=&cursor=— paginated query onswapsfiltered bysender. 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).app/api/openapi.json/route.ts.app/llms.txt,app/llms-full.txt,app/.well-known/agent.jsonentries 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)GET /extended/v1/tx/{txid}+/raw).registered_wallets, tx_status terminal, contract+function ∈ allowlist (see 3.1.d).INSERT OR IGNOREintoswapswithsource='agent'. First writer wins (cron/chainhook may have already inserted).sender,token_in,amount_in,burn_block_time), noqueuedstate in body (return202 Acceptedwith{ accepted: true }for not-yet-confirmed; agent re-polls viaGET /trades), nonetworkparameter.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)
'chainhook'.(txid)PK guarantees no double-insert; chainhook race with agent-submit is harmless.3.1.d — Allowlist storage
lib/competition/allowlist.tsholding the(contract_id, function_name)set. Cheap, reviewable in PRs, dashboards can introspect via static import. Promotion to aallowlist_contractstable is a future ask if per-campaign rotation becomes real.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-DEXrouter-*contracts atSPQC38….3.1.e — Nightly catch-up cron
wranglerscheduled trigger, daily at 02:00 UTC.registered_walletsaddress's recent Stacks tx history via Hiro paginated/extended/v1/address/{principal}/transactions.swaps, runs the verify pipeline withsource='cron'.3.1.f — Provider-address cross-check (audit only)
provider == SP1M8KHCJXB3SBRQRDBCG3J3859AA1CN0AWDHN17B(the AIBTC attribution tag — see aibtc-mcp-server#510), record it inraw_event_jsonfor audit.providerarg, so it's best-effort.What's intentionally NOT in this phase
scored_value/scored_atonswaps. Separate sub-issue.balances. Separate sub-issue. Ships with the 90-day TTL sweep per the RFC decision.cache:dashboardSWR for aSELECT … ORDER BY total_value DESCquery againstbalances. Separate sub-issue.(txid)PK forces it). Aswap_legschild table is a future migration if dashboards need it.ratelimitsbinding.Cross-refs
docs/rfc-d1-schema.md§swapscc @whoabuddy — you wrote the substrate (PR #668); flagging this so naming/decisions stay consistent with the RFC you shipped.