yoinkai-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +36 -0
  2. package/SKILL.md +23 -0
  3. package/index.js +137 -0
  4. package/package.json +12 -0
package/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # yoinkai-mcp
2
+
3
+ Read-only MCP server over the [YOINK](https://yoinkai.fun) launch graph on Robinhood Chain. Give any agent eyes on token launches as they land.
4
+
5
+ No keys. No custody. No trades through this server — buys, sells, and launches happen in your own wallet on [yoinkai.fun](https://yoinkai.fun).
6
+
7
+ ## Install
8
+
9
+ Claude Code:
10
+
11
+ ```
12
+ claude mcp add yoink -- npx -y yoinkai-mcp
13
+ ```
14
+
15
+ Cursor, Claude Desktop, or any MCP client:
16
+
17
+ ```json
18
+ { "mcpServers": { "yoink": { "command": "npx", "args": ["-y", "yoinkai-mcp"] } } }
19
+ ```
20
+
21
+ ## Tools
22
+
23
+ - **top_launches** — the newest token launches on Robinhood Chain: name, ticker, address, deployer, source.
24
+ - **scan_launch** / **xray_token** — safety X-ray of any token address: sellable, 0% tax, no owner control, LP locked, template-verified.
25
+ - **deployer_history** — every launch from a wallet, with a serial-deployer flag.
26
+ - **graph_stats** — totals for the launch graph: tokens indexed, unique deployers, top serial deployers.
27
+
28
+ The graph indexes launches as they land — launchpad launches, factory deploys, and raw contract deploys.
29
+
30
+ ## Notes
31
+
32
+ - Zero dependencies, Node >= 18, speaks MCP over stdio.
33
+ - `YOINK_API` env var overrides the graph endpoint (default `https://yoink-graph.fly.dev`).
34
+ - Read-only by design. Not financial advice.
35
+
36
+ Built by [YOINK](https://yoinkai.fun) — the agentic launch terminal on Robinhood Chain. Skill page: [yoinkai.fun/skill](https://yoinkai.fun/skill)
package/SKILL.md ADDED
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: yoink
3
+ description: Watch and X-ray token launches on Robinhood Chain via the YOINK launch graph. Use when the user asks about new tokens, launches, token safety, or deployer reputation on Robinhood Chain.
4
+ ---
5
+
6
+ # YOINK — launches on Robinhood Chain
7
+
8
+ You have the `yoink` MCP server (package `yoinkai-mcp`). It is read-only: it reads the YOINK launch graph and the chain but cannot touch funds. All trading happens in the user's own wallet at https://yoinkai.fun.
9
+
10
+ ## What you can do
11
+
12
+ 1. **Find new launches** — `top_launches` returns the newest tokens (name, ticker, address, deployer, source). Use it when the user asks "what just launched" or wants to watch the chain.
13
+ 2. **X-ray a token** — `scan_launch` (alias `xray_token`) with a 0x address answers: is it sellable, is there tax, does an owner control it, is the LP locked, does the bytecode match the verified standard template. ALWAYS X-ray a token before saying anything positive about it.
14
+ 3. **Check the deployer** — `deployer_history` with a wallet address shows every launch from that wallet and flags serial deployers. A deployer with many short-lived launches is a risk signal.
15
+ 4. **Graph totals** — `graph_stats` for indexed-token counts, unique deployers, and the top serial deployers.
16
+
17
+ ## Rules
18
+
19
+ - Read-only: never claim you can buy, sell, or launch for the user. When they want to trade, send them to the token's page on https://yoinkai.fun (append the 0x address to a token page link only if you have verified it via X-ray).
20
+ - Never invent metrics. Everything you state about a token must come from a tool result.
21
+ - An X-ray that does NOT match the template is a warning, not a verdict — say the contract needs manual verification before trading.
22
+ - Copycats exist: identical names or tickers on different contracts. The address is the identity; the name is not. When two tokens share a name, the older contract is usually the original.
23
+ - Not financial advice; say so when giving a safety read.
package/index.js ADDED
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ // yoinkai-mcp: read-only MCP server over the YOINK launch graph on Robinhood Chain.
3
+ // Any agent (Claude, Cursor, Codex) can scan launches, X-ray tokens, and read deployer
4
+ // history. Read-only by design: no keys, no trades, no launches. Money paths stay in
5
+ // the user's wallet on yoinkai.fun. Zero deps, Node >= 18 (global fetch), speaks MCP
6
+ // stdio JSON-RPC 2.0.
7
+
8
+ const API = process.env.YOINK_API || 'https://yoink-graph.fly.dev';
9
+ const RPCS = [API + '/rpc', 'https://robinhoodchain.blockscout.com/api/eth-rpc', 'https://rpc.mainnet.chain.robinhood.com'];
10
+ const WETH = '0x0bd7d308f8e1639fab988df18a8011f41eacad73';
11
+
12
+ const j = async (path) => (await fetch(API + path)).json();
13
+ const rpc = async (method, params) => {
14
+ let err;
15
+ for (const u of RPCS) {
16
+ try {
17
+ const r = await fetch(u, { method: 'POST', headers: { 'content-type': 'application/json' },
18
+ body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) });
19
+ if (!r.ok) throw new Error('http ' + r.status);
20
+ const o = await r.json();
21
+ if (o.error) throw new Error(o.error.message || 'rpc error');
22
+ return o.result;
23
+ } catch (e) { err = e; }
24
+ }
25
+ throw err;
26
+ };
27
+ const isAddr = (s) => /^0x[0-9a-fA-F]{40}$/.test(s || '');
28
+
29
+ // Noxa standard-template bytecode check: exact match everywhere outside the per-token
30
+ // immutable windows means provably sellable, 0% tax, no owner, LP locked by construction.
31
+ const IMM = [[632, 636], [2266, 2270], [3900, 3904], [4755, 4759]];
32
+ const inImm = (b) => IMM.some(([x, y]) => b >= x && b < y);
33
+ let ANCHOR = null;
34
+ async function anchor() {
35
+ if (ANCHOR) return ANCHOR;
36
+ ANCHOR = await rpc('eth_getCode', ['0xa2718f80f1fe0cdec69c9023ee006807dd487a8c', 'latest']);
37
+ return ANCHOR;
38
+ }
39
+ function tmatch(code, a) {
40
+ if (!code || code === '0x' || !a || a === '0x') return null; // could not read
41
+ if (code.length !== a.length) return false; // different bytecode = not the template
42
+ for (let i = 2; i < code.length; i += 2) if (code.substr(i, 2) !== a.substr(i, 2) && !inImm((i - 2) / 2)) return false;
43
+ return true;
44
+ }
45
+
46
+ // decode an ABI-encoded string return (offset + length + bytes)
47
+ function decStr(hex) {
48
+ if (!hex || hex.length < 130) return null;
49
+ try {
50
+ const len = parseInt(hex.slice(66, 130), 16);
51
+ const raw = hex.slice(130, 130 + len * 2);
52
+ return Buffer.from(raw, 'hex').toString('utf8').replace(/\0/g, '') || null;
53
+ } catch { return null; }
54
+ }
55
+ const viewStr = (addr, sel) => rpc('eth_call', [{ to: addr, data: sel }, 'latest']).then(decStr).catch(() => null);
56
+
57
+ async function xray(addr) {
58
+ const meta = await j('/v0/token/' + addr).catch(() => null);
59
+ const t = (meta && meta.token) || { address: addr };
60
+ // graph miss (pre-graph tokens like $YOINK itself): read name/symbol from the chain
61
+ if (!t.name) t.name = await viewStr(addr, '0x06fdde03');
62
+ if (!t.symbol) t.symbol = await viewStr(addr, '0x95d89b41');
63
+ const a = await anchor();
64
+ const code = await rpc('eth_getCode', [addr, 'latest']).catch(() => null);
65
+ const tpl = code ? tmatch(code, a) : null;
66
+ return {
67
+ address: addr, name: t.name || null, symbol: t.symbol || null,
68
+ deployer: t.deployer || null, source: t.source || null,
69
+ // true = proven by template match; null = unknown (NOT proven bad), stated in verdict
70
+ sellable: tpl === true ? true : null, zeroTax: tpl === true ? true : null,
71
+ noOwner: tpl === true ? true : null, lpLocked: tpl === true ? true : null,
72
+ templateVerified: tpl === true,
73
+ verdict: tpl === true ? 'passes the standard template: sellable, 0% tax, no owner, LP locked'
74
+ : tpl === false ? 'does NOT match the standard template, verify the contract before trading'
75
+ : 'could not read the contract code',
76
+ note: 'read-only X-ray from the YOINK launch graph. Not financial advice. Trades happen in your own wallet on yoinkai.fun.',
77
+ };
78
+ }
79
+
80
+ const TOOLS = [
81
+ { name: 'top_launches', description: 'Most active recent token launches on Robinhood Chain by newest block. Returns name, symbol, address, deployer, source.',
82
+ inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'how many, default 20, max 100' } } } },
83
+ { name: 'scan_launch', description: 'X-ray one token by contract address: is it sellable, taxed, owner-controlled, LP locked, and who deployed it.',
84
+ inputSchema: { type: 'object', properties: { address: { type: 'string', description: '0x contract address' } }, required: ['address'] } },
85
+ { name: 'xray_token', description: 'Alias of scan_launch. Full safety X-ray of a token by address.',
86
+ inputSchema: { type: 'object', properties: { address: { type: 'string' } }, required: ['address'] } },
87
+ { name: 'deployer_history', description: 'Every launch from a wallet in the graph, with a serial-deployer flag. Reputation signal for a launch.',
88
+ inputSchema: { type: 'object', properties: { address: { type: 'string', description: '0x deployer wallet' } }, required: ['address'] } },
89
+ { name: 'graph_stats', description: 'Totals for the YOINK launch graph: tokens indexed, unique deployers, top serial deployers.',
90
+ inputSchema: { type: 'object', properties: {} } },
91
+ ];
92
+
93
+ async function call(name, args) {
94
+ args = args || {};
95
+ if (name === 'top_launches') {
96
+ const lim = Math.min(100, Math.max(1, args.limit || 20));
97
+ const r = await j('/v0/launches?limit=' + lim);
98
+ return (r.tokens || []).map((t) => ({ name: t.name, symbol: t.symbol, address: t.address, deployer: t.deployer, source: t.source, block: t.block }));
99
+ }
100
+ if (name === 'scan_launch' || name === 'xray_token') {
101
+ if (!isAddr(args.address)) throw new Error('need a 0x contract address');
102
+ return xray(args.address.toLowerCase());
103
+ }
104
+ if (name === 'deployer_history') {
105
+ if (!isAddr(args.address)) throw new Error('need a 0x wallet address');
106
+ const r = await j('/v0/deployer/' + args.address.toLowerCase());
107
+ const launches = r.launches || [];
108
+ return { deployer: args.address.toLowerCase(), count: launches.length, serialDeployer: launches.length >= 3,
109
+ launches: launches.map((t) => ({ name: t.name, symbol: t.symbol, address: t.address, block: t.block })) };
110
+ }
111
+ if (name === 'graph_stats') return j('/v0/stats');
112
+ throw new Error('unknown tool ' + name);
113
+ }
114
+
115
+ // ---- MCP stdio transport ----
116
+ const send = (m) => process.stdout.write(JSON.stringify(m) + '\n');
117
+ let buf = '';
118
+ process.stdin.on('data', async (d) => {
119
+ buf += d;
120
+ let nl;
121
+ while ((nl = buf.indexOf('\n')) >= 0) {
122
+ const line = buf.slice(0, nl); buf = buf.slice(nl + 1);
123
+ if (!line.trim()) continue;
124
+ let msg; try { msg = JSON.parse(line); } catch { continue; }
125
+ const reply = (result) => send({ jsonrpc: '2.0', id: msg.id, result });
126
+ const fail = (message) => send({ jsonrpc: '2.0', id: msg.id, error: { code: -32000, message } });
127
+ try {
128
+ if (msg.method === 'initialize') reply({ protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'yoinkai-mcp', version: '0.1.0' } });
129
+ else if (msg.method === 'tools/list') reply({ tools: TOOLS });
130
+ else if (msg.method === 'tools/call') {
131
+ const out = await call(msg.params.name, msg.params.arguments);
132
+ reply({ content: [{ type: 'text', text: JSON.stringify(out, null, 2) }] });
133
+ } else if (msg.method === 'ping') reply({});
134
+ else if (msg.id !== undefined) fail('unknown method ' + msg.method);
135
+ } catch (e) { fail(String(e.message || e)); }
136
+ }
137
+ });
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "yoinkai-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Read-only MCP server over the YOINK launch graph on Robinhood Chain: scan launches, X-ray tokens for safety, read deployer history. No keys, no custody.",
5
+ "type": "commonjs",
6
+ "bin": { "yoinkai-mcp": "index.js" },
7
+ "files": ["index.js", "README.md", "SKILL.md"],
8
+ "engines": { "node": ">=18" },
9
+ "keywords": ["mcp", "model-context-protocol", "robinhood-chain", "yoink", "token-launch", "agent", "crypto"],
10
+ "homepage": "https://yoinkai.fun/skill",
11
+ "license": "MIT"
12
+ }