@oracle-agent/oracle 0.24.1 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/assets/skills/chain/SKILL.md +34 -0
  2. package/dist/assets/skills/chain-defi-ecosystem-analysis/SKILL.md +181 -0
  3. package/dist/assets/skills/chain-ecosystem-gap-analysis/SKILL.md +162 -0
  4. package/dist/assets/skills/cross-chain-twap-execution/SKILL.md +125 -0
  5. package/dist/assets/skills/defi-protocol-pmf-assessment/SKILL.md +332 -0
  6. package/dist/assets/skills/evm-contract-research.md +8 -3
  7. package/dist/assets/skills/multi-venue-prepare-only-ranking/SKILL.md +93 -0
  8. package/dist/assets/skills/oracle-access-control/SKILL.md +71 -0
  9. package/dist/assets/skills/oracle-action-arming/SKILL.md +99 -0
  10. package/dist/assets/skills/oracle-airdrop-calculator/SKILL.md +111 -0
  11. package/dist/assets/skills/oracle-desk-product/SKILL.md +341 -0
  12. package/dist/assets/skills/oracle-evm/SKILL.md +55 -0
  13. package/dist/assets/skills/oracle-harness/SKILL.md +41 -0
  14. package/dist/assets/skills/oracle-mcp-install/SKILL.md +140 -0
  15. package/dist/assets/skills/oracle-multichain-convert/SKILL.md +87 -0
  16. package/dist/assets/skills/oracle-native-harness/SKILL.md +32 -0
  17. package/dist/assets/skills/oracle-ownership-gate/SKILL.md +42 -0
  18. package/dist/assets/skills/oracle-public-product-ux/SKILL.md +114 -0
  19. package/dist/assets/skills/oracle-tailscale/SKILL.md +32 -0
  20. package/dist/assets/skills/oracle-thin-client/SKILL.md +47 -0
  21. package/dist/assets/skills/perp-venue-funding-research/SKILL.md +161 -0
  22. package/dist/assets/skills/polymarket/SKILL.md +160 -0
  23. package/dist/assets/skills/protocol-api-key-integration/SKILL.md +141 -0
  24. package/dist/assets/skills/self-custodial-onchain-execution/SKILL.md +1284 -0
  25. package/dist/assets/skills/setup/SKILL.md +40 -0
  26. package/dist/assets/skills/stable-launch-ops/SKILL.md +89 -0
  27. package/dist/assets/skills/trade-loop-circuit-breaker/SKILL.md +441 -0
  28. package/dist/assets/skills/venue-capability-boundaries/SKILL.md +32 -0
  29. package/dist/bin/desk-server.mjs +16 -16
  30. package/dist/bin/oracle-data-mcp.mjs +1 -1
  31. package/dist/bin/oracle-equities.mjs +1 -1
  32. package/dist/bin/oracle-init.mjs +9 -9
  33. package/dist/cli/commands/bootstrap.mjs +1 -1
  34. package/dist/cli/commands/chat.mjs +78 -77
  35. package/dist/cli/commands/doctor.mjs +8 -6
  36. package/dist/cli/commands/eval.mjs +1 -1
  37. package/dist/cli/commands/harness.mjs +6 -6
  38. package/dist/cli/commands/model.mjs +82 -81
  39. package/dist/cli/commands/receipt.mjs +5 -0
  40. package/dist/cli/commands/setup.mjs +1 -1
  41. package/dist/cli/commands/venues.mjs +3 -0
  42. package/dist/cli/commands/watch.mjs +16 -0
  43. package/dist/equities/index.mjs +1 -1
  44. package/dist/index.mjs +1 -1
  45. package/package.json +1 -1
