@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 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
- The **public package is prepare-only**: it never takes a private key and never
15
- broadcasts. Owner-local signer modules may exist in a private tree for operator
16
- deployments; they are excluded from the npm artifact and from every public
17
- entrypoint.
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** (owner-local source only) - signing and broadcast modules exist
82
- for private operator deployments, but are excluded from every public package
83
- entrypoint and from the npm artifact.
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 (incl. HyperCore staking), Polymarket |
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
- The npm package is intentionally unpublished while release hardening is in
198
- progress. Install from source:
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 / release-process
74
- constraints that still apply after the public boundary hardening:
75
-
76
- 1. **Owner-local signer modules may exist in the git tree** (`hl-exec`,
77
- `key-vault`, `oracle-vault`) and are excluded from public entrypoints and the
78
- npm artifact. Do not restore them to the public package for compatibility.
79
- Before making the repository public, keep that exclusion intentional or move
80
- the modules to a private operator repo.
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, code scanning, and vulnerability alerts** are
88
- process controls outside this package. Enable them on `main` before treating
89
- a public release as fully hardened operations.
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
- The npm package is intentionally unpublished while release hardening is in
9
- progress. Install the source checkout with Node 20.19 or newer:
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 private operator signing code, excluded from npm and public entrypoints | Yes |
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
- node bin/oracle-vault.mjs encrypt ~/.config/oracle/keys/hl.key
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
- node bin/oracle-vault.mjs inspect ~/.config/oracle/keys/hl.key.vault.json
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
- Oracle detects a vault file and decrypts it in memory. The key is never written
146
- back to disk, never logged, and never included in any returned object — there
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 Oracle is unlocked. Nothing file-based does. If you
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
- node bin/oracle-vault.mjs rekey <vault> # rotate the passphrase
157
- node bin/oracle-vault.mjs inspect <vault> # show KDF params, no secret
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`. Private operator deployments
182
- must wire the source-only signer behind their own authenticated execution
183
- boundary. It is intentionally not exported or documented as a public package
184
- API.
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
+ ```
@@ -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
- return send(res, 500, { error: String(e.message || e) });
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
 
@@ -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
- | PRIVATE (not published) |
26
- | keystore, house signer, exec-server |
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 (extraction left something behind)
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 here
131
+ ## What is not in the public package
131
132
 
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.
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.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.",
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
  }
@@ -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(args = {}, opts = {}) {
212
- if (args.execute !== true) {
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(intent = {}, opts = {}) {
335
- requireExecuteGate(opts);
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(signed = {}, opts = {}) {
363
- requireExecuteGate(opts);
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(cancel = {}, opts = {}) {
422
- requireExecuteGate(opts);
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(cancel = {}, opts = {}) {
441
- requireExecuteGate(opts);
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(