@oracle-agent/oracle 0.3.5 → 0.4.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/README.md +33 -13
- package/SETUP.md +59 -1
- package/artifacts/specialist-packs/oracle-full-crypto.json +31 -9
- package/bin/oracle-data-mcp.mjs +308 -4
- package/bin/oracle-init.mjs +100 -27
- package/bin/oracle-upgrade.mjs +42 -0
- package/docs/profiles.md +29 -7
- package/package.json +5 -1
- package/plugins/oracle-owner-gate/__init__.py +227 -0
- package/plugins/oracle-owner-gate/plugin.yaml +9 -0
- package/profiles/_template/SOUL.md +8 -1
- package/profiles/oracle/SOUL.md +36 -2
- package/profiles/oracle/profile.json +6 -2
- package/profiles/protocol-builder/SOUL.md +13 -6
- package/profiles/protocol-builder/profile.json +3 -1
- package/profiles/robinhood-agent/SOUL.md +9 -3
- package/profiles/robinhood-agent/profile.json +1 -0
- package/skills/balance/SKILL.md +176 -0
- package/skills/oracle-action-semantics/SKILL.md +40 -0
- package/skills/oracle-multichain-nft-launch/SKILL.md +338 -0
- package/skills/oracle-multichain-token-launch/SKILL.md +300 -0
- package/src/action-semantics.mjs +62 -0
- package/src/address-book.mjs +160 -0
- package/src/data/catalog.mjs +27 -3
- package/src/data/desk-data.mjs +34 -4
- package/src/data/providers/magiceden-sol.mjs +21 -2
- package/src/data/providers/nft-gallery.mjs +163 -0
- package/src/data/providers/nft-portfolio.mjs +494 -0
- package/src/data/providers/opensea-nft.mjs +272 -0
- package/src/data/providers/portfolio-history.mjs +394 -0
- package/src/data/providers/portfolio.mjs +594 -0
- package/src/data/providers/satflow.mjs +1 -0
- package/src/exec-policy.mjs +5 -0
- package/src/gmx-attestation.mjs +1 -0
- package/src/index.mjs +9 -0
- package/src/profile-upgrade.mjs +277 -0
- package/src/scanner/chains.config.mjs +2 -0
- package/src/vault-attestation.mjs +1 -0
- package/src/cards.mjs +0 -369
package/README.md
CHANGED
|
@@ -20,11 +20,11 @@ alongside it — same split we run ourselves.
|
|
|
20
20
|
|
|
21
21
|
## Start in 5 steps
|
|
22
22
|
|
|
23
|
-
Latest: **oracle `0.3.
|
|
23
|
+
Latest: **oracle `0.3.6`** · **operator `0.6.1`**
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
# 1) install both packages
|
|
27
|
-
npm i @oracle-agent/oracle@0.3.
|
|
27
|
+
npm i @oracle-agent/oracle@0.3.6 @oracle-agent/operator@0.6.1
|
|
28
28
|
|
|
29
29
|
# 2) put keys ONLY on your machine (example: HL/EVM hex key file)
|
|
30
30
|
mkdir -p ~/.config/oracle/keys && chmod 700 ~/.config/oracle/keys
|
|
@@ -161,12 +161,12 @@ oracle-scan risk base 0x8335...2913 # structural checks + sell simulation
|
|
|
161
161
|
oracle-scan sell base 0x8335...2913 # round trip: can you actually exit?
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
**
|
|
165
|
-
Arbitrum, Avalanche
|
|
166
|
-
round-trip sell simulation, and unsigned swap preparation. The remaining
|
|
167
|
-
(Stable, HyperEVM, Abstract
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
**8 of 11 chains ship verified venues**: Ethereum, Optimism, BNB, Polygon, Base,
|
|
165
|
+
Arbitrum, Avalanche, and Robinhood Chain. They have all 10 capabilities including
|
|
166
|
+
live quotes, round-trip sell simulation, and unsigned swap preparation. The remaining
|
|
167
|
+
three (Stable, HyperEVM, Abstract) are at 7 and **fail-closed for routing value** until
|
|
168
|
+
someone verifies a venue: read and research work, moving money does not. That is a safe
|
|
169
|
+
default, not a gap.
|
|
170
170
|
|
|
171
171
|
Every venue address was verified **functionally, not by codesize** — a re-runnable
|
|
172
172
|
prober (`scripts/verify-v3-venues.mjs`) asks each candidate to price a pair with a
|
|
@@ -199,10 +199,14 @@ Oracle's default pack is deliberately broad but disarmed:
|
|
|
199
199
|
|
|
200
200
|
- **Trader** — best-execution route comparison, quote/prepare, simulation, and
|
|
201
201
|
receipt checks; no set-and-forget custody.
|
|
202
|
-
- **Builder**
|
|
203
|
-
deploy/admin
|
|
202
|
+
- **Builder** - chain-family fungible-token and NFT-collection launch plans,
|
|
203
|
+
protocol, gacha, DEX, and launchpad scaffolds with unsigned deploy/admin actions.
|
|
204
|
+
Unsupported chain adapters fail closed instead of pretending one deploy fits all.
|
|
204
205
|
- **Analyzer** — token, contract, venue, portfolio, market, and risk research with
|
|
205
|
-
evidence labels.
|
|
206
|
+
evidence labels. `/balance` or plain `balance` runs one deterministic snapshot
|
|
207
|
+
across every configured EVM chain plus Solana, Bitcoin, Hyperliquid, and
|
|
208
|
+
discoverable NFTs. Profile-local observations power history and SVG value graphs;
|
|
209
|
+
unavailable values stay null instead of becoming fake zeroes.
|
|
206
210
|
- **On-chain scanner** — chain-config scanners for tokens, pools, launches,
|
|
207
211
|
risk, exits, and smart-wallet boards.
|
|
208
212
|
- **Meme-token sniper** — fast launch/liquidity monitoring across configured
|
|
@@ -253,6 +257,19 @@ owner-local source lane.** The short version:
|
|
|
253
257
|
- User wallets authorize prepared actions outside the public data plane.
|
|
254
258
|
- Owner-local signing is shipped separately as `@oracle-agent/operator`; its agent-facing daemon supports only policy-bounded Hyperliquid and Polymarket execution.
|
|
255
259
|
|
|
260
|
+
### Action vocabulary and execution planes
|
|
261
|
+
|
|
262
|
+
Oracle keeps capability and authorization separate:
|
|
263
|
+
|
|
264
|
+
- Public Oracle reads, quotes, simulates, and prepares unsigned artifacts.
|
|
265
|
+
- The generic unattended signer remains limited to `hl` and `poly`.
|
|
266
|
+
- Ordinary EVM preparation remains user-wallet signed.
|
|
267
|
+
- A deployment may separately install a same-host, owner-gated EVM executor. Oracle must verify that executor before describing bounded EVM execution as available. Missing deployment capability means "unavailable here," not "Oracle can never execute EVM."
|
|
268
|
+
- `watch`, `watch this`, and `ping me` always create `active: true, actionMode: alert_only`.
|
|
269
|
+
- `arm` creates `active: true, actionMode: execute` only for one exact owner-authorized action. It is never inferred from a watch.
|
|
270
|
+
|
|
271
|
+
The package exports this binding as `@oracle-agent/oracle/action-semantics`. Legacy watch stores can migrate `status: watching|armed` with `migrateLegacyWatchRecord()`; because it is specifically a watch-store migration, both statuses become `alert_only`, never execution authority.
|
|
272
|
+
|
|
256
273
|
Run the read-only data plane:
|
|
257
274
|
|
|
258
275
|
```bash
|
|
@@ -273,7 +290,8 @@ Oracle is a library. Drive it with Claude, GPT, Gemini, Grok, a local model, or
|
|
|
273
290
|
a plain script — the tools are ordinary functions plus an MCP server:
|
|
274
291
|
|
|
275
292
|
```bash
|
|
276
|
-
npx oracle-data
|
|
293
|
+
npx oracle-data # local read plane on 127.0.0.1:8787
|
|
294
|
+
npx oracle-data-mcp # MCP stdio server (any MCP client); needs oracle-data up
|
|
277
295
|
```
|
|
278
296
|
|
|
279
297
|
It is better under [Hermes](https://claude-code.nousresearch.com/docs), because
|
|
@@ -294,7 +312,9 @@ Oracle ships an installable 8-lane mesh for Hermes:
|
|
|
294
312
|
|
|
295
313
|
```bash
|
|
296
314
|
oracle-init # dry run -- shows exactly what it would do
|
|
297
|
-
oracle-init --apply # create profiles, install SOULs + skills, wire MCP
|
|
315
|
+
oracle-init --apply # create profiles, install SOULs + skills, wire MCP config
|
|
316
|
+
npx oracle-data # keep running — MCP tools call the local read plane on :8787
|
|
317
|
+
hermes -p oracle chat # router lane; inherits whatever model Hermes already uses
|
|
298
318
|
```
|
|
299
319
|
|
|
300
320
|
Lanes: `oracle` (router), `polymarket-agent`, `hyperliquid-agent`,
|
package/SETUP.md
CHANGED
|
@@ -38,6 +38,30 @@ environment. The npm artifact excludes the key vault and live signer modules.
|
|
|
38
38
|
Wallet signing, submission, receipt confirmation, and revocation happen outside
|
|
39
39
|
that public surface.
|
|
40
40
|
|
|
41
|
+
### Capability is not authorization
|
|
42
|
+
|
|
43
|
+
The public Oracle profile must report these as separate planes:
|
|
44
|
+
|
|
45
|
+
| Plane | Default capability |
|
|
46
|
+
|---|---|
|
|
47
|
+
| Public Oracle package | read, quote, simulate, unsigned prepare |
|
|
48
|
+
| Generic `oracle-signer` daemon | policy-bounded `hl` and `poly` only |
|
|
49
|
+
| Ordinary EVM | unsigned preparation, user-wallet signature |
|
|
50
|
+
| Optional owner-gated EVM executor | one exact bounded action after explicit `arm` |
|
|
51
|
+
|
|
52
|
+
The optional EVM executor is not bundled into this public package and must never be exposed over LAN or a tailnet. A profile may describe bounded EVM execution as available only after verifying that separate local executor and its owner gate. Otherwise say the current deployment is prepare-only. Do not claim EVM execution is universally impossible.
|
|
53
|
+
|
|
54
|
+
Action records require explicit fields:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"active": true,
|
|
59
|
+
"actionMode": "alert_only"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`watch`, `watch this`, and `ping me` always use `alert_only`. `arm` uses `execute` only for one exact owner-authorized action. A legacy `status: armed` field is not execution authority.
|
|
64
|
+
|
|
41
65
|
---
|
|
42
66
|
|
|
43
67
|
## Source-only operator credentials
|
|
@@ -103,6 +127,39 @@ export BASE_RPC_URL=https://...
|
|
|
103
127
|
export SOLANA_RPC_URL=https://...
|
|
104
128
|
```
|
|
105
129
|
|
|
130
|
+
`/balance` and plain `balance` need only public addresses. Set any families the
|
|
131
|
+
wallet uses, then start `oracle-data` from the same environment:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
export ORACLE_EVM_ADDRESS=0x...
|
|
135
|
+
export ORACLE_SOLANA_ADDRESS=...
|
|
136
|
+
export ORACLE_BITCOIN_ADDRESS=bc1...
|
|
137
|
+
export ORACLE_HYPERLIQUID_ADDRESS=0x... # optional, defaults to EVM address
|
|
138
|
+
npx oracle-data
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Missing address families are reported as `not-configured`, failed providers as
|
|
142
|
+
`unavailable`, and unsupported chain families as `unsupported`. They are never
|
|
143
|
+
reported as zero. EVM native balances cover every configured EVM chain. Solana
|
|
144
|
+
adds SPL and Token-2022 accounts, Bitcoin adds Runes and inscriptions when an
|
|
145
|
+
address indexer is configured, Hyperliquid adds spot and perp account state, and
|
|
146
|
+
NFT inventory adds OpenSea-supported EVM/Solana collections plus Bitcoin
|
|
147
|
+
inscriptions where owner indexers are available.
|
|
148
|
+
|
|
149
|
+
Every `portfolio_snapshot` call appends one compact observation to
|
|
150
|
+
`$HERMES_HOME/state/oracle/portfolio-history.jsonl` with mode `0600`. It stores a
|
|
151
|
+
public-address fingerprint, coverage, value breakdown, and warnings, not keys,
|
|
152
|
+
prepared transactions, or raw NFT metadata. Override the internal location only
|
|
153
|
+
when needed:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
export ORACLE_PORTFOLIO_HISTORY_FILE="$HOME/.local/state/oracle/portfolio-history.jsonl"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`portfolio_history` reads those observations. `portfolio_value_graph` returns an
|
|
160
|
+
SVG chart of known priced value and omits unavailable observations instead of
|
|
161
|
+
plotting them as zero.
|
|
162
|
+
|
|
106
163
|
---
|
|
107
164
|
|
|
108
165
|
## Where keys are stored
|
|
@@ -202,7 +259,8 @@ Gemini, Grok, a local Llama, or your own script at it. The tools are plain
|
|
|
202
259
|
functions and an MCP server.
|
|
203
260
|
|
|
204
261
|
```bash
|
|
205
|
-
npx oracle-data
|
|
262
|
+
npx oracle-data # local read plane on 127.0.0.1:8787
|
|
263
|
+
npx oracle-data-mcp # MCP stdio server, works with any MCP client (needs oracle-data up)
|
|
206
264
|
```
|
|
207
265
|
|
|
208
266
|
**Why [Hermes](https://claude-code.nousresearch.com/docs) is the better host: per-profile routing.**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "oracle-full-crypto",
|
|
3
3
|
"name": "Oracle Full Crypto Pack",
|
|
4
|
-
"version":
|
|
4
|
+
"version": 4,
|
|
5
5
|
"audience": "public-users",
|
|
6
|
-
"description": "Default capability set for public Oracle: trader, builder, analyzer, multichain scanner, meme-token sniping, RFQ routing, tokenized-asset buys, NFT mint gas-war limits, Solana, Bitcoin inscriptions, NFT
|
|
6
|
+
"description": "Default capability set for public Oracle: trader, builder, analyzer, deterministic /balance aggregation, multichain scanner, meme-token sniping, RFQ routing, tokenized-asset buys, NFT mint gas-war limits, Solana, Bitcoin inscriptions, chain-family token/NFT collections, gacha/DEX launches, and protocol build under bound grants. User signs everything.",
|
|
7
7
|
"posture": {
|
|
8
8
|
"default": "DISARMED",
|
|
9
9
|
"custody": "self-custodial",
|
|
@@ -62,6 +62,8 @@
|
|
|
62
62
|
"oracle-protocol-builder",
|
|
63
63
|
"oracle-contract-research",
|
|
64
64
|
"oracle-protocol-security",
|
|
65
|
+
"oracle-multichain-token-launch",
|
|
66
|
+
"oracle-multichain-nft-launch",
|
|
65
67
|
"oracle-nft-gacha-launch",
|
|
66
68
|
"oracle-dex-launch",
|
|
67
69
|
"protocol-api-key-integration",
|
|
@@ -80,6 +82,8 @@
|
|
|
80
82
|
"oracle-nft-gacha-launch",
|
|
81
83
|
"oracle-dex-launch",
|
|
82
84
|
"oracle-protocol-security",
|
|
85
|
+
"oracle-multichain-token-launch",
|
|
86
|
+
"oracle-multichain-nft-launch",
|
|
83
87
|
"oracle-nft-mint-gas-war"
|
|
84
88
|
],
|
|
85
89
|
"analysis": [
|
|
@@ -88,6 +92,7 @@
|
|
|
88
92
|
"oracle-smart-wallet-scanner",
|
|
89
93
|
"oracle-meme-token-sniper",
|
|
90
94
|
"oracle-best-execution",
|
|
95
|
+
"balance",
|
|
91
96
|
"oracle-chain-graphs-telegram-cards",
|
|
92
97
|
"oracle-rfq-tokenized-assets"
|
|
93
98
|
]
|
|
@@ -109,7 +114,9 @@
|
|
|
109
114
|
"write-deploy-scripts",
|
|
110
115
|
"prepare-unsigned-deploy-txs",
|
|
111
116
|
"security-checklist",
|
|
112
|
-
"launchpad-public-surface"
|
|
117
|
+
"launchpad-public-surface",
|
|
118
|
+
"multichain-token-launch",
|
|
119
|
+
"multichain-nft-collection-launch"
|
|
113
120
|
],
|
|
114
121
|
"user_signs": true,
|
|
115
122
|
"grant_actions_example": [
|
|
@@ -119,7 +126,7 @@
|
|
|
119
126
|
"prepare:verify"
|
|
120
127
|
],
|
|
121
128
|
"targets": "user-provided factory/router/implementations only unless added to destination allowlist",
|
|
122
|
-
"copy": "Oracle can
|
|
129
|
+
"copy": "Oracle can classify and prepare chain-family token, NFT collection, protocol, gacha, and DEX launches. Unsupported adapters fail closed. You still sign every side effect."
|
|
123
130
|
},
|
|
124
131
|
"profiles": [
|
|
125
132
|
{
|
|
@@ -201,10 +208,12 @@
|
|
|
201
208
|
"oracle-protocol-security",
|
|
202
209
|
"oracle-nft-gacha-launch",
|
|
203
210
|
"oracle-dex-launch",
|
|
204
|
-
"oracle-receipts"
|
|
211
|
+
"oracle-receipts",
|
|
212
|
+
"oracle-multichain-token-launch",
|
|
213
|
+
"oracle-multichain-nft-launch"
|
|
205
214
|
],
|
|
206
215
|
"label": "protocol builder",
|
|
207
|
-
"description": "
|
|
216
|
+
"description": "Classify by chain family, then scaffold, review, and prepare unsigned token, NFT collection, gacha, DEX, and protocol launches."
|
|
208
217
|
}
|
|
209
218
|
],
|
|
210
219
|
"default_tools": {
|
|
@@ -216,7 +225,14 @@
|
|
|
216
225
|
"btc_health",
|
|
217
226
|
"btc_fees",
|
|
218
227
|
"btc_inscription_info",
|
|
219
|
-
"solana_balance"
|
|
228
|
+
"solana_balance",
|
|
229
|
+
"portfolio_balance",
|
|
230
|
+
"portfolio_snapshot",
|
|
231
|
+
"portfolio_history",
|
|
232
|
+
"portfolio_value_graph",
|
|
233
|
+
"nft_inventory",
|
|
234
|
+
"nft_gallery",
|
|
235
|
+
"nft_pnl"
|
|
220
236
|
],
|
|
221
237
|
"prepare": [
|
|
222
238
|
"evm_prepare",
|
|
@@ -224,7 +240,8 @@
|
|
|
224
240
|
"evm_protocol_quote",
|
|
225
241
|
"jupiter_quote",
|
|
226
242
|
"jupiter_prepare",
|
|
227
|
-
"bitcoin_inscribe_prepare"
|
|
243
|
+
"bitcoin_inscribe_prepare",
|
|
244
|
+
"nft_prepare_list"
|
|
228
245
|
],
|
|
229
246
|
"never_public_default": [
|
|
230
247
|
"evm_sign",
|
|
@@ -268,6 +285,11 @@
|
|
|
268
285
|
"solana-nft-marketplace",
|
|
269
286
|
"solana-nft-mint",
|
|
270
287
|
"hypercore-hype-staking",
|
|
271
|
-
"hypercore-validator-delegation"
|
|
288
|
+
"hypercore-validator-delegation",
|
|
289
|
+
"multichain-token-launch",
|
|
290
|
+
"multichain-nft-collection-launch",
|
|
291
|
+
"multichain-balance",
|
|
292
|
+
"nft-inventory-gallery-pnl",
|
|
293
|
+
"portfolio-balance-history-graph"
|
|
272
294
|
]
|
|
273
295
|
}
|
package/bin/oracle-data-mcp.mjs
CHANGED
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
// Oracle data MCP — read-only multichain market/discovery plane.
|
|
3
3
|
// Loaded by an Oracle/Hermes profile. Never expose signing or broadcast here.
|
|
4
4
|
//
|
|
5
|
-
// Every tool is a READ
|
|
6
|
-
// data server read plane (/data/call),
|
|
5
|
+
// Every tool is a READ or a prepared unsigned action. No key handling, signing,
|
|
6
|
+
// or broadcast. Routes to the Oracle data server read/prepare plane (/data/call),
|
|
7
|
+
// which is unauthenticated on loopback.
|
|
7
8
|
// This is the "scan all public APIs" surface: DeFiLlama protocol TVL, DexScreener
|
|
8
9
|
// token/search, LI.FI + Uniswap quotes, multi-chain RPC balances/blocks.
|
|
9
10
|
//
|
|
10
11
|
// Mirrors the Oracle exec MCP stdio JSON-RPC shape.
|
|
11
12
|
|
|
13
|
+
import fs from "node:fs";
|
|
12
14
|
import process from "node:process";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
13
16
|
import { env } from "../src/oracle-env.mjs";
|
|
17
|
+
import { listAddresses, lookupAddress, rememberAddress, forgetAddress } from "../src/address-book.mjs";
|
|
14
18
|
|
|
15
19
|
const DATA_URL = (env("ORACLE_DATA_URL", "MAD_DESK_URL", "http://127.0.0.1:8787")).replace(/\/$/, "");
|
|
16
20
|
|
|
@@ -96,6 +100,201 @@ const tools = [
|
|
|
96
100
|
},
|
|
97
101
|
},
|
|
98
102
|
},
|
|
103
|
+
{
|
|
104
|
+
name: "portfolio_balance",
|
|
105
|
+
description:
|
|
106
|
+
"Read-only balance aggregation across every configured EVM chain plus Solana, Bitcoin, and Hyperliquid. Reports native assets, supported token/collectible reads, failed or missing providers, unverified assets, timestamps, and known priced value without pretending it is a complete total. Public addresses only; never signs or broadcasts.",
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {
|
|
110
|
+
addresses: {
|
|
111
|
+
type: "object",
|
|
112
|
+
description: "Optional public wallet addresses by family. Omit configured families to use ORACLE_*_ADDRESS defaults.",
|
|
113
|
+
properties: {
|
|
114
|
+
evm: { type: "string" },
|
|
115
|
+
solana: { type: "string" },
|
|
116
|
+
bitcoin: { type: "string" },
|
|
117
|
+
hyperliquid: { type: "string" },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
evmChainIds: {
|
|
121
|
+
type: "array",
|
|
122
|
+
items: { type: "number" },
|
|
123
|
+
description: "Optional EVM chain subset. Default is every configured EVM chain.",
|
|
124
|
+
},
|
|
125
|
+
includeTokens: { type: "boolean", description: "Include supported token reads. Default true." },
|
|
126
|
+
includeCollectibles: { type: "boolean", description: "Include supported collectible reads. Default true." },
|
|
127
|
+
includePrices: { type: "boolean", description: "Fetch current DeFiLlama native prices. Default true." },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "portfolio_snapshot",
|
|
133
|
+
description:
|
|
134
|
+
"Query the current multichain balance and NFT inventory, then append one compact profile-local observation for historical tracking. Stores public-address fingerprint, coverage, breakdown, and known priced value only; no keys, signatures, executable payloads, or raw NFT metadata. Returns the live balance, NFT result, and recorded snapshot.",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
addresses: {
|
|
139
|
+
type: "object",
|
|
140
|
+
properties: {
|
|
141
|
+
evm: { type: "string" },
|
|
142
|
+
solana: { type: "string" },
|
|
143
|
+
bitcoin: { type: "string" },
|
|
144
|
+
hyperliquid: { type: "string" },
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
evmChainIds: { type: "array", items: { type: "number" } },
|
|
148
|
+
includeTokens: { type: "boolean" },
|
|
149
|
+
includeCollectibles: { type: "boolean" },
|
|
150
|
+
includePrices: { type: "boolean" },
|
|
151
|
+
includeNfts: { type: "boolean", description: "Include provider-estimated NFT value. Default true." },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "portfolio_history",
|
|
157
|
+
description:
|
|
158
|
+
"Read profile-local portfolio observations in a time range. Unknown or unavailable values remain null and are never converted to zero. Defaults to the configured public-address portfolio fingerprint.",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
addresses: {
|
|
163
|
+
type: "object",
|
|
164
|
+
properties: {
|
|
165
|
+
evm: { type: "string" },
|
|
166
|
+
solana: { type: "string" },
|
|
167
|
+
bitcoin: { type: "string" },
|
|
168
|
+
hyperliquid: { type: "string" },
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
portfolioId: { type: "string" },
|
|
172
|
+
allPortfolios: { type: "boolean", description: "Return observations for every public-address fingerprint in this profile." },
|
|
173
|
+
since: { type: "string", description: "Inclusive ISO 8601 lower bound." },
|
|
174
|
+
until: { type: "string", description: "Inclusive ISO 8601 upper bound." },
|
|
175
|
+
limit: { type: "number", description: "1-1000, default 100." },
|
|
176
|
+
order: { type: "string", enum: ["asc", "desc"] },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "portfolio_value_graph",
|
|
182
|
+
description:
|
|
183
|
+
"Render a static SVG line graph from profile-local portfolio snapshots. Plots known priced value only, labels incomplete coverage, includes provider-estimated NFT values when recorded, and omits unavailable observations instead of plotting fake zeroes.",
|
|
184
|
+
inputSchema: {
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: {
|
|
187
|
+
addresses: {
|
|
188
|
+
type: "object",
|
|
189
|
+
properties: {
|
|
190
|
+
evm: { type: "string" },
|
|
191
|
+
solana: { type: "string" },
|
|
192
|
+
bitcoin: { type: "string" },
|
|
193
|
+
hyperliquid: { type: "string" },
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
portfolioId: { type: "string" },
|
|
197
|
+
allPortfolios: { type: "boolean" },
|
|
198
|
+
since: { type: "string" },
|
|
199
|
+
until: { type: "string" },
|
|
200
|
+
limit: { type: "number", description: "Historical observations to consider, max 1000." },
|
|
201
|
+
maxPoints: { type: "number", description: "Rendered point cap, 2-500, default 200." },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "nft_inventory",
|
|
207
|
+
description:
|
|
208
|
+
"Read all discoverable NFTs for configured EVM, Solana, and Bitcoin addresses. Returns normalized assets, image URLs, estimated values, per-chain partial failures, flagged/NSFW items separated from visible items, listing coverage, and explicit unsupported chains. Read-only.",
|
|
209
|
+
inputSchema: {
|
|
210
|
+
type: "object",
|
|
211
|
+
properties: {
|
|
212
|
+
addresses: {
|
|
213
|
+
type: "object",
|
|
214
|
+
properties: {
|
|
215
|
+
evm: { type: "string" },
|
|
216
|
+
solana: { type: "string" },
|
|
217
|
+
bitcoin: { type: "string" },
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
chains: { type: "array", items: { type: "string" }, description: "Optional chain slug subset." },
|
|
221
|
+
pageSize: { type: "number", description: "OpenSea items per page, max 200." },
|
|
222
|
+
maxPages: { type: "number", description: "Maximum pages per chain, max 25." },
|
|
223
|
+
bitcoinLimit: { type: "number" },
|
|
224
|
+
includePnl: { type: "boolean", description: "Include OpenSea-indexed account PnL when available. Default true." },
|
|
225
|
+
costBasis: { type: "object", description: "Optional user-supplied assetKey -> {costUsd,feesUsd,acquiredAt,source}. Missing basis stays unavailable, never zero." },
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: "nft_gallery",
|
|
231
|
+
description:
|
|
232
|
+
"Generate one static NFT contact-sheet image page from wallet inventory or supplied normalized items. External metadata is never rendered as HTML; only allowlisted JPEG/PNG/WebP bytes are embedded and unsafe or missing media becomes a placeholder.",
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: "object",
|
|
235
|
+
properties: {
|
|
236
|
+
addresses: {
|
|
237
|
+
type: "object",
|
|
238
|
+
properties: { evm: { type: "string" }, solana: { type: "string" }, bitcoin: { type: "string" } },
|
|
239
|
+
},
|
|
240
|
+
chains: { type: "array", items: { type: "string" } },
|
|
241
|
+
items: { type: "array", items: { type: "object" }, description: "Optional normalized inventory items. Omit to scan configured wallets." },
|
|
242
|
+
page: { type: "number", description: "1-based gallery page." },
|
|
243
|
+
pageSize: { type: "number", description: "1-12 items per page." },
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: "nft_pnl",
|
|
249
|
+
description:
|
|
250
|
+
"NFT PnL with explicit methodology. Returns OpenSea-indexed account-level PnL where available plus per-item unrealized estimates only when both user-supplied acquisition cost and provider estimated current value exist. Missing history is unavailable, never zero.",
|
|
251
|
+
inputSchema: {
|
|
252
|
+
type: "object",
|
|
253
|
+
properties: {
|
|
254
|
+
addresses: {
|
|
255
|
+
type: "object",
|
|
256
|
+
properties: { evm: { type: "string" }, solana: { type: "string" }, bitcoin: { type: "string" } },
|
|
257
|
+
},
|
|
258
|
+
chains: { type: "array", items: { type: "string" } },
|
|
259
|
+
costBasis: { type: "object" },
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "nft_prepare_list",
|
|
265
|
+
description:
|
|
266
|
+
"Prepare an NFT listing for OpenSea EVM, Magic Eden Solana, or Satflow Bitcoin after explicit user review. Requires userConfirmed=true plus asset, marketplace, price, currency where applicable, and future expiry. Returns approval/signing actions, an unsigned Solana transaction, or an unsigned Bitcoin PSBT. Never signs, submits, or broadcasts.",
|
|
267
|
+
inputSchema: {
|
|
268
|
+
type: "object",
|
|
269
|
+
required: ["marketplace", "userConfirmed"],
|
|
270
|
+
properties: {
|
|
271
|
+
marketplace: { type: "string", enum: ["opensea", "magiceden", "magiceden-sol", "satflow"] },
|
|
272
|
+
userConfirmed: { type: "boolean" },
|
|
273
|
+
chain: { type: "string" },
|
|
274
|
+
seller: { type: "string" },
|
|
275
|
+
contract: { type: "string" },
|
|
276
|
+
tokenId: { type: "string" },
|
|
277
|
+
quantity: { type: "number" },
|
|
278
|
+
priceAmount: { type: "string" },
|
|
279
|
+
currency: { type: "string" },
|
|
280
|
+
endTime: { type: "string" },
|
|
281
|
+
useCreatorFee: { type: "boolean" },
|
|
282
|
+
taker: { type: "string" },
|
|
283
|
+
tokenMint: { type: "string" },
|
|
284
|
+
tokenATA: { type: "string" },
|
|
285
|
+
auctionHouse: { type: "string" },
|
|
286
|
+
priceSol: { type: "string" },
|
|
287
|
+
expiry: { description: "Future ISO 8601 timestamp or Unix seconds." },
|
|
288
|
+
inscriptionId: { type: "string" },
|
|
289
|
+
runesOutput: { type: "string" },
|
|
290
|
+
ordAddress: { type: "string" },
|
|
291
|
+
receiveAddress: { type: "string" },
|
|
292
|
+
priceSats: { type: "string" },
|
|
293
|
+
tapKey: { type: "string" },
|
|
294
|
+
collectionSlug: { type: "string" },
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
99
298
|
{
|
|
100
299
|
name: "rpc_balance",
|
|
101
300
|
description:
|
|
@@ -341,6 +540,57 @@ const tools = [
|
|
|
341
540
|
required: ["chainId", "address"],
|
|
342
541
|
},
|
|
343
542
|
},
|
|
543
|
+
{
|
|
544
|
+
name: "address_book_remember",
|
|
545
|
+
description:
|
|
546
|
+
"Remember a wallet address with a label. Call this whenever someone states their address " +
|
|
547
|
+
"or an agent/counterparty wallet is discovered, so it survives the conversation. " +
|
|
548
|
+
"Stores labels only — never private keys, seeds, or passphrases.",
|
|
549
|
+
inputSchema: {
|
|
550
|
+
type: "object",
|
|
551
|
+
required: ["address", "label"],
|
|
552
|
+
properties: {
|
|
553
|
+
address: { type: "string", description: "0x address" },
|
|
554
|
+
label: { type: "string", description: "short name, e.g. carlo-agent" },
|
|
555
|
+
who: { type: "string", description: "person or entity the wallet belongs to" },
|
|
556
|
+
role: { type: "string", description: "agent | owner | person | counterparty | venue" },
|
|
557
|
+
chainIds: { type: "array", items: { type: "number" } },
|
|
558
|
+
notes: { type: "string" },
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: "address_book_list",
|
|
564
|
+
description:
|
|
565
|
+
"List remembered wallets (owner, agent, people, counterparties). Read-only. " +
|
|
566
|
+
"Check this before sending or bridging to a person.",
|
|
567
|
+
inputSchema: {
|
|
568
|
+
type: "object",
|
|
569
|
+
properties: {
|
|
570
|
+
q: { type: "string", description: "search across label, who, address, notes" },
|
|
571
|
+
role: { type: "string" },
|
|
572
|
+
who: { type: "string" },
|
|
573
|
+
},
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
name: "address_book_lookup",
|
|
578
|
+
description: "Look up one 0x address in the durable address book. Read-only.",
|
|
579
|
+
inputSchema: {
|
|
580
|
+
type: "object",
|
|
581
|
+
required: ["address"],
|
|
582
|
+
properties: { address: { type: "string" } },
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
name: "address_book_forget",
|
|
587
|
+
description: "Remove a remembered address, optionally only one label for it.",
|
|
588
|
+
inputSchema: {
|
|
589
|
+
type: "object",
|
|
590
|
+
required: ["address"],
|
|
591
|
+
properties: { address: { type: "string" }, label: { type: "string" } },
|
|
592
|
+
},
|
|
593
|
+
},
|
|
344
594
|
{
|
|
345
595
|
name: "best_swap_route",
|
|
346
596
|
description:
|
|
@@ -464,6 +714,24 @@ const ROUTES = {
|
|
|
464
714
|
fromAddress: a.fromAddress || DEFAULT_ADDRESS || undefined,
|
|
465
715
|
},
|
|
466
716
|
}),
|
|
717
|
+
portfolio_balance: (a) => ({
|
|
718
|
+
provider: "portfolio",
|
|
719
|
+
op: "balances",
|
|
720
|
+
args: {
|
|
721
|
+
addresses: a.addresses,
|
|
722
|
+
evmChainIds: a.evmChainIds,
|
|
723
|
+
includeTokens: a.includeTokens,
|
|
724
|
+
includeCollectibles: a.includeCollectibles,
|
|
725
|
+
includePrices: a.includePrices,
|
|
726
|
+
},
|
|
727
|
+
}),
|
|
728
|
+
portfolio_snapshot: (a) => ({ provider: "portfolio", op: "snapshot", args: a }),
|
|
729
|
+
portfolio_history: (a) => ({ provider: "portfolio", op: "history", args: a }),
|
|
730
|
+
portfolio_value_graph: (a) => ({ provider: "portfolio", op: "valueGraph", args: a }),
|
|
731
|
+
nft_inventory: (a) => ({ provider: "nft-portfolio", op: "inventory", args: a }),
|
|
732
|
+
nft_gallery: (a) => ({ provider: "nft-portfolio", op: "gallery", args: a }),
|
|
733
|
+
nft_pnl: (a) => ({ provider: "nft-portfolio", op: "pnl", args: a }),
|
|
734
|
+
nft_prepare_list: (a) => ({ provider: "nft-portfolio", op: "prepareList", args: a }),
|
|
467
735
|
rpc_balance: (a) => {
|
|
468
736
|
const addr = a.address || DEFAULT_ADDRESS;
|
|
469
737
|
if (!addr) throw new Error("address required (set ORACLE_DEFAULT_ADDRESS to supply a default)");
|
|
@@ -601,6 +869,15 @@ async function callTool(name, args = {}) {
|
|
|
601
869
|
const token = args.token ? await sc.tokenBalance(args.address, args.token).catch(() => null) : null;
|
|
602
870
|
return { chainId: Number(args.chainId), address: args.address, native, token };
|
|
603
871
|
}
|
|
872
|
+
if (name === "address_book_remember") {
|
|
873
|
+
return rememberAddress({
|
|
874
|
+
address: args.address, label: args.label, who: args.who,
|
|
875
|
+
role: args.role, chainIds: args.chainIds, notes: args.notes, source: "oracle-data-mcp",
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
if (name === "address_book_list") return listAddresses({ q: args.q, role: args.role, who: args.who });
|
|
879
|
+
if (name === "address_book_lookup") return lookupAddress(args.address);
|
|
880
|
+
if (name === "address_book_forget") return forgetAddress(args.address, args.label || null);
|
|
604
881
|
if (name === "data_catalog") return httpJson(`${DATA_URL}/data/catalog`);
|
|
605
882
|
if (name === "data_health") return httpJson(`${DATA_URL}/data/health`);
|
|
606
883
|
if (name === "data_call") {
|
|
@@ -637,7 +914,21 @@ async function handle(request) {
|
|
|
637
914
|
if (method === "tools/call") {
|
|
638
915
|
try {
|
|
639
916
|
const result = await callTool(params?.name, params?.arguments || {});
|
|
640
|
-
|
|
917
|
+
if (result?.dataBase64 && /^image\/(?:svg\+xml|png|jpeg|webp)$/.test(String(result.mimeType || ""))) {
|
|
918
|
+
const { dataBase64, ...metadata } = result;
|
|
919
|
+
send({
|
|
920
|
+
jsonrpc: "2.0",
|
|
921
|
+
id,
|
|
922
|
+
result: {
|
|
923
|
+
content: [
|
|
924
|
+
{ type: "image", data: dataBase64, mimeType: result.mimeType },
|
|
925
|
+
{ type: "text", text: JSON.stringify(metadata, null, 2) },
|
|
926
|
+
],
|
|
927
|
+
},
|
|
928
|
+
});
|
|
929
|
+
} else {
|
|
930
|
+
send({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] } });
|
|
931
|
+
}
|
|
641
932
|
} catch (err) {
|
|
642
933
|
send({ jsonrpc: "2.0", id, result: { isError: true, content: [{ type: "text", text: err.message }] } });
|
|
643
934
|
}
|
|
@@ -666,7 +957,20 @@ function startStdioLoop() {
|
|
|
666
957
|
});
|
|
667
958
|
}
|
|
668
959
|
|
|
669
|
-
|
|
960
|
+
// npm bin wrappers are symlinks. Comparing import.meta.url to argv[1] as strings
|
|
961
|
+
// fails for `npx oracle-data-mcp` / node_modules/.bin/oracle-data-mcp and the
|
|
962
|
+
// process exits without ever starting the stdio loop (Hermes sees Connection closed).
|
|
963
|
+
function isMainModule() {
|
|
964
|
+
const entry = process.argv[1];
|
|
965
|
+
if (!entry) return false;
|
|
966
|
+
try {
|
|
967
|
+
return fs.realpathSync(entry) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
968
|
+
} catch {
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
if (isMainModule()) {
|
|
670
974
|
startStdioLoop();
|
|
671
975
|
}
|
|
672
976
|
|