@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,501 @@
|
|
|
1
|
+
// CoW Swap Order Book API — public, no key. Quotes are read-only. Order
|
|
2
|
+
// prepare returns a guarded EIP-712 intent; signing/submission is a separate
|
|
3
|
+
// executable artifact flow and must never be treated as normal tx prepare.
|
|
4
|
+
|
|
5
|
+
import { TypedDataEncoder, getAddress, isAddress, verifyTypedData } from "ethers";
|
|
6
|
+
import { httpJson } from "../http.mjs";
|
|
7
|
+
import { attachAutoSlippage } from "../../auto-slippage.mjs";
|
|
8
|
+
import { autonomousTradingEnabled } from "../../capability-posture.mjs";
|
|
9
|
+
import { QUOTE_PLACEHOLDER_ADDRESS, quoteAddress } from "../quote-placeholder.mjs";
|
|
10
|
+
|
|
11
|
+
export const COW_HOSTS = {
|
|
12
|
+
1: "https://api.cow.fi/mainnet/api/v1",
|
|
13
|
+
100: "https://api.cow.fi/xdai/api/v1",
|
|
14
|
+
42161: "https://api.cow.fi/arbitrum_one/api/v1",
|
|
15
|
+
8453: "https://api.cow.fi/base/api/v1",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const COW_SETTLEMENT = Object.freeze({
|
|
19
|
+
1: "0x9008D19f58AAbD9eD0D60971565AA8510560ab41",
|
|
20
|
+
100: "0x9008D19f58AAbD9eD0D60971565AA8510560ab41",
|
|
21
|
+
42161: "0x9008D19f58AAbD9eD0D60971565AA8510560ab41",
|
|
22
|
+
8453: "0x9008D19f58AAbD9eD0D60971565AA8510560ab41",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const COW_VAULT_RELAYER = Object.freeze({
|
|
26
|
+
1: "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110",
|
|
27
|
+
100: "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110",
|
|
28
|
+
42161: "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110",
|
|
29
|
+
8453: "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const WETH = {
|
|
33
|
+
1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
34
|
+
8453: "0x4200000000000000000000000000000000000006",
|
|
35
|
+
42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
|
36
|
+
};
|
|
37
|
+
const USDC = {
|
|
38
|
+
1: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
39
|
+
8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
40
|
+
42161: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const COW_ORDER_TYPES = Object.freeze({
|
|
44
|
+
Order: [
|
|
45
|
+
{ name: "sellToken", type: "address" },
|
|
46
|
+
{ name: "buyToken", type: "address" },
|
|
47
|
+
{ name: "receiver", type: "address" },
|
|
48
|
+
{ name: "sellAmount", type: "uint256" },
|
|
49
|
+
{ name: "buyAmount", type: "uint256" },
|
|
50
|
+
{ name: "validTo", type: "uint32" },
|
|
51
|
+
{ name: "appData", type: "bytes32" },
|
|
52
|
+
{ name: "feeAmount", type: "uint256" },
|
|
53
|
+
{ name: "kind", type: "string" },
|
|
54
|
+
{ name: "partiallyFillable", type: "bool" },
|
|
55
|
+
{ name: "sellTokenBalance", type: "string" },
|
|
56
|
+
{ name: "buyTokenBalance", type: "string" },
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const COW_CANCEL_TYPES = Object.freeze({
|
|
61
|
+
OrderCancellations: [{ name: "orderUids", type: "bytes[]" }],
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
function host(chainId, opts = {}) {
|
|
65
|
+
if (opts.baseUrl) return opts.baseUrl.replace(/\/$/, "");
|
|
66
|
+
const h = COW_HOSTS[Number(chainId)];
|
|
67
|
+
if (!h) throw new Error(`cowswap: unsupported chainId ${chainId}`);
|
|
68
|
+
return h;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function apiHost(chainId, version = "v1", opts = {}) {
|
|
72
|
+
const h = host(chainId, opts);
|
|
73
|
+
if (version === "v1") return h;
|
|
74
|
+
return h.replace(/\/api\/v1$/, `/api/${version}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function addr(value, label) {
|
|
78
|
+
const text = String(value || "").trim();
|
|
79
|
+
if (!isAddress(text)) throw new Error(`cowswap: ${label} must be an address`);
|
|
80
|
+
return getAddress(text).toLowerCase();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function uintString(value, label) {
|
|
84
|
+
try {
|
|
85
|
+
const n = BigInt(String(value));
|
|
86
|
+
if (n < 0n) throw new Error("negative");
|
|
87
|
+
return n.toString();
|
|
88
|
+
} catch {
|
|
89
|
+
throw new Error(`cowswap: ${label} must be a non-negative integer string`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function bytes32(value, label) {
|
|
94
|
+
const text = String(value || "").trim();
|
|
95
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(text)) throw new Error(`cowswap: ${label} must be bytes32 hex`);
|
|
96
|
+
return text.toLowerCase();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function orderUid(value, label = "uid") {
|
|
100
|
+
const text = String(value || "").trim();
|
|
101
|
+
if (!/^0x[0-9a-fA-F]{112}$/.test(text)) throw new Error(`cowswap: ${label} must be 56-byte order UID hex`);
|
|
102
|
+
return text.toLowerCase();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Market submission gate.
|
|
107
|
+
*
|
|
108
|
+
* Posture: submission is ARMED once the user has a signer wired up — that is
|
|
109
|
+
* the authorization. `opts.execute === true` records that THIS placement was
|
|
110
|
+
* asked for, so an order is never posted as a side effect of quoting. The
|
|
111
|
+
* separate opt-in exists only for UNATTENDED trading, which is checked by the
|
|
112
|
+
* autonomous flag, not here.
|
|
113
|
+
*/
|
|
114
|
+
function requireExecuteGate(opts = {}) {
|
|
115
|
+
if (opts.execute !== true) throw new Error("cowswap execute gate required for order signing/submission/cancel");
|
|
116
|
+
if (opts.autonomous === true && !autonomousTradingEnabled()) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
"cowswap: autonomous order placement is off — set ORACLE_AUTONOMOUS_TRADING=1 to let Oracle trade unattended"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function assertCowOrderGuard(intent = {}, typedData = {}, owner, { nowMs = Date.now() } = {}) {
|
|
124
|
+
const guard = intent.cowGuard;
|
|
125
|
+
if (!guard || guard.mode !== "cow-order") throw new Error("cowswap: fresh cow order guard required");
|
|
126
|
+
if (Number(guard.expiresAtMs) <= Number(nowMs)) throw new Error("cowswap: cow order guard expired");
|
|
127
|
+
const chainId = Number(intent.chainId ?? typedData?.domain?.chainId);
|
|
128
|
+
if (Number(guard.chainId) !== chainId) throw new Error("cowswap: cow order guard chain mismatch");
|
|
129
|
+
if (addr(guard.owner, "guard owner") !== owner) throw new Error("cowswap: cow order guard owner mismatch");
|
|
130
|
+
const domain = orderDomain(chainId);
|
|
131
|
+
if (Number(typedData?.domain?.chainId) !== chainId) throw new Error("cowswap: typedData chain mismatch");
|
|
132
|
+
if (addr(typedData?.domain?.verifyingContract, "settlement") !== domain.verifyingContract.toLowerCase()) {
|
|
133
|
+
throw new Error("cowswap: typedData settlement mismatch");
|
|
134
|
+
}
|
|
135
|
+
if (addr(guard.settlement, "guard settlement") !== domain.verifyingContract.toLowerCase()) {
|
|
136
|
+
throw new Error("cowswap: cow order guard settlement mismatch");
|
|
137
|
+
}
|
|
138
|
+
const message = typedData?.message;
|
|
139
|
+
if (!message) throw new Error("cowswap: typedData order message required");
|
|
140
|
+
const typedDataHash = TypedDataEncoder.hash(domain, COW_ORDER_TYPES, message);
|
|
141
|
+
if (String(guard.typedDataHash || "").toLowerCase() !== typedDataHash.toLowerCase()) {
|
|
142
|
+
throw new Error("cowswap: cow order guard typedData hash mismatch");
|
|
143
|
+
}
|
|
144
|
+
if (addr(guard.sellToken, "guard sellToken") !== addr(message.sellToken, "sellToken")) throw new Error("cowswap: cow order guard sellToken mismatch");
|
|
145
|
+
if (addr(guard.buyToken, "guard buyToken") !== addr(message.buyToken, "buyToken")) throw new Error("cowswap: cow order guard buyToken mismatch");
|
|
146
|
+
if (String(guard.sellAmount) !== uintString(message.sellAmount, "sellAmount")) throw new Error("cowswap: cow order guard sellAmount mismatch");
|
|
147
|
+
if (String(guard.buyAmount) !== uintString(message.buyAmount, "buyAmount")) throw new Error("cowswap: cow order guard buyAmount mismatch");
|
|
148
|
+
if (Number(guard.validTo) !== Number(message.validTo)) throw new Error("cowswap: cow order guard validTo mismatch");
|
|
149
|
+
if (Number(message.validTo) * 1000 <= Number(nowMs)) throw new Error("cowswap: order expired");
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function toCowOrderUid({ digest, owner, validTo } = {}) {
|
|
154
|
+
const d = bytes32(digest, "order digest").slice(2);
|
|
155
|
+
const o = addr(owner, "owner").slice(2).toLowerCase();
|
|
156
|
+
const v = Number(validTo);
|
|
157
|
+
if (!Number.isInteger(v) || v < 0 || v > 0xffffffff) throw new Error("cowswap: validTo must fit uint32");
|
|
158
|
+
return `0x${d}${o}${v.toString(16).padStart(8, "0")}`.toLowerCase();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function orderDomain(chainId) {
|
|
162
|
+
const verifyingContract = COW_SETTLEMENT[Number(chainId)];
|
|
163
|
+
if (!verifyingContract) throw new Error(`cowswap: settlement contract not registered for chain ${chainId}`);
|
|
164
|
+
return {
|
|
165
|
+
name: "Gnosis Protocol",
|
|
166
|
+
version: "v2",
|
|
167
|
+
chainId: Number(chainId),
|
|
168
|
+
verifyingContract: getAddress(verifyingContract),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function normalizeOrderQuote(q = {}, chainId, owner) {
|
|
173
|
+
const sellToken = addr(q.sellToken, "sellToken");
|
|
174
|
+
const buyToken = addr(q.buyToken, "buyToken");
|
|
175
|
+
const receiver = addr(q.receiver || owner, "receiver");
|
|
176
|
+
const validTo = Number(q.validTo);
|
|
177
|
+
if (!Number.isInteger(validTo) || validTo <= 0) throw new Error("cowswap: quote.validTo required");
|
|
178
|
+
const appData = bytes32(q.appData || q.appDataHash || `0x${"0".repeat(64)}`, "appData");
|
|
179
|
+
return {
|
|
180
|
+
sellToken,
|
|
181
|
+
buyToken,
|
|
182
|
+
receiver,
|
|
183
|
+
sellAmount: uintString(q.sellAmount, "sellAmount"),
|
|
184
|
+
buyAmount: uintString(q.buyAmount, "buyAmount"),
|
|
185
|
+
validTo,
|
|
186
|
+
appData,
|
|
187
|
+
feeAmount: uintString(q.feeAmount ?? "0", "feeAmount"),
|
|
188
|
+
kind: String(q.kind || "sell"),
|
|
189
|
+
partiallyFillable: Boolean(q.partiallyFillable),
|
|
190
|
+
sellTokenBalance: String(q.sellTokenBalance || "erc20"),
|
|
191
|
+
buyTokenBalance: String(q.buyTokenBalance || "erc20"),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function cowHealth(opts = {}) {
|
|
196
|
+
try {
|
|
197
|
+
const q = await cowQuote(
|
|
198
|
+
{
|
|
199
|
+
chainId: 1,
|
|
200
|
+
sellToken: WETH[1],
|
|
201
|
+
buyToken: USDC[1],
|
|
202
|
+
sellAmountBeforeFee: "1000000000000000",
|
|
203
|
+
from: QUOTE_PLACEHOLDER_ADDRESS,
|
|
204
|
+
},
|
|
205
|
+
opts
|
|
206
|
+
);
|
|
207
|
+
return { ok: true, buyAmount: q.quote?.buyAmount || q.buyAmount };
|
|
208
|
+
} catch (e) {
|
|
209
|
+
return { ok: false, error: String(e.message || e).slice(0, 160) };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export async function cowQuote(q = {}, opts = {}) {
|
|
214
|
+
const chainId = Number(q.chainId ?? 1);
|
|
215
|
+
const from = quoteAddress(q.from || q.owner);
|
|
216
|
+
let sellToken = q.sellToken || WETH[chainId];
|
|
217
|
+
const buyToken = q.buyToken || USDC[chainId];
|
|
218
|
+
const sellAmountBeforeFee = String(q.sellAmountBeforeFee || q.sellAmount || "0");
|
|
219
|
+
if (!sellToken || !buyToken || sellAmountBeforeFee === "0") {
|
|
220
|
+
throw new Error("cowQuote requires sellToken, buyToken, sellAmountBeforeFee");
|
|
221
|
+
}
|
|
222
|
+
// Cow rejects native ETH — map to WETH
|
|
223
|
+
if (/eeeeeeee|native|^eth$/i.test(sellToken)) {
|
|
224
|
+
sellToken = WETH[chainId];
|
|
225
|
+
if (!sellToken) throw new Error("no WETH mapping for chain");
|
|
226
|
+
}
|
|
227
|
+
const body = {
|
|
228
|
+
sellToken,
|
|
229
|
+
buyToken,
|
|
230
|
+
from,
|
|
231
|
+
receiver: q.receiver || from,
|
|
232
|
+
sellAmountBeforeFee,
|
|
233
|
+
kind: q.kind || "sell",
|
|
234
|
+
signingScheme: q.signingScheme || "eip1271",
|
|
235
|
+
};
|
|
236
|
+
const result = await httpJson(`${host(chainId, opts)}/quote`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
body,
|
|
239
|
+
fetchImpl: opts.fetchImpl,
|
|
240
|
+
timeoutMs: opts.timeoutMs ?? 15_000,
|
|
241
|
+
});
|
|
242
|
+
const amountOut = result?.quote?.buyAmount || result?.buyAmount;
|
|
243
|
+
if (!amountOut) return result;
|
|
244
|
+
// A CoW sell order's signed buyAmount is itself the executable floor. The
|
|
245
|
+
// attached guard keeps policy/reporting consistent with direct swap routes.
|
|
246
|
+
return attachAutoSlippage(result, {
|
|
247
|
+
chainId,
|
|
248
|
+
venue: "cowswap-order",
|
|
249
|
+
amountOut,
|
|
250
|
+
liquidityUsd: q.liquidityUsd,
|
|
251
|
+
priceChange5m: q.priceChange5m,
|
|
252
|
+
requestedCapBps: q.maxSlippageBps ?? q.slippageBps,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function cowPrepareOrder(q = {}, opts = {}) {
|
|
257
|
+
const chainId = Number(q.chainId ?? 1);
|
|
258
|
+
const owner = addr(quoteAddress(q.from || q.owner || q.signer), "owner");
|
|
259
|
+
const quote = await cowQuote({ ...q, chainId, from: owner, signingScheme: q.signingScheme || "eip712" }, opts);
|
|
260
|
+
const order = normalizeOrderQuote(quote?.quote || quote, chainId, owner);
|
|
261
|
+
const domain = orderDomain(chainId);
|
|
262
|
+
const typedData = { domain, types: COW_ORDER_TYPES, primaryType: "Order", message: order };
|
|
263
|
+
const typedDataHash = TypedDataEncoder.hash(domain, COW_ORDER_TYPES, order);
|
|
264
|
+
const relayer = COW_VAULT_RELAYER[chainId];
|
|
265
|
+
if (!relayer) throw new Error(`cowswap: vault relayer not registered for chain ${chainId}`);
|
|
266
|
+
return {
|
|
267
|
+
provider: "cowswap",
|
|
268
|
+
orderReady: true,
|
|
269
|
+
executionReady: false,
|
|
270
|
+
chainId,
|
|
271
|
+
owner,
|
|
272
|
+
signingScheme: "eip712",
|
|
273
|
+
settlement: domain.verifyingContract.toLowerCase(),
|
|
274
|
+
vaultRelayer: getAddress(relayer).toLowerCase(),
|
|
275
|
+
quote,
|
|
276
|
+
quoteId: quote?.id ?? q.quoteId ?? null,
|
|
277
|
+
typedData,
|
|
278
|
+
cowGuard: {
|
|
279
|
+
mode: "cow-order",
|
|
280
|
+
version: 1,
|
|
281
|
+
provider: "cowswap",
|
|
282
|
+
chainId,
|
|
283
|
+
owner,
|
|
284
|
+
settlement: domain.verifyingContract.toLowerCase(),
|
|
285
|
+
vaultRelayer: getAddress(relayer).toLowerCase(),
|
|
286
|
+
sellToken: order.sellToken,
|
|
287
|
+
buyToken: order.buyToken,
|
|
288
|
+
sellAmount: order.sellAmount,
|
|
289
|
+
buyAmount: order.buyAmount,
|
|
290
|
+
validTo: order.validTo,
|
|
291
|
+
quoteId: quote?.id ?? q.quoteId ?? null,
|
|
292
|
+
typedDataHash,
|
|
293
|
+
issuedAtMs: Date.now(),
|
|
294
|
+
expiresAtMs: Math.min(order.validTo * 1000, Date.now() + 20_000),
|
|
295
|
+
},
|
|
296
|
+
requiresApproval: {
|
|
297
|
+
token: order.sellToken,
|
|
298
|
+
spender: getAddress(relayer).toLowerCase(),
|
|
299
|
+
amount: order.sellAmount,
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export async function cowOrderStatus(q = {}, opts = {}) {
|
|
305
|
+
const chainId = Number(q.chainId ?? 1);
|
|
306
|
+
const uid = String(q.uid || q.orderUid || "").trim();
|
|
307
|
+
if (!uid) throw new Error("cowOrderStatus requires uid");
|
|
308
|
+
return httpJson(`${host(chainId, opts)}/orders/${encodeURIComponent(uid)}/status`, {
|
|
309
|
+
fetchImpl: opts.fetchImpl,
|
|
310
|
+
timeoutMs: opts.timeoutMs ?? 15_000,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export async function cowOrder(q = {}, opts = {}) {
|
|
315
|
+
const chainId = Number(q.chainId ?? 1);
|
|
316
|
+
const uid = String(q.uid || q.orderUid || "").trim();
|
|
317
|
+
if (!uid) throw new Error("cowOrder requires uid");
|
|
318
|
+
return httpJson(`${host(chainId, opts)}/orders/${encodeURIComponent(uid)}`, {
|
|
319
|
+
fetchImpl: opts.fetchImpl,
|
|
320
|
+
timeoutMs: opts.timeoutMs ?? 15_000,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function cowOrderUid(intent = {}) {
|
|
325
|
+
const typedData = intent.typedData || intent;
|
|
326
|
+
const chainId = Number(intent.chainId ?? typedData?.domain?.chainId);
|
|
327
|
+
const message = typedData?.message || intent.order || intent.message;
|
|
328
|
+
if (!message) throw new Error("cowswap: typed order message required");
|
|
329
|
+
const digest = intent.cowGuard?.typedDataHash || TypedDataEncoder.hash(orderDomain(chainId), COW_ORDER_TYPES, message);
|
|
330
|
+
const owner = intent.owner || intent.from || intent.signer || message.receiver;
|
|
331
|
+
return toCowOrderUid({ digest, owner, validTo: message.validTo });
|
|
332
|
+
}
|
|
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
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
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
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function cowPrepareCancel(q = {}) {
|
|
403
|
+
const chainId = Number(q.chainId ?? 1);
|
|
404
|
+
const owner = addr(q.owner || q.from || q.signer, "owner");
|
|
405
|
+
const orderUids = (q.orderUids || q.uids || [q.uid]).filter(Boolean).map((u, i) => orderUid(u, `orderUids[${i}]`));
|
|
406
|
+
if (!orderUids.length) throw new Error("cowswap: orderUids required");
|
|
407
|
+
const domain = orderDomain(chainId);
|
|
408
|
+
const message = { orderUids };
|
|
409
|
+
return {
|
|
410
|
+
provider: "cowswap",
|
|
411
|
+
chainId,
|
|
412
|
+
owner,
|
|
413
|
+
orderUids,
|
|
414
|
+
signingScheme: "eip712",
|
|
415
|
+
typedData: { domain, types: COW_CANCEL_TYPES, primaryType: "OrderCancellations", message },
|
|
416
|
+
cancelReady: true,
|
|
417
|
+
executableArtifact: true,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
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
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
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 };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function tradeBuyAmount(trade = {}) {
|
|
458
|
+
return uintString(trade.buyAmount ?? trade.executedBuyAmount ?? "0", "trade.buyAmount");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export async function cowVerifyOrderFill(q = {}, opts = {}) {
|
|
462
|
+
const chainId = Number(q.chainId ?? 1);
|
|
463
|
+
const uid = orderUid(q.uid || q.orderUid);
|
|
464
|
+
const order = opts.order || await cowOrder({ chainId, uid }, opts);
|
|
465
|
+
const status = opts.status || order.status || await cowOrderStatus({ chainId, uid }, opts).catch(() => null);
|
|
466
|
+
const trades = opts.trades || await httpJson(`${apiHost(chainId, "v2", opts)}/trades?orderUid=${encodeURIComponent(uid)}`, {
|
|
467
|
+
fetchImpl: opts.fetchImpl,
|
|
468
|
+
timeoutMs: opts.timeoutMs ?? 15_000,
|
|
469
|
+
});
|
|
470
|
+
const list = Array.isArray(trades) ? trades : [];
|
|
471
|
+
const expectedOwner = q.expectedOwner ? addr(q.expectedOwner, "expectedOwner") : null;
|
|
472
|
+
if (expectedOwner) {
|
|
473
|
+
const owner = addr(order.owner || list[0]?.owner || expectedOwner, "owner");
|
|
474
|
+
if (owner !== expectedOwner) return { ok: false, provider: "cowswap", chainId, uid, reason: "owner mismatch", order, status, trades: list };
|
|
475
|
+
}
|
|
476
|
+
let executedBuy = 0n;
|
|
477
|
+
for (const t of list.filter((t) => String(t.orderUid || "").toLowerCase() === uid)) {
|
|
478
|
+
executedBuy += BigInt(tradeBuyAmount(t));
|
|
479
|
+
}
|
|
480
|
+
if (executedBuy === 0n && order.executedBuyAmount != null) executedBuy = BigInt(uintString(order.executedBuyAmount, "executedBuyAmount"));
|
|
481
|
+
const minBuy = q.minBuyAmount != null ? BigInt(uintString(q.minBuyAmount, "minBuyAmount")) : 0n;
|
|
482
|
+
const ok = executedBuy >= minBuy && (String(order.status || status?.status || "").toLowerCase() !== "expired");
|
|
483
|
+
return {
|
|
484
|
+
ok,
|
|
485
|
+
provider: "cowswap",
|
|
486
|
+
chainId,
|
|
487
|
+
uid,
|
|
488
|
+
status: order.status || status?.status || null,
|
|
489
|
+
executedBuyAmount: executedBuy.toString(),
|
|
490
|
+
minBuyAmount: q.minBuyAmount ?? null,
|
|
491
|
+
trades: list,
|
|
492
|
+
settlementTxs: [...new Set(list.map((t) => t.txHash).filter(Boolean))],
|
|
493
|
+
order,
|
|
494
|
+
orderStatus: status,
|
|
495
|
+
reason: ok ? null : "insufficient fill or expired order",
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export function cowChains() {
|
|
500
|
+
return Object.keys(COW_HOSTS).map(Number);
|
|
501
|
+
}
|