chain-insights 0.71.0 → 0.71.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/dist/cli.cjs +2 -2
- package/dist/cli.mjs +2 -2
- package/dist/{dex-tool-DqAHl4_z.cjs → dex-tool-B4T8Czhm.cjs} +1 -1
- package/dist/{dex-tool-CeggX4M4.mjs → dex-tool-B5Qwz1Zd.mjs} +2 -2
- package/dist/{dex-tool-CeggX4M4.mjs.map → dex-tool-B5Qwz1Zd.mjs.map} +1 -1
- package/dist/mcp-proxy.cjs +1 -1
- package/dist/mcp-proxy.mjs +1 -1
- package/package.json +2 -2
- package/registry/README.md +2 -2
- package/registry/robinhood.json +55 -2
- package/registry/robinhood.md +15 -4
- package/skills/chain-insights-dex/SKILL.md +3 -1
- package/skills/chain-insights-dex-launchpad/SKILL.md +63 -5
- package/skills/chain-insights-dex-vault/SKILL.md +11 -5
- package/skills/chain-insights-virtuals/SKILL.md +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -522,7 +522,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
|
|
|
522
522
|
}
|
|
523
523
|
}
|
|
524
524
|
console.log(formatToolsTable(visibleRemoteTools(tools)));
|
|
525
|
-
const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await Promise.resolve().then(() => require("./dex-tool-
|
|
525
|
+
const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await Promise.resolve().then(() => require("./dex-tool-B4T8Czhm.cjs")).then((n) => n.dex_tool_exports);
|
|
526
526
|
console.log("");
|
|
527
527
|
console.log(formatToolsTable([{
|
|
528
528
|
name: META_DEX_LOOKUP_TOOL,
|
|
@@ -556,7 +556,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
|
|
|
556
556
|
return;
|
|
557
557
|
}
|
|
558
558
|
if (tool === "meta_dex_lookup") {
|
|
559
|
-
const { handleMetaDexLookup } = await Promise.resolve().then(() => require("./dex-tool-
|
|
559
|
+
const { handleMetaDexLookup } = await Promise.resolve().then(() => require("./dex-tool-B4T8Czhm.cjs")).then((n) => n.dex_tool_exports);
|
|
560
560
|
const { loadRegistry } = await Promise.resolve().then(() => require("./dex-registry-KOTJhAQf.cjs")).then((n) => n.dex_registry_exports);
|
|
561
561
|
const pools = args["pools"] === void 0 ? void 0 : String(args["pools"]).toLowerCase() !== "false";
|
|
562
562
|
const result = await withGraphMcpClient("chain-insights-cli-dex-lookup", (client) => handleMetaDexLookup({
|
package/dist/cli.mjs
CHANGED
|
@@ -520,7 +520,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
|
|
|
520
520
|
}
|
|
521
521
|
}
|
|
522
522
|
console.log(formatToolsTable(visibleRemoteTools(tools)));
|
|
523
|
-
const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await import("./dex-tool-
|
|
523
|
+
const { META_DEX_LOOKUP_DESCRIPTION, META_DEX_LOOKUP_TITLE, META_DEX_LOOKUP_TOOL } = await import("./dex-tool-B5Qwz1Zd.mjs").then((n) => n.a);
|
|
524
524
|
console.log("");
|
|
525
525
|
console.log(formatToolsTable([{
|
|
526
526
|
name: META_DEX_LOOKUP_TOOL,
|
|
@@ -554,7 +554,7 @@ program.command("mcp").description("Low-level access to the Chain Insights MCP e
|
|
|
554
554
|
return;
|
|
555
555
|
}
|
|
556
556
|
if (tool === "meta_dex_lookup") {
|
|
557
|
-
const { handleMetaDexLookup } = await import("./dex-tool-
|
|
557
|
+
const { handleMetaDexLookup } = await import("./dex-tool-B5Qwz1Zd.mjs").then((n) => n.a);
|
|
558
558
|
const { loadRegistry } = await import("./dex-registry-05wAhSa0.mjs").then((n) => n.n);
|
|
559
559
|
const pools = args["pools"] === void 0 ? void 0 : String(args["pools"]).toLowerCase() !== "false";
|
|
560
560
|
const result = await withGraphMcpClient("chain-insights-cli-dex-lookup", (client) => handleMetaDexLookup({
|
|
@@ -360,7 +360,7 @@ function readMatch(entry, members, read) {
|
|
|
360
360
|
}
|
|
361
361
|
const ADDRESS_FORM = /^0x[0-9a-fA-F]{40}$/;
|
|
362
362
|
function hintFor(result, name, address = null) {
|
|
363
|
-
if (address !== null && !result.matched) return `${ANSWER_HINT} No exchange row holds ${address}. Send no read for it.`;
|
|
363
|
+
if (address !== null && !result.matched) return `${ANSWER_HINT} No exchange row holds ${address}: it is not a registered exchange contract. Send no exchange read for it; read the graph for what the address did.`;
|
|
364
364
|
if (address !== null && result.matched) {
|
|
365
365
|
const { matched } = result;
|
|
366
366
|
return `${ANSWER_HINT} Say in one line: "${address} is a ${result.took_for} row of ${matched.display}." Ask nothing. Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, anchored on the addresses above, and load ${matched.skill} when this host has skills. Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.`;
|
|
@@ -360,7 +360,7 @@ function readMatch(entry, members, read) {
|
|
|
360
360
|
}
|
|
361
361
|
const ADDRESS_FORM = /^0x[0-9a-fA-F]{40}$/;
|
|
362
362
|
function hintFor(result, name, address = null) {
|
|
363
|
-
if (address !== null && !result.matched) return `${ANSWER_HINT} No exchange row holds ${address}. Send no read for it.`;
|
|
363
|
+
if (address !== null && !result.matched) return `${ANSWER_HINT} No exchange row holds ${address}: it is not a registered exchange contract. Send no exchange read for it; read the graph for what the address did.`;
|
|
364
364
|
if (address !== null && result.matched) {
|
|
365
365
|
const { matched } = result;
|
|
366
366
|
return `${ANSWER_HINT} Say in one line: "${address} is a ${result.took_for} row of ${matched.display}." Ask nothing. Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, anchored on the addresses above, and load ${matched.skill} when this host has skills. Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.`;
|
|
@@ -515,4 +515,4 @@ async function handleMetaDexLookup(args, deps) {
|
|
|
515
515
|
//#endregion
|
|
516
516
|
export { dex_tool_exports as a, META_DEX_LOOKUP_TOOL as i, META_DEX_LOOKUP_DESCRIPTION as n, handleMetaDexLookup as o, META_DEX_LOOKUP_TITLE as r, withAnswerHintResult as s, META_DEX_LOOKUP_ANNOTATIONS as t };
|
|
517
517
|
|
|
518
|
-
//# sourceMappingURL=dex-tool-
|
|
518
|
+
//# sourceMappingURL=dex-tool-B5Qwz1Zd.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dex-tool-CeggX4M4.mjs","names":["isRecord"],"sources":["../src/mcp/answer-hint.ts","../src/mcp/dex-recipes.ts","../src/mcp/dex-tool.ts"],"sourcesContent":["/**\n * The two answer rules that still slip in long answers, carried on every\n * graph reply in the result's `hint` field. Claude Code has a Stop hook that\n * reads the answer; Codex and Claude Desktop have no such hook, and in the\n * loop's third prompt set they broke one of these two rules in three of\n * thirteen conversations. The reply is the one thing every host hands the\n * model right before it writes, so the rules ride on it. A `hint` the\n * server already set is kept.\n */\n\nexport const ANSWER_HINT =\n 'Answer: write every address in full (0x plus 40 characters), also when it repeats one named earlier and also when the user asks for shorter ones (name them instead); ' +\n 'a transfer moves tokens, so write \"tokens worth about N USD\", never \"sent N USD\" or a $ figure; ' +\n 'never draw the flows in text, ASCII or Mermaid, the host draws them.'\n\ntype TextBlock = { type: string; text?: string }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The structured content with the hint set when the server left it empty. */\nexport function withAnswerHint(structuredContent: unknown): unknown {\n if (!isRecord(structuredContent) || !isRecord(structuredContent.facts)) return structuredContent\n if (typeof structuredContent.hint === 'string' && structuredContent.hint.trim()) return structuredContent\n return { ...structuredContent, hint: ANSWER_HINT }\n}\n\n/** A graph tool result whose structured content and single JSON text block both carry the hint. */\nexport function withAnswerHintResult<T extends { content?: TextBlock[]; structuredContent?: unknown }>(result: T): T {\n const structured = withAnswerHint(result.structuredContent)\n if (structured === result.structuredContent) return result\n const content = result.content ?? []\n const only = content.length === 1 && content[0]?.type === 'text' ? content[0] : null\n let parsed: unknown = undefined\n try {\n parsed = only?.text ? JSON.parse(only.text) : undefined\n } catch {\n parsed = undefined\n }\n const textIsStructured = parsed !== undefined && JSON.stringify(parsed) === JSON.stringify(result.structuredContent)\n return { ...result, structuredContent: structured, content: textIsStructured ? [{ ...only, text: JSON.stringify(structured) }] : content }\n}\n","/**\n * The first reads of each trail type, filled with the addresses of one\n * exchange. meta_dex_lookup returns them, so a host that loads no skill\n * (Claude Desktop chat) still gets the tested reads of\n * skills/chain-insights-dex*. The shapes are the skills' own: R3 and R4 of\n * chain-insights-dex, G1, G4 and G5 of the gauge trail, V1, V3 and V4 of the\n * vault trail, L1 to L3 of the launchpad trail.\n *\n * A value the lookup cannot know (a seller, a day, a transaction) is written\n * as a placeholder in angle brackets, and the step's `why` says where it comes\n * from. Every address the lookup knows is written in full.\n */\n\nimport type { DexEntry, TrailType } from './dex-registry.js'\n\nexport interface RecipeStep {\n step: string\n why: string\n query: string\n}\n\n/** The trail-type skill of each trail, for hosts that load skills. */\nexport const TRAIL_SKILLS: Record<TrailType, string> = {\n plain: 'chain-insights-dex',\n gauge: 'chain-insights-dex-gauge',\n vault: 'chain-insights-dex-vault',\n launchpad: 'chain-insights-dex-launchpad',\n}\n\nconst SELLER = '<seller address>'\nconst DAY = '<YYYY-MM-DD>'\nconst TX = '<tx_id>'\nconst TOKEN = '<launched token address>'\n\n/** The pools one factory deployed, by lifetime volume (R3). */\nexport function poolsByFactoryQuery(factory: string, limit = 10): string {\n return (\n `USE topology MATCH (f:Address {address: \"${factory}\"})-[:DEPLOYED_CONTRACT]->(p:Pool) WHERE p.total_volume_usd IS NOT NULL ` +\n 'RETURN f.address AS from_address, p.address AS to_address, p.labels AS to_labels, p.total_volume_usd AS total_volume_usd, p.tx_total_count AS tx_total_count ' +\n `ORDER BY p.total_volume_usd DESC LIMIT ${limit}`\n )\n}\n\n/** The senders of one pool on FLOWS_TO, by lifetime volume (R4, G1). */\nfunction sellersIntoPoolQuery(pool: string): string {\n return (\n `USE topology MATCH (s:Address)-[f:FLOWS_TO]->(p:Address {address: \"${pool}\"}) WHERE NOT s:Pool ` +\n 'RETURN s.address AS from_address, p.address AS to_address, labels(s) AS from_node_labels, f.amount_usd_sum AS amount_usd_sum, f.tx_count AS tx_count, f.first_seen_timestamp AS first_seen_timestamp, f.last_seen_timestamp AS last_seen_timestamp, s.labels AS from_labels, p.labels AS to_labels, s.is_exchange AS from_is_exchange, p.is_exchange AS to_is_exchange ' +\n 'ORDER BY f.amount_usd_sum DESC LIMIT 25'\n )\n}\n\n/** The payers whose swaps name one contract as the recipient, by sold volume (V4 shape). */\nfunction sellersIntoContractQuery(contract: string): string {\n return (\n `USE topology MATCH (s:Address)-[sw:SWAPPED]->(r:Address {address: \"${contract}\"}) WHERE NOT s:Pool ` +\n 'RETURN s.address AS from_address, r.address AS to_address, labels(s) AS from_node_labels, sw.sold_asset AS sold_asset, sw.bought_asset AS bought_asset, sw.sold_usd AS sold_usd, sw.bought_usd AS bought_usd, sw.swap_count AS swap_count, sw.pools AS pools, sw.last_seen_height AS last_seen_height ' +\n 'ORDER BY sw.sold_usd DESC LIMIT 25'\n )\n}\n\n/** One seller's swaps (R5, G2, V1). */\nfunction sellerSwapsQuery(): string {\n return (\n `USE topology MATCH (s:Address {address: \"${SELLER}\"})-[sw:SWAPPED]->(r:Address) ` +\n 'RETURN s.address AS from_address, r.address AS to_address, labels(r) AS to_node_labels, sw.sold_asset AS sold_asset, sw.bought_asset AS bought_asset, sw.sold_usd AS sold_usd, sw.bought_usd AS bought_usd, sw.swap_count AS swap_count, sw.pools AS pools, sw.last_seen_height AS last_seen_height ' +\n 'ORDER BY sw.swap_count DESC LIMIT 25'\n )\n}\n\n/** The facts SWAP rows of a seller and one recipient on one day (R6, G4, V3). */\nfunction swapRowsQuery(recipient: string): string {\n return (\n `USE facts MATCH (s:Address {address: \"${SELLER}\"})-[x:SWAP]->(r:Address {address: \"${recipient}\"}) WHERE x.block_date = \"${DAY}\" ` +\n 'RETURN x.tx_id AS tx_id, x.block_timestamp AS block_timestamp, x.payer AS payer, x.recipient AS recipient, x.sold_asset_symbol AS sold_asset_symbol, x.sold_usd AS sold_usd, x.bought_asset_symbol AS bought_asset_symbol, x.bought_usd AS bought_usd LIMIT 50'\n )\n}\n\n/** The transfers one router made in one transaction (G5). */\nfunction routerPayoutQuery(router: string): string {\n return (\n `USE facts MATCH (r:Address {address: \"${router}\"})-[t:TRANSFER]->(b:Address) WHERE t.block_date = \"${DAY}\" AND t.tx_id = \"${TX}\" ` +\n 'RETURN r.address AS from_address, b.address AS to_address, t.amount_usd AS amount_usd, t.asset_symbol AS asset_symbol, t.kind AS kind, t.tx_id AS tx_id LIMIT 10'\n )\n}\n\nconst dayStep: RecipeStep = {\n step: 'The day of a swap link',\n why: 'A SWAPPED link carries heights, not times: read the block of its last_seen_height for the day the facts reads need.',\n query:\n 'USE chain MATCH (b:Block {block_height: <last_seen_height>}) RETURN b.block_date, b.block_timestamp',\n}\n\n/**\n * The pool a trail starts from: the busiest pool read, else the registry's\n * example pool, else none.\n */\nfunction startPool(entry: DexEntry, topPool: string | null): string | null {\n return topPool ?? entry.example_pool ?? null\n}\n\nfunction poolsStep(entry: DexEntry): RecipeStep | null {\n const factory = entry.factories.find((address) => entry.roles[address] === 'factory')\n if (!factory) return null\n return {\n step: 'Pools of the exchange, by factory',\n why: `The pools ${entry.display} deployed, by lifetime volume. One batch member per factory: ${entry.factories.filter((a) => entry.roles[a] === 'factory').join(', ')}.`,\n query: poolsByFactoryQuery(factory),\n }\n}\n\n/** The first reads of the exchange's trail type, with its addresses filled in. */\nexport function recipeFor(entry: DexEntry, topPool: string | null): RecipeStep[] {\n const pool = startPool(entry, topPool)\n const steps: RecipeStep[] = []\n switch (entry.trail_type) {\n case 'gauge': {\n const router = entry.routers[0]\n if (router) {\n steps.push({\n step: 'Sellers into the router',\n why: `On ${entry.display} a swap pays the router, and the router pays on. These are the payers whose swaps name the router ${router}; the router is never the buyer.`,\n query: sellersIntoContractQuery(router),\n })\n }\n if (pool) {\n steps.push({\n step: 'Sellers into the busiest pool',\n why: `The senders of ${pool} on FLOWS_TO. A sender that also adds liquidity is a liquidity provider, not a seller.`,\n query: sellersIntoPoolQuery(pool),\n })\n } else {\n const pools = poolsStep(entry)\n if (pools) steps.push(pools)\n }\n if (router) {\n steps.push(dayStep)\n steps.push({\n step: \"A seller's swaps through the router on one day\",\n why: 'Fill the seller from the first step and the day from the block read: each row gives the tx_id of one swap.',\n query: swapRowsQuery(router),\n })\n steps.push({\n step: 'Who the router paid in that transaction',\n why: 'Fill the day and the tx_id: the receiver of the bought asset is the buyer. A pool or a pool manager there is the next leg, not the buyer.',\n query: routerPayoutQuery(router),\n })\n }\n break\n }\n case 'vault': {\n const manager = entry.factories[0]!\n steps.push({\n step: 'The swaps the pool manager settles',\n why: `${entry.display} keeps every pool in one manager, ${manager}. A manager deploys nothing: its pools are the pools of the swaps it settles.`,\n query: sellersIntoContractQuery(manager),\n })\n steps.push({\n step: \"One seller's swaps\",\n why: 'Fill a seller from the first step. A recipient that is the manager is never the buyer: read the facts row past it.',\n query: sellerSwapsQuery(),\n })\n steps.push(dayStep)\n steps.push({\n step: 'The facts SWAP row past the manager',\n why: 'Fill the seller and the day: the recipient column names who the manager paid.',\n query: swapRowsQuery(manager),\n })\n break\n }\n case 'launchpad': {\n const factory = entry.factories[0]!\n steps.push({\n step: 'Launches of the launchpad, newest first',\n why: `The contracts ${factory} created, with the creator of each.`,\n query:\n `USE topology MATCH (f:Address {address: \"${factory}\"})-[d:DEPLOYED_CONTRACT]->(t:Address) ` +\n 'RETURN f.address AS from_address, t.address AS to_address, labels(t) AS to_node_labels, t.labels AS to_labels, d.tx_origin AS creator, d.tx_id AS tx_id, d.block_height AS block_height, d.block_timestamp AS created_timestamp ORDER BY d.block_height DESC LIMIT 25',\n })\n steps.push({\n step: 'Traders on the curve of one launched token',\n why: 'Fill a launched token from the first step.',\n query:\n `USE topology MATCH (t:Address {address: \"${TOKEN}\"})-[f:FLOWS_TO]-(b:Address) WHERE NOT t:Pool ` +\n 'RETURN startNode(f).address AS from_address, endNode(f).address AS to_address, labels(b) AS node_labels, f.tx_count AS tx_count, f.amount_usd_sum AS amount_usd_sum, f.first_seen_timestamp AS first_seen_timestamp, f.last_seen_timestamp AS last_seen_timestamp ORDER BY f.tx_count DESC LIMIT 25',\n })\n steps.push({\n step: 'The first pool of one launched token',\n why: 'The oldest ADDED_LIQUIDITY into a :Pool is the graduation. None means it has not graduated in the graph yet.',\n query:\n `USE topology MATCH (t:Address {address: \"${TOKEN}\"})-[l:ADDED_LIQUIDITY]->(p:Pool) ` +\n 'RETURN t.address AS from_address, p.address AS to_address, p.labels AS to_labels, l.asset AS asset, l.usd AS usd, l.first_seen_height AS first_seen_height ORDER BY l.first_seen_height ASC LIMIT 5',\n })\n break\n }\n case 'plain': {\n if (pool) {\n steps.push({\n step: 'Sellers into the busiest pool',\n why: `The senders of ${pool} on FLOWS_TO, by lifetime volume. A seller that is a contract is often an aggregator or a router: say \"a contract\".`,\n query: sellersIntoPoolQuery(pool),\n })\n } else {\n const pools = poolsStep(entry)\n if (pools) steps.push(pools)\n }\n steps.push({\n step: \"Who a seller's swaps paid\",\n why: 'Fill a seller from the first step. Keep the rows whose pools hold the pool: the recipient is the buyer.',\n query: sellerSwapsQuery(),\n })\n steps.push(dayStep)\n steps.push({\n step: 'The swaps of a seller and a recipient on one day',\n why: 'Fill the seller, the recipient and the day.',\n query: swapRowsQuery('<recipient address>'),\n })\n break\n }\n }\n return steps\n}\n","/**\n * meta_dex_lookup: an exchange's name becomes its addresses.\n *\n * A local tool of the proxy. It matches the name against the exchange registry\n * (src/mcp/dex-registry.ts) at no cost, then sends one graph_query_batch of\n * anchored reads: Head for the family lag, each factory, manager and router by\n * its address, and the pools each factory deployed. Nothing is read by name and\n * nothing is gated on a family lag: a factory deployed months ago is not stale\n * because the swap family is behind. A name that is not covered sends no read.\n *\n * The description below is the contract with every host, Claude Desktop chat\n * included, which reads no skill: it carries the rule the exchange skills open\n * with. tests/host-parity.test.ts holds it to the skills' trigger words.\n */\n\nimport { ANSWER_HINT } from './answer-hint.js'\nimport { recipeFor, TRAIL_SKILLS, type RecipeStep, poolsByFactoryQuery } from './dex-recipes.js'\nimport {\n DexRegistryError,\n matchDex,\n normalizeDexNetwork,\n type DexCandidate,\n type DexEntry,\n type DexMatchResult,\n type LoadedRegistry,\n type RegistrySource,\n type Role,\n type TrailType,\n} from './dex-registry.js'\n\nexport const META_DEX_LOOKUP_TOOL = 'meta_dex_lookup'\nexport const META_DEX_LOOKUP_SCHEMA = 'chain-insights.meta-dex-lookup.v1' as const\nexport const META_DEX_LOOKUP_TITLE = 'Exchange by name or address'\nexport const META_DEX_LOOKUP_DESCRIPTION =\n 'Call this first when a question names a decentralized exchange, a launchpad or a vault ' +\n '(Aero, Aerodrome, Velodrome, up., Raphael, Topaz, Uniswap, PancakeSwap, Balancer, Curve, ' +\n \"Penguin, Virtuals, ...) and gives no address. It turns the name into the exchange's \" +\n 'factories, routers or pool manager, its biggest pools, and the first reads to send, ' +\n 'with every address in full. A misspelling within two letters is taken and named in ' +\n 'took_for. When matched is null the name is not covered: say so, list covered, and send ' +\n 'no graph query for the name. Never ask the user for a pool, factory or router address. ' +\n 'When the user gives a contract address instead of a name, send it as address (0x plus 40 hex, ' +\n 'any case, with no name): the answer is the exchange whose factory, router or manager it is, ' +\n 'and took_for names the role it holds. When matched is null no exchange row holds the address: ' +\n 'send no read for it. Give a name or an address, not both.'\nexport const META_DEX_LOOKUP_ANNOTATIONS = {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n} as const\nexport const META_DEX_LOOKUP_ARGS = ['network', 'name', 'address', 'pools'] as const\n\n/** The pools returned per exchange, and per factory read. */\nexport const POOLS_LIMIT = 10\n/** A batch holds at most 20 members; Head and the pool reads take their share. */\nconst CONTRACT_MEMBERS_MAX = 12\n\nexport interface ContractRow {\n address: string\n role: Role\n confirmed: boolean\n confirmed_by?: string\n}\n\nexport interface PoolRow {\n address: string\n labels: string[]\n total_volume_usd: number\n tx_total_count: number\n}\n\nexport interface FamilyLag {\n behind_money: number | null\n behind_dex: number | null\n behind_transfers: number | null\n}\n\nexport interface DexMatch {\n dex: string\n display: string\n trail_type: TrailType\n /** The skill a host with skills loads next. */\n skill: string\n factories: ContractRow[]\n routers: ContractRow[]\n manager: ContractRow | null\n pools: PoolRow[]\n recipe: RecipeStep[]\n}\n\nexport interface CoveredExchange {\n dex: string\n display: string\n trail_type: TrailType\n factories: string[]\n routers: string[]\n}\n\nexport interface MetaDexLookupResult {\n schema: typeof META_DEX_LOOKUP_SCHEMA\n network: string\n registry_version: number\n registry_source: RegistrySource\n matched: DexMatch | null\n /** A near name hit: the name as typed. An address hit: the role it holds and the verification cell. */\n took_for?: string\n candidates: DexCandidate[]\n covered: string[]\n /** With no name: every covered exchange, with its key, trail type and contracts. */\n exchanges?: CoveredExchange[]\n families: FamilyLag | null\n /** What a read could not do: a lane that is not reachable, a member that was refused. */\n notes?: string[]\n hint: string\n}\n\n/** The batch reply as the remote client returns it. */\nexport type BatchAnswer = {\n content?: unknown[]\n structuredContent?: unknown\n isError?: boolean\n}\n\nexport interface DexToolDependencies {\n loadRegistry: (network: string) => Promise<LoadedRegistry>\n /** One graph_query_batch through the proxy's remote client, billed as any read. */\n graphQueryBatch: (args: {\n network: string\n queries: Array<{ id: string; query: string }>\n }) => Promise<BatchAnswer>\n /** The reason no read can be sent now, or undefined when the graph is reachable. */\n unavailable?: () => Promise<string | undefined> | string | undefined\n /** The text of a call that threw: payment required, transport. */\n describeFailure: (err: unknown) => string\n}\n\nexport type MetaDexLookupToolResult = {\n content: Array<{ type: 'text'; text: string }>\n structuredContent?: Record<string, unknown>\n isError: boolean\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction errorResult(text: string): MetaDexLookupToolResult {\n return { content: [{ type: 'text', text }], isError: true }\n}\n\ntype Member = { id?: unknown; ok?: unknown; results?: unknown; error?: unknown }\n\nfunction membersOf(answer: BatchAnswer): Map<string, Member> {\n const facts = isRecord(answer.structuredContent) ? answer.structuredContent.facts : undefined\n const queries = isRecord(facts) ? facts.queries : undefined\n const members = new Map<string, Member>()\n if (Array.isArray(queries)) {\n for (const member of queries)\n if (isRecord(member) && typeof member.id === 'string') members.set(member.id, member)\n }\n return members\n}\n\nconst rowsOf = (member: Member | undefined): Array<Record<string, unknown>> =>\n member?.ok === true && Array.isArray(member.results) ? member.results.filter(isRecord) : []\n\nconst numberOrNull = (value: unknown): number | null => {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value)))\n return Number(value)\n return null\n}\n\nconst stringList = (value: unknown): string[] =>\n Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : []\n\n/** A column of a row, read under its alias or as the query wrote it (h.behind_dex). */\nfunction column(row: Record<string, unknown>, name: string, variable: string): unknown {\n return name in row ? row[name] : row[`${variable}.${name}`]\n}\n\nexport const HEAD_QUERY =\n 'USE chain MATCH (h:Head) RETURN h.behind_money, h.behind_dex, h.behind_transfers'\n\nexport function contractQuery(address: string): string {\n return (\n `USE topology MATCH (a:Address {address: \"${address}\"}) ` +\n 'RETURN a.address AS address, labels(a) AS node_labels, a.labels AS labels, a.is_contract AS is_contract LIMIT 1'\n )\n}\n\nexport function contractListQuery(addresses: string[]): string {\n return (\n `USE topology MATCH (a:Address) WHERE a.address IN [${addresses.map((a) => `\"${a}\"`).join(', ')}] ` +\n `RETURN a.address AS address, labels(a) AS node_labels, a.labels AS labels, a.is_contract AS is_contract LIMIT ${addresses.length}`\n )\n}\n\n/** The anchored reads of one exchange: Head, each contract, and the pools of each factory. */\nexport function lookupQueries(\n entry: DexEntry,\n pools: boolean\n): Array<{ id: string; query: string }> {\n const contracts = Object.keys(entry.roles)\n const queries: Array<{ id: string; query: string }> = [{ id: 'head', query: HEAD_QUERY }]\n if (contracts.length <= CONTRACT_MEMBERS_MAX) {\n for (const address of contracts)\n queries.push({ id: `contract:${address}`, query: contractQuery(address) })\n } else {\n queries.push({ id: 'contracts', query: contractListQuery(contracts) })\n }\n if (pools) {\n for (const factory of entry.factories.filter((address) => entry.roles[address] === 'factory')) {\n queries.push({ id: `pools:${factory}`, query: poolsByFactoryQuery(factory, POOLS_LIMIT) })\n }\n }\n return queries\n}\n\nfunction confirmation(\n row: Record<string, unknown> | undefined\n): Pick<ContractRow, 'confirmed' | 'confirmed_by'> {\n if (!row) return { confirmed: false }\n if (row.is_contract === true) return { confirmed: true, confirmed_by: 'is_contract' }\n if (stringList(row.node_labels).includes('Contract'))\n return { confirmed: true, confirmed_by: ':Contract' }\n return { confirmed: false }\n}\n\nfunction readMatch(\n entry: DexEntry,\n members: Map<string, Member>,\n read: boolean\n): {\n contracts: Map<string, ContractRow>\n pools: PoolRow[]\n families: FamilyLag | null\n notes: string[]\n} {\n const notes: string[] = []\n const contracts = new Map<string, ContractRow>()\n const listed = new Map(rowsOf(members.get('contracts')).map((row) => [String(row.address), row]))\n for (const [address, role] of Object.entries(entry.roles)) {\n const row = members.has(`contract:${address}`)\n ? rowsOf(members.get(`contract:${address}`))[0]\n : listed.get(address)\n contracts.set(address, { address, role, ...confirmation(row) })\n }\n const byAddress = new Map<string, PoolRow>()\n for (const [id, member] of members) {\n if (!id.startsWith('pools:')) continue\n for (const row of rowsOf(member)) {\n const address = typeof row.to_address === 'string' ? row.to_address : null\n const volume = numberOrNull(row.total_volume_usd)\n if (!address || volume === null || byAddress.has(address)) continue\n byAddress.set(address, {\n address,\n labels: stringList(row.to_labels),\n total_volume_usd: volume,\n tx_total_count: numberOrNull(row.tx_total_count) ?? 0,\n })\n }\n }\n const pools = [...byAddress.values()]\n .sort((a, b) => b.total_volume_usd - a.total_volume_usd)\n .slice(0, POOLS_LIMIT)\n const head = rowsOf(members.get('head'))[0]\n const families: FamilyLag | null = head\n ? {\n behind_money: numberOrNull(column(head, 'behind_money', 'h')),\n behind_dex: numberOrNull(column(head, 'behind_dex', 'h')),\n behind_transfers: numberOrNull(column(head, 'behind_transfers', 'h')),\n }\n : null\n if (read) {\n for (const [id, member] of members) {\n if (member.ok !== true)\n notes.push(\n `${id}: ${typeof member.error === 'string' ? member.error : 'the read was not served'}`\n )\n }\n }\n return { contracts, pools, families, notes }\n}\n\nconst ADDRESS_FORM = /^0x[0-9a-fA-F]{40}$/\n\nfunction hintFor(\n result: Omit<MetaDexLookupResult, 'hint'>,\n name: string | null,\n address: string | null = null\n): string {\n if (address !== null && !result.matched) {\n return `${ANSWER_HINT} No exchange row holds ${address}. Send no read for it.`\n }\n if (address !== null && result.matched) {\n const { matched } = result\n return (\n `${ANSWER_HINT} Say in one line: \"${address} is a ${result.took_for} row of ${matched.display}.\" Ask nothing. ` +\n `Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, ` +\n `anchored on the addresses above, and load ${matched.skill} when this host has skills. ` +\n 'Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.'\n )\n }\n if (name === null) {\n return `${ANSWER_HINT} These are the exchanges the registry covers. Call meta_dex_lookup with a name for its addresses and first reads.`\n }\n if (result.matched) {\n const { matched } = result\n const took = result.took_for\n ? `Say in one line: \"I took ${matched.display} for ${result.took_for}.\" Ask nothing. `\n : ''\n return (\n `${ANSWER_HINT} ${took}Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, ` +\n `anchored on the addresses above, and load ${matched.skill} when this host has skills. ` +\n 'Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.'\n )\n }\n if (result.candidates.length > 0) {\n const names = result.candidates\n .map((candidate) => `${candidate.display} (${candidate.dex})`)\n .join(', ')\n return `${ANSWER_HINT} ${result.candidates.length} rows fit \"${name}\": ${names}. Name both or pick one: call meta_dex_lookup with each key. Never guess one, never ask the user for an address.`\n }\n return `${ANSWER_HINT} ${name} is not covered. Send no query for the name: say so in one line and list the covered exchanges by name.`\n}\n\nfunction textResult(structured: MetaDexLookupResult): MetaDexLookupToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }],\n structuredContent: structured as unknown as Record<string, unknown>,\n isError: false,\n }\n}\n\n/** meta_dex_lookup, {network, name?, address?, pools?}. Every argument is checked before the registry is read. */\nexport async function handleMetaDexLookup(\n args: unknown,\n deps: DexToolDependencies\n): Promise<MetaDexLookupToolResult> {\n const record = isRecord(args) ? args : {}\n let network: string\n try {\n network = normalizeDexNetwork(record.network)\n } catch (err) {\n return errorResult((err as Error).message)\n }\n const rawName = record.name\n if (rawName !== undefined && rawName !== null && typeof rawName !== 'string') {\n return errorResult('invalid_name: name must be the exchange name as text')\n }\n const name = typeof rawName === 'string' && rawName.trim() !== '' ? rawName.trim() : null\n const rawAddress = record.address\n if (rawAddress !== undefined && rawAddress !== null && typeof rawAddress !== 'string') {\n return errorResult('invalid_address: address must be 0x plus 40 hex characters')\n }\n const addressText = typeof rawAddress === 'string' && rawAddress.trim() !== '' ? rawAddress.trim() : null\n if (name !== null && addressText !== null) return errorResult('Give a name or an address, not both.')\n if (addressText !== null && !ADDRESS_FORM.test(addressText)) {\n return errorResult('invalid_address: address must be 0x plus 40 hex characters')\n }\n const address = addressText === null ? null : addressText.toLowerCase()\n const rawPools = record.pools\n if (rawPools !== undefined && rawPools !== null && typeof rawPools !== 'boolean') {\n return errorResult('invalid_pools: pools must be true or false')\n }\n const pools = rawPools !== false\n\n let loaded: LoadedRegistry\n try {\n loaded = await deps.loadRegistry(network)\n } catch (err) {\n return errorResult(\n err instanceof DexRegistryError\n ? err.message\n : `exchange registry unavailable: ${(err as Error).message}`\n )\n }\n const { document } = loaded\n const base = {\n schema: META_DEX_LOOKUP_SCHEMA,\n network,\n registry_version: document.registry_version,\n registry_source: loaded.source,\n }\n\n if (name === null && address === null) {\n const result = {\n ...base,\n matched: null,\n candidates: [],\n covered: document.rows.map((row) => row.display),\n exchanges: document.rows.map((row) => ({\n dex: row.dex,\n display: row.display,\n trail_type: row.trail_type,\n factories: row.factories,\n routers: row.routers,\n })),\n families: null,\n }\n return textResult({ ...result, hint: hintFor(result, null) })\n }\n\n let match: DexMatchResult\n if (address !== null) {\n const holder = document.rows.find((row) => address in row.roles)\n match = {\n matched: holder ?? null,\n ...(holder ? { took_for: `${holder.roles[address]}, served` } : {}),\n candidates: [],\n covered: document.rows.map((row) => row.display),\n }\n } else {\n match = matchDex(name as string, document.rows)\n }\n if (!match.matched) {\n const result = {\n ...base,\n matched: null,\n candidates: match.candidates,\n covered: match.covered,\n families: null,\n }\n return textResult({ ...result, hint: hintFor(result, name, address) })\n }\n\n const entry = match.matched\n const queries = lookupQueries(entry, pools)\n let members = new Map<string, Member>()\n const notes: string[] = []\n const unavailable = await deps.unavailable?.()\n if (unavailable) {\n notes.push(\n `No graph read was sent: ${unavailable} The contracts are the reviewed registry rows, not yet confirmed on the graph.`\n )\n } else {\n try {\n const answer = await deps.graphQueryBatch({ network, queries })\n members = membersOf(answer)\n if (answer.isError === true && members.size === 0) {\n const first = Array.isArray(answer.content) ? answer.content[0] : undefined\n notes.push(\n `The graph refused the reads: ${isRecord(first) && typeof first.text === 'string' ? first.text : 'no reason given'}`\n )\n }\n } catch (err) {\n notes.push(`The graph reads failed: ${deps.describeFailure(err)}`)\n }\n }\n const read = readMatch(entry, members, members.size > 0)\n notes.push(...read.notes)\n const contractsOf = (role: Role): ContractRow[] =>\n [...read.contracts.values()].filter((contract) => contract.role === role)\n const topPool = read.pools[0]?.address ?? null\n const matched: DexMatch = {\n dex: entry.dex,\n display: entry.display,\n trail_type: entry.trail_type,\n skill: TRAIL_SKILLS[entry.trail_type],\n factories: contractsOf('factory'),\n routers: contractsOf('router'),\n manager: contractsOf('manager')[0] ?? null,\n pools: read.pools,\n recipe: recipeFor(entry, topPool),\n }\n const result = {\n ...base,\n matched,\n ...(match.took_for ? { took_for: match.took_for } : {}),\n candidates: [],\n covered: match.covered,\n families: read.families,\n ...(notes.length > 0 ? { notes } : {}),\n }\n return textResult({ ...result, hint: hintFor(result, name, address) })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dex-tool-B5Qwz1Zd.mjs","names":["isRecord"],"sources":["../src/mcp/answer-hint.ts","../src/mcp/dex-recipes.ts","../src/mcp/dex-tool.ts"],"sourcesContent":["/**\n * The two answer rules that still slip in long answers, carried on every\n * graph reply in the result's `hint` field. Claude Code has a Stop hook that\n * reads the answer; Codex and Claude Desktop have no such hook, and in the\n * loop's third prompt set they broke one of these two rules in three of\n * thirteen conversations. The reply is the one thing every host hands the\n * model right before it writes, so the rules ride on it. A `hint` the\n * server already set is kept.\n */\n\nexport const ANSWER_HINT =\n 'Answer: write every address in full (0x plus 40 characters), also when it repeats one named earlier and also when the user asks for shorter ones (name them instead); ' +\n 'a transfer moves tokens, so write \"tokens worth about N USD\", never \"sent N USD\" or a $ figure; ' +\n 'never draw the flows in text, ASCII or Mermaid, the host draws them.'\n\ntype TextBlock = { type: string; text?: string }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The structured content with the hint set when the server left it empty. */\nexport function withAnswerHint(structuredContent: unknown): unknown {\n if (!isRecord(structuredContent) || !isRecord(structuredContent.facts)) return structuredContent\n if (typeof structuredContent.hint === 'string' && structuredContent.hint.trim()) return structuredContent\n return { ...structuredContent, hint: ANSWER_HINT }\n}\n\n/** A graph tool result whose structured content and single JSON text block both carry the hint. */\nexport function withAnswerHintResult<T extends { content?: TextBlock[]; structuredContent?: unknown }>(result: T): T {\n const structured = withAnswerHint(result.structuredContent)\n if (structured === result.structuredContent) return result\n const content = result.content ?? []\n const only = content.length === 1 && content[0]?.type === 'text' ? content[0] : null\n let parsed: unknown = undefined\n try {\n parsed = only?.text ? JSON.parse(only.text) : undefined\n } catch {\n parsed = undefined\n }\n const textIsStructured = parsed !== undefined && JSON.stringify(parsed) === JSON.stringify(result.structuredContent)\n return { ...result, structuredContent: structured, content: textIsStructured ? [{ ...only, text: JSON.stringify(structured) }] : content }\n}\n","/**\n * The first reads of each trail type, filled with the addresses of one\n * exchange. meta_dex_lookup returns them, so a host that loads no skill\n * (Claude Desktop chat) still gets the tested reads of\n * skills/chain-insights-dex*. The shapes are the skills' own: R3 and R4 of\n * chain-insights-dex, G1, G4 and G5 of the gauge trail, V1, V3 and V4 of the\n * vault trail, L1 to L3 of the launchpad trail.\n *\n * A value the lookup cannot know (a seller, a day, a transaction) is written\n * as a placeholder in angle brackets, and the step's `why` says where it comes\n * from. Every address the lookup knows is written in full.\n */\n\nimport type { DexEntry, TrailType } from './dex-registry.js'\n\nexport interface RecipeStep {\n step: string\n why: string\n query: string\n}\n\n/** The trail-type skill of each trail, for hosts that load skills. */\nexport const TRAIL_SKILLS: Record<TrailType, string> = {\n plain: 'chain-insights-dex',\n gauge: 'chain-insights-dex-gauge',\n vault: 'chain-insights-dex-vault',\n launchpad: 'chain-insights-dex-launchpad',\n}\n\nconst SELLER = '<seller address>'\nconst DAY = '<YYYY-MM-DD>'\nconst TX = '<tx_id>'\nconst TOKEN = '<launched token address>'\n\n/** The pools one factory deployed, by lifetime volume (R3). */\nexport function poolsByFactoryQuery(factory: string, limit = 10): string {\n return (\n `USE topology MATCH (f:Address {address: \"${factory}\"})-[:DEPLOYED_CONTRACT]->(p:Pool) WHERE p.total_volume_usd IS NOT NULL ` +\n 'RETURN f.address AS from_address, p.address AS to_address, p.labels AS to_labels, p.total_volume_usd AS total_volume_usd, p.tx_total_count AS tx_total_count ' +\n `ORDER BY p.total_volume_usd DESC LIMIT ${limit}`\n )\n}\n\n/** The senders of one pool on FLOWS_TO, by lifetime volume (R4, G1). */\nfunction sellersIntoPoolQuery(pool: string): string {\n return (\n `USE topology MATCH (s:Address)-[f:FLOWS_TO]->(p:Address {address: \"${pool}\"}) WHERE NOT s:Pool ` +\n 'RETURN s.address AS from_address, p.address AS to_address, labels(s) AS from_node_labels, f.amount_usd_sum AS amount_usd_sum, f.tx_count AS tx_count, f.first_seen_timestamp AS first_seen_timestamp, f.last_seen_timestamp AS last_seen_timestamp, s.labels AS from_labels, p.labels AS to_labels, s.is_exchange AS from_is_exchange, p.is_exchange AS to_is_exchange ' +\n 'ORDER BY f.amount_usd_sum DESC LIMIT 25'\n )\n}\n\n/** The payers whose swaps name one contract as the recipient, by sold volume (V4 shape). */\nfunction sellersIntoContractQuery(contract: string): string {\n return (\n `USE topology MATCH (s:Address)-[sw:SWAPPED]->(r:Address {address: \"${contract}\"}) WHERE NOT s:Pool ` +\n 'RETURN s.address AS from_address, r.address AS to_address, labels(s) AS from_node_labels, sw.sold_asset AS sold_asset, sw.bought_asset AS bought_asset, sw.sold_usd AS sold_usd, sw.bought_usd AS bought_usd, sw.swap_count AS swap_count, sw.pools AS pools, sw.last_seen_height AS last_seen_height ' +\n 'ORDER BY sw.sold_usd DESC LIMIT 25'\n )\n}\n\n/** One seller's swaps (R5, G2, V1). */\nfunction sellerSwapsQuery(): string {\n return (\n `USE topology MATCH (s:Address {address: \"${SELLER}\"})-[sw:SWAPPED]->(r:Address) ` +\n 'RETURN s.address AS from_address, r.address AS to_address, labels(r) AS to_node_labels, sw.sold_asset AS sold_asset, sw.bought_asset AS bought_asset, sw.sold_usd AS sold_usd, sw.bought_usd AS bought_usd, sw.swap_count AS swap_count, sw.pools AS pools, sw.last_seen_height AS last_seen_height ' +\n 'ORDER BY sw.swap_count DESC LIMIT 25'\n )\n}\n\n/** The facts SWAP rows of a seller and one recipient on one day (R6, G4, V3). */\nfunction swapRowsQuery(recipient: string): string {\n return (\n `USE facts MATCH (s:Address {address: \"${SELLER}\"})-[x:SWAP]->(r:Address {address: \"${recipient}\"}) WHERE x.block_date = \"${DAY}\" ` +\n 'RETURN x.tx_id AS tx_id, x.block_timestamp AS block_timestamp, x.payer AS payer, x.recipient AS recipient, x.sold_asset_symbol AS sold_asset_symbol, x.sold_usd AS sold_usd, x.bought_asset_symbol AS bought_asset_symbol, x.bought_usd AS bought_usd LIMIT 50'\n )\n}\n\n/** The transfers one router made in one transaction (G5). */\nfunction routerPayoutQuery(router: string): string {\n return (\n `USE facts MATCH (r:Address {address: \"${router}\"})-[t:TRANSFER]->(b:Address) WHERE t.block_date = \"${DAY}\" AND t.tx_id = \"${TX}\" ` +\n 'RETURN r.address AS from_address, b.address AS to_address, t.amount_usd AS amount_usd, t.asset_symbol AS asset_symbol, t.kind AS kind, t.tx_id AS tx_id LIMIT 10'\n )\n}\n\nconst dayStep: RecipeStep = {\n step: 'The day of a swap link',\n why: 'A SWAPPED link carries heights, not times: read the block of its last_seen_height for the day the facts reads need.',\n query:\n 'USE chain MATCH (b:Block {block_height: <last_seen_height>}) RETURN b.block_date, b.block_timestamp',\n}\n\n/**\n * The pool a trail starts from: the busiest pool read, else the registry's\n * example pool, else none.\n */\nfunction startPool(entry: DexEntry, topPool: string | null): string | null {\n return topPool ?? entry.example_pool ?? null\n}\n\nfunction poolsStep(entry: DexEntry): RecipeStep | null {\n const factory = entry.factories.find((address) => entry.roles[address] === 'factory')\n if (!factory) return null\n return {\n step: 'Pools of the exchange, by factory',\n why: `The pools ${entry.display} deployed, by lifetime volume. One batch member per factory: ${entry.factories.filter((a) => entry.roles[a] === 'factory').join(', ')}.`,\n query: poolsByFactoryQuery(factory),\n }\n}\n\n/** The first reads of the exchange's trail type, with its addresses filled in. */\nexport function recipeFor(entry: DexEntry, topPool: string | null): RecipeStep[] {\n const pool = startPool(entry, topPool)\n const steps: RecipeStep[] = []\n switch (entry.trail_type) {\n case 'gauge': {\n const router = entry.routers[0]\n if (router) {\n steps.push({\n step: 'Sellers into the router',\n why: `On ${entry.display} a swap pays the router, and the router pays on. These are the payers whose swaps name the router ${router}; the router is never the buyer.`,\n query: sellersIntoContractQuery(router),\n })\n }\n if (pool) {\n steps.push({\n step: 'Sellers into the busiest pool',\n why: `The senders of ${pool} on FLOWS_TO. A sender that also adds liquidity is a liquidity provider, not a seller.`,\n query: sellersIntoPoolQuery(pool),\n })\n } else {\n const pools = poolsStep(entry)\n if (pools) steps.push(pools)\n }\n if (router) {\n steps.push(dayStep)\n steps.push({\n step: \"A seller's swaps through the router on one day\",\n why: 'Fill the seller from the first step and the day from the block read: each row gives the tx_id of one swap.',\n query: swapRowsQuery(router),\n })\n steps.push({\n step: 'Who the router paid in that transaction',\n why: 'Fill the day and the tx_id: the receiver of the bought asset is the buyer. A pool or a pool manager there is the next leg, not the buyer.',\n query: routerPayoutQuery(router),\n })\n }\n break\n }\n case 'vault': {\n const manager = entry.factories[0]!\n steps.push({\n step: 'The swaps the pool manager settles',\n why: `${entry.display} keeps every pool in one manager, ${manager}. A manager deploys nothing: its pools are the pools of the swaps it settles.`,\n query: sellersIntoContractQuery(manager),\n })\n steps.push({\n step: \"One seller's swaps\",\n why: 'Fill a seller from the first step. A recipient that is the manager is never the buyer: read the facts row past it.',\n query: sellerSwapsQuery(),\n })\n steps.push(dayStep)\n steps.push({\n step: 'The facts SWAP row past the manager',\n why: 'Fill the seller and the day: the recipient column names who the manager paid.',\n query: swapRowsQuery(manager),\n })\n break\n }\n case 'launchpad': {\n const factory = entry.factories[0]!\n steps.push({\n step: 'Launches of the launchpad, newest first',\n why: `The contracts ${factory} created, with the creator of each.`,\n query:\n `USE topology MATCH (f:Address {address: \"${factory}\"})-[d:DEPLOYED_CONTRACT]->(t:Address) ` +\n 'RETURN f.address AS from_address, t.address AS to_address, labels(t) AS to_node_labels, t.labels AS to_labels, d.tx_origin AS creator, d.tx_id AS tx_id, d.block_height AS block_height, d.block_timestamp AS created_timestamp ORDER BY d.block_height DESC LIMIT 25',\n })\n steps.push({\n step: 'Traders on the curve of one launched token',\n why: 'Fill a launched token from the first step.',\n query:\n `USE topology MATCH (t:Address {address: \"${TOKEN}\"})-[f:FLOWS_TO]-(b:Address) WHERE NOT t:Pool ` +\n 'RETURN startNode(f).address AS from_address, endNode(f).address AS to_address, labels(b) AS node_labels, f.tx_count AS tx_count, f.amount_usd_sum AS amount_usd_sum, f.first_seen_timestamp AS first_seen_timestamp, f.last_seen_timestamp AS last_seen_timestamp ORDER BY f.tx_count DESC LIMIT 25',\n })\n steps.push({\n step: 'The first pool of one launched token',\n why: 'The oldest ADDED_LIQUIDITY into a :Pool is the graduation. None means it has not graduated in the graph yet.',\n query:\n `USE topology MATCH (t:Address {address: \"${TOKEN}\"})-[l:ADDED_LIQUIDITY]->(p:Pool) ` +\n 'RETURN t.address AS from_address, p.address AS to_address, p.labels AS to_labels, l.asset AS asset, l.usd AS usd, l.first_seen_height AS first_seen_height ORDER BY l.first_seen_height ASC LIMIT 5',\n })\n break\n }\n case 'plain': {\n if (pool) {\n steps.push({\n step: 'Sellers into the busiest pool',\n why: `The senders of ${pool} on FLOWS_TO, by lifetime volume. A seller that is a contract is often an aggregator or a router: say \"a contract\".`,\n query: sellersIntoPoolQuery(pool),\n })\n } else {\n const pools = poolsStep(entry)\n if (pools) steps.push(pools)\n }\n steps.push({\n step: \"Who a seller's swaps paid\",\n why: 'Fill a seller from the first step. Keep the rows whose pools hold the pool: the recipient is the buyer.',\n query: sellerSwapsQuery(),\n })\n steps.push(dayStep)\n steps.push({\n step: 'The swaps of a seller and a recipient on one day',\n why: 'Fill the seller, the recipient and the day.',\n query: swapRowsQuery('<recipient address>'),\n })\n break\n }\n }\n return steps\n}\n","/**\n * meta_dex_lookup: an exchange's name becomes its addresses.\n *\n * A local tool of the proxy. It matches the name against the exchange registry\n * (src/mcp/dex-registry.ts) at no cost, then sends one graph_query_batch of\n * anchored reads: Head for the family lag, each factory, manager and router by\n * its address, and the pools each factory deployed. Nothing is read by name and\n * nothing is gated on a family lag: a factory deployed months ago is not stale\n * because the swap family is behind. A name that is not covered sends no read.\n *\n * The description below is the contract with every host, Claude Desktop chat\n * included, which reads no skill: it carries the rule the exchange skills open\n * with. tests/host-parity.test.ts holds it to the skills' trigger words.\n */\n\nimport { ANSWER_HINT } from './answer-hint.js'\nimport { recipeFor, TRAIL_SKILLS, type RecipeStep, poolsByFactoryQuery } from './dex-recipes.js'\nimport {\n DexRegistryError,\n matchDex,\n normalizeDexNetwork,\n type DexCandidate,\n type DexEntry,\n type DexMatchResult,\n type LoadedRegistry,\n type RegistrySource,\n type Role,\n type TrailType,\n} from './dex-registry.js'\n\nexport const META_DEX_LOOKUP_TOOL = 'meta_dex_lookup'\nexport const META_DEX_LOOKUP_SCHEMA = 'chain-insights.meta-dex-lookup.v1' as const\nexport const META_DEX_LOOKUP_TITLE = 'Exchange by name or address'\nexport const META_DEX_LOOKUP_DESCRIPTION =\n 'Call this first when a question names a decentralized exchange, a launchpad or a vault ' +\n '(Aero, Aerodrome, Velodrome, up., Raphael, Topaz, Uniswap, PancakeSwap, Balancer, Curve, ' +\n \"Penguin, Virtuals, ...) and gives no address. It turns the name into the exchange's \" +\n 'factories, routers or pool manager, its biggest pools, and the first reads to send, ' +\n 'with every address in full. A misspelling within two letters is taken and named in ' +\n 'took_for. When matched is null the name is not covered: say so, list covered, and send ' +\n 'no graph query for the name. Never ask the user for a pool, factory or router address. ' +\n 'When the user gives a contract address instead of a name, send it as address (0x plus 40 hex, ' +\n 'any case, with no name): the answer is the exchange whose factory, router or manager it is, ' +\n 'and took_for names the role it holds. When matched is null no exchange row holds the address: ' +\n 'send no read for it. Give a name or an address, not both.'\nexport const META_DEX_LOOKUP_ANNOTATIONS = {\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n} as const\nexport const META_DEX_LOOKUP_ARGS = ['network', 'name', 'address', 'pools'] as const\n\n/** The pools returned per exchange, and per factory read. */\nexport const POOLS_LIMIT = 10\n/** A batch holds at most 20 members; Head and the pool reads take their share. */\nconst CONTRACT_MEMBERS_MAX = 12\n\nexport interface ContractRow {\n address: string\n role: Role\n confirmed: boolean\n confirmed_by?: string\n}\n\nexport interface PoolRow {\n address: string\n labels: string[]\n total_volume_usd: number\n tx_total_count: number\n}\n\nexport interface FamilyLag {\n behind_money: number | null\n behind_dex: number | null\n behind_transfers: number | null\n}\n\nexport interface DexMatch {\n dex: string\n display: string\n trail_type: TrailType\n /** The skill a host with skills loads next. */\n skill: string\n factories: ContractRow[]\n routers: ContractRow[]\n manager: ContractRow | null\n pools: PoolRow[]\n recipe: RecipeStep[]\n}\n\nexport interface CoveredExchange {\n dex: string\n display: string\n trail_type: TrailType\n factories: string[]\n routers: string[]\n}\n\nexport interface MetaDexLookupResult {\n schema: typeof META_DEX_LOOKUP_SCHEMA\n network: string\n registry_version: number\n registry_source: RegistrySource\n matched: DexMatch | null\n /** A near name hit: the name as typed. An address hit: the role it holds and the verification cell. */\n took_for?: string\n candidates: DexCandidate[]\n covered: string[]\n /** With no name: every covered exchange, with its key, trail type and contracts. */\n exchanges?: CoveredExchange[]\n families: FamilyLag | null\n /** What a read could not do: a lane that is not reachable, a member that was refused. */\n notes?: string[]\n hint: string\n}\n\n/** The batch reply as the remote client returns it. */\nexport type BatchAnswer = {\n content?: unknown[]\n structuredContent?: unknown\n isError?: boolean\n}\n\nexport interface DexToolDependencies {\n loadRegistry: (network: string) => Promise<LoadedRegistry>\n /** One graph_query_batch through the proxy's remote client, billed as any read. */\n graphQueryBatch: (args: {\n network: string\n queries: Array<{ id: string; query: string }>\n }) => Promise<BatchAnswer>\n /** The reason no read can be sent now, or undefined when the graph is reachable. */\n unavailable?: () => Promise<string | undefined> | string | undefined\n /** The text of a call that threw: payment required, transport. */\n describeFailure: (err: unknown) => string\n}\n\nexport type MetaDexLookupToolResult = {\n content: Array<{ type: 'text'; text: string }>\n structuredContent?: Record<string, unknown>\n isError: boolean\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction errorResult(text: string): MetaDexLookupToolResult {\n return { content: [{ type: 'text', text }], isError: true }\n}\n\ntype Member = { id?: unknown; ok?: unknown; results?: unknown; error?: unknown }\n\nfunction membersOf(answer: BatchAnswer): Map<string, Member> {\n const facts = isRecord(answer.structuredContent) ? answer.structuredContent.facts : undefined\n const queries = isRecord(facts) ? facts.queries : undefined\n const members = new Map<string, Member>()\n if (Array.isArray(queries)) {\n for (const member of queries)\n if (isRecord(member) && typeof member.id === 'string') members.set(member.id, member)\n }\n return members\n}\n\nconst rowsOf = (member: Member | undefined): Array<Record<string, unknown>> =>\n member?.ok === true && Array.isArray(member.results) ? member.results.filter(isRecord) : []\n\nconst numberOrNull = (value: unknown): number | null => {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value)))\n return Number(value)\n return null\n}\n\nconst stringList = (value: unknown): string[] =>\n Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string') : []\n\n/** A column of a row, read under its alias or as the query wrote it (h.behind_dex). */\nfunction column(row: Record<string, unknown>, name: string, variable: string): unknown {\n return name in row ? row[name] : row[`${variable}.${name}`]\n}\n\nexport const HEAD_QUERY =\n 'USE chain MATCH (h:Head) RETURN h.behind_money, h.behind_dex, h.behind_transfers'\n\nexport function contractQuery(address: string): string {\n return (\n `USE topology MATCH (a:Address {address: \"${address}\"}) ` +\n 'RETURN a.address AS address, labels(a) AS node_labels, a.labels AS labels, a.is_contract AS is_contract LIMIT 1'\n )\n}\n\nexport function contractListQuery(addresses: string[]): string {\n return (\n `USE topology MATCH (a:Address) WHERE a.address IN [${addresses.map((a) => `\"${a}\"`).join(', ')}] ` +\n `RETURN a.address AS address, labels(a) AS node_labels, a.labels AS labels, a.is_contract AS is_contract LIMIT ${addresses.length}`\n )\n}\n\n/** The anchored reads of one exchange: Head, each contract, and the pools of each factory. */\nexport function lookupQueries(\n entry: DexEntry,\n pools: boolean\n): Array<{ id: string; query: string }> {\n const contracts = Object.keys(entry.roles)\n const queries: Array<{ id: string; query: string }> = [{ id: 'head', query: HEAD_QUERY }]\n if (contracts.length <= CONTRACT_MEMBERS_MAX) {\n for (const address of contracts)\n queries.push({ id: `contract:${address}`, query: contractQuery(address) })\n } else {\n queries.push({ id: 'contracts', query: contractListQuery(contracts) })\n }\n if (pools) {\n for (const factory of entry.factories.filter((address) => entry.roles[address] === 'factory')) {\n queries.push({ id: `pools:${factory}`, query: poolsByFactoryQuery(factory, POOLS_LIMIT) })\n }\n }\n return queries\n}\n\nfunction confirmation(\n row: Record<string, unknown> | undefined\n): Pick<ContractRow, 'confirmed' | 'confirmed_by'> {\n if (!row) return { confirmed: false }\n if (row.is_contract === true) return { confirmed: true, confirmed_by: 'is_contract' }\n if (stringList(row.node_labels).includes('Contract'))\n return { confirmed: true, confirmed_by: ':Contract' }\n return { confirmed: false }\n}\n\nfunction readMatch(\n entry: DexEntry,\n members: Map<string, Member>,\n read: boolean\n): {\n contracts: Map<string, ContractRow>\n pools: PoolRow[]\n families: FamilyLag | null\n notes: string[]\n} {\n const notes: string[] = []\n const contracts = new Map<string, ContractRow>()\n const listed = new Map(rowsOf(members.get('contracts')).map((row) => [String(row.address), row]))\n for (const [address, role] of Object.entries(entry.roles)) {\n const row = members.has(`contract:${address}`)\n ? rowsOf(members.get(`contract:${address}`))[0]\n : listed.get(address)\n contracts.set(address, { address, role, ...confirmation(row) })\n }\n const byAddress = new Map<string, PoolRow>()\n for (const [id, member] of members) {\n if (!id.startsWith('pools:')) continue\n for (const row of rowsOf(member)) {\n const address = typeof row.to_address === 'string' ? row.to_address : null\n const volume = numberOrNull(row.total_volume_usd)\n if (!address || volume === null || byAddress.has(address)) continue\n byAddress.set(address, {\n address,\n labels: stringList(row.to_labels),\n total_volume_usd: volume,\n tx_total_count: numberOrNull(row.tx_total_count) ?? 0,\n })\n }\n }\n const pools = [...byAddress.values()]\n .sort((a, b) => b.total_volume_usd - a.total_volume_usd)\n .slice(0, POOLS_LIMIT)\n const head = rowsOf(members.get('head'))[0]\n const families: FamilyLag | null = head\n ? {\n behind_money: numberOrNull(column(head, 'behind_money', 'h')),\n behind_dex: numberOrNull(column(head, 'behind_dex', 'h')),\n behind_transfers: numberOrNull(column(head, 'behind_transfers', 'h')),\n }\n : null\n if (read) {\n for (const [id, member] of members) {\n if (member.ok !== true)\n notes.push(\n `${id}: ${typeof member.error === 'string' ? member.error : 'the read was not served'}`\n )\n }\n }\n return { contracts, pools, families, notes }\n}\n\nconst ADDRESS_FORM = /^0x[0-9a-fA-F]{40}$/\n\nfunction hintFor(\n result: Omit<MetaDexLookupResult, 'hint'>,\n name: string | null,\n address: string | null = null\n): string {\n if (address !== null && !result.matched) {\n return `${ANSWER_HINT} No exchange row holds ${address}: it is not a registered exchange contract. Send no exchange read for it; read the graph for what the address did.`\n }\n if (address !== null && result.matched) {\n const { matched } = result\n return (\n `${ANSWER_HINT} Say in one line: \"${address} is a ${result.took_for} row of ${matched.display}.\" Ask nothing. ` +\n `Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, ` +\n `anchored on the addresses above, and load ${matched.skill} when this host has skills. ` +\n 'Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.'\n )\n }\n if (name === null) {\n return `${ANSWER_HINT} These are the exchanges the registry covers. Call meta_dex_lookup with a name for its addresses and first reads.`\n }\n if (result.matched) {\n const { matched } = result\n const took = result.took_for\n ? `Say in one line: \"I took ${matched.display} for ${result.took_for}.\" Ask nothing. `\n : ''\n return (\n `${ANSWER_HINT} ${took}Exchange ${matched.display}, trail ${matched.trail_type}: send the first reads of recipe, ` +\n `anchored on the addresses above, and load ${matched.skill} when this host has skills. ` +\n 'Read families before a SWAPPED search: it is sent when behind_dex is at most 100000. Never ask the user for an address.'\n )\n }\n if (result.candidates.length > 0) {\n const names = result.candidates\n .map((candidate) => `${candidate.display} (${candidate.dex})`)\n .join(', ')\n return `${ANSWER_HINT} ${result.candidates.length} rows fit \"${name}\": ${names}. Name both or pick one: call meta_dex_lookup with each key. Never guess one, never ask the user for an address.`\n }\n return `${ANSWER_HINT} ${name} is not covered. Send no query for the name: say so in one line and list the covered exchanges by name.`\n}\n\nfunction textResult(structured: MetaDexLookupResult): MetaDexLookupToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }],\n structuredContent: structured as unknown as Record<string, unknown>,\n isError: false,\n }\n}\n\n/** meta_dex_lookup, {network, name?, address?, pools?}. Every argument is checked before the registry is read. */\nexport async function handleMetaDexLookup(\n args: unknown,\n deps: DexToolDependencies\n): Promise<MetaDexLookupToolResult> {\n const record = isRecord(args) ? args : {}\n let network: string\n try {\n network = normalizeDexNetwork(record.network)\n } catch (err) {\n return errorResult((err as Error).message)\n }\n const rawName = record.name\n if (rawName !== undefined && rawName !== null && typeof rawName !== 'string') {\n return errorResult('invalid_name: name must be the exchange name as text')\n }\n const name = typeof rawName === 'string' && rawName.trim() !== '' ? rawName.trim() : null\n const rawAddress = record.address\n if (rawAddress !== undefined && rawAddress !== null && typeof rawAddress !== 'string') {\n return errorResult('invalid_address: address must be 0x plus 40 hex characters')\n }\n const addressText = typeof rawAddress === 'string' && rawAddress.trim() !== '' ? rawAddress.trim() : null\n if (name !== null && addressText !== null) return errorResult('Give a name or an address, not both.')\n if (addressText !== null && !ADDRESS_FORM.test(addressText)) {\n return errorResult('invalid_address: address must be 0x plus 40 hex characters')\n }\n const address = addressText === null ? null : addressText.toLowerCase()\n const rawPools = record.pools\n if (rawPools !== undefined && rawPools !== null && typeof rawPools !== 'boolean') {\n return errorResult('invalid_pools: pools must be true or false')\n }\n const pools = rawPools !== false\n\n let loaded: LoadedRegistry\n try {\n loaded = await deps.loadRegistry(network)\n } catch (err) {\n return errorResult(\n err instanceof DexRegistryError\n ? err.message\n : `exchange registry unavailable: ${(err as Error).message}`\n )\n }\n const { document } = loaded\n const base = {\n schema: META_DEX_LOOKUP_SCHEMA,\n network,\n registry_version: document.registry_version,\n registry_source: loaded.source,\n }\n\n if (name === null && address === null) {\n const result = {\n ...base,\n matched: null,\n candidates: [],\n covered: document.rows.map((row) => row.display),\n exchanges: document.rows.map((row) => ({\n dex: row.dex,\n display: row.display,\n trail_type: row.trail_type,\n factories: row.factories,\n routers: row.routers,\n })),\n families: null,\n }\n return textResult({ ...result, hint: hintFor(result, null) })\n }\n\n let match: DexMatchResult\n if (address !== null) {\n const holder = document.rows.find((row) => address in row.roles)\n match = {\n matched: holder ?? null,\n ...(holder ? { took_for: `${holder.roles[address]}, served` } : {}),\n candidates: [],\n covered: document.rows.map((row) => row.display),\n }\n } else {\n match = matchDex(name as string, document.rows)\n }\n if (!match.matched) {\n const result = {\n ...base,\n matched: null,\n candidates: match.candidates,\n covered: match.covered,\n families: null,\n }\n return textResult({ ...result, hint: hintFor(result, name, address) })\n }\n\n const entry = match.matched\n const queries = lookupQueries(entry, pools)\n let members = new Map<string, Member>()\n const notes: string[] = []\n const unavailable = await deps.unavailable?.()\n if (unavailable) {\n notes.push(\n `No graph read was sent: ${unavailable} The contracts are the reviewed registry rows, not yet confirmed on the graph.`\n )\n } else {\n try {\n const answer = await deps.graphQueryBatch({ network, queries })\n members = membersOf(answer)\n if (answer.isError === true && members.size === 0) {\n const first = Array.isArray(answer.content) ? answer.content[0] : undefined\n notes.push(\n `The graph refused the reads: ${isRecord(first) && typeof first.text === 'string' ? first.text : 'no reason given'}`\n )\n }\n } catch (err) {\n notes.push(`The graph reads failed: ${deps.describeFailure(err)}`)\n }\n }\n const read = readMatch(entry, members, members.size > 0)\n notes.push(...read.notes)\n const contractsOf = (role: Role): ContractRow[] =>\n [...read.contracts.values()].filter((contract) => contract.role === role)\n const topPool = read.pools[0]?.address ?? null\n const matched: DexMatch = {\n dex: entry.dex,\n display: entry.display,\n trail_type: entry.trail_type,\n skill: TRAIL_SKILLS[entry.trail_type],\n factories: contractsOf('factory'),\n routers: contractsOf('router'),\n manager: contractsOf('manager')[0] ?? null,\n pools: read.pools,\n recipe: recipeFor(entry, topPool),\n }\n const result = {\n ...base,\n matched,\n ...(match.took_for ? { took_for: match.took_for } : {}),\n candidates: [],\n covered: match.covered,\n families: read.families,\n ...(notes.length > 0 ? { notes } : {}),\n }\n return textResult({ ...result, hint: hintFor(result, name, address) })\n}\n"],"mappings":"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"}
|
package/dist/mcp-proxy.cjs
CHANGED
|
@@ -8,7 +8,7 @@ const require_subscription_status = require("./subscription-status-ru99UCPZ.cjs"
|
|
|
8
8
|
const require_capabilities = require("./capabilities-DGavTSoW.cjs");
|
|
9
9
|
const require_graph_schema = require("./graph-schema-BoUMa03H.cjs");
|
|
10
10
|
const require_action_log = require("./action-log-HniMMF7f.cjs");
|
|
11
|
-
const require_dex_tool = require("./dex-tool-
|
|
11
|
+
const require_dex_tool = require("./dex-tool-B4T8Czhm.cjs");
|
|
12
12
|
const require_dex_registry = require("./dex-registry-KOTJhAQf.cjs");
|
|
13
13
|
let node_url = require("node:url");
|
|
14
14
|
let node_path = require("node:path");
|
package/dist/mcp-proxy.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { n as unavailableSubscriptionStatus } from "./subscription-status-DA9DCF
|
|
|
6
6
|
import { r as mirrorGraphNetworkCapabilities } from "./capabilities-BBVDIECk.mjs";
|
|
7
7
|
import { a as handleMetaSchema, c as GRAPH_EXPAND_TITLE, l as GRAPH_EXPAND_TOOL, n as META_SCHEMA_DESCRIPTION, o as FLOWS_MAX_OFFSET, r as META_SCHEMA_TITLE, s as GRAPH_EXPAND_DESCRIPTION, t as GraphSchemaError, u as handleGraphExpand } from "./graph-schema-DqZUTnP0.mjs";
|
|
8
8
|
import { r as appendActionLog, t as actionLogSignalsFromResult } from "./action-log-D49Par9P.mjs";
|
|
9
|
-
import { i as META_DEX_LOOKUP_TOOL, n as META_DEX_LOOKUP_DESCRIPTION, o as handleMetaDexLookup, r as META_DEX_LOOKUP_TITLE, s as withAnswerHintResult, t as META_DEX_LOOKUP_ANNOTATIONS } from "./dex-tool-
|
|
9
|
+
import { i as META_DEX_LOOKUP_TOOL, n as META_DEX_LOOKUP_DESCRIPTION, o as handleMetaDexLookup, r as META_DEX_LOOKUP_TITLE, s as withAnswerHintResult, t as META_DEX_LOOKUP_ANNOTATIONS } from "./dex-tool-B5Qwz1Zd.mjs";
|
|
10
10
|
import { r as loadRegistry } from "./dex-registry-05wAhSa0.mjs";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
import path from "node:path";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chain-insights",
|
|
3
|
-
"version": "0.71.
|
|
3
|
+
"version": "0.71.2",
|
|
4
4
|
"description": "AML investigation CLI and MCP proxy for blockchain risk screening and Chain Insights Graph access",
|
|
5
5
|
"homepage": "https://chain-insights.ai",
|
|
6
6
|
"repository": {
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"vitest": "^5.0.3"
|
|
94
94
|
},
|
|
95
95
|
"chainInsights": {
|
|
96
|
-
"dexRegistryVersion":
|
|
96
|
+
"dexRegistryVersion": 3
|
|
97
97
|
},
|
|
98
98
|
"overrides": {
|
|
99
99
|
"ws": "8.21.0",
|
package/registry/README.md
CHANGED
|
@@ -22,8 +22,8 @@ flowchart LR
|
|
|
22
22
|
|
|
23
23
|
| File | Rows served | Why |
|
|
24
24
|
|---|---|---|
|
|
25
|
-
| [robinhood.md](robinhood.md) |
|
|
26
|
-
| [robinhood.json](robinhood.json) |
|
|
25
|
+
| [robinhood.md](robinhood.md) | 47 | registry version 3, recorded 2026-10-25; the `dyor` row and the Ekubo positions row are not served |
|
|
26
|
+
| [robinhood.json](robinhood.json) | 31 exchanges | the served copy of `robinhood.md`, never edited by hand |
|
|
27
27
|
|
|
28
28
|
A change to the rows merged on `main` reaches every running proxy within one
|
|
29
29
|
hour. No release is needed for a data change.
|
package/registry/robinhood.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "chain-insights.dex-registry.v1",
|
|
3
3
|
"network": "robinhood",
|
|
4
|
-
"registry_version":
|
|
5
|
-
"recorded_on": "2026-10-
|
|
4
|
+
"registry_version": 3,
|
|
5
|
+
"recorded_on": "2026-10-25",
|
|
6
6
|
"rows": [
|
|
7
7
|
{
|
|
8
8
|
"dex": "aero",
|
|
@@ -519,6 +519,32 @@
|
|
|
519
519
|
"since_block": 0,
|
|
520
520
|
"source": "https://sourcify.dev/server/v2/contract/4663/0x28082618ba2073e602230188e4f4c46e9b2169eb"
|
|
521
521
|
},
|
|
522
|
+
{
|
|
523
|
+
"dex": "ekubo",
|
|
524
|
+
"display": "Ekubo",
|
|
525
|
+
"aliases": [
|
|
526
|
+
"Ekubo",
|
|
527
|
+
"Ekubo Protocol",
|
|
528
|
+
"ekubo.org"
|
|
529
|
+
],
|
|
530
|
+
"trail_type": "vault",
|
|
531
|
+
"adapters": [
|
|
532
|
+
"ekubo-core"
|
|
533
|
+
],
|
|
534
|
+
"roles": {
|
|
535
|
+
"0x00000000000014aa86c5d3c41765bb24e11bd701": "manager"
|
|
536
|
+
},
|
|
537
|
+
"factories": [
|
|
538
|
+
"0x00000000000014aa86c5d3c41765bb24e11bd701"
|
|
539
|
+
],
|
|
540
|
+
"routers": [
|
|
541
|
+
"0x00000000000014aa86c5d3c41765bb24e11bd701"
|
|
542
|
+
],
|
|
543
|
+
"example_pool": null,
|
|
544
|
+
"pool_floor": null,
|
|
545
|
+
"since_block": 33534,
|
|
546
|
+
"source": "https://github.com/EkuboProtocol/evm-contracts/blob/933c813ca154475a3c7eab8234869f6db151c682/broadcast/DeployAll.s.sol/4663/run-latest.json"
|
|
547
|
+
},
|
|
522
548
|
{
|
|
523
549
|
"dex": "ramses",
|
|
524
550
|
"display": "Ramses",
|
|
@@ -718,6 +744,33 @@
|
|
|
718
744
|
"since_block": 0,
|
|
719
745
|
"source": "https://github.com/chainswarm/devops/blob/de68f8676adac3e2f004fb0cd24c313f9516846e/charts/chainswarm-indexer-evm-dex/registry/robinhood-v24.json"
|
|
720
746
|
},
|
|
747
|
+
{
|
|
748
|
+
"dex": "spcx",
|
|
749
|
+
"display": "SPCX launchpad",
|
|
750
|
+
"aliases": [
|
|
751
|
+
"SPCX",
|
|
752
|
+
"SPCX launchpad",
|
|
753
|
+
"spcx launchpad"
|
|
754
|
+
],
|
|
755
|
+
"trail_type": "launchpad",
|
|
756
|
+
"adapters": [
|
|
757
|
+
"spcx-launchpad"
|
|
758
|
+
],
|
|
759
|
+
"roles": {
|
|
760
|
+
"0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091": "factory",
|
|
761
|
+
"0x190b452d16e12e3beedd76a030ea991fe3856c33": "router"
|
|
762
|
+
},
|
|
763
|
+
"factories": [
|
|
764
|
+
"0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091"
|
|
765
|
+
],
|
|
766
|
+
"routers": [
|
|
767
|
+
"0x190b452d16e12e3beedd76a030ea991fe3856c33"
|
|
768
|
+
],
|
|
769
|
+
"example_pool": "0x1de3f839184cf48696cbb9cc283855eb32dc2c51",
|
|
770
|
+
"pool_floor": 1,
|
|
771
|
+
"since_block": 0,
|
|
772
|
+
"source": "https://github.com/chainswarm/AP1000/blob/main/openspec/changes/spcx-launchpad-adapter/design.md"
|
|
773
|
+
},
|
|
721
774
|
{
|
|
722
775
|
"dex": "pons",
|
|
723
776
|
"display": "pons",
|
package/registry/robinhood.md
CHANGED
|
@@ -6,9 +6,9 @@ columns and the check are in the [README](README.md). The served copy is
|
|
|
6
6
|
[robinhood.json](robinhood.json), written by
|
|
7
7
|
`node scripts/pin-dex-registry.mjs registry/robinhood.md`.
|
|
8
8
|
|
|
9
|
-
Registry version:
|
|
9
|
+
Registry version: 3.
|
|
10
10
|
|
|
11
|
-
Recorded on: 2026-10-
|
|
11
|
+
Recorded on: 2026-10-25.
|
|
12
12
|
|
|
13
13
|
## Rows
|
|
14
14
|
|
|
@@ -46,6 +46,8 @@ Recorded on: 2026-10-24.
|
|
|
46
46
|
| orvex | Orvex | Orvex, Orvex MetaDEX, orvex.fi, ORVX | vault | pancake-infinity-cl | manager | `0xd01c774d4a66408326bc65728ac5ae5aaf004032` | 0 | - | - | [DefiLlama orvex](https://github.com/DefiLlama/dimension-adapters/blob/master/dexs/orvex.ts) | served |
|
|
47
47
|
| swaap | Swaap v2 | Swaap, Swaap Finance, swaap.xyz, Balancer, Balancer v2, Balancer v2 vault | vault | balancer-v2 | manager | `0xd315a9c38ec871068fec378e4ce78af528c76293` | 0 | - | - | [Swaap docs](https://docs.swaap.xyz/) | served |
|
|
48
48
|
| balancer-v3 | unnamed Balancer v3 vault | Balancer, Balancer v3, Balancer v3 vault | vault | balancer-v3 | manager | `0x28082618ba2073e602230188e4f4c46e9b2169eb` | 0 | - | - | [Sourcify verified source](https://sourcify.dev/server/v2/contract/4663/0x28082618ba2073e602230188e4f4c46e9b2169eb) | served |
|
|
49
|
+
| ekubo | Ekubo | Ekubo, Ekubo Protocol, ekubo.org | vault | ekubo-core | manager | `0x00000000000014aa86c5d3c41765bb24e11bd701` | 33534 | - | - | [Ekubo robinhood deployment](https://github.com/EkuboProtocol/evm-contracts/blob/933c813ca154475a3c7eab8234869f6db151c682/broadcast/DeployAll.s.sol/4663/run-latest.json) | served |
|
|
50
|
+
| ekubo | Ekubo | | vault | ekubo-core | positions | `0xda38ac72ce7220c4dd7719d114ef94edadb8f068` | 18272006 | - | - | [Ekubo robinhood Ve33 deployment](https://github.com/EkuboProtocol/evm-contracts/blob/933c813ca154475a3c7eab8234869f6db151c682/broadcast/DeployVe33.s.sol/4663/run-latest.json) | not served: the positions NFT creates no pool, holds no pool and forwards no swap; the registry serves the roles factory, manager and router only |
|
|
49
51
|
| ramses | Ramses | Ramses, Ramses Exchange, RAM, ramses.xyz, Ramses DLMM | plain | trader-joe-lb | factory | `0xdcd5f77697914e27f56fd263ef82923c8524abac` | 0 | 0x06ffaa507a9c3e7025294a642f1e1c75da3dfb4a | 150 | [Ramses contract addresses](https://www.ramses.xyz/docs/contract-addresses) | served |
|
|
50
52
|
| ramses | Ramses | | plain | uniswap-v2 | factory | `0x43b2bf9f33036a02fc7a00935571c2a6b0108e66` | 0 | - | - | [Ramses contract addresses](https://www.ramses.xyz/docs/contract-addresses) | served |
|
|
51
53
|
| ramses | Ramses | | plain | trader-joe-lb | router | `0xd0019e86edb35e1fedaab03aed5c3c60f115d28b` | 0 | - | - | [Ramses contract addresses](https://www.ramses.xyz/docs/contract-addresses) | served |
|
|
@@ -57,8 +59,8 @@ Recorded on: 2026-10-24.
|
|
|
57
59
|
| curve | Curve | Curve, Curve Finance, CRV, curve.finance | plain | curve-stable | factory | `0x8271e06e5887fe5ba05234f5315c19f3ec90e8ad` | 0 | 0x7e0539140f16b3a8ed23ad69d6fea6a38fb8b299 | 25 | [curve-core deployments](https://github.com/curvefi/curve-core/blob/main/deployments/prod/robinhood.yaml) | served |
|
|
58
60
|
| metric | Metric | Metric, Metric OMM, metric.xyz | plain | metric-omm | factory | `0x2e0a99a2e352043d1ca489cf45bf7b511bccc738` | 0 | 0x584ac6010b095bf99398fae8203a3260a03d2b30 | 30 | [Metric SDK addresses](https://github.com/Metricxyz/sdk/blob/main/src/addresses.ts) | served |
|
|
59
61
|
| penguin | Penguin | Penguin, Penguin launchpad | launchpad | penguin-launchpad | factory | `0x09f1b640d53c9c4c1807bf5aa306745128ee4375` | 0 | - | - | [devops registry v24](https://github.com/chainswarm/devops/blob/de68f8676adac3e2f004fb0cd24c313f9516846e/charts/chainswarm-indexer-evm-dex/registry/robinhood-v24.json) | served |
|
|
60
|
-
| spcx | SPCX launchpad | SPCX, SPCX launchpad, spcx launchpad | launchpad | spcx-launchpad | factory | `0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091` | 0 | 0x1de3f839184cf48696cbb9cc283855eb32dc2c51 | 1 | [AP1000 change spcx-launchpad-adapter](https://github.com/chainswarm/AP1000/blob/main/openspec/changes/spcx-launchpad-adapter/design.md) |
|
|
61
|
-
| spcx | SPCX launchpad | | launchpad | spcx-launchpad | router | `0x190b452d16e12e3beedd76a030ea991fe3856c33` | 0 | - | - | [AP1000 change spcx-launchpad-adapter](https://github.com/chainswarm/AP1000/blob/main/openspec/changes/spcx-launchpad-adapter/design.md) |
|
|
62
|
+
| spcx | SPCX launchpad | SPCX, SPCX launchpad, spcx launchpad | launchpad | spcx-launchpad | factory | `0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091` | 0 | 0x1de3f839184cf48696cbb9cc283855eb32dc2c51 | 1 | [AP1000 change spcx-launchpad-adapter](https://github.com/chainswarm/AP1000/blob/main/openspec/changes/spcx-launchpad-adapter/design.md) | served |
|
|
63
|
+
| spcx | SPCX launchpad | | launchpad | spcx-launchpad | router | `0x190b452d16e12e3beedd76a030ea991fe3856c33` | 0 | - | - | [AP1000 change spcx-launchpad-adapter](https://github.com/chainswarm/AP1000/blob/main/openspec/changes/spcx-launchpad-adapter/design.md) | served |
|
|
62
64
|
| pons | pons | pons, pons launchpad | launchpad | bonding-curve | factory | `0x7ed598bcef8bd9edd8c97a195c6d13f40801ec7e` | 0 | - | - | [pons deployment](https://github.com/DefiLlama/DefiLlama-Adapters/blob/main/projects/pons-v2/index.js) | served |
|
|
63
65
|
| virtuals | Virtuals Protocol | Virtuals, VIRTUAL, Virtuals Protocol | launchpad | uniswap-v2 | factory | `0xfc2e4da3edb2e18100473339c763705d263d20a9` | 0 | - | - | [change silent-factory-discovery-and-labels](https://github.com/chainswarm/AP1000/blob/main/openspec/changes/archive/2026-10-16-silent-factory-discovery-and-labels/design.md) | served |
|
|
64
66
|
|
|
@@ -88,6 +90,10 @@ swaps it forwarded.
|
|
|
88
90
|
| `0x09f1b640d53c9c4c1807bf5aa306745128ee4375` | factory | created 48 contracts; none carries `:Pool` on lane A yet |
|
|
89
91
|
| `0x7ed598bcef8bd9edd8c97a195c6d13f40801ec7e` | factory | 1,469,383 transactions on the contract itself; created 1 contract, no `:Pool` yet |
|
|
90
92
|
| `0x8bceaa40b9acdfaedf85adf4ff01f5ad6517937f` | factory | 27,926 `uniswap-v2` pools, 10,573,207,859 USD. Uniswap's own v2 factory per its deployments page; Virtuals agents create most of its pairs, which is why the indexer registry stacks it as `virtuals`. The exchange row is `uniswap-v2`; `0xfc2e4da3edb2e18100473339c763705d263d20a9` is the Virtuals bonding factory per the Virtuals change |
|
|
93
|
+
| `0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091` | factory | read from the production node on 2026-10-25: its creation event names the launched token, its curve and the creator (sample transaction `0x77b1bcc700d5f1d7513d080d3fe265831529c237065fffed83e665f89cc3f019`, block 95,797,703); 2,182 curves, quoted in SPCX `0x4a0e65a3eccec6dbe60ae065f2e7bb85fae35eea` and two other stock tokens. The example curve `0x1de3f839184cf48696cbb9cc283855eb32dc2c51` logged 1,028 trades in 7M blocks. No source names the operator, so the key stays `spcx` |
|
|
94
|
+
| `0x190b452d16e12e3beedd76a030ea991fe3856c33` | router | the SPCX legs of the sample trade `0xe200a831908a9b937e08eb4c16fdefd016f606980b9ef71f788994c76020708e` run trader, this contract, curve. The trader stays the payer: the router is never a swap end |
|
|
95
|
+
| `0x00000000000014aa86c5d3c41765bb24e11bd701` | manager | the Ekubo core, deployed at block 33,534 per Ekubo's robinhood deployment file. Read from the production node on 2026-10-25 over 20,000 blocks: 111 swaps and 65 position updates logged by the core itself, 28 pools initialized, each pool kept by a 32-byte pool id. Sample transaction `0x7926c9fab003029a42e7f398e3183611c49583a082b4aac781003cb43f5a96d9`, block 97,745,015 |
|
|
96
|
+
| `0xda38ac72ce7220c4dd7719d114ef94edadb8f068` | positions | "Ekubo Ve33 Positions", deployed at block 18,272,006 with the core as its first argument per Ekubo's Ve33 deployment file. It owns the core's position updates and mints one NFT per position. It is not served: it creates no pool, holds no pool and forwards no swap, and a liquidity owner is the NFT's holder, never this contract |
|
|
91
97
|
|
|
92
98
|
`0x0d1ebb179cdbca88d74c923c4255cb2b17474afd` created 254,650 `uniswap-v2`
|
|
93
99
|
pairs with 38 USD of volume in all: the Flap launchpad's pair factory per
|
|
@@ -102,3 +108,8 @@ Registry version 2 (2026-10-24) moved the file from the private registry of
|
|
|
102
108
|
version 1 to this repository, renamed the key column `dex`, and gave `Balancer`
|
|
103
109
|
to the two Balancer vaults (`balancer-v3` and `swaap`), so a question that says
|
|
104
110
|
"Balancer" lists both instead of guessing one.
|
|
111
|
+
|
|
112
|
+
Registry version 3 (2026-10-25) serves the SPCX launchpad and Ekubo. Both
|
|
113
|
+
adapters (`spcx-launchpad`, `ekubo-core`) run on lane A and lane B from
|
|
114
|
+
indexer registry version 27, rolled at 21:42 CET. Their backfills were still
|
|
115
|
+
running at the flip, so neither exchange carries a pool floor yet.
|
|
@@ -85,7 +85,7 @@ hand. When the tool and the memo differ, follow the tool: it reads the
|
|
|
85
85
|
newest file.
|
|
86
86
|
|
|
87
87
|
<!-- dex-registry:start -->
|
|
88
|
-
Memo of the exchange registry, version
|
|
88
|
+
Memo of the exchange registry, version 3, robinhood, recorded 2026-10-25.
|
|
89
89
|
|
|
90
90
|
| Exchange | Key | Also written | Trail | Factories, or the manager | Router |
|
|
91
91
|
|---|---|---|---|---|---|
|
|
@@ -108,6 +108,7 @@ Memo of the exchange registry, version 2, robinhood, recorded 2026-10-24.
|
|
|
108
108
|
| Orvex | `orvex` | Orvex, Orvex MetaDEX, orvex.fi, ORVX | vault | manager `0xd01c774d4a66408326bc65728ac5ae5aaf004032` | the manager |
|
|
109
109
|
| Swaap v2 | `swaap` | Swaap, Swaap Finance, swaap.xyz, Balancer, Balancer v2, Balancer v2 vault | vault | manager `0xd315a9c38ec871068fec378e4ce78af528c76293` | the manager |
|
|
110
110
|
| unnamed Balancer v3 vault | `balancer-v3` | Balancer, Balancer v3, Balancer v3 vault | vault | manager `0x28082618ba2073e602230188e4f4c46e9b2169eb` | the manager |
|
|
111
|
+
| Ekubo | `ekubo` | Ekubo, Ekubo Protocol, ekubo.org | vault | manager `0x00000000000014aa86c5d3c41765bb24e11bd701` | the manager |
|
|
111
112
|
| Ramses | `ramses` | Ramses, Ramses Exchange, RAM, ramses.xyz, Ramses DLMM | plain | `0xdcd5f77697914e27f56fd263ef82923c8524abac`, `0x43b2bf9f33036a02fc7a00935571c2a6b0108e66` | `0xd0019e86edb35e1fedaab03aed5c3c60f115d28b` |
|
|
112
113
|
| SectorOne | `sectorone` | SectorOne, SectorOne DLMM | plain | `0x3d8dc63ddc9b992cfde89f7aea6f18717e84687d` | none |
|
|
113
114
|
| Hoodit | `hoodit` | Hoodit, hoodit.xyz | plain | `0x22602d966defd638ee94e97a92e2eb0934c3fe1b` | none |
|
|
@@ -116,6 +117,7 @@ Memo of the exchange registry, version 2, robinhood, recorded 2026-10-24.
|
|
|
116
117
|
| Curve | `curve` | Curve, Curve Finance, CRV, curve.finance | plain | `0x8271e06e5887fe5ba05234f5315c19f3ec90e8ad` | none |
|
|
117
118
|
| Metric | `metric` | Metric, Metric OMM, metric.xyz | plain | `0x2e0a99a2e352043d1ca489cf45bf7b511bccc738` | none |
|
|
118
119
|
| Penguin | `penguin` | Penguin, Penguin launchpad | launchpad | `0x09f1b640d53c9c4c1807bf5aa306745128ee4375` | none |
|
|
120
|
+
| SPCX launchpad | `spcx` | SPCX, SPCX launchpad, spcx launchpad | launchpad | `0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091` | `0x190b452d16e12e3beedd76a030ea991fe3856c33` |
|
|
119
121
|
| pons | `pons` | pons, pons launchpad | launchpad | `0x7ed598bcef8bd9edd8c97a195c6d13f40801ec7e` | none |
|
|
120
122
|
| Virtuals Protocol | `virtuals` | Virtuals, VIRTUAL, Virtuals Protocol | launchpad | `0xfc2e4da3edb2e18100473339c763705d263d20a9` | none |
|
|
121
123
|
<!-- dex-registry:end -->
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: chain-insights-dex-launchpad
|
|
3
3
|
door: meta_dex_lookup
|
|
4
|
-
description: Use for the dex-launchpad trail - token launchpads with a bonding curve on robinhood other than Virtuals, such as Penguin and the unnamed bonding-curve launchpad - when a question asks who launched a token there, who bought or sold on its curve, whether and when it graduated, or which pool it moved to. Loaded by chain-insights-dex for a launchpad row of its memo. Virtuals questions belong to chain-insights-virtuals.
|
|
4
|
+
description: Use for the dex-launchpad trail - token launchpads with a bonding curve on robinhood other than Virtuals, such as Penguin, the SPCX launchpad and the unnamed bonding-curve launchpad, and the unnamed v4 launch factories that create a token and open its Uniswap v4 pool in one transaction - when a question asks who launched a token there or through a factory address, who bought or sold on its curve, whether and when it graduated, or which pool it moved to. Loaded by chain-insights-dex for a launchpad row of its memo. Virtuals questions belong to chain-insights-virtuals.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Chain Insights: the launchpad trail (Penguin, bonding curves)
|
|
7
|
+
# Chain Insights: the launchpad trail (Penguin, SPCX, bonding curves)
|
|
8
8
|
|
|
9
9
|
Load `chain-insights-cypher` first. The first step for a launchpad named
|
|
10
|
-
with no address is `meta_dex_lookup` (Penguin, pons, Flap): it returns the
|
|
10
|
+
with no address is `meta_dex_lookup` (Penguin, SPCX, pons, Flap): it returns the
|
|
11
11
|
launchpad's factory and its first reads. With no such tool, the memo of
|
|
12
|
-
`chain-insights-dex` names the factory.
|
|
12
|
+
`chain-insights-dex` names the factory. A contract address that no row
|
|
13
|
+
holds may be a v4 launch factory: see "A v4 launch factory" below. Their
|
|
14
|
+
answer rules hold here:
|
|
13
15
|
every address in full, tokens worth an amount in USD, no query text in the
|
|
14
16
|
answer, times from the `_utc` twins.
|
|
15
17
|
|
|
@@ -31,6 +33,7 @@ A launchpad token lives in two stages:
|
|
|
31
33
|
| Launchpad | Factory | What the factory deploys |
|
|
32
34
|
|---|---|---|
|
|
33
35
|
| Penguin | `0x09f1b640d53c9c4c1807bf5aa306745128ee4375` | the launched token, which is also its own curve |
|
|
36
|
+
| SPCX launchpad | `0x3d6ea4e97b8ab9e1a1b4943febffe22d8c920091` | the launched token and its curve, a contract of its own, quoted in SPCX or another stock token |
|
|
34
37
|
| unnamed launchpad | `0x7ed598bcef8bd9edd8c97a195c6d13f40801ec7e` | a launch contract; its name is not proven yet |
|
|
35
38
|
|
|
36
39
|
Call the second one "an unnamed bonding-curve launchpad". Never give it a
|
|
@@ -50,7 +53,9 @@ name you have not read.
|
|
|
50
53
|
3. **Who bought and sold on the curve** (L2): the launched contract's own
|
|
51
54
|
`FLOWS_TO` links, both directions, ranked by transactions. Money in is a
|
|
52
55
|
buy, money out is a payout. On Penguin the token contract is the curve,
|
|
53
|
-
so these are its traders.
|
|
56
|
+
so these are its traders. On the SPCX launchpad the curve is a contract
|
|
57
|
+
of its own and a `:Pool`: its traders are the `SWAPPED` links into the
|
|
58
|
+
curve, and the token's own links are not the curve stage.
|
|
54
59
|
4. A fresh token has no price: its `amount_usd` can be empty. Value a buy by
|
|
55
60
|
what was paid for it, and say when no price exists.
|
|
56
61
|
|
|
@@ -64,6 +69,51 @@ name you have not read.
|
|
|
64
69
|
7. After graduation the token trades on that pool. Load `chain-insights-dex`
|
|
65
70
|
for the pool's exchange and its trail.
|
|
66
71
|
|
|
72
|
+
## A v4 launch factory
|
|
73
|
+
|
|
74
|
+
Some launchpads have no name and no registry row, on purpose. The DEX
|
|
75
|
+
indexer finds them by their shape, with one rule for every operator:
|
|
76
|
+
`v4-launch-factory-v1`.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
creator ── one transaction ──▶ factory F ──DEPLOYED_CONTRACT──▶ token
|
|
80
|
+
│
|
|
81
|
+
└──▶ Uniswap v4 pool manager opens a pool
|
|
82
|
+
of that token: its first pool, a pool id
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- **The shape.** In one transaction a contract F creates a token, and the
|
|
86
|
+
Uniswap v4 pool manager `0x8366a39cc670b4001a1121b8f6a443a643e40951`
|
|
87
|
+
opens a pool holding that token. F is a launch factory.
|
|
88
|
+
- **A launchpad needs two launches.** F is a launchpad when it repeats the
|
|
89
|
+
shape: two tokens or more. A contract with one launch is a one-off: say
|
|
90
|
+
"a contract that launched one token", never a launchpad.
|
|
91
|
+
- **No name, no row.** `meta_dex_lookup` with F's address answers
|
|
92
|
+
`matched: null`. For F that means "no exchange row", not "no data": read
|
|
93
|
+
the graph below. Never give F a name you have not read.
|
|
94
|
+
- **What the graph holds today.** The launches are rows of the rule
|
|
95
|
+
`v4-launch-factory-v1` in the warehouse. The graph does not mark F yet:
|
|
96
|
+
no `:Launchpad`, no `:Pool`, no rule name in its `labels`. The
|
|
97
|
+
`:Launchpad` mark for a factory of two launches or more is coming in the
|
|
98
|
+
graph's pools layer. Until a read of F shows it, do not ask by mark for
|
|
99
|
+
F: read the shape from its links.
|
|
100
|
+
|
|
101
|
+
1. **The launches**: L1, anchored on F. Each `DEPLOYED_CONTRACT` link is a
|
|
102
|
+
contract F created. Count the tokens to say whether F is a launchpad.
|
|
103
|
+
2. **Who launched a token**: `tx_origin` on the link, the sender of the
|
|
104
|
+
launch transaction. F is the factory, never the creator. A launch sent
|
|
105
|
+
through a bundler of contract wallets names the bundler, not the user:
|
|
106
|
+
say so when the same `tx_origin` sends many unrelated launches.
|
|
107
|
+
3. **The first pool** (L4): the token's Uniswap v4 pool, kept inside the
|
|
108
|
+
pool manager by a 32-byte pool id. It is not a contract of its own: say
|
|
109
|
+
"a pool of the Uniswap v4 pool manager". Read the id from `pools` on a
|
|
110
|
+
`SWAPPED` link that sold or bought the token, anchored on the creator.
|
|
111
|
+
When no such link shows, say the graph does not serve that pool id yet.
|
|
112
|
+
The creator and the launch transaction still answer who launched it.
|
|
113
|
+
4. **The trades** are ordinary Uniswap v4 swaps on the pool manager. Load
|
|
114
|
+
`chain-insights-dex-vault` and follow its trail. The pool manager is
|
|
115
|
+
never the buyer, and no topology hop goes out of it.
|
|
116
|
+
|
|
67
117
|
## The next lead
|
|
68
118
|
|
|
69
119
|
End with who to trace next: the creator's first funders (the newest-links
|
|
@@ -95,3 +145,11 @@ USE topology
|
|
|
95
145
|
MATCH (t:Address {address: "0xf3b01a81dc8a4216d92e74b40113e9e1b10a4dac"})-[l:ADDED_LIQUIDITY]->(p:Pool)
|
|
96
146
|
RETURN t.address AS from_address, p.address AS to_address, p.labels AS to_labels, l.asset AS asset, l.usd AS usd, l.first_seen_height AS first_seen_height ORDER BY l.first_seen_height ASC LIMIT 5
|
|
97
147
|
```
|
|
148
|
+
|
|
149
|
+
### L4. The first pool of a v4 launch, from the creator's swaps
|
|
150
|
+
|
|
151
|
+
```cypher
|
|
152
|
+
USE topology
|
|
153
|
+
MATCH (c:Address {address: "0x705da6eb557a987960294597954d75e9be2a1983"})-[sw:SWAPPED]->(m:Address {address: "0x8366a39cc670b4001a1121b8f6a443a643e40951"}) WHERE sw.sold_asset = "0xf3b01a81dc8a4216d92e74b40113e9e1b10a4dac" OR sw.bought_asset = "0xf3b01a81dc8a4216d92e74b40113e9e1b10a4dac"
|
|
154
|
+
RETURN c.address AS from_address, m.address AS to_address, sw.sold_asset AS sold_asset, sw.bought_asset AS bought_asset, sw.sold_usd AS sold_usd, sw.bought_usd AS bought_usd, sw.swap_count AS swap_count, sw.pools AS pools, sw.last_seen_height AS last_seen_height ORDER BY sw.last_seen_height ASC LIMIT 25
|
|
155
|
+
```
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: chain-insights-dex-vault
|
|
3
3
|
door: meta_dex_lookup
|
|
4
|
-
description: Use for the dex-vault trail - exchanges where one pool manager contract holds every pool, such as Uniswap v4, Balancer v2, Balancer v3
|
|
4
|
+
description: Use for the dex-vault trail - exchanges where one pool manager contract holds every pool, such as Uniswap v4, Balancer v2, Balancer v3, PancakeSwap Infinity and Ekubo on robinhood - when a seller's swap names the pool manager or vault, when a question asks who bought past the manager, or which pools such an exchange has. Loaded by chain-insights-dex for a vault row of its memo.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# Chain Insights: the vault trail (Uniswap v4, Balancer, PancakeSwap Infinity)
|
|
7
|
+
# Chain Insights: the vault trail (Uniswap v4, Balancer, PancakeSwap Infinity, Ekubo)
|
|
8
8
|
|
|
9
9
|
Load `chain-insights-cypher` first, then `chain-insights-dex`: the
|
|
10
10
|
exchange's manager comes from `meta_dex_lookup`, or from the memo there when
|
|
@@ -13,8 +13,8 @@ address in full, tokens worth an amount in USD, no query text in the answer,
|
|
|
13
13
|
times from the `_utc` twins.
|
|
14
14
|
|
|
15
15
|
On a vault exchange one contract, the **pool manager** (Balancer calls it
|
|
16
|
-
the vault), holds the tokens of every pool. A swap pays the
|
|
17
|
-
manager pays out:
|
|
16
|
+
the vault, Ekubo the core), holds the tokens of every pool. A swap pays the
|
|
17
|
+
manager and the manager pays out:
|
|
18
18
|
|
|
19
19
|
```
|
|
20
20
|
seller ──SWAPPED──▶ pool manager ──▶ buyer
|
|
@@ -32,6 +32,7 @@ buyer.** Name it for what it is, in full: "the Uniswap v4 pool manager
|
|
|
32
32
|
| Balancer v2 | `0xd315a9c38ec871068fec378e4ce78af528c76293` |
|
|
33
33
|
| Balancer v3 | `0x28082618ba2073e602230188e4f4c46e9b2169eb` |
|
|
34
34
|
| PancakeSwap Infinity | `0xd01c774d4a66408326bc65728ac5ae5aaf004032` |
|
|
35
|
+
| Ekubo | `0x00000000000014aa86c5d3c41765bb24e11bd701` |
|
|
35
36
|
|
|
36
37
|
## The buyer past the manager
|
|
37
38
|
|
|
@@ -64,7 +65,12 @@ the `pools` of the swaps it settles:
|
|
|
64
65
|
2. **The events of one pool**: take one seller from V4 and read its facts
|
|
65
66
|
`SWAP` rows with the manager on one day (V3).
|
|
66
67
|
3. A pool id here may name the pool, not a contract of its own. Say "a pool
|
|
67
|
-
of the Uniswap v4 pool manager", never "a pool contract".
|
|
68
|
+
of the Uniswap v4 pool manager", never "a pool contract". Ekubo keeps
|
|
69
|
+
every pool by a 32-byte pool id the same way.
|
|
70
|
+
4. On Ekubo, every position is opened through the positions NFT
|
|
71
|
+
`0xda38ac72ce7220c4dd7719d114ef94edadb8f068` (Ekubo Ve33 Positions). It is
|
|
72
|
+
shared infrastructure, never a liquidity provider: the provider is the
|
|
73
|
+
holder of the position's NFT.
|
|
68
74
|
|
|
69
75
|
## The next lead
|
|
70
76
|
|
|
@@ -69,7 +69,7 @@ agent is created on Uniswap's own v2 factory
|
|
|
69
69
|
most of that factory's pairs are Virtuals agents.
|
|
70
70
|
|
|
71
71
|
<!-- dex-registry:start -->
|
|
72
|
-
The factories of the exchange registry, version
|
|
72
|
+
The factories of the exchange registry, version 3, row `virtuals`:
|
|
73
73
|
|
|
74
74
|
| Factory | Address |
|
|
75
75
|
|---|---|
|