@oracle-agent/oracle 0.6.0 → 0.7.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.
Files changed (32) hide show
  1. package/bin/oracle-data-mcp.mjs +32 -0
  2. package/package.json +1 -1
  3. package/public/oracle-splash/assets/llms/claude.svg +1 -1
  4. package/public/oracle-splash/assets/llms/codex.svg +1 -1
  5. package/public/oracle-splash/assets/llms/cursor-icon.svg +12 -0
  6. package/public/oracle-splash/assets/llms/cursor.svg +1 -1
  7. package/public/oracle-splash/assets/llms/deepseek.svg +1 -1
  8. package/public/oracle-splash/assets/llms/gemini.svg +1 -1
  9. package/public/oracle-splash/assets/llms/kimi-icon.svg +1 -0
  10. package/public/oracle-splash/assets/llms/kimi.svg +1 -1
  11. package/public/oracle-splash/assets/llms/openclaw.svg +1 -1
  12. package/public/oracle-splash/assets/llms/perplexity.svg +1 -1
  13. package/public/oracle-splash/assets/llms/qwen.svg +1 -1
  14. package/public/oracle-splash/assets/wordmarks/cowswap.svg +1 -1
  15. package/public/oracle-splash/assets/wordmarks/curve.png +0 -0
  16. package/public/oracle-splash/assets/wordmarks/gmx.svg +1 -1
  17. package/public/oracle-splash/assets/wordmarks/kinetiq.svg +1 -0
  18. package/public/oracle-splash/assets/wordmarks/lfj.webp +0 -0
  19. package/public/oracle-splash/assets/wordmarks/magic-eden.svg +1 -1
  20. package/public/oracle-splash/assets/wordmarks/morpho.svg +1 -1
  21. package/public/oracle-splash/assets/wordmarks/odos.svg +1 -1
  22. package/public/oracle-splash/assets/wordmarks/opensea.svg +1 -1
  23. package/public/oracle-splash/assets/wordmarks/paragon.svg +9 -0
  24. package/public/oracle-splash/assets/wordmarks/paraswap.svg +1 -1
  25. package/public/oracle-splash/index.html +85 -95
  26. package/src/data/catalog.mjs +11 -0
  27. package/src/data/desk-data.mjs +6 -0
  28. package/src/data/providers/rfq.mjs +15 -0
  29. package/src/index.mjs +7 -0
  30. package/src/rfq/intent.mjs +180 -0
  31. package/src/rfq/sources.mjs +181 -0
  32. package/protocols/templates/safe-erc20/README.md +0 -9
