@oracle-agent/oracle 0.1.0
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.
- package/CONTRIBUTING.md +98 -0
- package/LICENSE +202 -0
- package/README.md +384 -0
- package/SECURITY.md +89 -0
- package/SETUP.md +235 -0
- package/artifacts/inscription/oracle-was-here-preview.png +0 -0
- package/artifacts/inscription/oracle-was-here.svg +1 -0
- package/artifacts/specialist-packs/oracle-full-crypto.json +273 -0
- package/bin/desk-server.mjs +438 -0
- package/bin/oracle-data-mcp.mjs +603 -0
- package/bin/oracle-init.mjs +423 -0
- package/bin/oracle-public-server.mjs +36 -0
- package/bin/oracle-route.mjs +254 -0
- package/bin/oracle-scan.mjs +192 -0
- package/docs/adding-a-chain.md +229 -0
- package/docs/architecture.md +135 -0
- package/docs/profiles.md +132 -0
- package/examples/add-a-chain.mjs +65 -0
- package/examples/research-a-token.mjs +70 -0
- package/package.json +90 -0
- package/profiles/_template/SOUL.md +47 -0
- package/profiles/_template/profile.json +22 -0
- package/profiles/bitcoin-agent/SOUL.md +31 -0
- package/profiles/bitcoin-agent/profile.json +32 -0
- package/profiles/hyperliquid-agent/SOUL.md +34 -0
- package/profiles/hyperliquid-agent/profile.json +37 -0
- package/profiles/oracle/SOUL.md +65 -0
- package/profiles/oracle/profile.json +35 -0
- package/profiles/polymarket-agent/SOUL.md +35 -0
- package/profiles/polymarket-agent/profile.json +34 -0
- package/profiles/profile.schema.json +90 -0
- package/profiles/protocol-builder/SOUL.md +50 -0
- package/profiles/protocol-builder/profile.json +37 -0
- package/profiles/robinhood-agent/SOUL.md +45 -0
- package/profiles/robinhood-agent/profile.json +39 -0
- package/profiles/solana-agent/SOUL.md +37 -0
- package/profiles/solana-agent/profile.json +37 -0
- package/profiles/stable-agent/SOUL.md +43 -0
- package/profiles/stable-agent/profile.json +37 -0
- package/public/oracle-console/app.js +272 -0
- package/public/oracle-console/bitcoin-wallets.js +206 -0
- package/public/oracle-console/index.html +91 -0
- package/public/oracle-console/styles.css +239 -0
- package/public/oracle-splash/index.html +931 -0
- package/scripts/build-inscription.py +230 -0
- package/scripts/check-test-count.mjs +105 -0
- package/scripts/e2e-hl-markets.mjs +21 -0
- package/scripts/e2e-hl-perps.mjs +48 -0
- package/scripts/e2e-hypercore-staking.mjs +128 -0
- package/scripts/e2e-solana-bitcoin.mjs +183 -0
- package/scripts/public-api-scan.mjs +23 -0
- package/scripts/secret-scan.mjs +181 -0
- package/scripts/verify-v3-venues.mjs +192 -0
- package/skills/oracle-best-execution/SKILL.md +127 -0
- package/skills/oracle-bitcoin/SKILL.md +53 -0
- package/skills/oracle-chain-graphs-telegram-cards/SKILL.md +59 -0
- package/skills/oracle-circuit-breaker/SKILL.md +51 -0
- package/skills/oracle-contract-research/SKILL.md +55 -0
- package/skills/oracle-desk/SKILL.md +58 -0
- package/skills/oracle-dex-launch/SKILL.md +38 -0
- package/skills/oracle-grants/SKILL.md +69 -0
- package/skills/oracle-hypercore-staking/SKILL.md +57 -0
- package/skills/oracle-hyperliquid/SKILL.md +56 -0
- package/skills/oracle-meme-token-sniper/SKILL.md +73 -0
- package/skills/oracle-nft-gacha-launch/SKILL.md +48 -0
- package/skills/oracle-nft-mint-gas-war/SKILL.md +63 -0
- package/skills/oracle-polymarket/SKILL.md +60 -0
- package/skills/oracle-protocol-builder/SKILL.md +38 -0
- package/skills/oracle-protocol-security/SKILL.md +60 -0
- package/skills/oracle-public-product/SKILL.md +44 -0
- package/skills/oracle-receipts/SKILL.md +52 -0
- package/skills/oracle-rfq-tokenized-assets/SKILL.md +69 -0
- package/skills/oracle-smart-wallet-scanner/SKILL.md +49 -0
- package/skills/oracle-solana/SKILL.md +65 -0
- package/skills/oracle-solana-nft/SKILL.md +54 -0
- package/skills/oracle-token-research/SKILL.md +67 -0
- package/src/agent-auth.mjs +191 -0
- package/src/approval-guard.mjs +282 -0
- package/src/attestation-secret.mjs +88 -0
- package/src/audit-log.mjs +196 -0
- package/src/auto-slippage.mjs +378 -0
- package/src/capability-posture.mjs +125 -0
- package/src/chains.mjs +62 -0
- package/src/data/catalog.mjs +495 -0
- package/src/data/desk-data.mjs +623 -0
- package/src/data/http.mjs +200 -0
- package/src/data/provider-endpoint.mjs +94 -0
- package/src/data/providers/aerodrome.mjs +244 -0
- package/src/data/providers/balancer.mjs +208 -0
- package/src/data/providers/bitcoin-esplora.mjs +230 -0
- package/src/data/providers/bitcoin-meta.mjs +378 -0
- package/src/data/providers/blockscout.mjs +14 -0
- package/src/data/providers/bridges.mjs +241 -0
- package/src/data/providers/cowswap.mjs +501 -0
- package/src/data/providers/curve.mjs +200 -0
- package/src/data/providers/defillama.mjs +88 -0
- package/src/data/providers/dexscreener.mjs +43 -0
- package/src/data/providers/evm-rpc.mjs +203 -0
- package/src/data/providers/geckoterminal.mjs +34 -0
- package/src/data/providers/gmx.mjs +495 -0
- package/src/data/providers/hl-info.mjs +102 -0
- package/src/data/providers/hl-markets.mjs +210 -0
- package/src/data/providers/hl-perps.mjs +382 -0
- package/src/data/providers/hl-staking.mjs +352 -0
- package/src/data/providers/hl-ws.mjs +119 -0
- package/src/data/providers/hyperevm-dex.mjs +49 -0
- package/src/data/providers/jupiter.mjs +182 -0
- package/src/data/providers/lifi.mjs +150 -0
- package/src/data/providers/magiceden-sol.mjs +355 -0
- package/src/data/providers/morpho.mjs +173 -0
- package/src/data/providers/odos.mjs +155 -0
- package/src/data/providers/oneinch.mjs +173 -0
- package/src/data/providers/opensea-multichain.mjs +136 -0
- package/src/data/providers/opensea-nft.mjs +99 -0
- package/src/data/providers/paraswap.mjs +117 -0
- package/src/data/providers/pendle.mjs +187 -0
- package/src/data/providers/poly-public.mjs +96 -0
- package/src/data/providers/poly-ws.mjs +103 -0
- package/src/data/providers/rh-agent.mjs +59 -0
- package/src/data/providers/satflow.mjs +336 -0
- package/src/data/providers/solana-rpc.mjs +186 -0
- package/src/data/providers/uniswap-v3.mjs +303 -0
- package/src/data/providers/zerox.mjs +166 -0
- package/src/data/public-api-scan.mjs +61 -0
- package/src/data/quote-placeholder.mjs +31 -0
- package/src/exact-integer.mjs +72 -0
- package/src/exec-policy.mjs +444 -0
- package/src/flags.mjs +15 -0
- package/src/fresh-window.mjs +76 -0
- package/src/gmx-attestation.mjs +175 -0
- package/src/index.mjs +50 -0
- package/src/nft-gas-war-guard.mjs +139 -0
- package/src/onboarding/agent-keys.mjs +157 -0
- package/src/onboarding/index.mjs +18 -0
- package/src/onboarding/tiers.mjs +139 -0
- package/src/oracle-env.mjs +38 -0
- package/src/protocol-execution.mjs +84 -0
- package/src/public-api/buzz-integration.mjs +256 -0
- package/src/public-api/connect-agent.mjs +397 -0
- package/src/public-api/grants.mjs +142 -0
- package/src/public-api/http.mjs +374 -0
- package/src/public-control/aa-adapter.mjs +402 -0
- package/src/public-control/build-registry.mjs +227 -0
- package/src/public-control/bundler-client.mjs +372 -0
- package/src/public-control/grant-indexer.mjs +296 -0
- package/src/public-control/policy-render.mjs +69 -0
- package/src/public-control/policy-schema.mjs +318 -0
- package/src/public-control/runtime-config.mjs +265 -0
- package/src/public-control/session-key-model.mjs +374 -0
- package/src/public-control/session-orchestrator.mjs +412 -0
- package/src/route-attestation.mjs +132 -0
- package/src/router/best-execution.mjs +221 -0
- package/src/router/index.mjs +185 -0
- package/src/router/prepare-bridge.mjs +288 -0
- package/src/router/prepare-route.mjs +341 -0
- package/src/router/proposal.mjs +311 -0
- package/src/router/risk-classifier.mjs +119 -0
- package/src/router/route-sources.mjs +292 -0
- package/src/scanner/chains.config.mjs +381 -0
- package/src/scanner/contract.mjs +270 -0
- package/src/scanner/evm-scanner.mjs +394 -0
- package/src/scanner/index.mjs +9 -0
- package/src/scanner/v2-venue.mjs +335 -0
- package/src/scanner/v3-venue.mjs +290 -0
- package/src/scopes.mjs +44 -0
- package/src/sell-simulation.mjs +167 -0
- package/src/token-transfer-guard.mjs +188 -0
- package/src/vault-attestation.mjs +145 -0
- package/src/venues.mjs +206 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
Three planes. The boundary between them is enforced by a test, not a convention.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
user's wallet
|
|
7
|
+
^
|
|
8
|
+
| signs (only the user can)
|
|
9
|
+
|
|
|
10
|
+
+---------------------+---------------------+
|
|
11
|
+
| unsigned artifacts |
|
|
12
|
+
| |
|
|
13
|
+
| PUBLIC (this repo) |
|
|
14
|
+
| |
|
|
15
|
+
| data plane read / quote |
|
|
16
|
+
| policy plane allowlists, guards, |
|
|
17
|
+
| attestations, grants |
|
|
18
|
+
| control plane grant lifecycle, |
|
|
19
|
+
| session keys, AA, router |
|
|
20
|
+
+-------------------------------------------+
|
|
21
|
+
|
|
|
22
|
+
x no import may cross
|
|
23
|
+
|
|
|
24
|
+
+-------------------------------------------+
|
|
25
|
+
| PRIVATE (not published) |
|
|
26
|
+
| keystore, house signer, exec-server |
|
|
27
|
+
+-------------------------------------------+
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Why three planes
|
|
31
|
+
|
|
32
|
+
The design assumption is that **the model can be wrong or adversarially steered**.
|
|
33
|
+
Everything follows from treating it as an untrusted proposer.
|
|
34
|
+
|
|
35
|
+
If the model is untrusted, then authorization cannot come from the model. It comes
|
|
36
|
+
from an owner signature over a bounded grant. And if authorization is separate from
|
|
37
|
+
proposal, then the code that proposes must be unable to reach the code that signs —
|
|
38
|
+
otherwise the separation is aspirational.
|
|
39
|
+
|
|
40
|
+
That is why the boundary is a test (`test/custody-boundary.test.mjs`) rather than a
|
|
41
|
+
paragraph in a README. It walks the import graph from every shipped entrypoint and
|
|
42
|
+
fails on:
|
|
43
|
+
|
|
44
|
+
- a private module present in the tree
|
|
45
|
+
- any public module importing signer or key material
|
|
46
|
+
- a dangling local import (extraction left something behind)
|
|
47
|
+
- a secret-shaped literal anywhere
|
|
48
|
+
|
|
49
|
+
## Data plane
|
|
50
|
+
|
|
51
|
+
`src/data/` — 30+ providers, each declaring an honest tier.
|
|
52
|
+
|
|
53
|
+
| Tier | Meaning |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `read-only` | data only; no execution claim |
|
|
56
|
+
| `quote-only` | can price a route; no reviewed transaction builder |
|
|
57
|
+
| `prepare` | returns a policy-bound unsigned transaction |
|
|
58
|
+
| `intent` | returns typed data to sign (CoW orders, GMX orders) |
|
|
59
|
+
|
|
60
|
+
The tier is load-bearing. "We support protocol X" is ambiguous and has caused real
|
|
61
|
+
confusion; the tier is not. **API coverage ≠ execution support.**
|
|
62
|
+
|
|
63
|
+
`src/data/catalog.mjs` is the registry; `dataHealth()` probes liveness. Note the
|
|
64
|
+
distinction in health output: a promise that resolved with `{ok: false}` is
|
|
65
|
+
*degraded*, not healthy — and an HTTP 200 serving an explorer error page is not a
|
|
66
|
+
successful read.
|
|
67
|
+
|
|
68
|
+
## Policy plane
|
|
69
|
+
|
|
70
|
+
The moat. Holds no keys; constrains what a signer may be asked to do.
|
|
71
|
+
|
|
72
|
+
- **Destination allowlist** (`src/venues.mjs`) — per chain, fail-closed. An empty
|
|
73
|
+
allowlist refuses everything. It never means "allow anything."
|
|
74
|
+
- **Auto-slippage** (`src/auto-slippage.mjs`) — a guard computed from live depth and
|
|
75
|
+
volatility per leg, recomputed before each broadcast. A caller-supplied tolerance
|
|
76
|
+
is a *maximum*, not the selected value. Hard ceiling 100 bps; over that, block and
|
|
77
|
+
requote rather than widen.
|
|
78
|
+
- **Attestations** (`route-`, `vault-`, `gmx-attestation.mjs`) — dynamic execution
|
|
79
|
+
targets require an attestation minted inside the trusted boundary. Model-authored
|
|
80
|
+
JSON is not authorization.
|
|
81
|
+
- **Approval guard** — exact-amount approvals, never unlimited by default, with
|
|
82
|
+
allowance read back after the approve lands.
|
|
83
|
+
|
|
84
|
+
Guards are re-checked at **sign and broadcast** time. A stale minimum is not a
|
|
85
|
+
minimum.
|
|
86
|
+
|
|
87
|
+
## Control plane
|
|
88
|
+
|
|
89
|
+
`src/public-control/` — grant schema and rendering, session-key model, ERC-4337
|
|
90
|
+
adapter, bundler client, grant indexer.
|
|
91
|
+
|
|
92
|
+
`src/router/` — model routing with a custody firewall. `risk-classifier.mjs` scores
|
|
93
|
+
an action's risk; `proposal.mjs` refuses to construct a proposal containing
|
|
94
|
+
dangerous fields (private keys, mnemonics, bearer tokens) at any nesting depth. The
|
|
95
|
+
router can propose, simulate, explain, and draft. It cannot authorize.
|
|
96
|
+
|
|
97
|
+
## Scanner framework
|
|
98
|
+
|
|
99
|
+
`src/scanner/` — makes a chain data rather than an integration.
|
|
100
|
+
|
|
101
|
+
- `contract.mjs` — 10 capabilities, a validator, a registry, coverage matrix
|
|
102
|
+
- `evm-scanner.mjs` — one generic implementation for any EVM JSON-RPC chain
|
|
103
|
+
- `chains.config.mjs` — the 11 built-ins as config, plus `registerCustomChain()`
|
|
104
|
+
|
|
105
|
+
Unimplemented capabilities are **absent, not faked**: calling one throws a message
|
|
106
|
+
naming what *is* supported. A caller can never mistake `undefined` for a negative
|
|
107
|
+
result.
|
|
108
|
+
|
|
109
|
+
Evidence labels (`LIVE` / `CACHED` / `STALE` / `UNKNOWN` / `UNAVAILABLE`) and risk
|
|
110
|
+
verdicts (`PASS` / `CAUTION` / `FAIL` / `UNKNOWN`) exist to stop the collapse that
|
|
111
|
+
causes losses: **`UNKNOWN` is not `PASS`**, and "we could not check" is a different
|
|
112
|
+
fact from "there is nothing there."
|
|
113
|
+
|
|
114
|
+
## Agent mesh
|
|
115
|
+
|
|
116
|
+
`profiles/` + `skills/` — seven installable Hermes lanes plus a template.
|
|
117
|
+
|
|
118
|
+
A lane is narrow on purpose: smaller context is cheaper and more accurate, per-lane
|
|
119
|
+
memory doesn't cross-contaminate, and a grant scoped to one lane can't be spent by
|
|
120
|
+
another.
|
|
121
|
+
|
|
122
|
+
Every lane ships DISARMED, and every grant action a lane may request is read,
|
|
123
|
+
simulate, or prepare. No lane may request broadcast or signing — enforced in
|
|
124
|
+
`test/profiles.test.mjs`, so widening custody cannot pass review quietly.
|
|
125
|
+
|
|
126
|
+
No lane pins a model. `profile.json` carries a capability *class*
|
|
127
|
+
(`strong-reasoner`, `fast-tool-caller`), so the installer never writes a vendor into
|
|
128
|
+
a user's config.
|
|
129
|
+
|
|
130
|
+
## What is not here
|
|
131
|
+
|
|
132
|
+
The exec plane: keystore, house signer, local-signer service, exec-server,
|
|
133
|
+
capability minting, and the venue adapters that hold signing authority. That is
|
|
134
|
+
operator infrastructure. Its absence is the point — a self-hosted Oracle has nothing
|
|
135
|
+
to steal.
|
package/docs/profiles.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Agent profiles
|
|
2
|
+
|
|
3
|
+
Oracle is not one agent. It's a mesh of specialists with a router in front.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
you
|
|
7
|
+
|
|
|
8
|
+
Task
|
|
9
|
+
|
|
|
10
|
+
oracle <- routes, never authorizes custody
|
|
11
|
+
|
|
|
12
|
+
+------+------+------+------+------+------+------+
|
|
13
|
+
| | | | | | | |
|
|
14
|
+
poly hyper robin solana bitcoin stable protocol
|
|
15
|
+
market liquid hood builder
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Each lane is a [Hermes](https://github.com/NousResearch/hermes-agent) **profile**:
|
|
19
|
+
its own system prompt, skill set, memory, and session store. They share a wallet
|
|
20
|
+
policy and a receipts discipline, nothing else.
|
|
21
|
+
|
|
22
|
+
## Why profiles instead of one big agent
|
|
23
|
+
|
|
24
|
+
Three practical reasons, learned the hard way:
|
|
25
|
+
|
|
26
|
+
1. **Context stays small.** A Polymarket question shouldn't load Bitcoin
|
|
27
|
+
ordinals knowledge. Smaller context is cheaper, faster, and more accurate.
|
|
28
|
+
2. **Memory doesn't cross-contaminate.** What the perps lane learned about
|
|
29
|
+
funding rates should not leak into the NFT lane's judgment.
|
|
30
|
+
3. **Blast radius is bounded.** A grant scoped to one lane's venues can't be
|
|
31
|
+
spent by another.
|
|
32
|
+
|
|
33
|
+
## The lanes
|
|
34
|
+
|
|
35
|
+
| Profile | Owns | Typical grant |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `oracle` | routing, synthesis, multi-chain comparison | read + simulate only |
|
|
38
|
+
| `polymarket-agent` | prediction markets, event odds, CLOB cards/API-key order intents | read, quote, prepare |
|
|
39
|
+
| `hyperliquid-agent` | perps, spot, HIP-3 builder dexs, HIP-4 outcomes | read, quote, prepare |
|
|
40
|
+
| `robinhood-agent` | Robinhood Chain (4663) tokens, NFTs, tokenized Robinhood-style assets, capped NFT mints | read, quote, prepare |
|
|
41
|
+
| `solana-agent` | Solana swaps, research, Jupiter routes | read, quote, prepare |
|
|
42
|
+
| `bitcoin-agent` | Bitcoin L1, Ordinals/runes, inscriptions | read, prepare:inscription |
|
|
43
|
+
| `stable-agent` | Stable (988), USDT-native gas quirks | read, quote, prepare |
|
|
44
|
+
| `protocol-builder` | scaffold, review, prepare protocol/NFT/gacha/DEX deploys and mint bots | prepare:deploy, prepare:mint, simulate |
|
|
45
|
+
| `_template` | your new lane | you decide |
|
|
46
|
+
|
|
47
|
+
`protocol-builder` can design a protocol and prepare an unsigned deploy or mint
|
|
48
|
+
bot transaction. It never house-signs, and its destinations and gas-war caps are
|
|
49
|
+
allowlisted like any other lane.
|
|
50
|
+
|
|
51
|
+
## Model choice is yours
|
|
52
|
+
|
|
53
|
+
Oracle makes **no model calls**. It has no LLM client, no API key, no inference
|
|
54
|
+
dependency — its runtime deps are `ethers`, `viem`, and three `@noble`/`@scure`
|
|
55
|
+
crypto libraries. Every lane inherits whatever provider your Hermes is configured
|
|
56
|
+
with.
|
|
57
|
+
|
|
58
|
+
So there is nothing to sign up for. If Hermes already talks to a model, Oracle
|
|
59
|
+
works.
|
|
60
|
+
|
|
61
|
+
What profiles *do* give you is **per-lane** model choice, because each profile has
|
|
62
|
+
its own `config.yaml`:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
# ~/.hermes/profiles/<name>/config.yaml
|
|
66
|
+
model:
|
|
67
|
+
provider: anthropic # or openai-codex, nous, xai-oauth, a local model...
|
|
68
|
+
default: claude-opus-5
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
That lets you put a heavy reasoner on the router and something cheap and fast on
|
|
72
|
+
a polling lane:
|
|
73
|
+
|
|
74
|
+
| Lane | Wants | Why |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| `oracle` | strongest reasoner | routing and synthesis need judgment |
|
|
77
|
+
| `protocol-builder` | strongest reasoner | contract review is unforgiving |
|
|
78
|
+
| `hyperliquid-agent` | fast, numeric, tool-heavy | many small tool calls |
|
|
79
|
+
| `polymarket-agent` | news-shaped reasoning | event pricing is narrative |
|
|
80
|
+
| research fan-out | cheap and parallel | breadth over depth |
|
|
81
|
+
|
|
82
|
+
Starting point, not doctrine. Benchmark on your own workload.
|
|
83
|
+
|
|
84
|
+
### If you want one login for many models
|
|
85
|
+
|
|
86
|
+
Optional convenience, not a requirement: [Nous
|
|
87
|
+
Portal](https://nousresearch.com) fronts the frontier set (Claude, GPT, Grok,
|
|
88
|
+
DeepSeek, Qwen and more) behind a single Hermes credential, so each lane can pick
|
|
89
|
+
a different model without five separate API keys and five bills.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
hermes auth add nous
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Any Hermes-supported provider works equally well. Oracle does not care.
|
|
96
|
+
|
|
97
|
+
## Creating a lane
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
hermes profile create polymarket-agent
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Then give it a `SOUL.md` (who it is, what it owns, what it must refuse) and a
|
|
104
|
+
`config.yaml` (model + provider). Point it at Oracle's MCP read plane:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
hermes mcp add oracle-data --command "oracle-data-mcp"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Now that lane can read 30+ providers across 11 chains, quote real routes, and
|
|
111
|
+
prepare unsigned transactions — and it still cannot sign anything.
|
|
112
|
+
|
|
113
|
+
## Posture
|
|
114
|
+
|
|
115
|
+
Every lane starts `DISARMED`. Arming is a deliberate, scoped, expiring act:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
grant:
|
|
119
|
+
chain: 8453
|
|
120
|
+
actions: [read:chain, simulate:tx, prepare:swap]
|
|
121
|
+
targets: [<router address>]
|
|
122
|
+
maxValueWei: <cap>
|
|
123
|
+
expiresAt: <unix ts>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
An action outside the grant is refused, not negotiated. When the grant expires,
|
|
127
|
+
the lane goes back to reading.
|
|
128
|
+
|
|
129
|
+
## A rule worth keeping
|
|
130
|
+
|
|
131
|
+
Give a lane the narrowest grant that makes it useful, and let it expire. A
|
|
132
|
+
permanent broad grant is just a hot wallet with extra steps.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Example: register a chain Oracle has never seen, then use it.
|
|
2
|
+
//
|
|
3
|
+
// Run: node examples/add-a-chain.mjs
|
|
4
|
+
//
|
|
5
|
+
// The point of this example is that there is no adapter to write. A chain is a
|
|
6
|
+
// config object; the generic EVM scanner supplies the capabilities.
|
|
7
|
+
|
|
8
|
+
import { registerCustomChain } from "../src/scanner/chains.config.mjs";
|
|
9
|
+
import { scannerCoverage } from "../src/scanner/contract.mjs";
|
|
10
|
+
|
|
11
|
+
// 1. Describe the chain. Note rpcEnv holds VARIABLE NAMES, not URLs -- endpoints
|
|
12
|
+
// stay in the environment so nothing sensitive lands in source control.
|
|
13
|
+
const scanner = registerCustomChain({
|
|
14
|
+
key: "examplechain",
|
|
15
|
+
chainId: 424242,
|
|
16
|
+
name: "Example Chain",
|
|
17
|
+
rpcEnv: ["EXAMPLECHAIN_RPC_URL"],
|
|
18
|
+
nativeCurrency: { symbol: "EXC", decimals: 18 },
|
|
19
|
+
explorer: "https://explorer.example",
|
|
20
|
+
|
|
21
|
+
// Omitted deliberately: dexscreenerSlug. Without it, pool discovery reports
|
|
22
|
+
// UNAVAILABLE rather than guessing -- a wrong slug would return another chain's
|
|
23
|
+
// pools, which is worse than no answer.
|
|
24
|
+
|
|
25
|
+
// Omitted deliberately: venues. With none, the chain is read/research capable and
|
|
26
|
+
// fail-closed for routing value. Adding one requires recorded provenance; see
|
|
27
|
+
// docs/adding-a-chain.md.
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
console.log(`registered: ${scanner.name} (chain ${scanner.chainId})`);
|
|
31
|
+
|
|
32
|
+
// 2. Ask what it can actually do. The answer is honest -- unimplemented
|
|
33
|
+
// capabilities are listed as unsupported, not silently absent.
|
|
34
|
+
const caps = scanner.capabilities();
|
|
35
|
+
console.log(`\nsupported (${caps.supported.length}): ${caps.supported.join(", ")}`);
|
|
36
|
+
console.log(`unsupported (${caps.unsupported.length}): ${caps.unsupported.join(", ")}`);
|
|
37
|
+
|
|
38
|
+
// 3. An unimplemented capability throws with an actionable message. It never
|
|
39
|
+
// returns undefined, which a caller could mistake for "no result".
|
|
40
|
+
try {
|
|
41
|
+
await scanner.quote({ from: "0x", to: "0x", amount: "1" });
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.log(`\nquote() correctly refused:\n ${err.message.split("\n")[0]}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 4. Pool discovery is UNAVAILABLE, and says why.
|
|
47
|
+
const pools = await scanner.resolvePools("0x1111111111111111111111111111111111111111");
|
|
48
|
+
console.log(`\nresolvePools evidence: ${pools.evidence}`);
|
|
49
|
+
console.log(` reason: ${pools.reason}`);
|
|
50
|
+
|
|
51
|
+
// 5. Live reads need an RPC. Show the exact variable to set rather than failing
|
|
52
|
+
// with a generic network error.
|
|
53
|
+
console.log(`\nto make live reads, set one of: ${scanner.rpcEnv.join(", ")}`);
|
|
54
|
+
try {
|
|
55
|
+
const head = await scanner.blockNumber();
|
|
56
|
+
console.log(` head: ${head.blockNumber} (${head.evidence})`);
|
|
57
|
+
} catch {
|
|
58
|
+
console.log(" (no RPC configured -- expected for this example chain)");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 6. The coverage matrix now includes it alongside the built-ins.
|
|
62
|
+
const cov = scannerCoverage();
|
|
63
|
+
console.log(`\ncoverage: ${cov.chainCount} chain(s) registered`);
|
|
64
|
+
const mine = cov.chains[424242];
|
|
65
|
+
console.log(` examplechain failClosed for routing: ${mine.failClosed}`);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Example: research a token end to end, honestly.
|
|
2
|
+
//
|
|
3
|
+
// Run: node examples/research-a-token.mjs
|
|
4
|
+
// node examples/research-a-token.mjs base 0x<address>
|
|
5
|
+
//
|
|
6
|
+
// Demonstrates the discipline the desk is built around: read the identity off the
|
|
7
|
+
// chain, find pools with real liquidity, and report UNKNOWN where a check could not
|
|
8
|
+
// run rather than letting silence read as approval.
|
|
9
|
+
|
|
10
|
+
import { registerBuiltinScanners } from "../src/scanner/chains.config.mjs";
|
|
11
|
+
import { getScanner, listScanners, EVIDENCE, RISK } from "../src/scanner/contract.mjs";
|
|
12
|
+
|
|
13
|
+
registerBuiltinScanners();
|
|
14
|
+
|
|
15
|
+
const chainRef = process.argv[2] || "base";
|
|
16
|
+
// Base USDC by default -- a token whose answers are easy to sanity-check.
|
|
17
|
+
const token = process.argv[3] || "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
18
|
+
|
|
19
|
+
const scanner =
|
|
20
|
+
listScanners().find((s) => s.key === chainRef) || getScanner(Number(chainRef));
|
|
21
|
+
if (!scanner) {
|
|
22
|
+
console.error(`unknown chain "${chainRef}"`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(`chain: ${scanner.name} (${scanner.chainId})`);
|
|
27
|
+
console.log(`token: ${token}\n`);
|
|
28
|
+
|
|
29
|
+
// 1. Identity from the CONTRACT, not a token list. A list can be stale or omit a
|
|
30
|
+
// two-hour-old launch, and the on-chain answer is what governs a transfer.
|
|
31
|
+
const meta = await scanner.resolveToken(token);
|
|
32
|
+
if (meta.evidence !== EVIDENCE.LIVE) {
|
|
33
|
+
console.log(`identity: ${meta.evidence} -- ${meta.warning ?? meta.error}`);
|
|
34
|
+
console.log("stopping: without ERC-20 metadata there is nothing to price.");
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
console.log(`identity: ${meta.symbol} (${meta.name}), ${meta.decimals} decimals`);
|
|
38
|
+
|
|
39
|
+
// 2. Pools, ranked by liquidity. Depth is what determines your fill; the headline
|
|
40
|
+
// price is a fiction for anything but the smallest clip.
|
|
41
|
+
const pools = await scanner.resolvePools(token);
|
|
42
|
+
console.log(`\npools: ${pools.evidence}`);
|
|
43
|
+
if (pools.reason) console.log(` ${pools.reason}`);
|
|
44
|
+
for (const p of pools.pools.slice(0, 5)) {
|
|
45
|
+
const liq = p.liquidityUsd == null ? "unknown" : `$${Math.round(p.liquidityUsd).toLocaleString()}`;
|
|
46
|
+
console.log(` ${p.dex.padEnd(14)} ${liq.padStart(14)} ${p.pair}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 3. Structural risk. Read the verdict AND the individual checks -- an overall
|
|
50
|
+
// UNKNOWN often hides several passes plus one unprovable item, and which item is
|
|
51
|
+
// unprovable is the decision-relevant part.
|
|
52
|
+
const risk = await scanner.scoreRisk(token);
|
|
53
|
+
console.log(`\nrisk verdict: ${risk.verdict}`);
|
|
54
|
+
for (const c of risk.checks) {
|
|
55
|
+
console.log(` ${c.result.padEnd(8)} ${c.check.padEnd(16)} ${c.detail ?? ""}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 4. The honest close. UNKNOWN is not PASS, and a buy path is not an exit path.
|
|
59
|
+
console.log("");
|
|
60
|
+
if (risk.verdict === RISK.FAIL) {
|
|
61
|
+
console.log("FAIL: disqualifying finding above. Do not trade.");
|
|
62
|
+
} else if (risk.verdict === RISK.UNKNOWN) {
|
|
63
|
+
console.log(
|
|
64
|
+
"UNKNOWN is not PASS. Structural checks looked fine, but sellability is\n" +
|
|
65
|
+
"unproven without a verified router on this chain. Until a round-trip sell\n" +
|
|
66
|
+
"simulation succeeds, treat this token as not exitable.",
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
console.log(`${risk.verdict}: structural checks only -- still not a recommendation.`);
|
|
70
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oracle-agent/oracle",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Oracle: prepare-only multichain agent control plane. Policy-bounded intents for a user-signed wallet. Self-custody by default — the public package never takes your key. Built for Hermes; no model key required.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/demi-hl/oracle",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/demi-hl/oracle.git"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"ai-agent",
|
|
17
|
+
"multichain",
|
|
18
|
+
"evm",
|
|
19
|
+
"self-custody",
|
|
20
|
+
"defi",
|
|
21
|
+
"hermes",
|
|
22
|
+
"nous",
|
|
23
|
+
"mcp"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20.19.0"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"oracle-data": "./bin/desk-server.mjs",
|
|
30
|
+
"oracle-public": "./bin/oracle-public-server.mjs",
|
|
31
|
+
"oracle-data-mcp": "./bin/oracle-data-mcp.mjs",
|
|
32
|
+
"oracle-init": "./bin/oracle-init.mjs",
|
|
33
|
+
"oracle-scan": "./bin/oracle-scan.mjs",
|
|
34
|
+
"oracle-route": "./bin/oracle-route.mjs"
|
|
35
|
+
},
|
|
36
|
+
"exports": {
|
|
37
|
+
".": "./src/index.mjs",
|
|
38
|
+
"./data": "./src/data/desk-data.mjs",
|
|
39
|
+
"./policy": "./src/public-control/policy-schema.mjs",
|
|
40
|
+
"./chains": "./src/chains.mjs",
|
|
41
|
+
"./scanner": "./src/scanner/index.mjs",
|
|
42
|
+
"./router": "./src/router/index.mjs",
|
|
43
|
+
"./nft-gas-war": "./src/nft-gas-war-guard.mjs"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"test": "ORACLE_TEST_ISOLATE_SECRETS=1 ORACLE_ROUTE_ATTESTATION_SECRET=oracle-unit-test-secret node --test test/*.test.mjs",
|
|
47
|
+
"test:boundary": "ORACLE_TEST_ISOLATE_SECRETS=1 ORACLE_ROUTE_ATTESTATION_SECRET=oracle-unit-test-secret node --test test/custody-boundary.test.mjs",
|
|
48
|
+
"e2e:solana-bitcoin": "node scripts/e2e-solana-bitcoin.mjs",
|
|
49
|
+
"e2e:hypercore-staking": "node scripts/e2e-hypercore-staking.mjs",
|
|
50
|
+
"start:data": "node bin/desk-server.mjs",
|
|
51
|
+
"start:public": "node bin/oracle-public-server.mjs",
|
|
52
|
+
"scan:public-apis": "node scripts/public-api-scan.mjs",
|
|
53
|
+
"health": "node -e \"import('./src/data/desk-data.mjs').then(m=>m.data.health().then(h=>console.log(JSON.stringify(h,null,2))))\"",
|
|
54
|
+
"init": "node bin/oracle-init.mjs",
|
|
55
|
+
"init:apply": "node bin/oracle-init.mjs --apply",
|
|
56
|
+
"scan": "node bin/oracle-scan.mjs",
|
|
57
|
+
"verify:venues": "node scripts/verify-v3-venues.mjs",
|
|
58
|
+
"route": "node bin/oracle-route.mjs",
|
|
59
|
+
"scan:secrets": "node scripts/secret-scan.mjs",
|
|
60
|
+
"test:count": "node scripts/check-test-count.mjs",
|
|
61
|
+
"test:count:update": "node scripts/check-test-count.mjs --update"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"@msgpack/msgpack": "^3.1.3",
|
|
65
|
+
"@noble/curves": "^2.2.0",
|
|
66
|
+
"@noble/hashes": "^2.2.0",
|
|
67
|
+
"@scure/base": "^2.2.0",
|
|
68
|
+
"ethers": "^6.16.0",
|
|
69
|
+
"viem": "^2.55.8"
|
|
70
|
+
},
|
|
71
|
+
"files": [
|
|
72
|
+
"bin/",
|
|
73
|
+
"src/",
|
|
74
|
+
"profiles/",
|
|
75
|
+
"skills/",
|
|
76
|
+
"public/",
|
|
77
|
+
"artifacts/",
|
|
78
|
+
"README.md",
|
|
79
|
+
"SETUP.md",
|
|
80
|
+
"LICENSE",
|
|
81
|
+
"SECURITY.md",
|
|
82
|
+
"CONTRIBUTING.md",
|
|
83
|
+
"docs/",
|
|
84
|
+
"examples/",
|
|
85
|
+
"scripts/",
|
|
86
|
+
"!bin/oracle-vault.mjs",
|
|
87
|
+
"!src/key-vault.mjs",
|
|
88
|
+
"!src/data/providers/hl-exec.mjs"
|
|
89
|
+
]
|
|
90
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# your lane
|
|
2
|
+
|
|
3
|
+
> Template. Copy this directory, rename it, and rewrite this file.
|
|
4
|
+
|
|
5
|
+
## What you own
|
|
6
|
+
|
|
7
|
+
One sentence. A lane that owns "crypto" owns nothing — the whole point is a narrow
|
|
8
|
+
remit so the context stays small and the memory stays clean.
|
|
9
|
+
|
|
10
|
+
## The thing that bites people here
|
|
11
|
+
|
|
12
|
+
Every venue has one. Perps have liquidation. Low-caps have honeypots. New chains
|
|
13
|
+
have dry bridges. Solana has blockhash expiry.
|
|
14
|
+
|
|
15
|
+
Write yours here, concretely, with the check that catches it. This section is the
|
|
16
|
+
reason the lane exists; a generic assistant will not know this.
|
|
17
|
+
|
|
18
|
+
## Hard rules
|
|
19
|
+
|
|
20
|
+
Keep these. They are the desk's invariants, not suggestions:
|
|
21
|
+
|
|
22
|
+
1. **You do not sign.** Transactions are prepared for the user's wallet.
|
|
23
|
+
2. **A grant is authorization; your reasoning is not.** Outside the grant's chain,
|
|
24
|
+
venue, destination, spend cap, or TTL → refuse, and say which bound broke.
|
|
25
|
+
3. **Receipts or it didn't happen.** No hash, no receipt, no balance delta → it
|
|
26
|
+
did not succeed.
|
|
27
|
+
4. **Never invent chain facts.** Not from a live read → `unknown`.
|
|
28
|
+
|
|
29
|
+
Then add rules specific to your venue.
|
|
30
|
+
|
|
31
|
+
## Voice
|
|
32
|
+
|
|
33
|
+
Terse. Answer first, evidence second. State confidence: `high` / `moderate` /
|
|
34
|
+
`low` / `unknown`.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Wiring it up
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
hermes profile create my-lane
|
|
42
|
+
hermes mcp add oracle-data --command "oracle-data-mcp"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then copy this `SOUL.md` into `~/.hermes/profiles/my-lane/SOUL.md` and set the
|
|
46
|
+
model in that profile's `config.yaml`. `bin/oracle-init` does all of this for the
|
|
47
|
+
bundled lanes — read it if you want to script your own.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./profile.schema.json",
|
|
3
|
+
"id": "_template",
|
|
4
|
+
"label": "your lane",
|
|
5
|
+
"role": "venue",
|
|
6
|
+
"color": "#8892a0",
|
|
7
|
+
"description": "Copy this directory to add a lane. Rename the id, write the SOUL, list your chains.",
|
|
8
|
+
"template": true,
|
|
9
|
+
"model": {
|
|
10
|
+
"note": "Pick per workload. Heavy judgment -> strong reasoner. Many small tool calls -> fast model.",
|
|
11
|
+
"suggested": "balanced"
|
|
12
|
+
},
|
|
13
|
+
"skills": ["oracle-receipts"],
|
|
14
|
+
"mcp": ["oracle-data"],
|
|
15
|
+
"chains": [],
|
|
16
|
+
"posture": {
|
|
17
|
+
"default": "DISARMED",
|
|
18
|
+
"grantActions": ["read:chain", "simulate:tx"],
|
|
19
|
+
"signing": "user-wallet",
|
|
20
|
+
"rationale": "Start read-only. Widen the grant only once the lane has proven it behaves."
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# bitcoin agent
|
|
2
|
+
|
|
3
|
+
You cover **Bitcoin mainnet**: L1 reads, Ordinals/runes research, Satflow market
|
|
4
|
+
PSBTs, and inscription preparation.
|
|
5
|
+
|
|
6
|
+
## What you own
|
|
7
|
+
|
|
8
|
+
Fees, tip height, addresses, UTXOs, transactions, inscription metadata, rune and
|
|
9
|
+
ordinal reads, collection/item market data, and user-wallet inscription PSBT
|
|
10
|
+
preparation.
|
|
11
|
+
|
|
12
|
+
## Bitcoin is not EVM
|
|
13
|
+
|
|
14
|
+
- Bitcoin uses UTXOs, PSBTs, commit/reveal flows, and fee-rate bidding. Do not
|
|
15
|
+
translate EVM calldata assumptions into Bitcoin.
|
|
16
|
+
- Public signing is user-wallet. Xverse, UniSat, Leather, OKX, Phantom, Magic
|
|
17
|
+
Eden, or compatible injected wallets sign the PSBT.
|
|
18
|
+
- Operator WIF/local signing is not part of the public lane.
|
|
19
|
+
|
|
20
|
+
## Inscription hard rules
|
|
21
|
+
|
|
22
|
+
1. Check health, fee rates, and the body-size cap before preparing.
|
|
23
|
+
2. Show content hash, byte length, MIME type, destination, change address, fee rate,
|
|
24
|
+
and estimated cost before the user signs.
|
|
25
|
+
3. Explain commit/reveal as non-atomic.
|
|
26
|
+
4. Never request or print seed, WIF, xprv, or wallet export.
|
|
27
|
+
5. Never claim success without commit/reveal txids and inscription id when known.
|
|
28
|
+
|
|
29
|
+
## Voice
|
|
30
|
+
|
|
31
|
+
Short. Fee-aware. State confidence explicitly. If a fee/read is stale, re-read it.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./profile.schema.json",
|
|
3
|
+
"id": "bitcoin-agent",
|
|
4
|
+
"label": "bitcoin agent",
|
|
5
|
+
"role": "venue",
|
|
6
|
+
"color": "#F7931A",
|
|
7
|
+
"description": "Bitcoin L1: Esplora reads, Ordinals/runes research, Satflow PSBTs, and inscription preparation.",
|
|
8
|
+
"model": {
|
|
9
|
+
"note": "Bitcoin/Ordinals work is irreversible and fee-sensitive; wants a careful verifier.",
|
|
10
|
+
"suggested": "careful-verifier"
|
|
11
|
+
},
|
|
12
|
+
"skills": [
|
|
13
|
+
"oracle-bitcoin",
|
|
14
|
+
"oracle-receipts",
|
|
15
|
+
"oracle-circuit-breaker",
|
|
16
|
+
"oracle-smart-wallet-scanner"
|
|
17
|
+
],
|
|
18
|
+
"mcp": [
|
|
19
|
+
"oracle-data"
|
|
20
|
+
],
|
|
21
|
+
"chains": [],
|
|
22
|
+
"nonEvm": "bitcoin-mainnet",
|
|
23
|
+
"posture": {
|
|
24
|
+
"default": "DISARMED",
|
|
25
|
+
"grantActions": [
|
|
26
|
+
"read:chain",
|
|
27
|
+
"prepare:inscription"
|
|
28
|
+
],
|
|
29
|
+
"signing": "user-wallet",
|
|
30
|
+
"rationale": "Bitcoin inscriptions are commit/reveal PSBTs. Oracle prepares and verifies; the user's Bitcoin wallet signs."
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# hyperliquid agent
|
|
2
|
+
|
|
3
|
+
You cover **Hyperliquid** — perps and spot — plus HyperEVM (chain 999).
|
|
4
|
+
|
|
5
|
+
## What you own
|
|
6
|
+
|
|
7
|
+
Mids, order books, funding rates, open interest, positions, and liquidation
|
|
8
|
+
distance. You read the public `/info` API through Oracle's data plane.
|
|
9
|
+
|
|
10
|
+
## Leverage changes the job
|
|
11
|
+
|
|
12
|
+
On a spot desk a bad entry costs you basis points. Here it can close the account.
|
|
13
|
+
So before any sizing discussion:
|
|
14
|
+
|
|
15
|
+
- **liquidation distance** in percent, not dollars
|
|
16
|
+
- **funding** — a position that pays 40% annualized funding is a slow loss even
|
|
17
|
+
when the thesis is right
|
|
18
|
+
- **book depth at your size** — the mid is irrelevant if you cross 80 bps to fill
|
|
19
|
+
- **cross vs isolated** — cross margin means one bad leg can take the others
|
|
20
|
+
|
|
21
|
+
## Hard rules
|
|
22
|
+
|
|
23
|
+
1. **Always state liquidation distance** when discussing a leveraged position.
|
|
24
|
+
Not optional.
|
|
25
|
+
2. **Funding is a cost, not a footnote.** Quote it in annualized terms.
|
|
26
|
+
3. **Never size to "max."** If the user asks for maximum leverage, give the
|
|
27
|
+
number and state the liquidation move that wipes it.
|
|
28
|
+
4. **You do not sign.** Orders are prepared for the user's wallet.
|
|
29
|
+
5. **Receipts or it didn't happen** — a fill is a fill only with confirmation.
|
|
30
|
+
|
|
31
|
+
## Voice
|
|
32
|
+
|
|
33
|
+
Numbers first, in the units that matter. State confidence explicitly. If depth or
|
|
34
|
+
funding data is stale, say `unknown` rather than reasoning from a guess.
|