@oracle-agent/oracle 0.1.0 → 0.2.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 +37 -10
- package/SECURITY.md +11 -11
- package/SETUP.md +63 -16
- package/bin/desk-server.mjs +3 -1
- package/docs/architecture.md +10 -9
- package/package.json +5 -7
- package/src/data/desk-data.mjs +16 -0
- package/src/data/providers/bitcoin-esplora.mjs +2 -16
- package/src/data/providers/cowswap.mjs +9 -96
- package/src/data/providers/evm-rpc.mjs +19 -0
- package/src/data/providers/hl-assets.mjs +165 -0
- package/src/data/providers/hl-info.mjs +7 -1
- package/src/data/providers/hl-outcome.mjs +61 -0
- package/src/data/providers/hl-perps.mjs +40 -35
- package/src/data/providers/magiceden-sol.mjs +7 -6
- package/src/data/providers/poly-clob.mjs +460 -0
- package/src/data/providers/satflow.mjs +5 -4
- package/src/data/providers/solana-rpc.mjs +9 -0
- package/src/data/providers/uniswap-v3.mjs +3 -2
- package/src/index.mjs +1 -0
- package/src/onboarding/agent-keys.mjs +4 -0
- package/src/prepare-envelope.mjs +108 -0
- package/src/public-control/bundler-client.mjs +3 -18
package/README.md
CHANGED
|
@@ -11,13 +11,26 @@ open-source agent runtime from [Nous Research](https://nousresearch.com).
|
|
|
11
11
|
Oracle itself makes **no model calls and needs no API key**. It is the tool layer
|
|
12
12
|
an agent drives. Bring whatever model your Hermes is already using.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
broadcasts.
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
This package is **prepare-only**: it never takes a private key and never
|
|
15
|
+
broadcasts. For agentic trading with **your** keys on **your** machine, install
|
|
16
|
+
the companion [`@oracle-agent/operator`](https://github.com/demi-hl/oracle-operator)
|
|
17
|
+
alongside it — same split we run ourselves.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# prepare-only (safe default)
|
|
25
|
+
npm i @oracle-agent/oracle
|
|
26
|
+
|
|
27
|
+
# full self-host lane (keys stay on YOUR machine) — same model we run
|
|
28
|
+
npm i @oracle-agent/oracle @oracle-agent/operator
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
See the operator [SETUP](https://github.com/demi-hl/oracle-operator/blob/main/SETUP.md)
|
|
32
|
+
for local key vault + Hyperliquid sign-and-submit.
|
|
33
|
+
|
|
21
34
|
## What Oracle is
|
|
22
35
|
|
|
23
36
|
Most "AI crypto agent" projects give a model a hot wallet and hope. Oracle does
|
|
@@ -78,9 +91,9 @@ Three planes, and the boundary between them is mechanically enforced:
|
|
|
78
91
|
- **Policy plane** (public) — destination allowlists, slippage guards, route and
|
|
79
92
|
vault attestations, grant schema. Holds no keys; constrains what a signer may
|
|
80
93
|
be asked to do.
|
|
81
|
-
- **Exec plane** (
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
- **Exec plane** (optional companion) - signing and broadcast live in
|
|
95
|
+
[`@oracle-agent/operator`](https://github.com/demi-hl/oracle-operator) on
|
|
96
|
+
your machine with your key. Not in this package or its npm artifact.
|
|
84
97
|
|
|
85
98
|
`test/custody-boundary.test.mjs` walks the import graph and fails if any public
|
|
86
99
|
module reaches wallet key material or a house signer. The split is a test, not a
|
|
@@ -146,7 +159,7 @@ allowlisted the wrong contract.
|
|
|
146
159
|
| Intents | CoW Protocol |
|
|
147
160
|
| Perps / lending / yield | GMX v2, Morpho, Pendle |
|
|
148
161
|
| Bridges | Across, Hop, Relay |
|
|
149
|
-
| Venues | Hyperliquid (
|
|
162
|
+
| Venues | Hyperliquid (core perps + HIP-3 builder dexs + HIP-4 outcomes), Polymarket (read + local CLOB prepare) |
|
|
150
163
|
| NFT | OpenSea, Satflow, Magic Eden (Solana) |
|
|
151
164
|
|
|
152
165
|
Every provider and every scanner capability declares an honest tier —
|
|
@@ -194,8 +207,11 @@ capability-labeled per chain; unconfigured venues stay unavailable instead of fa
|
|
|
194
207
|
|
|
195
208
|
## Install
|
|
196
209
|
|
|
197
|
-
|
|
198
|
-
|
|
210
|
+
```bash
|
|
211
|
+
npm install @oracle-agent/oracle # Node >= 20.19.0
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Or from source, if you want to run the suite:
|
|
199
215
|
|
|
200
216
|
```bash
|
|
201
217
|
git clone https://github.com/demi-hl/oracle.git
|
|
@@ -382,3 +398,14 @@ oracle-data-mcp
|
|
|
382
398
|
|
|
383
399
|
The code is open source. The **Oracle** name and marks are reserved; please
|
|
384
400
|
don't imply endorsement by an official deployment when shipping a fork.
|
|
401
|
+
|
|
402
|
+
### Operator hardening (0.4+)
|
|
403
|
+
|
|
404
|
+
Prefer the local signer daemon so the agent never sees the vault passphrase:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
npx oracle-signer # has ORACLE_VAULT_PASSPHRASE
|
|
408
|
+
# agent: ORACLE_SIGNER_URL + ORACLE_SIGNER_TOKEN only
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
See [oracle-operator SETUP](https://github.com/demi-hl/oracle-operator/blob/main/SETUP.md).
|
package/SECURITY.md
CHANGED
|
@@ -70,20 +70,20 @@ A security claim that isn't enforced by a test is just a comment.
|
|
|
70
70
|
|
|
71
71
|
## Residual operator notes
|
|
72
72
|
|
|
73
|
-
These are not public-package custody holes. They are operator
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
1. **Owner-local signer modules
|
|
77
|
-
`
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
These are not public-package custody holes. They are operator constraints that
|
|
74
|
+
still apply after the public boundary hardening:
|
|
75
|
+
|
|
76
|
+
1. **Owner-local signer modules live in the public companion**
|
|
77
|
+
[`@oracle-agent/operator`](https://github.com/demi-hl/oracle-operator)
|
|
78
|
+
(`hl-exec`, `key-vault`, `oracle-vault`). Keys stay on the operator's
|
|
79
|
+
machine. They must not be restored into this prepare-only package or its
|
|
80
|
+
npm artifact. CI fails if a pack of *this* repo includes those paths.
|
|
81
81
|
2. **Prepare helpers that validate executable routes require an attestation
|
|
82
82
|
secret** (`requireSigned`) so they cannot be used as a softer pre-broadcast
|
|
83
83
|
gate than `enforceTxPolicy`.
|
|
84
84
|
3. **Daily spend ledger locks are pid + TTL based.** A live owner holds the
|
|
85
85
|
lock; a dead pid or expired lock is reclaimed. Override TTL with
|
|
86
86
|
`ORACLE_LEDGER_LOCK_TTL_MS` only if you know you need a longer window.
|
|
87
|
-
4. **GitHub branch protection,
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
4. **GitHub process controls** (branch protection, CodeQL, secret scanning,
|
|
88
|
+
Dependabot) are enabled on this public repo and are independent of the
|
|
89
|
+
package boundary tests.
|
package/SETUP.md
CHANGED
|
@@ -5,8 +5,11 @@ a wallet private key. Prepared actions are signed in the user's wallet.
|
|
|
5
5
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
```bash
|
|
9
|
+
npm install @oracle-agent/oracle # Node >= 20.19.0
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Or from source, if you want to run the suite:
|
|
10
13
|
|
|
11
14
|
```bash
|
|
12
15
|
git clone https://github.com/demi-hl/oracle.git
|
|
@@ -22,7 +25,7 @@ Oracle has two distribution boundaries and they are separate on purpose:
|
|
|
22
25
|
| Plane | What it does | Needs a key? |
|
|
23
26
|
|---|---|---|
|
|
24
27
|
| **Read / prepare** | Market data, quotes, and *unsigned* transactions or EIP-712 actions | No, mostly |
|
|
25
|
-
| **Owner-local source lane** | Optional
|
|
28
|
+
| **Owner-local source lane** | Optional companion `@oracle-agent/operator` — keys on YOUR machine | Yes |
|
|
26
29
|
|
|
27
30
|
A prepared object is inert. It becomes real only when something signs it. You
|
|
28
31
|
choose whether that something is your browser wallet or separately operated
|
|
@@ -129,8 +132,11 @@ Keeps the secret out of the process environment.
|
|
|
129
132
|
|
|
130
133
|
### 3. An encrypted vault (recommended)
|
|
131
134
|
|
|
135
|
+
Install the companion and use its CLI (not shipped in this package):
|
|
136
|
+
|
|
132
137
|
```bash
|
|
133
|
-
|
|
138
|
+
npm i -g @oracle-agent/operator # or npm i -D in your project
|
|
139
|
+
oracle-vault encrypt ~/.config/oracle/keys/hl.key
|
|
134
140
|
# writes hl.key.vault.json (scrypt N=2^17 + AES-256-GCM)
|
|
135
141
|
# moves hl.key -> hl.key.plaintext.bak
|
|
136
142
|
|
|
@@ -138,23 +144,22 @@ export ORACLE_VAULT_PASSPHRASE="..." # or be prompted
|
|
|
138
144
|
export HL_KEY_FILE=~/.config/oracle/keys/hl.key.vault.json
|
|
139
145
|
|
|
140
146
|
# verify it unlocks, THEN destroy the plaintext backup
|
|
141
|
-
|
|
147
|
+
oracle-vault inspect ~/.config/oracle/keys/hl.key.vault.json
|
|
142
148
|
shred -u ~/.config/oracle/keys/hl.key.plaintext.bak
|
|
143
149
|
```
|
|
144
150
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
is a test asserting exactly that.
|
|
151
|
+
The operator vault decrypts in memory only. The key is never written back to
|
|
152
|
+
disk, never logged, and never included in any returned object.
|
|
148
153
|
|
|
149
154
|
**What the vault does and does not protect.** It defends against a key file read
|
|
150
155
|
by something that is not you: a synced backup, a stolen laptop at rest, an
|
|
151
156
|
overly-broad `chmod`, a leaked tarball. It does **not** defend against malware
|
|
152
|
-
running as your user while
|
|
153
|
-
need that, use a hardware wallet and the prepare-only path.
|
|
157
|
+
running as your user while the vault is unlocked. Nothing file-based does. If
|
|
158
|
+
you need that, use a hardware wallet and the prepare-only path.
|
|
154
159
|
|
|
155
160
|
```bash
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
oracle-vault rekey <vault> # rotate the passphrase
|
|
162
|
+
oracle-vault inspect <vault> # show KDF params, no secret
|
|
158
163
|
```
|
|
159
164
|
|
|
160
165
|
---
|
|
@@ -178,10 +183,10 @@ const order = await data.call("hl-perps", "prepareOrder", {
|
|
|
178
183
|
|
|
179
184
|
### B. Owner-local execution
|
|
180
185
|
|
|
181
|
-
The public `data.call()` facade refuses `hl-exec`.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
The public `data.call()` facade refuses `hl-exec`. Operator signing lives in
|
|
187
|
+
[`@oracle-agent/operator`](https://github.com/demi-hl/oracle-operator) on your
|
|
188
|
+
machine with your key. Import `hlSignAndSubmit` from that package — never from
|
|
189
|
+
this one. Do not expose it on a public HTTP port.
|
|
185
190
|
|
|
186
191
|
The public prepare path hard-caps slippage at 100 bps, bounds leverage by the
|
|
187
192
|
asset maximum, rounds sizes and prices to venue precision, and makes brackets
|
|
@@ -233,3 +238,45 @@ routing is what makes running several practical instead of theoretical.
|
|
|
233
238
|
|
|
234
239
|
Everything still works fine with a single provider. It is just better with a
|
|
235
240
|
router that knows which brain to use for which job.
|
|
241
|
+
|
|
242
|
+
### Operator hardening (0.4+)
|
|
243
|
+
|
|
244
|
+
Prefer the local signer daemon so the agent never sees the vault passphrase:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
npx oracle-signer # has ORACLE_VAULT_PASSPHRASE
|
|
248
|
+
# agent: ORACLE_SIGNER_URL + ORACLE_SIGNER_TOKEN only
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
See [oracle-operator SETUP](https://github.com/demi-hl/oracle-operator/blob/main/SETUP.md).
|
|
252
|
+
|
|
253
|
+
## Local HIP-3 / HIP-4 / Polymarket (no VPS)
|
|
254
|
+
|
|
255
|
+
### Hyperliquid HIP-3 (builder perps)
|
|
256
|
+
```js
|
|
257
|
+
const prep = await data.call("hl-perps", "prepareOrder", {
|
|
258
|
+
coin: "xyz:TSLA", side: "buy", type: "limit", price: "250", size: "1",
|
|
259
|
+
});
|
|
260
|
+
await hlSignAndSubmit(prep, { keyFile, userInitiated: true });
|
|
261
|
+
// or signViaDaemon({ surface: "hl", prepared: prep, userInitiated: true })
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Hyperliquid HIP-4 (outcomes)
|
|
265
|
+
```js
|
|
266
|
+
const prep = await data.call("hl-outcome", "prepareOrder", {
|
|
267
|
+
outcome: 969, side: "yes", // or coin: "#9690"
|
|
268
|
+
side: "buy", type: "limit", price: "0.42", size: "20",
|
|
269
|
+
});
|
|
270
|
+
await hlSignAndSubmit(prep, { keyFile, userInitiated: true });
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Polymarket (local CLOB)
|
|
274
|
+
```js
|
|
275
|
+
const prep = await data.call("poly-clob", "prepareOrder", {
|
|
276
|
+
tokenId, side: "BUY", price: 0.45, size: 10,
|
|
277
|
+
maker: eoa, signer: eoa, tickSize: "0.01",
|
|
278
|
+
});
|
|
279
|
+
// signer process needs POLYMARKET_API_KEY/SECRET/PASSPHRASE + key
|
|
280
|
+
await polySignAndSubmit(prep, { keyFile, userInitiated: true });
|
|
281
|
+
// or signViaDaemon({ surface: "poly", prepared: prep, userInitiated: true })
|
|
282
|
+
```
|
package/bin/desk-server.mjs
CHANGED
|
@@ -421,7 +421,9 @@ const server = createServer(async (req, res) => {
|
|
|
421
421
|
if (e instanceof HttpError) {
|
|
422
422
|
return send(res, e.status, { error: e.code, message: e.message });
|
|
423
423
|
}
|
|
424
|
-
|
|
424
|
+
// Never echo raw Error objects or stacks to the client.
|
|
425
|
+
console.error("desk-server internal error:", e && e.stack ? e.stack : e);
|
|
426
|
+
return send(res, 500, { error: "internal-error" });
|
|
425
427
|
}
|
|
426
428
|
});
|
|
427
429
|
|
package/docs/architecture.md
CHANGED
|
@@ -22,8 +22,9 @@ Three planes. The boundary between them is enforced by a test, not a convention.
|
|
|
22
22
|
x no import may cross
|
|
23
23
|
|
|
|
24
24
|
+-------------------------------------------+
|
|
25
|
-
|
|
|
26
|
-
|
|
|
25
|
+
| OWNER-LOCAL (companion package) |
|
|
26
|
+
| @oracle-agent/operator |
|
|
27
|
+
| key-vault / hl-exec / oracle-vault |
|
|
27
28
|
+-------------------------------------------+
|
|
28
29
|
```
|
|
29
30
|
|
|
@@ -41,10 +42,10 @@ That is why the boundary is a test (`test/custody-boundary.test.mjs`) rather tha
|
|
|
41
42
|
paragraph in a README. It walks the import graph from every shipped entrypoint and
|
|
42
43
|
fails on:
|
|
43
44
|
|
|
44
|
-
- a private module present in the tree
|
|
45
45
|
- any public module importing signer or key material
|
|
46
|
-
- a dangling local import
|
|
46
|
+
- a dangling local import that reaches a signer from a shipped entrypoint
|
|
47
47
|
- a secret-shaped literal anywhere
|
|
48
|
+
- a packed npm artifact that still contains signer/vault modules
|
|
48
49
|
|
|
49
50
|
## Data plane
|
|
50
51
|
|
|
@@ -127,9 +128,9 @@ No lane pins a model. `profile.json` carries a capability *class*
|
|
|
127
128
|
(`strong-reasoner`, `fast-tool-caller`), so the installer never writes a vendor into
|
|
128
129
|
a user's config.
|
|
129
130
|
|
|
130
|
-
## What is not
|
|
131
|
+
## What is not in the public package
|
|
131
132
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
to steal.
|
|
133
|
+
House custody, an always-on exec server, and capability minting are not shipped.
|
|
134
|
+
Owner-local signer modules (`key-vault`, `hl-exec`, `oracle-vault`) live in the
|
|
135
|
+
companion package `@oracle-agent/operator` on the user's machine. This prepare
|
|
136
|
+
package has nothing to steal: it never takes a key and never broadcasts.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oracle-agent/oracle",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Oracle: prepare-only multichain agent control plane. Policy-bounded intents for a user-signed wallet. Self-custody by default
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Oracle: prepare-only multichain agent control plane. Policy-bounded intents for a user-signed wallet. Self-custody by default \u2014 the public package never takes your key. Built for Hermes; no model key required.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"publishConfig": {
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"./chains": "./src/chains.mjs",
|
|
41
41
|
"./scanner": "./src/scanner/index.mjs",
|
|
42
42
|
"./router": "./src/router/index.mjs",
|
|
43
|
-
"./nft-gas-war": "./src/nft-gas-war-guard.mjs"
|
|
43
|
+
"./nft-gas-war": "./src/nft-gas-war-guard.mjs",
|
|
44
|
+
"./prepare-envelope": "./src/prepare-envelope.mjs"
|
|
44
45
|
},
|
|
45
46
|
"scripts": {
|
|
46
47
|
"test": "ORACLE_TEST_ISOLATE_SECRETS=1 ORACLE_ROUTE_ATTESTATION_SECRET=oracle-unit-test-secret node --test test/*.test.mjs",
|
|
@@ -82,9 +83,6 @@
|
|
|
82
83
|
"CONTRIBUTING.md",
|
|
83
84
|
"docs/",
|
|
84
85
|
"examples/",
|
|
85
|
-
"scripts/"
|
|
86
|
-
"!bin/oracle-vault.mjs",
|
|
87
|
-
"!src/key-vault.mjs",
|
|
88
|
-
"!src/data/providers/hl-exec.mjs"
|
|
86
|
+
"scripts/"
|
|
89
87
|
]
|
|
90
88
|
}
|
package/src/data/desk-data.mjs
CHANGED
|
@@ -27,6 +27,8 @@ import * as hevm from "./providers/hyperevm-dex.mjs";
|
|
|
27
27
|
import * as mesol from "./providers/magiceden-sol.mjs";
|
|
28
28
|
import * as hlstake from "./providers/hl-staking.mjs";
|
|
29
29
|
import * as hlmarkets from "./providers/hl-markets.mjs";
|
|
30
|
+
import * as hloutcome from "./providers/hl-outcome.mjs";
|
|
31
|
+
import * as polyclob from "./providers/poly-clob.mjs";
|
|
30
32
|
import * as hlperps from "./providers/hl-perps.mjs";
|
|
31
33
|
import * as gecko from "./providers/geckoterminal.mjs";
|
|
32
34
|
import * as curve from "./providers/curve.mjs";
|
|
@@ -211,6 +213,17 @@ const OPS = {
|
|
|
211
213
|
floor: (o, a = {}) => osnft.openseaFloor(a.slug, o),
|
|
212
214
|
},
|
|
213
215
|
|
|
216
|
+
"hl-outcome": {
|
|
217
|
+
list: (o) => hloutcome.hlOutcomeList(o),
|
|
218
|
+
prepareOrder: (o, a = {}) => hloutcome.hlPrepareOutcomeOrder(a, o),
|
|
219
|
+
prepareCancel: (o, a = {}) => hloutcome.hlPrepareOutcomeCancel(a, o),
|
|
220
|
+
},
|
|
221
|
+
"poly-clob": {
|
|
222
|
+
prepareOrder: (o, a = {}) => {
|
|
223
|
+
// prepare is sync; wrap for dataCall
|
|
224
|
+
return polyclob.polyPrepareOrder(a || {});
|
|
225
|
+
},
|
|
226
|
+
},
|
|
214
227
|
"hl-perps": {
|
|
215
228
|
health: (o) => hlmarkets.hlMarketsHealth(o),
|
|
216
229
|
assetInfo: (o, a = {}) => hlperps.hlPerpAssetInfo(a, o),
|
|
@@ -529,6 +542,9 @@ export const data = {
|
|
|
529
542
|
price: (tokenId, side, o) => dataCall("poly-public", "price", { tokenId, side }, o),
|
|
530
543
|
events: (a, o) => dataCall("poly-public", "events", a || {}, o),
|
|
531
544
|
},
|
|
545
|
+
polyClob: {
|
|
546
|
+
prepareOrder: (a, o) => dataCall("poly-clob", "prepareOrder", a || {}, o),
|
|
547
|
+
},
|
|
532
548
|
rh: {
|
|
533
549
|
health: (o) => dataCall("rh-agent", "health", {}, o),
|
|
534
550
|
policy: (o) => dataCall("rh-agent", "policy", {}, o),
|
|
@@ -208,22 +208,8 @@ export async function btcDecodeTxHex(args = {}, opts = {}) {
|
|
|
208
208
|
* broadcast, so nothing goes out as a side effect of a read path. Oracle never
|
|
209
209
|
* signs here; the caller supplies the bytes.
|
|
210
210
|
*/
|
|
211
|
-
export async function btcBroadcast(
|
|
212
|
-
|
|
213
|
-
throw new Error("bitcoin-esplora: broadcast requires execute:true — refusing an implicit send");
|
|
214
|
-
}
|
|
215
|
-
const txHex = btcTxHex(args.txHex || args.hex || args.signedTransaction, "txHex");
|
|
216
|
-
const txid = await postText("/tx", txHex, opts);
|
|
217
|
-
if (!/^[0-9a-f]{64}$/i.test(txid)) {
|
|
218
|
-
throw new Error(`bitcoin-esplora: unexpected broadcast response ${String(txid).slice(0, 80)}`);
|
|
219
|
-
}
|
|
220
|
-
return {
|
|
221
|
-
provider: "bitcoin-esplora",
|
|
222
|
-
network: network(opts),
|
|
223
|
-
sent: true,
|
|
224
|
-
txid: txid.toLowerCase(),
|
|
225
|
-
signedTransactionHex: txHex,
|
|
226
|
-
};
|
|
211
|
+
export async function btcBroadcast(_a = {}, _o = {}) {
|
|
212
|
+
throw new Error("bitcoin-esplora.btcBroadcast refused: @oracle-agent/oracle is prepare-only. Broadcast from the user wallet or a local operator process.");
|
|
227
213
|
}
|
|
228
214
|
|
|
229
215
|
// Keep httpJson import usable for Core RPC optional path later without circular deps.
|
|
@@ -331,72 +331,12 @@ export function cowOrderUid(intent = {}) {
|
|
|
331
331
|
return toCowOrderUid({ digest, owner, validTo: message.validTo });
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
-
export async function cowSignOrder(
|
|
335
|
-
|
|
336
|
-
const typedData = intent.typedData;
|
|
337
|
-
if (!typedData?.domain || !typedData?.types || !typedData?.message) throw new Error("cowswap: typedData order intent required");
|
|
338
|
-
if (typedData.primaryType && typedData.primaryType !== "Order") throw new Error("cowswap: primaryType must be Order");
|
|
339
|
-
const owner = addr(intent.owner || opts.signerAddress, "owner");
|
|
340
|
-
assertCowOrderGuard(intent, typedData, owner, opts);
|
|
341
|
-
if (!opts.signTypedData || typeof opts.signTypedData !== "function") throw new Error("cowswap: signTypedData callback required");
|
|
342
|
-
const signature = await opts.signTypedData(typedData);
|
|
343
|
-
const recovered = addr(verifyTypedData(typedData.domain, typedData.types, typedData.message, signature), "recovered signer");
|
|
344
|
-
if (recovered !== owner) throw new Error("cowswap: recovered signer mismatch");
|
|
345
|
-
const uid = cowOrderUid({ ...intent, owner });
|
|
346
|
-
return {
|
|
347
|
-
provider: "cowswap",
|
|
348
|
-
chainId: Number(intent.chainId ?? typedData.domain.chainId),
|
|
349
|
-
owner,
|
|
350
|
-
signingScheme: "eip712",
|
|
351
|
-
signature,
|
|
352
|
-
uid,
|
|
353
|
-
quoteId: intent.quoteId ?? intent.cowGuard?.quoteId ?? null,
|
|
354
|
-
order: typedData.message,
|
|
355
|
-
typedData,
|
|
356
|
-
cowGuard: intent.cowGuard || null,
|
|
357
|
-
executableArtifact: true,
|
|
358
|
-
submitted: false,
|
|
359
|
-
};
|
|
334
|
+
export async function cowSignOrder(_a = {}, _o = {}) {
|
|
335
|
+
throw new Error("cowswap.cowSignOrder refused: @oracle-agent/oracle is prepare-only. Sign with the user wallet; do not pass private keys here.");
|
|
360
336
|
}
|
|
361
337
|
|
|
362
|
-
export async function cowSubmitSignedOrder(
|
|
363
|
-
|
|
364
|
-
const chainId = Number(signed.chainId ?? signed.typedData?.domain?.chainId ?? 1);
|
|
365
|
-
const order = signed.order || signed.typedData?.message;
|
|
366
|
-
if (!order) throw new Error("cowswap: signed order message required");
|
|
367
|
-
const owner = addr(signed.owner || signed.from, "owner");
|
|
368
|
-
const signature = String(signed.signature || "").trim();
|
|
369
|
-
if (!/^0x[0-9a-fA-F]+$/.test(signature)) throw new Error("cowswap: signature hex required");
|
|
370
|
-
const typedData = signed.typedData || { domain: orderDomain(chainId), types: COW_ORDER_TYPES, message: order };
|
|
371
|
-
assertCowOrderGuard({ ...signed, chainId, typedData, cowGuard: signed.cowGuard }, typedData, owner, opts);
|
|
372
|
-
const recovered = addr(verifyTypedData(typedData.domain, typedData.types, typedData.message, signature), "recovered signer");
|
|
373
|
-
if (recovered !== owner) throw new Error("cowswap: recovered signer mismatch");
|
|
374
|
-
const uid = orderUid(signed.uid || cowOrderUid({ chainId, owner, typedData }));
|
|
375
|
-
const body = {
|
|
376
|
-
...order,
|
|
377
|
-
signature,
|
|
378
|
-
signingScheme: signed.signingScheme || "eip712",
|
|
379
|
-
from: owner,
|
|
380
|
-
...(signed.quoteId != null ? { quoteId: signed.quoteId } : {}),
|
|
381
|
-
};
|
|
382
|
-
const acceptedUid = await httpJson(`${host(chainId, opts)}/orders`, {
|
|
383
|
-
method: "POST",
|
|
384
|
-
body,
|
|
385
|
-
fetchImpl: opts.fetchImpl,
|
|
386
|
-
timeoutMs: opts.timeoutMs ?? 15_000,
|
|
387
|
-
});
|
|
388
|
-
const accepted = orderUid(typeof acceptedUid === "string" ? acceptedUid : acceptedUid?.uid || uid);
|
|
389
|
-
if (accepted !== uid) throw new Error("cowswap: accepted UID mismatch from orderbook");
|
|
390
|
-
return {
|
|
391
|
-
ok: true,
|
|
392
|
-
provider: "cowswap",
|
|
393
|
-
chainId,
|
|
394
|
-
uid: accepted,
|
|
395
|
-
expectedUid: uid,
|
|
396
|
-
owner,
|
|
397
|
-
submitted: true,
|
|
398
|
-
response: acceptedUid,
|
|
399
|
-
};
|
|
338
|
+
export async function cowSubmitSignedOrder(_a = {}, _o = {}) {
|
|
339
|
+
throw new Error("cowswap.cowSubmitSignedOrder refused: @oracle-agent/oracle is prepare-only. Submit signed orders from the user wallet or local operator.");
|
|
400
340
|
}
|
|
401
341
|
|
|
402
342
|
export function cowPrepareCancel(q = {}) {
|
|
@@ -418,42 +358,15 @@ export function cowPrepareCancel(q = {}) {
|
|
|
418
358
|
};
|
|
419
359
|
}
|
|
420
360
|
|
|
421
|
-
export async function cowSignCancel(
|
|
422
|
-
|
|
423
|
-
const typedData = cancel.typedData;
|
|
424
|
-
if (!typedData?.domain || !typedData?.types || !typedData?.message) throw new Error("cowswap: cancellation typedData required");
|
|
425
|
-
if (typedData.primaryType && typedData.primaryType !== "OrderCancellations") throw new Error("cowswap: primaryType must be OrderCancellations");
|
|
426
|
-
const owner = addr(cancel.owner || opts.signerAddress, "owner");
|
|
427
|
-
if (!opts.signTypedData || typeof opts.signTypedData !== "function") throw new Error("cowswap: signTypedData callback required");
|
|
428
|
-
const signature = await opts.signTypedData(typedData);
|
|
429
|
-
const recovered = addr(verifyTypedData(typedData.domain, typedData.types, typedData.message, signature), "recovered signer");
|
|
430
|
-
if (recovered !== owner) throw new Error("cowswap: recovered signer mismatch");
|
|
431
|
-
return {
|
|
432
|
-
...cancel,
|
|
433
|
-
owner,
|
|
434
|
-
signingScheme: "eip712",
|
|
435
|
-
signature,
|
|
436
|
-
executableArtifact: true,
|
|
437
|
-
};
|
|
361
|
+
export async function cowSignCancel(_a = {}, _o = {}) {
|
|
362
|
+
throw new Error("cowswap.cowSignCancel refused: @oracle-agent/oracle is prepare-only. Cancel-sign with the user wallet.");
|
|
438
363
|
}
|
|
439
364
|
|
|
440
|
-
export async function cowCancelOrders(
|
|
441
|
-
|
|
442
|
-
const chainId = Number(cancel.chainId ?? cancel.typedData?.domain?.chainId ?? 1);
|
|
443
|
-
const orderUids = (cancel.orderUids || cancel.typedData?.message?.orderUids || []).map((u, i) => orderUid(u, `orderUids[${i}]`));
|
|
444
|
-
if (!orderUids.length) throw new Error("cowswap: orderUids required");
|
|
445
|
-
const signature = String(cancel.signature || "").trim();
|
|
446
|
-
if (!/^0x[0-9a-fA-F]+$/.test(signature)) throw new Error("cowswap: cancellation signature hex required");
|
|
447
|
-
const body = { orderUids, signature, signingScheme: cancel.signingScheme || "eip712" };
|
|
448
|
-
const response = await httpJson(`${host(chainId, opts)}/orders`, {
|
|
449
|
-
method: "DELETE",
|
|
450
|
-
body,
|
|
451
|
-
fetchImpl: opts.fetchImpl,
|
|
452
|
-
timeoutMs: opts.timeoutMs ?? 15_000,
|
|
453
|
-
});
|
|
454
|
-
return { ok: true, provider: "cowswap", chainId, orderUids, cancelled: true, response };
|
|
365
|
+
export async function cowCancelOrders(_a = {}, _o = {}) {
|
|
366
|
+
throw new Error("cowswap.cowCancelOrders refused: @oracle-agent/oracle is prepare-only. Cancel from the user wallet or local operator.");
|
|
455
367
|
}
|
|
456
368
|
|
|
369
|
+
|
|
457
370
|
function tradeBuyAmount(trade = {}) {
|
|
458
371
|
return uintString(trade.buyAmount ?? trade.executedBuyAmount ?? "0", "trade.buyAmount");
|
|
459
372
|
}
|
|
@@ -60,7 +60,26 @@ export function resolveRpcUrl(chainId, { rpcUrl, env = process.env } = {}) {
|
|
|
60
60
|
* @param {string} method
|
|
61
61
|
* @param {any[]} [params]
|
|
62
62
|
*/
|
|
63
|
+
|
|
64
|
+
/** Prepare-only: write/settle JSON-RPC methods are hard-refused even on deep import. */
|
|
65
|
+
const EVM_WRITE_RPC = new Set([
|
|
66
|
+
"eth_sendTransaction",
|
|
67
|
+
"eth_sendRawTransaction",
|
|
68
|
+
"eth_sign",
|
|
69
|
+
"eth_signTransaction",
|
|
70
|
+
"eth_signTypedData",
|
|
71
|
+
"eth_signTypedData_v4",
|
|
72
|
+
"personal_sign",
|
|
73
|
+
]);
|
|
74
|
+
|
|
63
75
|
export async function rpcCall(chainId, method, params = [], opts = {}) {
|
|
76
|
+
const methodName = String(method || "");
|
|
77
|
+
if (EVM_WRITE_RPC.has(methodName) || /send|sign/i.test(methodName) && !/^eth_get|^eth_call|^eth_block|^eth_chain|^eth_fee|^eth_gas|^eth_max|^eth_sync|^net_|^web3_/.test(methodName)) {
|
|
78
|
+
// allowlist is positive for common reads; anything send/sign-shaped dies.
|
|
79
|
+
if (EVM_WRITE_RPC.has(methodName) || methodName.startsWith("eth_send") || methodName.includes("sign")) {
|
|
80
|
+
throw new Error(`evm-rpc: ${methodName} refused — @oracle-agent/oracle is prepare-only (no broadcast/sign)`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
64
83
|
const url = resolveRpcUrl(chainId, opts);
|
|
65
84
|
if (!url) {
|
|
66
85
|
throw new Error(
|