@@ -0,0 +1,181 @@
1
+ import { lifiQuote } from "../data/providers/lifi.mjs";
2
+ import { paraswapPrice } from "../data/providers/paraswap.mjs";
3
+ import { zeroxQuote } from "../data/providers/zerox.mjs";
4
+ import { cowQuote } from "../data/providers/cowswap.mjs";
5
+ import { uniV3QuoteExactIn, UNI_V3_CHAINS } from "../data/providers/uniswap-v3.mjs";
6
+ import { normalizeFirmQuote } from "./intent.mjs";
7
+
8
+ function allowed(intent, source) {
9
+ return Array.isArray(intent?.allowedSources) && intent.allowedSources.includes(source);
10
+ }
11
+
12
+ function envValue(env, key) {
13
+ return String(env?.[key] ?? "").trim();
14
+ }
15
+
16
+ function candidate(source, run) {
17
+ return Object.freeze({ source, run });
18
+ }
19
+
20
+ export function sourceCandidates(intent, opts = {}) {
21
+ if (!intent || intent.kind !== "rfq-intent") throw new Error("rfq: intent required");
22
+ const env = opts.env ?? process.env;
23
+ const providers = opts.providers ?? {};
24
+ const uniChains = opts.supportedUniV3Chains ?? UNI_V3_CHAINS;
25
+ const sameChain = Number(intent.fromChainId) === Number(intent.toChainId);
26
+ const out = [];
27
+ if (allowed(intent, "lifi")) {
28
+ out.push(candidate("lifi", async () => {
29
+ const fn = providers.lifiQuote ?? lifiQuote;
30
+ const q = await fn({
31
+ fromChain: intent.fromChainId,
32
+ toChain: intent.toChainId,
33
+ fromToken: intent.sellToken,
34
+ toToken: intent.buyToken,
35
+ fromAmount: intent.sellAmount,
36
+ fromAddress: intent.receiver,
37
+ }, opts);
38
+ const est = q?.estimate ?? q?.[0]?.estimate ?? q;
39
+ return {
40
+ quoteId: q?.id ?? q?.routeId ?? null,
41
+ amountOut: est?.toAmount ?? q?.toAmount,
42
+ minBuyAmount: est?.toAmountMin ?? q?.toAmountMin ?? est?.toAmount ?? q?.toAmount,
43
+ expiryMs: Date.now() + Number(opts.defaultQuoteTtlMs ?? 20_000),
44
+ artifact: { type: "lifi-route", calldataHash: q?.transactionRequest?.data ? await hashMaybe(opts, q.transactionRequest.data) : undefined, route: q },
45
+ metadata: { tool: q?.tool ?? q?.toolDetails?.name ?? null },
46
+ };
47
+ }));
48
+ }
49
+ if (!sameChain) return out;
50
+ if (allowed(intent, "paraswap")) {
51
+ out.push(candidate("paraswap", async () => {
52
+ const fn = providers.paraswapPrice ?? paraswapPrice;
53
+ const q = await fn({
54
+ chainId: intent.fromChainId,
55
+ srcToken: intent.sellToken,
56
+ destToken: intent.buyToken,
57
+ amount: intent.sellAmount,
58
+ srcDecimals: opts.decimalsIn,
59
+ destDecimals: opts.decimalsOut,
60
+ }, opts);
61
+ const pr = q?.priceRoute ?? q;
62
+ return {
63
+ quoteId: pr?.id ?? null,
64
+ amountOut: pr?.destAmount,
65
+ minBuyAmount: q?.amountOutMinimum ?? pr?.destAmount,
66
+ expiryMs: Date.now() + Number(opts.defaultQuoteTtlMs ?? 20_000),
67
+ artifact: { type: "paraswap-price-route", route: pr },
68
+ metadata: { contractAddress: pr?.contractAddress ?? null },
69
+ };
70
+ }));
71
+ }
72
+ if (allowed(intent, "0x") && (envValue(env, "ZEROX_API_KEY") || envValue(env, "0X_API_KEY") || providers.zeroxQuote)) {
73
+ out.push(candidate("0x", async () => {
74
+ const fn = providers.zeroxQuote ?? zeroxQuote;
75
+ const q = await fn({ chainId: intent.fromChainId, sellToken: intent.sellToken, buyToken: intent.buyToken, sellAmount: intent.sellAmount, taker: intent.receiver }, opts);
76
+ return {
77
+ quoteId: q?.id ?? null,
78
+ amountOut: q?.buyAmount ?? q?.grossBuyAmount,
79
+ minBuyAmount: q?.minBuyAmount ?? q?.buyAmount,
80
+ expiryMs: Date.now() + Number(opts.defaultQuoteTtlMs ?? 20_000),
81
+ artifact: { type: "0x-transaction", to: q?.transaction?.to, data: q?.transaction?.data, value: q?.transaction?.value ?? "0" },
82
+ metadata: { route: q?.route ?? null },
83
+ };
84
+ }));
85
+ }
86
+ if (allowed(intent, "cow")) {
87
+ out.push(candidate("cow", async () => {
88
+ const fn = providers.cowQuote ?? cowQuote;
89
+ const q = await fn({ chainId: intent.fromChainId, sellToken: intent.sellToken, buyToken: intent.buyToken, sellAmountBeforeFee: intent.sellAmount, from: intent.receiver, signingScheme: "eip712" }, opts);
90
+ const quote = q?.quote ?? q;
91
+ return {
92
+ quoteId: q?.id ?? null,
93
+ amountOut: quote?.buyAmount,
94
+ minBuyAmount: quote?.buyAmount,
95
+ expiryMs: Math.min(Number(quote?.validTo ?? 0) * 1000 || Date.now() + Number(opts.defaultQuoteTtlMs ?? 20_000), intent.deadlineMs),
96
+ artifact: { type: "cow-order", order: quote },
97
+ metadata: { solver: true },
98
+ };
99
+ }));
100
+ }
101
+ if (allowed(intent, "uniswap-v3") && uniChains[Number(intent.fromChainId)]) {
102
+ out.push(candidate("uniswap-v3", async () => {
103
+ const fn = providers.uniV3QuoteExactIn ?? uniV3QuoteExactIn;
104
+ const q = await fn({ chainId: intent.fromChainId, tokenIn: intent.sellToken, tokenOut: intent.buyToken, amountIn: intent.sellAmount }, opts);
105
+ return {
106
+ quoteId: q?.id ?? null,
107
+ amountOut: q?.amountOut,
108
+ minBuyAmount: q?.minOut ?? q?.amountOutMinimum ?? q?.amountOut,
109
+ expiryMs: Date.now() + Number(opts.defaultQuoteTtlMs ?? 20_000),
110
+ artifact: { type: "uniswap-v3-quote", quoter: q?.quoter, fee: q?.fee },
111
+ metadata: { onchain: true },
112
+ };
113
+ }));
114
+ }
115
+ return out;
116
+ }
117
+
118
+ async function hashMaybe(opts, value) {
119
+ const crypto = await import("node:crypto");
120
+ return `0x${crypto.createHash("sha256").update(String(value)).digest("hex")}`;
121
+ }
122
+
123
+ async function runWithTimeout(c, timeoutMs) {
124
+ return Promise.race([
125
+ Promise.resolve().then(c.run),
126
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`${c.source}: timeout`)), timeoutMs)),
127
+ ]);
128
+ }
129
+
130
+ export async function executeRfqCandidates(intent, candidates, opts = {}) {
131
+ const timeoutMs = Number(opts.timeoutMs ?? 12_000);
132
+ const settled = await Promise.allSettled(candidates.map((c) => runWithTimeout(c, timeoutMs)));
133
+ const quotes = [];
134
+ const failed = [];
135
+ for (let i = 0; i < settled.length; i++) {
136
+ const c = candidates[i];
137
+ const r = settled[i];
138
+ if (r.status === "rejected") {
139
+ failed.push({ source: c.source, error: String(r.reason?.message || r.reason) });
140
+ continue;
141
+ }
142
+ try {
143
+ quotes.push(normalizeFirmQuote(intent, { ...r.value, source: c.source }, opts));
144
+ } catch (e) {
145
+ failed.push({ source: c.source, error: String(e?.message || e) });
146
+ }
147
+ }
148
+ return { quotes, failed };
149
+ }
150
+
151
+ export function rankRfqQuotes(quotes = [], opts = {}) {
152
+ const nowMs = Number(opts.nowMs ?? Date.now());
153
+ const usable = quotes.filter((q) => Number(q.expiryMs ?? 0) > nowMs);
154
+ usable.sort((a, b) => {
155
+ const floor = BigInt(b.minBuyAmount ?? 0) - BigInt(a.minBuyAmount ?? 0);
156
+ if (floor !== 0n) return floor > 0n ? 1 : -1;
157
+ const gross = BigInt(b.amountOut ?? 0) - BigInt(a.amountOut ?? 0);
158
+ return gross > 0n ? 1 : gross < 0n ? -1 : 0;
159
+ });
160
+ const ranked = usable.map((q) => ({ ...q, scoreBasis: "minBuyAmount" }));
161
+ const warnings = [];
162
+ if (!ranked.length) warnings.push("no RFQ source returned a usable firm quote");
163
+ if (quotes.length !== ranked.length) warnings.push("expired RFQ quotes were excluded from ranking");
164
+ return { best: ranked[0] ?? null, quotes: ranked, failed: [], warnings };
165
+ }
166
+
167
+ export async function requestRfqQuotes(intent, opts = {}) {
168
+ const candidates = sourceCandidates(intent, opts);
169
+ const { quotes, failed } = await executeRfqCandidates(intent, candidates, opts);
170
+ const ranked = rankRfqQuotes(quotes, opts);
171
+ return {
172
+ kind: "rfq-result",
173
+ intent,
174
+ best: ranked.best,
175
+ quotes: ranked.quotes,
176
+ failed,
177
+ warnings: [...ranked.warnings],
178
+ sourcesTried: candidates.length,
179
+ sourcesAnswered: quotes.length,
180
+ };
181
+ }
@@ -1,9 +0,0 @@
1
- # safe-erc20
2
-
3
- ```bash
4
- forge install foundry-rs/forge-std --no-git
5
- forge install OpenZeppelin/openzeppelin-contracts@v5.0.2 --no-git
6
- forge test
7
- ```
8
-
9
- Libs are not vendored. Gate auto-installs when missing.