@@ -0,0 +1,40 @@
1
+ ---
2
+ name: setup
3
+ description: "Use when the user types /setup or wants to connect telegram, discord, slack, or another messaging platform to oracle."
4
+ version: 1.0.0
5
+ disable-model-invocation: true
6
+ ---
7
+
8
+ > Oracle native tools: `oracle_cli` (read/prepare), `vault_status`, `signer_status`, `signer_execute` (needs human confirmationNonce), `skill_load`. No generic shell. No fleet SSH.
9
+
10
+
11
+ # /setup
12
+
13
+ Configure messaging for the oracle profile. Secrets stay local.
14
+
15
+ ```bash
16
+ # menu + status (never prints tokens)
17
+ oracle setup status
18
+
19
+ # open full hermes wizard
20
+ oracle setup messaging
21
+
22
+ # platform shortcuts
23
+ oracle setup telegram
24
+ oracle setup discord
25
+ oracle setup slack
26
+ oracle setup whatsapp
27
+
28
+ # gateway control
29
+ oracle setup gateway status
30
+ oracle setup gateway restart
31
+ ```
32
+
33
+ If the user passed args after `/setup`, forward them:
34
+
35
+ ```bash
36
+ oracle setup {{arg1}} {{arg2}} {{arg3}}
37
+ ```
38
+
39
+ If no args, show `oracle setup status`.
40
+ Never echo bot tokens, app tokens, or passwords back into chat.
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: stable-launch-ops
3
+ description: "Use for Stable Launch chain 988 deploy or testnet asks."
4
+ version: 1.0.0
5
+ ---
6
+
7
+ > Oracle native tools: `oracle_cli` (read/prepare), `vault_status`, `signer_status`, `signer_execute` (needs human confirmationNonce), `skill_load`. No generic shell. No fleet SSH.
8
+
9
+
10
+ # Stable Launch ops (chain 988)
11
+
12
+ Repos: `~/projects/stable-launch` (contracts) · `stable-launch-web` · `stable-launch-indexer` · `stable-agent` (personal signals, never bundled into public web).
13
+
14
+ Board: Obsidian `Projects/Stable Launch Board.md` · preview `https://stablelaunch.demi.la`
15
+
16
+ ## Chain fact (do not invent a testnet)
17
+
18
+ - Product chain is **Stable Mainnet chainId 988** only in this stack.
19
+ - Addresses file: `stable-launch/addresses/988.json` (null until factory GO).
20
+ - Public RPC: `https://rpc.stable.xyz` (pruned ~156k blocks — indexer needs **archive** paid RPC for first backfill).
21
+ - There is **no** separate Stable public testnet config in-repo. INTERFACE.md "testnet first" means **anvil-988 fork rehearsal**, not a second public network.
22
+
23
+ When DEMI asks **"should we deploy on testnet to test?"**:
24
+
25
+ | Path | Do it? | Why |
26
+ |---|---|---|
27
+ | Traffic stack (paid RPC + indexer host + CF secrets/DNS) | **Yes** | Proves launch-day read path; no factory GO |
28
+ | Frontend edge cutover off demi-poly | **Yes** | demi-poly is forbidden for launch frontend/indexer |
29
+ | Physical wallet QA on 988 | **Yes** | Cannot verify headlessly |
30
+ | Persistent **anvil fork of 988** + point web/indexer at it | **Yes** | Full E2E, zero mainnet spend |
31
+ | Factory `forge script --broadcast` on live 988 | **No** without explicit GO | Real USDT0 gas + irreversible keeper wiring |
32
+ | Invented external "Stable testnet" deploy | **No** | Not in config; fork already covers contract risk |
33
+
34
+ ## Already proven (do not re-litigate)
35
+
36
+ - Foundry suite green; dual red-team APPROVE (Opus + Sol)
37
+ - Full lifecycle on live-RPC anvil-988 fork: deploy→launch→buy→graduate→v3 LP→swap→buyback/burn
38
+ - Deploy without code-bearing `STABLE_KEEPER` reverts as designed
39
+ - LI.FI bridge Base USDC→USDT0 on 988 quoted live
40
+ - Indexer-first radar path + k6 load scripts exist
41
+
42
+ ## Hard GO gates before live factory
43
+
44
+ From board / `docs/MAINNET_GO_RUNBOOK.md`:
45
+
46
+ 1. Recoverable **Safe** as `STABLE_KEEPER` on 988 (`cast codesize` > 0) — not an EOA
47
+ 2. Funded **dedicated** deployer key (USDT0 gas) — never the chat/hot wallet
48
+ 3. Paid `STABLE_RPC_PRIMARY` / `FAILOVER` (archive primary)
49
+ 4. Indexer host on Fly/Railway/Render — **never demi-poly**
50
+ 5. DNS / CF custom-domain bind for `stablelaunch.demi.la` (see DNS trap)
51
+ 6. Explicit DEMI **GO** for broadcast
52
+
53
+ ### CF / edge (do not re-mint tokens blindly)
54
+
55
+ - Same CF **account** as HL Media (users see hostnames only).
56
+ - Web repo already has CF secrets + green deploys (2026-07-25).
57
+ - Live: `https://stable-launch-web.christophergervais92.workers.dev` (account-derived sub — never guess `demi-hl`).
58
+ - Still need: `STABLE_RPC_*`, `INDEXER_URL`, factory `NEXT_PUBLIC_*` after GO.
59
+ - Vault "cfat_ expired" may be stale after green CI.
60
+
61
+ ### Indexer Fly prep
62
+
63
+ - `fly.toml` in `stable-launch-indexer` (lax, volume `/data`). Empty factory → `live:false` radar OK.
64
+ - See indexer README + `docs/PRE_LIVE_OPS.md`.
65
+
66
+ ### DNS trap — never blind-CNAME to workers.dev
67
+
68
+ `stablelaunch.demi.la` A→demi-poly works. CNAME to workers.dev **without** CF zone + Worker domain bind → **403** on custom Host (probed). Keep VPS A + edge-QA on workers.dev, or CF zone audit → NS → bind. Recipe: `references/dns-cf-workers-trap-2026-07-25.md`. GoDaddy API 401 → mint fresh key before API DNS edits.
69
+
70
+ ## Forbidden
71
+
72
+ - Launch frontend, indexer, or RPC proxy on demi-poly
73
+ - Bundling `stable-agent` into public `stable-launch-web`
74
+ - Mainnet broadcast "just to test" when fork rehearsal already passed
75
+ - Deploy from the Oracle chat hot wallet
76
+
77
+ ## Safe next steps (default recommendation order)
78
+
79
+ 1. Confirm CF green; set archive RPC secrets; Fly indexer empty-factory + `INDEXER_URL`
80
+ 2. Wallet physical QA on preview and/or workers.dev
81
+ 3. Keeper Safe + dedicated deployer
82
+ 4. CF zone path for custom domain (or leave VPS A)
83
+ 5. Only then: `docs/MAINNET_GO_RUNBOOK.md`
84
+
85
+ ## Pointers
86
+
87
+ - Status: `stable-launch/STATUS.md`
88
+ - Runbooks: `docs/MAINNET_GO_RUNBOOK.md`, `docs/KEEPER_RUNBOOK.md`, `docs/LAUNCH_TRAFFIC.md`, `docs/PRE_LIVE_OPS.md`
89
+ - Notes: `references/testnet-decision-2026-07-25.md`, `references/dns-cf-workers-trap-2026-07-25.md`
@@ -0,0 +1,441 @@
1
+ ---
2
+ name: trade-loop-circuit-breaker
3
+ description: Apply mandatory guardrails to any autonomous or agentic live-money trading loop. Use before touching the Polymarket bot, building a new scanner/execution dashboard, enabling wallet/CEX orders, responding to a drawdown, or resuming after a halt. Makes the "anything touching real capital is surgical, backed-up, verified, and GO-gated" invariant executable.
4
+ ---
5
+
6
+ > Oracle native tools: `oracle_cli` (read/prepare), `vault_status`, `signer_status`, `signer_execute` (needs human confirmationNonce), `skill_load`. No generic shell. No fleet SSH.
7
+
8
+
9
+ # Trade-Loop Circuit Breaker
10
+
11
+ Any runaway loop or unverified execution path is existential when it can move real capital. This skill gates Polymarket operations and new agentic trading products. It does NOT change strategy; it controls when execution is allowed.
12
+
13
+ ## Context
14
+ - Live sports arb bot. VPS `demi-poly`, path `/root/polymarket-arbitrage-bot`.
15
+ - Has a daily loss limit that HALTS the bot (has fired before — 14.2% daily loss → manual reset needed).
16
+ - pm2-managed process map; extensive cron pipeline (reconcile, clv, snapshots, retrain, shadow, rf-advisory).
17
+ - See vault: Polymarket Bot Operational Reference, Demi Revert Runbook, Demi Bot Architecture.
18
+
19
+ ## Before ANY change to the live loop
20
+ 1. **Back up first.** Snapshot the file(s) and the relevant DB/state before editing. Note the exact revert command (see Demi Revert Runbook).
21
+ 2. **Surgical only.** Touch exactly what the task requires. No "while I'm here" cleanup in the money path.
22
+ 3. **GO-gate.** Hard-to-reverse actions (deploy to live loop, reset after halt, change position sizing, drop/alter state) require explicit DEMI confirmation. Do not self-approve.
23
+
24
+ ## Circuit breakers on any autonomous trading/execution loop
25
+ - **Retry ceiling:** max 3 attempts on a failing external call (Polymarket API, RPC). Then stop, don't hammer.
26
+ - **Spend/exposure cap:** respect the daily loss limit; never code around a halt. A halt is a signal, not an obstacle.
27
+ - **Backoff + jitter** on API timeouts — cascading timeouts are a known cost/failure path.
28
+ - **Kill-switch check** before resume: verify why it halted before clearing it. Reset ≠ diagnosis.
29
+ - **Signer separation:** automatic execution may use only a separate capped hot wallet/session key/smart wallet. Never DEMI's main seed/private key.
30
+ - **Two-step deploy boundary:** shipping executor code and securely installing a dedicated signer are not the same as enabling broadcasts. A signer may be installed root-only in an unfunded, execution-off state after explicit installation approval so its derived address and status path can be verified. Keep `agent:execute` blocked, capital at zero, and asset allowlists empty until a separate explicit DEMI GO enables the capped signer. Never expose or print the key during either stage.
31
+ - **Policy proof:** after every deploy, pull the live policy and state `execution.enabled`, `backendSigner`, `liveExecute`, and whether `agent:execute` is blocked before claiming auto-exec status.
32
+
33
+ ## Resume-after-halt sequence
34
+ 1. Read the halt reason (loss limit? API? reconcile mismatch?). Don't clear blindly.
35
+ 2. Reconcile positions (`reconcile_positions.py --apply`) — confirm on-chain vs recorded state matches.
36
+ 3. Confirm the condition that tripped it has actually cleared.
37
+ 4. GO-gate the resume with DEMI. Report the halt reason + current exposure in the ask.
38
+ 5. Resume, then watch the first cycle before walking away.
39
+
40
+ ## Escalation message format
41
+ When a breaker trips or a halt needs a human: state the trigger, current exposure/PnL, what you've verified, and the specific decision needed — one message, no burying the ask.
42
+
43
+ ## Evaluating a NEW venue / market before wiring it into the bot (HIP-4 pattern)
44
+ When DEMI says "make the bot also trade <new venue/market> (e.g. HIP-4)", the
45
+ change is a NEW EDGE that shares infra — NOT the existing edge pointed at a new
46
+ venue. Pressure-test the premise before writing a line.
47
+
48
+ **STEP 0 (do this BEFORE proposing any build): inventory what already exists in
49
+ the repo.** A prior session — or DEMI — may have already built most of the venue
50
+ stack, gated off. One grep saves a duplicate build: `git log --oneline | grep -i
51
+ <venue>`, `ls strategies/ lib/ | grep -i <venue>`, `grep -rn "<VENUE>\|exec-client"
52
+ .env server/api.js`, and `pm2 logs demi-server --lines 300 --nostream | grep -i
53
+ <venue>` (is it FIRING right now?). This exact miss happened with HIP-4: the FULL
54
+ stack (exec client, divergence monitor, strategy, TP guard, per-user creds path)
55
+ was already committed and live-trading for one pilot wallet — nearly rebuilt a
56
+ duplicate. Also re-scope "for others": if the bot is already a MULTI-USER
57
+ per-wallet system, "trade X for everyone" = per-user opt-in (own wallet/key/
58
+ settings), NOT DEMI custodying others' funds — don't reflexively push back on
59
+ custody grounds before checking the architecture. Full worked example (what
60
+ existed, the pilot-gate→per-user conversion, deploy discipline, open GO-gates):
61
+ `references/hip4-already-built-per-user-conversion.md`.
62
+
63
+ If the venue is genuinely greenfield, THEN pressure-test as below:
64
+ 1. **Pull real liquidity first, argue from numbers not vibes.** Get the actual
65
+ book depth on the new venue (for HIP-4: `outcomeMeta` + `l2Book "#<encoding>"`,
66
+ see the `hyperliquid` skill). Thin/favorite-concentrated books cap strategy
67
+ size to the top handful of markets — model that before proposing trades.
68
+ 2. **Cross-venue arb hinges on RESOLUTION-SOURCE PARITY.** If the new venue's
69
+ oracle can resolve an event differently than the leg you're hedging against
70
+ (e.g. HL HIP-4 oracle vs Polymarket UMA), the "arb" is a naked directional bet
71
+ with BOTH legs able to lose. Verify identical resolution criteria before ever
72
+ calling it an arb.
73
+ 3. **Separate the two builds and push back on the risky one.** "Trade X for
74
+ others" is ambiguous: (a) extend our own trading to a new venue = engineering;
75
+ (b) trade other people's capital = custody + trust + reg surface → push back
76
+ hard, do not wire third-party capital through our signer on day one.
77
+ 4. **Shadow/paper FIRST, always.** A new edge goes through this circuit breaker:
78
+ build a read module + a shadow-mode spread scanner that LOGS would-be trades
79
+ without executing, collect a few days of data to prove the edge is real and
80
+ capturable, THEN GO-gate live with capital caps. Never hot-wire directional
81
+ trades on a new venue into the live money loop on introduction day.
82
+ 5. Confirm the live bot's current state (pm2 status + halt-file check) before
83
+ proposing any change, so the new-venue work doesn't collide with a halt.
84
+
85
+ Worked example (HIP-4 outcome markets: endpoint probe, coin encoding, WC-2026
86
+ liquidity table, both-vs-illiquid analysis): `references/hip4-venue-evaluation.md`.
87
+
88
+ ## Converting a pilot-gated venue strategy to FULL per-user (the "trade X for everyone" ask)
89
+ When DEMI says "make HIP-4 trade for others / everyone, like the PM bot," and STEP 0 finds
90
+ the venue stack already exists but gated to ONE pilot wallet (`HIP4_MANUAL_WALLETS`), the
91
+ work is a GATE CONVERSION, not a build. The PM bot is already multi-user per-wallet, and the
92
+ HIP-4 exec path was built per-user-ready — verify these before promising a rewrite:
93
+ - **The exec client already takes a key as a PARAMETER**, not a hardcoded wallet
94
+ (`createClient({ agentPrivateKey })`). The `_clients` map is `wallet -> client`. Per-user
95
+ creds already decrypt per-wallet (`auth.decryptCredentials(wallet, 'hl-credentials.json')`).
96
+ The onboarding endpoint (`POST /api/hip4/credentials`) + the encrypted-file whitelist in
97
+ `server/auth.js` already exist. So "per-user" is mostly ALREADY THERE.
98
+ - **What actually blocks it = two gate sites**, both single lines: (1) the strategy's
99
+ `pilotWallets().includes(wallet)` check in `execute()`, (2) the API's `requireHip4Pilot`
100
+ middleware on `/hip4/order` + `/hip4/cancel`. Convert BOTH to `optedIn || isPilot` where
101
+ `optedIn = bot.settings.trading.hip4Enabled === true` (mirror PM's `settings.trading.mode==='live'`
102
+ opt-in). Keep the pilot allowlist as a backward-compat OR so the pilot keeps trading.
103
+ - **Per-user sizing/caps**: global env `STAKE`/`MAX_POSITIONS` become per-user reads from
104
+ `bot.settings.trading.positionSizing.maxPerTradeUsd` + `settings.risk.maxConcurrentPositions`,
105
+ with the env value as fallback. Clamp stake to the HL 10.1 USDC min-notional floor.
106
+ - **A UI toggle is required for real parity** — the API accepts the setting but the dashboard
107
+ page (`src/pages/HIP4.jsx`) needs a control writing `settings.trading.hip4Enabled` via
108
+ `/api/settings` (copy the Settings.jsx save pattern). Without it, opt-in is a manual JSON flip.
109
+ - **THE FUNDING FACT (state it every time — it's the #1 user question):** HIP-4 outcome markets
110
+ settle in **USDC on the user's HYPERLIQUID SPOT wallet** — NOT Polymarket USDC, NOT an HL perps
111
+ balance. `getSpotState(wallet)` reads the spot `USDC` balance; the strategy skips if `usdcFree < stake`.
112
+ So a user needs: funded HL spot USDC + their own HL agent/API-wallet key stored + the toggle on.
113
+ PM's Polygon-USDC-on-the-CLOB and HIP-4's HL-native-spot-USDC are two SEPARATE funding pools;
114
+ a user can trade HIP-4 with zero Polymarket balance.
115
+ - **Merging is DORMANT — "won't trade unless they sign" is TRUE.** After merge, the gate ladder in
116
+ `execute()` is: `HIP4_ARB_LIVE=1` (global, already on) -> opted-in-OR-pilot -> **hl-credentials.json
117
+ exists with agentPrivateKey (the "sign" — hard stop if missing)** -> USDC>=stake -> position room ->
118
+ fresh-edge recheck. A user who does nothing stays invisible; only their explicit toggle+key opens
119
+ execution. State this to reassure DEMI a merge doesn't auto-expose anyone. (The pilot wallet keeps
120
+ trading regardless of merge — merge only ADDS the opt-in path.)
121
+ - **RISK-PROFILE GAP to flag before scaling capital:** the TP guard rests a GTC sell at 0.90 (env
122
+ `HIP4_TAKE_PROFIT_PRICE`) on a 60s poll — that is a take-profit-or-bust exit, NOT risk management.
123
+ There is NO stop-loss, NO mid-range/partial TP, NO time exit. Each position is ~all-or-nothing to
124
+ match resolution with an early cash-out ramp at 0.90. Fine at $10 pilot size; the single biggest
125
+ gap vs PM's risk discipline before bigger stakes or other users' capital.
126
+
127
+ Full worked conversion (exact edits, the CRLF-diff footgun that inflates a 15-line change to
128
+ 9600 lines, the sell-direction notifier-label bug, deploy discipline, PnL-before-scale gate):
129
+ `references/hip4-already-built-per-user-conversion.md`.
130
+
131
+ ## Adding an independent per-venue START/STOP toggle (the "let users run PM-only / HIP-4-only / both / neither" ask)
132
+ When DEMI says "give Polymarket and HIP-4 separate start/stop" / "different bot
133
+ trigger so people can run either or both," the architecture reality is usually:
134
+ ONE master start/stop loop scans ALL strategies each cycle (PM's `sports-odds` +
135
+ HIP-4's `hip4-arb` in the same `registry.scanAll`), so it's NOT two bots — it's one
136
+ loop with per-venue CONTENT switches. Build them as **OFF-only gates that default ON**:
137
+ - **Gate site = right after the strategy scan, before execution**, in the loop's
138
+ `_runScan` (`lib/trading-loop.js`, just after `setStrategyOpportunities`). Filter the
139
+ opportunity array by the venue tag: HIP-4 opps carry `venue:'hyperliquid'`, PM opps
140
+ don't. `polymarketEnabled===false` → keep only `venue==='hyperliquid'`;
141
+ `hip4Enabled===false` → drop `venue==='hyperliquid'`.
142
+ - **OFF-only, default ON is the safest class of money-path edit** — read the flag as
143
+ `!== false` (undefined = on) so behavior is byte-identical unless a user EXPLICITLY
144
+ disables a venue. The gate can only ever DROP opportunities, never create one, so it
145
+ cannot cause a trade — only prevent one. This is why it's shippable without a shadow
146
+ cycle (unlike a NEW edge, which must paper-first). Keep the asymmetry deliberate: PM
147
+ defaults ON (preserves every existing user), HIP-4 defaults OFF (still needs key + opt-in).
148
+ - **No restart needed** — `bot.settings` hot-reloads via an `fs.watch` on the user's
149
+ `settings.json` (debounced), so a dashboard toggle takes effect NEXT scan. Mirror the
150
+ existing per-venue opt-in read (`bot.settings.trading.hip4Enabled`) rather than baking
151
+ a value into the loop constructor.
152
+ - **UI = a second card, not a second power button.** Keep the master Start/Stop as the
153
+ engine power; add a "Venue Controls" card of two independent ON/OFF toggles under it,
154
+ each writing `settings.trading.{polymarketEnabled,hip4Enabled}` via the page's existing
155
+ `/api/settings` save pattern. Add both flags to the frontend `DEFAULT_SETTINGS.trading`
156
+ (PM:true, HIP-4:false).
157
+ - **VERIFY BY EXTRACTING THE FILTER INTO A UNIT TEST, not by trusting the default path.**
158
+ Copy the exact gate function into a throwaway `node` script and assert all four states
159
+ (both / PM-only / HIP-4-only / neither) plus the `undefined=on` case against a fixture
160
+ opp array. Default-on means the live log shows NO drop message on a normal deploy (correct,
161
+ not a failure) — so the standalone test is the real proof the OFF paths work, since prod
162
+ won't exercise them until a user flips a switch. Then confirm the loop is still scanning
163
+ live post-restart.
164
+ Full worked recipe (exact gate code, the venue-tag fact, the 6-case unit test, the master-loop-
165
+ vs-venue-content design decision, the "each venue as its own standalone bot" bigger-change note):
166
+ `references/per-venue-enable-toggle.md`.
167
+
168
+ ## Wiring a NEW strategy into the SHARED learning loop (the "both bots self-learn" ask)
169
+ When DEMI says "wire X into learning / both strategies should self-learn / have the
170
+ agent take the trades so it self-learns," separate the two ideas he's fusing —
171
+ they point OPPOSITE directions:
172
+ - **"Agent takes the trades" = NO.** An LLM/agent in the hot execution path is wrong
173
+ for a deterministic arb: (1) latency — an arb edge lives seconds, an LLM round-trip
174
+ is 1-10s, so by the time it "decides" the edge is gone; (2) the edge is arithmetic
175
+ (book price vs consensus, a subtraction), nothing to reason about, a model only adds
176
+ hallucination + jitter; (3) money-path invariant — a non-deterministic process with
177
+ signing authority violates the circuit breaker. Execution stays deterministic and fast.
178
+ - **"It self-learns" = YES, but as the COLD layer beside the loop, not inside it.**
179
+ The right shape: Execution (hot, deterministic, ms) writes closed-trade outcomes →
180
+ Learning (cold, async, minutes) digests them → Params (thresholds, sizing, market
181
+ whitelist) the loop reads next scan. The agent's job is tuning PARAMETERS from the
182
+ feedback, never placing ORDERS.
183
+ - **The real gap is usually plumbing, not intelligence.** The PM bot already has the
184
+ full learning loop (`lib/trade-feedback.js` scores closed trades → LLM filter gets
185
+ more selective; `bayesian-updater.js`, `edge-scorer.js`, `clv-cutoff.js`). Grep the
186
+ learning modules for the new strategy/venue name — if it's ZERO, the new strategy
187
+ feeds nothing back and "it self-learns" is simply false for it. Wiring it in ≠ adding
188
+ an agent; it's connecting the recorder.
189
+ - **The hard part for an on-chain venue (HIP-4): positions VANISH on close.** HIP-4
190
+ positions are on-chain spot coins (`+N` balances) that disappear from state when they
191
+ TP or settle — nothing writes a closed-trade record, so `trade-feedback` (which globs
192
+ `data/users/*/portfolio*.json` for records with `realizedPnl`) sees zero history. The
193
+ fix is a small RECORDER + RECONCILER, mirroring PM's own `backfillRealizedPnl`
194
+ (sell-fills matched against buys): (1) `recordEntry` at fill time into a
195
+ `portfolio-hip4.json` in the EXACT shape trade-feedback already reads (`strategy`,
196
+ `realizedPnl:null` while open, `entryCost`, `edgePercent`, `question`, timestamps) —
197
+ no reader-side wiring needed, the glob picks it up; (2) a read-only `reconcile` (pulls
198
+ `userFills` + `spotState`, NO signing) that books `realizedPnl = sellProceeds − entryCost`
199
+ when the coin's balance is gone, and `−entryCost` for coins that settled worthless with
200
+ no sell fill; (3) call reconcile on an existing per-wallet poll you already run (the TP
201
+ guard's 60s loop is ideal — it already iterates the same wallets).
202
+ - **Never let the ledger block the fill.** Wrap `recordEntry`/`reconcile` in try/catch —
203
+ a learning-ledger write failure must NEVER fail an executed trade or halt the guard.
204
+ - **VERIFY with a real end-to-end unit test against the actual modules**, mocking only the
205
+ network reads. Record a winner + a loser, mock a TP sell-fill + a worthless-settle, run
206
+ reconcile, then call `trade-feedback.getStrategyFeedback()` and assert the new strategy
207
+ bucket now shows wins/losses/totalPnl (exact arithmetic). Then confirm the live read
208
+ endpoint (`userFills`) is real against a funded wallet — a mock passing proves logic,
209
+ the live call proves the endpoint + fill shape (`dir:'Buy'/'Sell'`, `side:'B'/'A'`).
210
+ - **Set the honest expectation:** the ledger only captures fills AFTER deploy, and can't
211
+ learn until positions round-trip. If the venue has zero realized PnL, the plumbing is
212
+ correct-but-empty — build it BEFORE trades close (capture in place) but don't claim it's
213
+ "learning" until real closed trades exist.
214
+ Full worked recipe (the 4-file wiring, the ledger module, getUserFills read-only method,
215
+ the on-chain-position reconciliation math, the agent-cold-layer-not-hot-path decision, the
216
+ end-to-end test): `references/shared-learning-loop-wiring.md`.
217
+
218
+ ### The bot alerts ITSELF on round-trips — not Hermes (DEMI preference, corrected)
219
+ When the reconciler detects a close and DEMI wants to be pinged, the alert must come from
220
+ **the bot's own Notifier**, not a Hermes cron/watch. DEMI's exact steer: "telegram bot should
221
+ be able to alert not you." The mechanics fall out naturally because the reconcile runs INSIDE
222
+ the TP guard, which already holds a `notifier`:
223
+ - **Make `reconcile` RETURN the closed trades** (`{ closed, stillOpen, closedTrades }`), then the
224
+ guard (which owns the notifier) fires one `notifier.sendTelegram(...)` per round-trip. Same
225
+ channel as the fill/TP alerts — same infra DEMI already reads. No new watcher, no Hermes in the loop.
226
+ - **Wrap the alert send in try/catch** — a Telegram failure must never fail the guard cycle
227
+ (same non-blocking rule as the ledger writes).
228
+ - **Sign-before-`$` formatting:** a negative PnL rendered as `${pnl>=0?'+':''}$${pnl}` prints the
229
+ ugly `$-10.00`. Use `const sign = pnl>=0?'+':'-'` then `${sign}$${Math.abs(pnl).toFixed(2)}` →
230
+ `-$10.00`. Caught this only because the end-to-end test asserted the exact rendered string —
231
+ format bugs are invisible until you assert the literal output.
232
+ - The notifier auto-silences on staging (`STAGING==='1'` chokepoint), so a shadow deploy won't spam.
233
+
234
+ ## When a new venue "won't fire" — the SHARED-GUARD cross-venue mismatch (the "shouldn't it be trading like my main wallet?" ask)
235
+ After wiring a new venue (HIP-4) into the shared loop, DEMI will ask "shouldn't it
236
+ be firing?" and the honest answer is often **it's a real bug, not "waiting"** — the
237
+ new venue's opportunities are being BLOCKED by the shared safety guard applying the
238
+ WRONG venue's rules. Diagnose from the live log FIRST, then fix venue-aware:
239
+ - **Read the block reasons before theorizing.** `pm2 logs demi-server --lines 300
240
+ --nostream | grep -iE 'guard blocked <venue>|<strat>.*skip'` then pipe through
241
+ `sed`/`sort`/`uniq -c` to rank reasons. Separate the CORRECT skips (strategy's own
242
+ rules — e.g. HIP-4's `already holding a side of outcome NNN (one position per
243
+ outcome)`, or `not opted in`) from the WRONG blocks (the shared PM guard).
244
+ - **The mismatch class: the shared `safety-guard.js checkTrade()` judges the new
245
+ venue by Polymarket's bankroll + market rules.** HIP-4 opps carry
246
+ `sport:'soccer'` + `liquidity:$8-14` (real HL book depth) and get slammed by FIVE
247
+ PM-specific gates that measure the wrong thing: (1) position-size cap sized off PM
248
+ equity (`Position $10.5 > max $1.47` — tiny + fluctuating because the PM portfolio
249
+ read is near-empty / stale from a dead RPC), (2) total-exposure %, (3) 20% cash
250
+ reserve, (4) the sport whitelist (`mlb,nba,nhl,ufc,esports` — soccer isn't in it,
251
+ so `Sport not whitelisted: 'soccer'` blocks EVERY WC outcome even after fixing
252
+ size/liquidity), (5) the `soccer:2000` dead-market liquidity floor (thin HIP-4
253
+ books can't clear it, and the `clobDepth>=size*1.5` bypass fails on $8 depth).
254
+ - **Fix = make the guard venue-aware; bypass ONLY the PM-portfolio/PM-market gates
255
+ for `opportunity.venue==='hyperliquid'`.** Define `const isHyperliquid =
256
+ opportunity.venue === 'hyperliquid'` once at the top of the size-cap block, then
257
+ wrap each of the 5 gates with `if (!isHyperliquid && <original condition>)`. The
258
+ new venue is already gated correctly by its OWN `execute()`-time checks against
259
+ ITS bankroll (per-user stake, `usdcFree>=stake`, live `ask*askSz>=stake` depth,
260
+ `PHANTOM_CAP` edge ceiling, fresh-edge recheck, per-outcome maxPositions) — the PM
261
+ floor is redundant AND wrong. **KEEP the account-level, venue-agnostic gates
262
+ applied to both** (halt, daily-loss, hourly/daily rate limits,
263
+ `LIVE_TRADING_CONFIRMED` file). Don't blanket-skip the whole guard.
264
+ - **This is loosening a live safety gate → circuit-breaker discipline applies.**
265
+ GO-gate it. But note it's LOW-risk to ship direct once you've proven the strategy's
266
+ own depth/phantom/fresh-edge gates are STRICTER than the PM floor being removed —
267
+ the bypass can only let a HIP-4 opp reach ITS OWN gates, not skip them.
268
+ - **The counterintuitive answer to "will it trade LESS?": no, MORE.** Removing the
269
+ wrong blocks UNBLOCKS firing. What still (correctly) caps volume is structural and
270
+ stays: one-position-per-outcome + hold-to-resolution. So the venue is episodic by
271
+ design — it fires on a NEW un-held divergence or when a held position closes, not
272
+ high-frequency. If every current opp maps to an already-held outcome, "not firing
273
+ right now" is the per-outcome rule working, NOT a block.
274
+ - **VERIFY the guard change with a unit test against the REAL safety-guard module:**
275
+ a HL opp must PASS all 5 gates; a PM opp with the same params must STILL be blocked
276
+ by each (proves you didn't punch a hole in the Polymarket path). Assert the exact
277
+ block reasons (`Position $X > max`, `Sport not whitelisted`, `Illiquid`). Then
278
+ restart and confirm the `guard blocked <venue>` lines are GONE from the live log.
279
+ - **Ground-truth the pilot state before declaring "working":** pull `getSpotState`
280
+ for the pilot — free USDC (has capital?), held outcome coins (`+N` balances), and
281
+ compare held-outcome-IDs vs the live outcome universe (`outcomeMeta`). That tells
282
+ you definitively whether "not firing" = blocked (bug) vs = every opp is an
283
+ already-held outcome (correct) vs = no un-held divergence exists right now.
284
+ Full worked recipe (the 5 gates + exact wrap edits, the venue-tag routing, the
285
+ real-module PM-still-blocked unit test, the pilot ground-truth probe):
286
+ `references/venue-aware-safety-guard.md`.
287
+
288
+ ## PR hygiene when another agent is active on the same trading repo
289
+ When a live trading PR was assembled while another agent pushed to `origin/main`, treat
290
+ "mergeable" as necessary but not sufficient. Clean the PR branch before asking DEMI or
291
+ the peer agent to review it:
292
+ - Strip accidental backup/scratch files from broad `git add -A` commits (`*.bak*`,
293
+ timestamped recovery files, unreferenced analysis scripts, generated build dirt).
294
+ - Fix CRLF/LF phantom rewrites before push: restore the file from `origin/main`, reapply
295
+ only the real code changes, and verify the **normal** diff is reviewable (not just
296
+ `git diff -w`). A `server/api.js` `+4855/-4800` phantom diff hid a real `+57/-2` endpoint
297
+ change — do not leave that for reviewers.
298
+ - Re-run syntax, focused money-path probes, and frontend build on the CLEANED branch;
299
+ reset build artifacts after the build.
300
+ - Push a PR branch and comment with exactly what hygiene changed + verification results;
301
+ don't push directly to `main` while a peer agent is live unless DEMI explicitly chooses it.
302
+ Full cleanup recipe and HIP-4 PR #222 example: `references/pr-hygiene-active-peer-merge.md`.
303
+
304
+ ## Deploying a strategy-file change to the LIVE bot (pm2, not deploy.sh)
305
+ - Prod runs under **pm2 `demi-server`** (not the docker `deploy.sh`). A strategy/API change needs
306
+ a `pm2 restart demi-server --update-env` to load; a dashboard change needs `npm run build`
307
+ (rebuilds `dist/`, no restart). The restart briefly interrupts the sports loop (~seconds; it
308
+ resumes on boot — routine, dozens of prior restarts) and reloads code from disk.
309
+ - **Staging is a SEPARATE checkout** (`/root/polymarket-arbitrage-bot-staging`, `SHADOW_GATES=1`,
310
+ NO signing keys → physically cannot move money) and can lag prod by many commits — it may not even
311
+ have the venue code yet. To shadow-test on staging, `git fetch <prod-path> main` + checkout the
312
+ feature branch there first. A shadow gate test can prove new gate LOGIC fires (opted-in wallet
313
+ passes, non-opted skips) even with no keys — the execute() path stops at the creds check, which is
314
+ exactly the "past the gate" proof you want.
315
+ - **CRLF footgun:** the repo's JS files are CRLF; a python string-writer normalizes to LF and git
316
+ then shows EVERY line changed (a 15-line edit -> 9600-line phantom diff). Detect with
317
+ `git diff -w --stat` (real change size) and restore endings with a python
318
+ `.replace(b'\r\n',b'\n').replace(b'\n',b'\r\n')` pass before committing, so `git blame`/review stay clean.
319
+ - **REMOTE-PATCH MECHANICS — prefer pull-local → fuzzy `patch` tool → scp-back over remote string-writers.**
320
+ Editing VPS files by `scp`ing a python string-writer script (or worse, a `ssh ... python3 << 'PYEOF'`
321
+ heredoc) is brittle and burned two attempts this session: (1) a `raw.replace()` anchor silently
322
+ no-ops when the on-disk text differs by a whitespace/CRLF hair from your assumed string, and the
323
+ script's own `assert count==1` then throws AFTER a partial edit — leaving you unsure what changed;
324
+ (2) template-literal backticks + `${...}` + emoji `\u` escapes inside a remote heredoc get mangled
325
+ by the shell before python ever sees them (`unexpected EOF while looking for matching quote`). The
326
+ reliable path: `scp demi-poly:<path> /tmp/f.local.js`, apply the `patch` tool (fuzzy match tolerates
327
+ minor whitespace drift, auto-lints, returns a real diff), then `scp` it back and `node -c` on the VPS.
328
+ Idempotency-guard any string-writer you DO keep with an early `if MARKER in raw: skip` so a re-run
329
+ can't double-apply. And when a unit test embeds a REPLICA of the code under test (an inline
330
+ `buildAlert`), fixing the real file leaves the replica stale → the test fails on format you already
331
+ fixed; point the test at the real module, or assert against the deployed source text, so it can't drift.
332
+ - **Protect uncommitted prod hotfixes:** prod's working tree often carries live-edited tracked files
333
+ not on any branch (e.g. `gpu-client.js`, `trading-loop.js`, `loop-manager.js`). Snapshot them
334
+ before any branch op, stage ONLY your files, and prefer `git branch -f main <feature>` (ref update,
335
+ worktree untouched) over a checkout that could clobber them.
336
+ - **Verify the deploy from the LIVE log, not the restart exit code:** after restart, grep
337
+ `demi-server-out.log` for the NEW gate message (e.g. the per-user skip reason) to prove the new
338
+ code path is the one running — a green pm2 status only means the process booted.
339
+ - **MERGING when ANOTHER AGENT is active in the same repo (the "the other agent already pushed to
340
+ main" situation).** DEMI runs a dual-machine + multi-agent setup, so `origin/main` can move under
341
+ you mid-session. Deploy discipline here shipped by moving the LOCAL `main` ref (`git branch -f`) on
342
+ the prod worktree WITHOUT pushing — so local `main` and `origin/main` DIVERGE (local ahead with your
343
+ N commits, origin ahead with the peer's push). Reconcile safely:
344
+ 1. **`git fetch origin` and read the divergence BEFORE touching anything:** `git rev-list
345
+ --left-right --count origin/main...main` (are you ahead/behind/both?), `git diff --name-only main
346
+ origin/main` (conflict surface — overlapping files = real risk, not a clean FF). NEVER force-push
347
+ over a branch a peer just pushed to.
348
+ 2. **Merge in an ISOLATED `git worktree`, never the running prod checkout:** `git worktree add -f
349
+ /root/_merge <yourtip>` then `git merge origin/main` there. Prod's checkout + its uncommitted
350
+ hotfixes stay untouched. Symlink `node_modules` from prod so the build works.
351
+ 3. **"Clean auto-merge" ≠ correct — re-verify on the MERGED tree.** git's ort strategy resolves
352
+ non-overlapping regions of the same file silently; that's conflict-free, not proven-correct. Re-run
353
+ the money-path unit tests (venue-guard: HL passes / PM still blocked), rebuild the frontend, and
354
+ `node -c` the money-path files ON the merged worktree before trusting it.
355
+ 4. **Confirm it's a true fast-forward (peer's work preserved as a parent, no history rewrite):**
356
+ `git merge-base --is-ancestor origin/main <mergetip>` → if origin/main is an ancestor, the push is
357
+ a clean FF and the peer's commit survives as a merge parent. If NOT an ancestor, you'd need a force
358
+ — STOP and reconsider.
359
+ 5. **DEMI's preference under an active peer: PR, don't push straight to main.** Even when the git is a
360
+ clean FF and everything's verified, when a SECOND agent is actively working the repo DEMI chose
361
+ "open a PR so the other agent can review before it lands" over "push to main now." Push your merge
362
+ to a feature branch, open the PR into main (call out the commits that OVERLAP the peer's domain in
363
+ the PR body), and leave the merge to human/peer approval. Rationale: force-landing onto a branch a
364
+ peer just touched clobbers their mental model even when the bytes reconcile. Offer the choice
365
+ (PR / push-direct / hold) rather than auto-deciding — but lead with PR when a peer is live.
366
+ 6. **DEMI-DELIVERY / TELEGRAM: send a real message to prove a claim, don't just format it.** When the
367
+ deliverable is "the bot texts him," the not-yet-verified piece is DELIVERY, not formatting. Construct
368
+ the actual Notifier and do ONE real labeled `sendTelegram` test (assert `ok===true`) — a unit test
369
+ of the string proves layout, only a live send proves the channel works. (`dotenv`/`node_modules`
370
+ resolve from the REPO dir, so run the probe from inside the repo, not `/tmp`.)
371
+
372
+ ## Diagnosing "why isn't the bot trading?" — DON'T THEORIZE, QUERY GROUND TRUTH (the five-wrong-turns lesson)
373
+ When DEMI asks "why haven't we been trading" / "shouldn't it be on" / "did the money go
374
+ back to X", the failure mode is answering from CONFIG or INFERENCE and being wrong. One
375
+ session produced FIVE consecutive wrong conclusions on a live-money bot, each corrected only
376
+ by pulling actual live state: (halted→wrong) → (capital-starved→wrong, wallet had funds) →
377
+ (zombie loop→wrong, loop had 16k scans) → (session-state stopped→wrong, marked running) →
378
+ (finally: the strategy evaluates the WRONG wallet). Every wrong turn was a guess from a file;
379
+ every correction came from a live query. The rule: **for any "why isn't it doing X" on the
380
+ bot, the FIRST move is a ground-truth pull, not a hypothesis.** State confidence honestly and
381
+ walk back cleanly when the data contradicts you — do NOT defend a prior guess.
382
+
383
+ The ground-truth checklist, run these BEFORE proposing any cause:
384
+ - **Live venue balance, not the config or the vault claim.** HL spot USDC via the HL info API
385
+ (`spotClearinghouseState` for spot USDC + held `+N` outcome coins; `clearinghouseState` for
386
+ perps/withdrawable). Polymarket via the proxy wallet on-chain. The vault's P0 line and the
387
+ bot's cached portfolio can BOTH be a month stale — I asserted "capital starved" when the
388
+ wallet had $173 free because I inferred drawdown from losing trades instead of querying.
389
+ - **Which wallet is the strategy actually evaluating?** `pm2 logs demi-server --lines 300
390
+ --nostream | grep -iE '<strat>' | grep -oE 'wallet 0x[a-f0-9]{6}' | sort | uniq -c`. The
391
+ loop scans opps globally then each running wallet-loop tries to EXECUTE them with its OWN
392
+ address. If the wallet with the key/funds/allowlist entry isn't in that list, it's never
393
+ evaluated — and the OTHER wallets correctly skip ("not opted in and not a pilot"). This was
394
+ the actual root cause and it was invisible until I counted wallets in the log.
395
+ - **Is the loop even instantiated for that wallet?** `data/user-session-state.json`: a wallet
396
+ needs `startedAt && !stoppedAt` to auto-resume on restart. But "marked running" ≠ "actually
397
+ scanning" — cross-check against real scan activity in the log. `POST /api/bot/start` (auth via
398
+ a minted `lo_` API key, see below) reinstantiates a dead loop; its response reveals `scanCount`
399
+ + `lastScanAt` which tells you if the loop was live all along.
400
+ - **What is it skipping on, RIGHT NOW?** The skip lines are the answer, read them literally:
401
+ `ask depth $8.00 < stake $10.5` (thin book, untradeable at min stake — NOT a bug),
402
+ `already holding a side of outcome NNN` (per-outcome cap working), `edgeThreshold 0.03` floor
403
+ (a 2.2% edge is below the gate, correct). Most "it's dead" is the safety gates + thin liquidity
404
+ working as designed, not a fault. Distinguish "correctly quiet" from "wrongly blocked."
405
+
406
+ Minting an API key to hit an authed bot endpoint from the VPS (no browser): keys are stored as
407
+ SHA-256 hashes in `data/api-keys.json` so you can't recover one, but `lib/api-auth.js`'s
408
+ `generateKey(wallet)` mints a fresh one (auto-revokes the wallet's prior key, clean + reversible):
409
+ `KEY=$(node -e "console.log(require('./lib/api-auth').generateKey('0xWALLET'))")` then
410
+ `curl -s -X POST http://localhost:<port>/api/bot/start -H "X-API-Key: $KEY"`. Note the `/api`
411
+ prefix (bare `/bot/start` 404s). Get the port from `ss -tlnp | grep node`, not a guess.
412
+
413
+ The "money moved from HL back to Polygon" class of question is usually a FALSE PREMISE: HL spot
414
+ USDC and Polymarket Polygon USDC are SEPARATE pools that never auto-transfer. "It went back" is
415
+ almost always "the HL pile was traded down / is separate and untouched," not a sweep. Pull both
416
+ balances and the HL trade ledger (`portfolio-hip4.json`) before accepting the premise.
417
+
418
+ Fix "always be trading" the RIGHT way: it's rarely a flag flip. If capital + key + allowlist are
419
+ present and it's still quiet, the cause is structural (wrong-wallet routing, thin books, per-outcome
420
+ cap, edge floor) — and "loosen the gates to force fills" makes it trade WORSE (slippage into $2-8
421
+ books = the exact losing pattern). Recommend the surgical routing/visibility fix, not gate-loosening,
422
+ and GO-gate anything that re-arms live execution. Full worked investigation (the five wrong turns,
423
+ every query that corrected them, the HL API calls, the wallet-routing root cause):
424
+ `references/why-isnt-the-bot-trading.md`.
425
+
426
+ ## Building a public scanner + wallet/CEX execution product
427
+
428
+ A scanner dashboard that can buy, sell, place exits, or delegate authority to agents is a NEW live-money loop, even when its default mode is paper. Load and follow `references/agentic-scanner-execution-gate.md` before implementation. It covers canonical onchain discovery vs promoted feeds, Rabby as signer rather than router, unsupported honeypot-chain semantics, fail-closed slippage, authenticated preview binding, real kill switches, honest exit-policy states, persistent shadow-agent rollout, third-party chart embed boundaries, draggable TP/SL architecture, and DEMI's required Fable → Sol → Sonnet → Opus build/review sequence.
429
+
430
+ **UI truthfulness gate for Quick Buy, APE, TP, and SL:** Quick Buy and APE may only select a market and preset size; they do not bypass quote freshness, route verification, round-trip/sell simulation, policy limits, or wallet confirmation. An APE button is an aggressive preset, not permission for blind broadcast. TP/SL controls may be called active or automated only after a durable background executor has been verified to monitor prices and submit protected exits. If the backend only stores policies, label the UI and API response `DRAFT`, return `executable:false`, and state that every sell still needs a fresh quote, simulation, and wallet signature. A polished control that implies nonexistent automation is a money-path defect.
431
+
432
+ When the product moves from scanner/paper mode to public self-custodial wallet swaps or NFT mints, also load `references/public-self-custodial-scanner-execution.md`. It defines public-wallet vs operator/admin authority, wallet-connect prominence, the honest execution ladder, verified-ABI mint simulation, unsupported-chain coverage labels, action-first trading UI hierarchy, and the mandatory post-integration security gate covering process-isolated signers, principal-relative round-trip loss, enforceable mint freshness or simulation-only fallback, per-user watchlist isolation, complete-vs-partial portfolio valuation, and fail-closed data labels.
433
+
434
+ ## Pitfalls
435
+ - Treating wallet signature verification as account authorization → any stranger can create a wallet and become an operator; bind signed addresses to a server-owned operator allowlist/enrollment and separate admin authority.
436
+ - Treating `CONTROLS_READY=1` or `LIVE_ENABLED=0` as a security boundary → flags do not implement holdings/PnL/exposure controls and can be flipped; compile live submission out of incomplete preview builds.
437
+ - Treating a halt as a bug to work around → it's a capital-protection feature. Never bypass.
438
+ - Answering "why isn't it trading" from config/vault/inference instead of a live ground-truth pull → you WILL be wrong; five times in one session. Query first, theorize never.
439
+ - Resetting without reconciling → you can resume into a wrong-state position.
440
+ - Editing the live loop without a backup + revert command in hand → violates the core invariant.
441
+ - Assuming pm2 "online" means healthy → check heartbeat + recent trade/reconcile logs.
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: venue-capability-boundaries
3
+ description: Check what each venue supports before routing user requests.
4
+ category: trading
5
+ ---
6
+
7
+ > Oracle native tools: `oracle_cli` (read/prepare), `vault_status`, `signer_status`, `signer_execute` (needs human confirmationNonce), `skill_load`. No generic shell. No fleet SSH.
8
+
9
+
10
+ # Venue capability boundaries
11
+
12
+ Users ask for things venues don't support. Cross-reference before routing.
13
+
14
+ ## Hyperliquid HIP-4
15
+
16
+ BTC daily binaries only (2026-08). No geopolitical/event markets on mainnet.
17
+ → Event bets → Polymarket, not HL.
18
+
19
+ ## Hyperliquid HIP-3 builder dexs
20
+
21
+ Perps only. No spot equities. `xyz:NVDA` is a perp.
22
+ → "spot NVDA on TradeXYZ" must clarify: HIP-3 has no spot.
23
+
24
+ ## TradeXYZ (xyz)
25
+
26
+ 96+ assets on HL, all perps: `xyz:NVDA`, `xyz:TSLA`, `xyz:AAPL`, `xyz:SPX`.
27
+
28
+ ## Oracle fees
29
+
30
+ Swaps: 10 bps. Bridges: +10 bps. HL builder codes: cap 10 bps perp / 1% spot.
31
+
32
+ Live HL builder dex data + HIP-4 docs: `references/hip4-and-builder-limits.md`.