@finchagentic/mcp 4.6.1 → 4.6.2
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/package.json +5 -6
- package/dist/_http-cache.js +0 -96
- package/dist/_text-search.js +0 -39
- package/dist/agent-loop.js +0 -301
- package/dist/annotations.js +0 -122
- package/dist/cli.js +0 -1391
- package/dist/clink-input.js +0 -15
- package/dist/config.js +0 -132
- package/dist/convex.js +0 -175
- package/dist/dex-pair.js +0 -54
- package/dist/enrichment-router.js +0 -315
- package/dist/index.js +0 -258
- package/dist/llm.js +0 -298
- package/dist/local-memory-file.js +0 -150
- package/dist/local-memory.js +0 -135
- package/dist/local-vault.js +0 -456
- package/dist/output-schemas.js +0 -605
- package/dist/project.js +0 -36
- package/dist/prompts.js +0 -111
- package/dist/public-url.js +0 -107
- package/dist/resources.js +0 -111
- package/dist/server.js +0 -322
- package/dist/signal-gate.js +0 -57
- package/dist/token-decimals.js +0 -26
- package/dist/token-gate.js +0 -88
- package/dist/tool-filter.js +0 -53
- package/dist/tools/_solidity-scan.js +0 -313
- package/dist/tools/agents.js +0 -441
- package/dist/tools/automation.js +0 -354
- package/dist/tools/base-mcp.js +0 -466
- package/dist/tools/base.js +0 -283
- package/dist/tools/chronicle.js +0 -268
- package/dist/tools/coder.js +0 -94
- package/dist/tools/deep-research.js +0 -1421
- package/dist/tools/defi.js +0 -292
- package/dist/tools/equity.js +0 -372
- package/dist/tools/events.js +0 -182
- package/dist/tools/github.js +0 -564
- package/dist/tools/insider.js +0 -264
- package/dist/tools/insight.js +0 -630
- package/dist/tools/market.js +0 -555
- package/dist/tools/memory.js +0 -1059
- package/dist/tools/miroshark.js +0 -350
- package/dist/tools/monitor.js +0 -319
- package/dist/tools/os.js +0 -236
- package/dist/tools/packets.js +0 -296
- package/dist/tools/research-chain.js +0 -226
- package/dist/tools/research-compare.js +0 -280
- package/dist/tools/research.js +0 -188
- package/dist/tools/rh-bridge.js +0 -148
- package/dist/tools/rh-mcp.js +0 -1448
- package/dist/tools/rh-orders.js +0 -556
- package/dist/tools/scanner.js +0 -564
- package/dist/tools/stake.js +0 -369
- package/dist/tools/vault.js +0 -1020
- package/dist/tools/wallet.js +0 -200
- package/dist/types.js +0 -2
- package/dist/wallet.js +0 -372
package/dist/clink-input.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.dedupClinkInput = dedupClinkInput;
|
|
4
|
-
// Deduplicates doubled input from a known Clink v1.7.6 terminal bug (garbled/
|
|
5
|
-
// doubled keystrokes, e.g. "finch_sk_xxfinch_sk_xx" -> "finch_sk_xx"). Used by
|
|
6
|
-
// every raw-input prompt in cli.ts (login, setup wizard provider/URL prompts)
|
|
7
|
-
// before validating what the user typed.
|
|
8
|
-
function dedupClinkInput(s) {
|
|
9
|
-
if (s.length > 0 && s.length % 2 === 0) {
|
|
10
|
-
const half = s.length / 2;
|
|
11
|
-
if (s.slice(0, half) === s.slice(half))
|
|
12
|
-
return s.slice(0, half);
|
|
13
|
-
}
|
|
14
|
-
return s;
|
|
15
|
-
}
|
package/dist/config.js
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.env = env;
|
|
37
|
-
exports.readConfig = readConfig;
|
|
38
|
-
exports.writeConfig = writeConfig;
|
|
39
|
-
exports.getSavedToken = getSavedToken;
|
|
40
|
-
exports.isApiKey = isApiKey;
|
|
41
|
-
exports.hydrateEnvFromConfig = hydrateEnvFromConfig;
|
|
42
|
-
const fs = __importStar(require("fs"));
|
|
43
|
-
const os = __importStar(require("os"));
|
|
44
|
-
const path = __importStar(require("path"));
|
|
45
|
-
const CONFIG_DIR = path.join(os.homedir(), ".finch");
|
|
46
|
-
const LEGACY_DIR = path.join(os.homedir(), ".finch");
|
|
47
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
48
|
-
const LEGACY_FILE = path.join(LEGACY_DIR, "config.json");
|
|
49
|
-
/** Prefer FINCH_* env, fall back to legacy FINCH_* during rebrand. */
|
|
50
|
-
function env(name, legacy) {
|
|
51
|
-
const primary = process.env[name];
|
|
52
|
-
if (primary !== undefined && primary !== "")
|
|
53
|
-
return primary;
|
|
54
|
-
if (legacy) {
|
|
55
|
-
const v = process.env[legacy];
|
|
56
|
-
if (v !== undefined && v !== "")
|
|
57
|
-
return v;
|
|
58
|
-
}
|
|
59
|
-
// Auto-map FINCH_X → FINCH_X when legacy not passed
|
|
60
|
-
if (name.startsWith("FINCH_")) {
|
|
61
|
-
const auto = "FINCH_" + name.slice("FINCH_".length);
|
|
62
|
-
const v = process.env[auto];
|
|
63
|
-
if (v !== undefined && v !== "")
|
|
64
|
-
return v;
|
|
65
|
-
}
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
function readConfig() {
|
|
69
|
-
try {
|
|
70
|
-
if (fs.existsSync(CONFIG_FILE)) {
|
|
71
|
-
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
|
|
72
|
-
}
|
|
73
|
-
// migrate-read: still pick up old ~/.finch/config.json
|
|
74
|
-
if (fs.existsSync(LEGACY_FILE)) {
|
|
75
|
-
return JSON.parse(fs.readFileSync(LEGACY_FILE, "utf8"));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
// Missing/corrupt config file - treat as no saved config.
|
|
80
|
-
}
|
|
81
|
-
return {};
|
|
82
|
-
}
|
|
83
|
-
function writeConfig(patch) {
|
|
84
|
-
const current = readConfig();
|
|
85
|
-
const updated = { ...current, ...patch };
|
|
86
|
-
if (!fs.existsSync(CONFIG_DIR))
|
|
87
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
88
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2), { mode: 0o600 });
|
|
89
|
-
}
|
|
90
|
-
function getSavedToken() {
|
|
91
|
-
// env var always wins over saved config
|
|
92
|
-
return env("FINCH_SESSION_TOKEN", "FINCH_SESSION_TOKEN") ?? readConfig().sessionToken;
|
|
93
|
-
}
|
|
94
|
-
/** Accept finch_sk_* (new) and noel_sk_* (backend still issues these). */
|
|
95
|
-
function isApiKey(value) {
|
|
96
|
-
if (!value)
|
|
97
|
-
return false;
|
|
98
|
-
return value.startsWith("finch_sk_") || value.startsWith("noel_sk_");
|
|
99
|
-
}
|
|
100
|
-
// Injects provider keys saved via `finch setup` into process.env, so both
|
|
101
|
-
// the interactive CLI and the MCP server process (spawned fresh by Claude
|
|
102
|
-
// Desktop/Cursor/etc, which never sees ~/.finch/config.json otherwise)
|
|
103
|
-
// pick them up the same way. An env var already set by the parent process
|
|
104
|
-
// always wins - this only fills gaps, never overrides.
|
|
105
|
-
function hydrateEnvFromConfig() {
|
|
106
|
-
const cfg = readConfig();
|
|
107
|
-
if (!process.env.BANKR_API_KEY && cfg.bankrApiKey)
|
|
108
|
-
process.env.BANKR_API_KEY = cfg.bankrApiKey;
|
|
109
|
-
if (!process.env.ANTHROPIC_API_KEY && cfg.anthropicApiKey)
|
|
110
|
-
process.env.ANTHROPIC_API_KEY = cfg.anthropicApiKey;
|
|
111
|
-
if (!process.env.OPENAI_API_KEY && cfg.openaiApiKey)
|
|
112
|
-
process.env.OPENAI_API_KEY = cfg.openaiApiKey;
|
|
113
|
-
if (!process.env.OPENAI_BASE_URL && cfg.openaiBaseUrl)
|
|
114
|
-
process.env.OPENAI_BASE_URL = cfg.openaiBaseUrl;
|
|
115
|
-
// Bridge legacy env into FINCH_* so the rest of the codebase can read one name.
|
|
116
|
-
const bridges = [
|
|
117
|
-
["FINCH_SESSION_TOKEN", "FINCH_SESSION_TOKEN"],
|
|
118
|
-
["FINCH_API_KEY", "FINCH_API_KEY"],
|
|
119
|
-
["FINCH_CONVEX_URL", "FINCH_CONVEX_URL"],
|
|
120
|
-
["FINCH_TOOLS", "FINCH_TOOLS"],
|
|
121
|
-
["FINCH_PROVIDER", "FINCH_PROVIDER"],
|
|
122
|
-
["FINCH_MODEL", "FINCH_MODEL"],
|
|
123
|
-
["FINCH_RPC_URL", "FINCH_RPC_URL"],
|
|
124
|
-
["FINCH_BROADCAST_RPC", "FINCH_BROADCAST_RPC"],
|
|
125
|
-
["FINCH_WALLET_PASSPHRASE", "FINCH_WALLET_PASSPHRASE"],
|
|
126
|
-
["FINCH_PAYMENT_HEADER", "FINCH_PAYMENT_HEADER"],
|
|
127
|
-
];
|
|
128
|
-
for (const [fin, leg] of bridges) {
|
|
129
|
-
if (!process.env[fin] && process.env[leg])
|
|
130
|
-
process.env[fin] = process.env[leg];
|
|
131
|
-
}
|
|
132
|
-
}
|
package/dist/convex.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PaymentRequiredError = exports.CONVEX_SITE = void 0;
|
|
4
|
-
exports.buildPaymentHeader = buildPaymentHeader;
|
|
5
|
-
exports.callConvex = callConvex;
|
|
6
|
-
exports.callConvexRaw = callConvexRaw;
|
|
7
|
-
const wallet_js_1 = require("./wallet.js");
|
|
8
|
-
const config_js_1 = require("./config.js");
|
|
9
|
-
exports.CONVEX_SITE = process.env.FINCH_CONVEX_URL ?? "https://befitting-porcupine-276.convex.site";
|
|
10
|
-
const RETRY_STATUSES = new Set([429, 500, 502, 503, 504]);
|
|
11
|
-
const RETRY_DELAYS = [500, 1000, 2000];
|
|
12
|
-
class PaymentRequiredError extends Error {
|
|
13
|
-
constructor(details) {
|
|
14
|
-
super("Payment required");
|
|
15
|
-
this.name = "PaymentRequiredError";
|
|
16
|
-
this.details = details;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
exports.PaymentRequiredError = PaymentRequiredError;
|
|
20
|
-
function buildPaymentHeader(txHash, requestId) {
|
|
21
|
-
return Buffer.from(`${txHash}:${requestId}`).toString("base64");
|
|
22
|
-
}
|
|
23
|
-
async function attemptConvex(url, method, headers, body, timeoutMs = 30000) {
|
|
24
|
-
return fetch(url, {
|
|
25
|
-
method,
|
|
26
|
-
headers,
|
|
27
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
28
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
async function callConvex(path, method, body, toolName = "unknown", timeoutMs = 30000,
|
|
32
|
-
// Fund-moving mutations (stake, unstake, claim rewards, a real - non-
|
|
33
|
-
// dryRun - automation trigger) are NOT idempotent server-side: there is
|
|
34
|
-
// no request-id dedup on the backend, so silently retrying a POST whose
|
|
35
|
-
// response was merely lost (a network blip or client-side timeout AFTER
|
|
36
|
-
// the server already processed it) can double-execute a real transfer.
|
|
37
|
-
// Pass true here for any such call - it trades the convenience of an
|
|
38
|
-
// automatic retry for never risking a duplicate execution; the caller
|
|
39
|
-
// sees a clear "may have already gone through" error instead and can
|
|
40
|
-
// check status before deciding to retry by hand.
|
|
41
|
-
noRetry = false) {
|
|
42
|
-
const url = `${exports.CONVEX_SITE}${path}`;
|
|
43
|
-
const headers = { "Content-Type": "application/json" };
|
|
44
|
-
const apiKey = process.env.FINCH_API_KEY;
|
|
45
|
-
const sessionToken = (0, config_js_1.getSavedToken)(); // env var → saved config fallback
|
|
46
|
-
// Prefer session token (resolved by backend) over API key for Convex API calls
|
|
47
|
-
const authHeader = sessionToken
|
|
48
|
-
? `Bearer ${sessionToken}`
|
|
49
|
-
: apiKey
|
|
50
|
-
? `Bearer ${apiKey}`
|
|
51
|
-
: null;
|
|
52
|
-
if (authHeader) {
|
|
53
|
-
headers["Authorization"] = authHeader;
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
try {
|
|
57
|
-
const { address, signature, timestamp } = await (0, wallet_js_1.signRequest)(toolName);
|
|
58
|
-
headers["X-Wallet-Address"] = address;
|
|
59
|
-
headers["X-Wallet-Signature"] = signature;
|
|
60
|
-
headers["X-Wallet-Timestamp"] = timestamp;
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
// continue without wallet headers - server will respond with 401/402
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
const paymentHeader = process.env.FINCH_PAYMENT_HEADER;
|
|
67
|
-
if (paymentHeader)
|
|
68
|
-
headers["X-Payment"] = paymentHeader;
|
|
69
|
-
// BYOK headers - user pays for their own AI/service costs
|
|
70
|
-
if (process.env.ANTHROPIC_API_KEY)
|
|
71
|
-
headers["X-User-Anthropic-Key"] = process.env.ANTHROPIC_API_KEY;
|
|
72
|
-
if (process.env.OPENAI_API_KEY)
|
|
73
|
-
headers["X-User-OpenAI-Key"] = process.env.OPENAI_API_KEY;
|
|
74
|
-
if (process.env.GROK_API_KEY)
|
|
75
|
-
headers["X-User-Grok-Key"] = process.env.GROK_API_KEY;
|
|
76
|
-
if (process.env.BANKR_API_KEY)
|
|
77
|
-
headers["X-User-Bankr-Key"] = process.env.BANKR_API_KEY;
|
|
78
|
-
let lastError = null;
|
|
79
|
-
const attempts = noRetry ? 1 : RETRY_DELAYS.length;
|
|
80
|
-
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
81
|
-
if (attempt > 0) {
|
|
82
|
-
await new Promise((r) => setTimeout(r, RETRY_DELAYS[attempt - 1]));
|
|
83
|
-
}
|
|
84
|
-
let res;
|
|
85
|
-
try {
|
|
86
|
-
res = await attemptConvex(url, method, headers, body, timeoutMs);
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
// Ambiguous: the request may never have reached the server, or it may
|
|
90
|
-
// have been received and even processed before the connection died -
|
|
91
|
-
// there's no way to tell from here. Safe to retry for read-only/
|
|
92
|
-
// idempotent calls; for a fund-moving one, retrying risks resending a
|
|
93
|
-
// mutation that already landed.
|
|
94
|
-
lastError = noRetry
|
|
95
|
-
? new Error(`${err?.message ?? err} - request may or may not have completed (no response received). Check status before retrying manually.`)
|
|
96
|
-
: err;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
if (res.status === 402) {
|
|
100
|
-
const b = await res.json().catch(() => ({}));
|
|
101
|
-
throw new PaymentRequiredError(b);
|
|
102
|
-
}
|
|
103
|
-
if (res.status === 401) {
|
|
104
|
-
const b = await res.json().catch(() => ({}));
|
|
105
|
-
throw new Error(`🔑 ${b.message || "Authentication required"}\n\n` +
|
|
106
|
-
`→ Sign in at: ${b.url || "https://finchagentic.com"}\n\n` +
|
|
107
|
-
`Hint: ${b.hint || 'Add FINCH_SESSION_TOKEN=… to the env block in your MCP config'}\n\n` +
|
|
108
|
-
`${b.alternative ? `Alternative: ${b.alternative}` : ""}`);
|
|
109
|
-
}
|
|
110
|
-
if (RETRY_STATUSES.has(res.status) && attempt < attempts - 1) {
|
|
111
|
-
// Capture the actual body so a deterministic error (e.g. "unknown
|
|
112
|
-
// token") that happens to come back on a 500 still surfaces its real
|
|
113
|
-
// message if retries exhaust - previously this discarded the body
|
|
114
|
-
// entirely and threw a bare "Finch API error: 500", hiding exactly the
|
|
115
|
-
// information the caller needed to fix the request.
|
|
116
|
-
const bodyText = await res.text().catch(() => "");
|
|
117
|
-
lastError = new Error(`Finch API error ${res.status}: ${bodyText.slice(0, 300) || "(no body)"}`);
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
// noRetry + a retryable status on the one and only attempt: the server
|
|
121
|
-
// definitely received this one (unlike the network-exception case
|
|
122
|
-
// above), so it may have partially or fully processed it before
|
|
123
|
-
// erroring - same "check before retrying" caution, worded for the case
|
|
124
|
-
// where we know a response did come back.
|
|
125
|
-
if (RETRY_STATUSES.has(res.status) && noRetry) {
|
|
126
|
-
const bodyText = await res.text().catch(() => "");
|
|
127
|
-
throw new Error(`Finch API error ${res.status}: ${bodyText.slice(0, 300) || "(no body)"} - the server received this request and may have processed it before erroring. Check status before retrying manually.`);
|
|
128
|
-
}
|
|
129
|
-
if (!res.ok)
|
|
130
|
-
throw new Error(`Finch API error: ${res.status} ${await res.text()}`);
|
|
131
|
-
return res.json();
|
|
132
|
-
}
|
|
133
|
-
throw lastError ?? new Error("Request failed after retries");
|
|
134
|
-
}
|
|
135
|
-
// Variant that returns the response body as raw text. Used for endpoints
|
|
136
|
-
// that stream non-JSON content like /vault/blob (large vault entries that
|
|
137
|
-
// were offloaded to Convex File Storage).
|
|
138
|
-
async function callConvexRaw(path, toolName = "unknown", timeoutMs = 60000) {
|
|
139
|
-
const url = `${exports.CONVEX_SITE}${path}`;
|
|
140
|
-
const headers = {};
|
|
141
|
-
const apiKey = process.env.FINCH_API_KEY;
|
|
142
|
-
const sessionToken = (0, config_js_1.getSavedToken)();
|
|
143
|
-
// Same precedence as callConvex() above - prefer session token over API
|
|
144
|
-
// key. These two functions previously disagreed (this one checked apiKey
|
|
145
|
-
// first), so the same env/config could pick a different credential
|
|
146
|
-
// depending on which helper a tool happened to call.
|
|
147
|
-
const authHeader = sessionToken
|
|
148
|
-
? `Bearer ${sessionToken}`
|
|
149
|
-
: apiKey
|
|
150
|
-
? `Bearer ${apiKey}`
|
|
151
|
-
: null;
|
|
152
|
-
if (authHeader) {
|
|
153
|
-
headers["Authorization"] = authHeader;
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
try {
|
|
157
|
-
const { address, signature, timestamp } = await (0, wallet_js_1.signRequest)(toolName);
|
|
158
|
-
headers["X-Wallet-Address"] = address;
|
|
159
|
-
headers["X-Wallet-Signature"] = signature;
|
|
160
|
-
headers["X-Wallet-Timestamp"] = timestamp;
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
// No local wallet available to sign with - continue without wallet
|
|
164
|
-
// headers, server will respond with 401/402 if auth was required.
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
const res = await fetch(url, {
|
|
168
|
-
method: "GET",
|
|
169
|
-
headers,
|
|
170
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
171
|
-
});
|
|
172
|
-
if (!res.ok)
|
|
173
|
-
throw new Error(`Finch API error: ${res.status}`);
|
|
174
|
-
return res.text();
|
|
175
|
-
}
|
package/dist/dex-pair.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Pick the DexScreener pair whose numbers actually describe the token asked for.
|
|
4
|
-
*
|
|
5
|
-
* `priceUsd`, `marketCap`, `fdv`, `priceChange` and `txns` on a pair all describe
|
|
6
|
-
* the BASE token. Taking the deepest pair regardless of side therefore reports a
|
|
7
|
-
* different token entirely, and does it convincingly — the caller's own contract
|
|
8
|
-
* address is still printed alongside. Two real cases:
|
|
9
|
-
*
|
|
10
|
-
* - Canonical USDC on Base. Its deepest pair is `AERO/USDC`, so asking for
|
|
11
|
-
* USDC returned Aerodrome at $0.4332 with an $840M FDV.
|
|
12
|
-
* - NVDA on Robinhood Chain. Its deepest pair is `AI/NVDA`, so it reported AI
|
|
13
|
-
* at $0.0063 as though that were NVDA at $205 — and that price fed the
|
|
14
|
-
* take-profit and stop-loss triggers.
|
|
15
|
-
*
|
|
16
|
-
* Base-side pairs are preferred by depth. When a token only ever appears as the
|
|
17
|
-
* quote, its price is still derivable as `basePriceUsd / basePriceInOurToken`,
|
|
18
|
-
* so the pair is rebuilt with the sides swapped and the fields that describe the
|
|
19
|
-
* other token's flow dropped rather than passed off as ours.
|
|
20
|
-
*/
|
|
21
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.pickTokenPair = pickTokenPair;
|
|
23
|
-
const depth = (p) => p.liquidity?.usd ?? 0;
|
|
24
|
-
function pickTokenPair(pairs, tokenAddress) {
|
|
25
|
-
const want = tokenAddress.toLowerCase();
|
|
26
|
-
const all = pairs ?? [];
|
|
27
|
-
const baseSide = all
|
|
28
|
-
.filter((p) => p.baseToken?.address?.toLowerCase() === want)
|
|
29
|
-
.sort((a, b) => depth(b) - depth(a));
|
|
30
|
-
if (baseSide.length)
|
|
31
|
-
return baseSide[0];
|
|
32
|
-
const q = all
|
|
33
|
-
.filter((p) => p.quoteToken?.address?.toLowerCase() === want)
|
|
34
|
-
.sort((a, b) => depth(b) - depth(a))[0];
|
|
35
|
-
if (!q)
|
|
36
|
-
return null;
|
|
37
|
-
const baseUsd = Number(q.priceUsd);
|
|
38
|
-
const baseInOurs = Number(q.priceNative);
|
|
39
|
-
if (!isFinite(baseUsd) || !isFinite(baseInOurs) || baseInOurs <= 0)
|
|
40
|
-
return null;
|
|
41
|
-
return {
|
|
42
|
-
...q,
|
|
43
|
-
baseToken: q.quoteToken,
|
|
44
|
-
quoteToken: q.baseToken,
|
|
45
|
-
priceUsd: String(baseUsd / baseInOurs),
|
|
46
|
-
priceNative: undefined,
|
|
47
|
-
// Supply-based and direction-based figures belong to the other token.
|
|
48
|
-
marketCap: undefined,
|
|
49
|
-
fdv: undefined,
|
|
50
|
-
txns: undefined,
|
|
51
|
-
priceChange: undefined,
|
|
52
|
-
derivedFromQuoteSide: true,
|
|
53
|
-
};
|
|
54
|
-
}
|