yoinkai-mcp 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/SKILL.md +19 -7
- package/index.js +378 -12
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# yoinkai-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server over the [YOINK](https://yoinkai.fun) launch graph on Robinhood Chain. Give any agent eyes on token launches as they land — quote live prices, and prep a launch of your own in one command.
|
|
4
4
|
|
|
5
|
-
No keys. No custody.
|
|
5
|
+
No keys. No custody. This server never signs or sends anything: `launch_token` builds the exact unsigned transaction and hands it to you; you review and sign in your own wallet. Buys and sells happen in your own wallet on [yoinkai.fun](https://yoinkai.fun).
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -20,12 +20,14 @@ Cursor, Claude Desktop, or any MCP client:
|
|
|
20
20
|
|
|
21
21
|
## Tools
|
|
22
22
|
|
|
23
|
-
- **top_launches** — the newest token launches on Robinhood Chain: name,
|
|
24
|
-
- **scan_launch** / **xray_token** — safety
|
|
23
|
+
- **top_launches** — the newest token launches on Robinhood Chain: name, symbol, address, deployer, source.
|
|
24
|
+
- **scan_launch** / **xray_token** — safety Trust Score of any token address: sellable, 0% tax, no owner control, LP locked, template-verified.
|
|
25
25
|
- **deployer_history** — every launch from a wallet, with a serial-deployer flag.
|
|
26
26
|
- **graph_stats** — totals for the launch graph: tokens indexed, unique deployers, top serial deployers.
|
|
27
|
+
- **quote_token** — current price and expected receive amount for any token, routed to its live venue: a token still bonding quotes from its bonding curve contract (1% fee included); a graduated or pool token quotes the Uniswap V3 pool via QuoterV2 exact-in simulation plus the terminal fee. USD figures use a sanity-checked ETH/USD rate and are omitted when price sources disagree. Read-only.
|
|
28
|
+
- **launch_token** — build the unsigned transaction that launches a new token through the YOINK bonding-curve factory: fixed 1B supply, no upfront liquidity needed, flat 0.0005 ETH fee (verified on-chain before every build), optional creator first buy. Trading starts on the curve immediately; at 4.2 ETH raised the token graduates — a Uniswap V3 pool is created and the LP position is locked forever. Returns `to`/`value`/`data` plus a plain-language review block; pass your wallet address as `from` and it dry-runs the exact tx against the chain before anyone signs. You sign in your own wallet — the server can't.
|
|
27
29
|
|
|
28
|
-
The graph indexes launches as they land — launchpad launches, factory deploys, and raw contract deploys.
|
|
30
|
+
The graph indexes launches as they land — bonding-curve launches, launchpad launches, factory deploys, and raw contract deploys. Launches built through this MCP are labeled as agent launches on yoinkai.fun.
|
|
29
31
|
|
|
30
32
|
## Notes
|
|
31
33
|
|
package/SKILL.md
CHANGED
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: yoink
|
|
3
|
-
description: Watch and
|
|
3
|
+
description: Watch, score, quote, and launch tokens on Robinhood Chain via the YOINK launch graph. Use when the user asks about new tokens, launches, token safety, token prices, deployer reputation, or wants to launch a token on Robinhood Chain.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# YOINK — launches on Robinhood Chain
|
|
7
7
|
|
|
8
|
-
You have the `yoink` MCP server (package `yoinkai-mcp`). It
|
|
8
|
+
You have the `yoink` MCP server (package `yoinkai-mcp`). It reads the YOINK launch graph and the chain, and it can BUILD an unsigned launch transaction — but it holds no keys and can never sign or send. All signing happens in the user's own wallet; trading happens at https://yoinkai.fun.
|
|
9
9
|
|
|
10
10
|
## What you can do
|
|
11
11
|
|
|
12
|
-
1. **Find new launches** — `top_launches` returns the newest tokens (name,
|
|
13
|
-
2. **
|
|
12
|
+
1. **Find new launches** — `top_launches` returns the newest tokens (name, symbol, address, deployer, source). Use it when the user asks "what just launched" or wants to watch the chain.
|
|
13
|
+
2. **Check a token's Trust Score** — `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 check a token's Trust Score before saying anything positive about it.
|
|
14
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
15
|
4. **Graph totals** — `graph_stats` for indexed-token counts, unique deployers, and the top serial deployers.
|
|
16
|
+
5. **Quote a price** — `quote_token` with a token address returns the current price and, given an amount, the expected receive amount. It routes to the live venue automatically: a token still bonding quotes from its bonding curve contract (1% fee included); a graduated or pool token quotes the Uniswap V3 pool (exact-in simulation plus the terminal fee). Size a buy with `amountUsd` or `amountEth`; size a sell with `amountTokens` (or a USD/ETH target). USD figures come from a sanity-checked ETH/USD rate and are omitted when price sources disagree — never fill that gap with a guess.
|
|
17
|
+
6. **Launch a token** — `launch_token` with a name and symbol builds the exact unsigned transaction for the YOINK bonding-curve factory: fixed 1B supply, no upfront liquidity needed, flat 0.0005 ETH fee (verified on-chain before every build), optional creator first buy (`initialBuyEth`). Trading starts on the curve immediately; at 4.2 ETH raised the token graduates — a Uniswap V3 pool is created and the LP position is locked in a contract with no withdraw path. If you know the user's wallet address, pass it as `from` so the tool dry-runs the exact tx on-chain before anyone signs.
|
|
18
|
+
|
|
19
|
+
## Launch flow (follow exactly)
|
|
20
|
+
|
|
21
|
+
1. Collect name + symbol (description, logo URI, links, first buy optional). Logo must already be an `ipfs://` or `https://` URI — image uploads happen on yoinkai.fun, not here.
|
|
22
|
+
2. Call `launch_token`. If it errors, relay the reason plainly; nothing was built.
|
|
23
|
+
3. SHOW THE USER THE REVIEW BLOCK before anything is signed: name, symbol, total ETH (fee + first buy), and that supply, curve, and graduation terms are fixed by the factory. Never skip this.
|
|
24
|
+
4. The user signs the returned `to`/`value`/`data` on chain 4663 from their own wallet or signer. If they have no signer wired up, send them to https://yoinkai.fun to launch there instead.
|
|
25
|
+
5. Only claim the token is live after the transaction confirms — the token address is in the receipt, or `scan_launch` it once the graph indexes it. Never claim a launch happened because the tx was built.
|
|
26
|
+
|
|
27
|
+
Launches built through this skill are labeled as agent launches on yoinkai.fun.
|
|
16
28
|
|
|
17
29
|
## Rules
|
|
18
30
|
|
|
19
|
-
-
|
|
31
|
+
- Non-custodial: never claim you can sign, send, buy, or sell for the user. `launch_token` only PREPARES a transaction and `quote_token` only READS prices. 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 the Trust Score).
|
|
20
32
|
- Never invent metrics. Everything you state about a token must come from a tool result.
|
|
21
|
-
-
|
|
22
|
-
- Copycats exist: identical names or
|
|
33
|
+
- A Trust Score that does NOT match the template is a warning, not a verdict — say the contract needs manual verification before trading.
|
|
34
|
+
- Copycats exist: identical names or symbols 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
35
|
- Not financial advice; say so when giving a safety read.
|
package/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// yoinkai-mcp:
|
|
3
|
-
// Any agent (Claude, Cursor, Codex) can scan launches, X-ray tokens,
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
2
|
+
// yoinkai-mcp: MCP server over the YOINK launch graph on Robinhood Chain.
|
|
3
|
+
// Any agent (Claude, Cursor, Codex) can scan launches, X-ray tokens, quote prices,
|
|
4
|
+
// read deployer history, and BUILD an unsigned token-launch transaction. Non-custodial
|
|
5
|
+
// by design: no keys, nothing is ever signed or sent by this server — the user reviews
|
|
6
|
+
// and signs every transaction in their own wallet. Zero deps, Node >= 18 (global fetch),
|
|
7
|
+
// speaks MCP stdio JSON-RPC 2.0.
|
|
7
8
|
|
|
8
9
|
const API = process.env.YOINK_API || 'https://yoink-graph.fly.dev';
|
|
9
10
|
const RPCS = [API + '/rpc', 'https://robinhoodchain.blockscout.com/api/eth-rpc', 'https://rpc.mainnet.chain.robinhood.com'];
|
|
@@ -73,21 +74,380 @@ async function xray(addr) {
|
|
|
73
74
|
verdict: tpl === true ? 'passes the standard template: sellable, 0% tax, no owner, LP locked'
|
|
74
75
|
: tpl === false ? 'does NOT match the standard template, verify the contract before trading'
|
|
75
76
|
: 'could not read the contract code',
|
|
76
|
-
note: '
|
|
77
|
+
note: 'Read-only Trust Score from the YOINK launch graph. Not financial advice. Trades happen in your own wallet on yoinkai.fun.',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---- launch verb: builds the tx, NEVER signs or sends (no keys in this process) ----
|
|
82
|
+
// YoinkCurveFactory (docs/CHAIN.md, docs/SPEC_BONDING_CURVE_V2.md): launches deploy the
|
|
83
|
+
// audited YoinkToken template (fixed 1B supply) plus a per-token YoinkBondingCurve
|
|
84
|
+
// holding the full supply — no upfront liquidity needed. Trading starts on the curve;
|
|
85
|
+
// at 4.2 ETH net raised the token graduates: a Uniswap V3 pool is created and the LP
|
|
86
|
+
// position is locked in a contract with no withdraw path. launch(LaunchParams) has the
|
|
87
|
+
// SAME selector and struct as the old V1 factory (byte-exact port of the proven web
|
|
88
|
+
// path, landing/terminal.html encYoinkLaunch) and returns (token, curve).
|
|
89
|
+
const CURVE_FACTORY = '0xbb49b39141cbaf28bb89c5f16d0613d3beab04b8'; // LIVE mainnet 2026-07-07, flip-verified
|
|
90
|
+
const FACTORY_V1 = '0xbcc67639cd2c3e9498714be53018949e3b17ea5e'; // old born-graduated factory, reference only
|
|
91
|
+
const FACTORY = CURVE_FACTORY; // launch target
|
|
92
|
+
const LAUNCH_FEE = 500000000000000n; // 0.0005 ETH — LAUNCH_FEE() in the curve factory, re-verified on-chain per build
|
|
93
|
+
const SEL_LAUNCH = '0xe1f7d84a'; // launch((string,string,string,string,string[3],uint256)) -> (token, curve)
|
|
94
|
+
const SEL_FEE = '0x09197a81'; // LAUNCH_FEE()
|
|
95
|
+
const CHAIN_ID = 4663; // 0x1237 Robinhood Chain
|
|
96
|
+
const GRAD_WEI = 4200000000000000000n; // graduation threshold: 4.2 ETH net raised on the curve
|
|
97
|
+
|
|
98
|
+
const w256 = (n) => BigInt(n).toString(16).padStart(64, '0');
|
|
99
|
+
const padA = (a) => '000000000000000000000000' + a.toLowerCase().slice(2);
|
|
100
|
+
const padStr = (s) => { const b = Array.from(new TextEncoder().encode(s)); const hx = b.map((x) => x.toString(16).padStart(2, '0')).join(''); return w256(b.length) + (b.length ? hx.padEnd(Math.ceil(hx.length / 64) * 64, '0') : ''); };
|
|
101
|
+
const tupDyn = (parts) => { let head = '', tail = '', off = parts.length * 32; for (const p of parts) { head += w256(off); tail += p; off += p.length / 2; } return head + tail; };
|
|
102
|
+
// launch(LaunchParams{name,symbol,logo,description,socials[3],minTokensOut})
|
|
103
|
+
function encYoinkLaunch(o, minOut) {
|
|
104
|
+
const soc = tupDyn([padStr(o.socials[0] || ''), padStr(o.socials[1] || ''), padStr(o.socials[2] || '')]);
|
|
105
|
+
const dyn = [padStr(o.name), padStr(o.symbol), padStr(o.logo || ''), padStr(o.desc || ''), soc];
|
|
106
|
+
let head = '', tail = '', off = 6 * 32;
|
|
107
|
+
for (const d of dyn) { head += w256(off); tail += d; off += d.length / 2; }
|
|
108
|
+
head += w256(minOut);
|
|
109
|
+
return SEL_LAUNCH + w256(0x20) + head + tail;
|
|
110
|
+
}
|
|
111
|
+
// expected tokens out for the creator initial buy on a FRESH curve (deterministic: the
|
|
112
|
+
// curve deploys inside this same launch tx, nothing can trade it first). Byte-exact
|
|
113
|
+
// BigInt mirror of YoinkBondingCurve._quoteBuy at e0=0, sold0=0 (same function the live
|
|
114
|
+
// web path uses): fee = value/100 (floor), net capped at GRAD_WEI (a big first buy
|
|
115
|
+
// graduates the curve, the excess refunds to the creator in the same tx), tokens out
|
|
116
|
+
// rounds DOWN via ceilDiv on the kept virtual reserve. Verified against the deployed
|
|
117
|
+
// contract: exact minOut simulates clean, minOut+1 reverts "minOut".
|
|
118
|
+
function ycExpectedOut(buyWei) {
|
|
119
|
+
const VE = 1050000000000000000n, VT = 1000000000000000000000000000n, K = VE * VT;
|
|
120
|
+
let net = buyWei - buyWei / 100n; // 1% fee off the gross first
|
|
121
|
+
if (net >= GRAD_WEI) net = GRAD_WEI; // graduation cap, contract refunds the unused gross
|
|
122
|
+
const kept = (K + (VE + net) - 1n) / (VE + net); // ceilDiv(K, VE+net)
|
|
123
|
+
return VT > kept ? VT - kept : 0n;
|
|
124
|
+
}
|
|
125
|
+
const fmtEth = (wei) => { const s = wei.toString().padStart(19, '0'); const i = s.slice(0, -18), f = s.slice(-18).replace(/0+$/, ''); return (i || '0') + (f ? '.' + f : ''); };
|
|
126
|
+
|
|
127
|
+
// telemetry, NOT money path: label agent-built launches so yoinkai.fun can show an
|
|
128
|
+
// "Agent" chip on them. Fire-and-forget with a short timeout — any failure (including
|
|
129
|
+
// a 404 while the indexer endpoint is not deployed yet) is swallowed and can never
|
|
130
|
+
// block, delay, or fail the launch build. Sends only what the caller already gave us.
|
|
131
|
+
function agentIntent(o) {
|
|
132
|
+
try {
|
|
133
|
+
const ac = new AbortController(); const tm = setTimeout(() => ac.abort(), 2000);
|
|
134
|
+
fetch(API + '/v0/agent-intent', { method: 'POST', headers: { 'content-type': 'application/json' },
|
|
135
|
+
body: JSON.stringify(o), signal: ac.signal }).catch(() => {}).finally(() => clearTimeout(tm));
|
|
136
|
+
} catch { /* telemetry must never throw */ }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function buildLaunch(args) {
|
|
140
|
+
const name = String(args.name || '').trim();
|
|
141
|
+
if (!name) throw new Error('Need a token name');
|
|
142
|
+
if (name.length > 32) throw new Error('Name too long (max 32 characters)');
|
|
143
|
+
const symbol = String(args.symbol || '').toUpperCase().replace(/[^A-Z0-9]/g, '');
|
|
144
|
+
if (!symbol) throw new Error('Need a symbol (letters and digits)');
|
|
145
|
+
if (symbol.length > 10) throw new Error('Symbol too long (max 10 characters)');
|
|
146
|
+
const desc = String(args.description || '');
|
|
147
|
+
if (desc.length > 180) throw new Error('Description too long (max 180 characters)');
|
|
148
|
+
const logo = String(args.logo || '');
|
|
149
|
+
if (logo && !/^(ipfs:\/\/|https:\/\/)/.test(logo)) throw new Error('Logo must be an ipfs:// or https:// URI (upload the image on yoinkai.fun to get one)');
|
|
150
|
+
if (logo.length > 256) throw new Error('Logo URI too long (max 256 characters)');
|
|
151
|
+
const socials = {};
|
|
152
|
+
for (const k of ['x', 'telegram', 'website']) {
|
|
153
|
+
const v = String(args[k] || '');
|
|
154
|
+
if (v && !/^https:\/\//.test(v)) throw new Error(k + ' link must start with https://');
|
|
155
|
+
if (v.length > 256) throw new Error(k + ' link too long (max 256 characters)');
|
|
156
|
+
socials[k] = v;
|
|
157
|
+
}
|
|
158
|
+
let buyWei = 0n;
|
|
159
|
+
if (args.initialBuyEth != null && args.initialBuyEth !== '') {
|
|
160
|
+
const n = parseFloat(args.initialBuyEth);
|
|
161
|
+
if (!isFinite(n) || n < 0) throw new Error('initialBuyEth must be a non-negative number of ETH');
|
|
162
|
+
if (n > 1) throw new Error('initialBuyEth over 1 ETH — set it lower, or launch on yoinkai.fun if you really mean it');
|
|
163
|
+
if (n > 0) buyWei = BigInt(Math.round(n * 1e18)); // same conversion as the web path
|
|
164
|
+
}
|
|
165
|
+
// exact minOut: a fresh curve cannot be front-run (it deploys in this same tx), and the
|
|
166
|
+
// pre-sign simulation fails closed if this mirror ever drifts from the contract math.
|
|
167
|
+
const minOut = buyWei > 0n ? ycExpectedOut(buyWei) : 0n;
|
|
168
|
+
const data = encYoinkLaunch({ name, symbol, logo, desc, socials: [socials.x, socials.telegram, socials.website] }, minOut);
|
|
169
|
+
const value = LAUNCH_FEE + buyWei;
|
|
170
|
+
const valueHex = '0x' + value.toString(16);
|
|
171
|
+
|
|
172
|
+
// tripwire: the deployed factory must still charge exactly the expected fee.
|
|
173
|
+
// A malformed/empty return (e.g. '0x') counts as unverified, not as a mismatch.
|
|
174
|
+
let feeVerified = false;
|
|
175
|
+
const feeHex = await rpc('eth_call', [{ to: FACTORY, data: SEL_FEE }, 'latest']).catch(() => null);
|
|
176
|
+
if (typeof feeHex === 'string' && /^0x[0-9a-fA-F]{64}$/.test(feeHex)) {
|
|
177
|
+
if (BigInt(feeHex) !== LAUNCH_FEE) throw new Error('on-chain LAUNCH_FEE does not match the expected constant — refusing to build. Report this.');
|
|
178
|
+
feeVerified = true;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// optional dry run from the user's wallet (needs their address, not their keys)
|
|
182
|
+
let simulation = { ran: false, note: 'pass your wallet address as "from" to dry-run the exact tx before signing' };
|
|
183
|
+
if (args.from != null && args.from !== '') {
|
|
184
|
+
if (!isAddr(args.from)) throw new Error('"from" must be a 0x wallet address');
|
|
185
|
+
const from = args.from.toLowerCase();
|
|
186
|
+
try {
|
|
187
|
+
const pre = await rpc('eth_call', [{ from, to: FACTORY, value: valueHex, data }, 'latest']);
|
|
188
|
+
if (pre && pre.length >= 130) simulation = { ran: true, ok: true, predictedToken: '0x' + pre.slice(26, 66), predictedCurve: '0x' + pre.slice(90, 130) };
|
|
189
|
+
else throw new Error('empty result');
|
|
190
|
+
} catch (e) {
|
|
191
|
+
throw new Error('simulation reverted — this launch would fail on-chain, nothing to sign. Check the wallet holds fee + buy + gas (~0.002 ETH headroom) on Robinhood Chain and retry. (' + String(e.message || e) + ')');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// agent-launch label (fire-and-forget telemetry; see agentIntent above)
|
|
196
|
+
agentIntent({ deployer: isAddr(args.from) ? args.from.toLowerCase() : null, name, symbol, ts: Date.now() });
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
action: 'REVIEW_THEN_SIGN',
|
|
200
|
+
network: { chainId: CHAIN_ID, chainIdHex: '0x1237', name: 'Robinhood Chain', explorer: 'https://robinhoodchain.blockscout.com', rpc: 'https://rpc.mainnet.chain.robinhood.com' },
|
|
201
|
+
tx: { to: FACTORY, value: valueHex, data },
|
|
202
|
+
cost: { launchFeeEth: fmtEth(LAUNCH_FEE), initialBuyEth: fmtEth(buyWei), totalValueEth: fmtEth(value), gasNote: 'keep ~0.002 ETH extra for gas' },
|
|
203
|
+
review: {
|
|
204
|
+
name, symbol, description: desc || null, logo: logo || null,
|
|
205
|
+
x: socials.x || null, telegram: socials.telegram || null, website: socials.website || null,
|
|
206
|
+
supply: '1,000,000,000 (fixed by the factory, 18 decimals)',
|
|
207
|
+
trading: 'starts immediately on a bonding curve — no upfront liquidity. At ' + fmtEth(GRAD_WEI) + ' ETH raised the token graduates: a Uniswap V3 pool is created and the LP position is locked in a contract with no withdraw path.',
|
|
208
|
+
initialBuy: buyWei > 0n ? fmtEth(buyWei) + ' ETH spent as the creator first buy on the fresh curve (includes the 1% curve fee), exact tokens out ' + minOut.toString() + ' — deterministic, nothing can trade the curve before this tx' : 'none',
|
|
209
|
+
factory: FACTORY, feeVerifiedOnChain: feeVerified, simulation,
|
|
210
|
+
agentLabel: 'this launch will be labeled as an agent launch on yoinkai.fun',
|
|
211
|
+
},
|
|
212
|
+
launchDesign: 'YoinkCurveFactory: the token deploys with its full 1B supply on a per-token bonding curve in one tx. Buys and sells trade against the curve (1% fee) until it raises ' + fmtEth(GRAD_WEI) + ' ETH; graduation then creates the Uniswap V3 pool and locks the LP position in a contract with no withdraw path.',
|
|
213
|
+
sign: 'This server holds no keys and CANNOT send this transaction. Show the review to the user, then have them sign exactly this tx (to/value/data on chain 4663) from their own wallet or agent signer — or launch by hand at https://yoinkai.fun. The new token and curve addresses are in the receipt (or scan_launch it once the graph picks it up).',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ---- quote verb: current price + expected receive on the token's live venue ----
|
|
218
|
+
// Tokens still bonding quote from their own YoinkBondingCurve previewBuy/previewSell —
|
|
219
|
+
// the contract is the single source of quote truth (1% fee included), no curve math is
|
|
220
|
+
// duplicated here. Graduated and non-curve tokens quote the Uniswap V3 pool through
|
|
221
|
+
// QuoterV2 (exact-in simulation) with the terminal fee applied the same way the
|
|
222
|
+
// yoinkai.fun trade path charges it. Selectors mirror landing/curve.js and
|
|
223
|
+
// landing/swap.js (flip-verified there before wiring).
|
|
224
|
+
const QUOTER = '0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7'; // QuoterV2.quoteExactInputSingle 0xc6a5026a
|
|
225
|
+
const V4_ROUTER = '0x4146465c858f34ee210cae1547bec03a70b37f6c'; // per-token terminal fee oracle (feeBps 0xe868ce52)
|
|
226
|
+
const LAUNCHED_TOPIC = '0xec774f0683e9ac48e8d835f412f9f877a8a5dee9af3170d78cf3ef33149d15e7'; // Launched(address,address,address,uint256,uint256)
|
|
227
|
+
const CURVE_START = 4046164; // curve factory deploy block
|
|
228
|
+
const CSEL = { pBuy: '0x48153279', pSell: '0xfb3dd95f', grad: '0xe7c2b772', realEth: '0x7a2a2a2b' };
|
|
229
|
+
|
|
230
|
+
// token -> curve resolution: the graph is primary (ONLY its own miss body is an
|
|
231
|
+
// authoritative "no curve"); fallback = the factory's Launched events (token = topic1).
|
|
232
|
+
async function resolveCurve(token) {
|
|
233
|
+
try {
|
|
234
|
+
const r = await fetch(API + '/v0/curve/' + token);
|
|
235
|
+
if (r.status === 404) {
|
|
236
|
+
const o = await r.json().catch(() => null);
|
|
237
|
+
if (o && o.error === 'no curve for this token') return null; // authoritative miss
|
|
238
|
+
} else if (r.ok) {
|
|
239
|
+
const o = await r.json();
|
|
240
|
+
if (o && /^0x[0-9a-f]{40}$/i.test(o.curve || '')) return o.curve.toLowerCase();
|
|
241
|
+
}
|
|
242
|
+
} catch { /* fall through to logs */ }
|
|
243
|
+
const logs = await rpc('eth_getLogs', [{ address: CURVE_FACTORY, fromBlock: '0x' + CURVE_START.toString(16), toBlock: 'latest',
|
|
244
|
+
topics: [LAUNCHED_TOPIC, '0x' + padA(token)] }]).catch(() => null);
|
|
245
|
+
if (!Array.isArray(logs)) throw new Error('could not resolve the trading venue (graph and RPC both unavailable) — try again');
|
|
246
|
+
return logs[0] ? ('0x' + logs[0].topics[2].slice(26)).toLowerCase() : null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// $ conversion MONEY GUARD (ported from landing/swap.js): a rate is accepted only when
|
|
250
|
+
// two independent sources agree within 20%, or a lone source agrees within 20% with the
|
|
251
|
+
// last in-process good value (<24h). Anything else -> null -> USD fields are OMITTED
|
|
252
|
+
// from the answer, never guessed.
|
|
253
|
+
let _usdGood = null; // { v, t }
|
|
254
|
+
async function ethUsdNow() {
|
|
255
|
+
if (_usdGood && Date.now() - _usdGood.t < 120000) return _usdGood.v;
|
|
256
|
+
const srcs = [
|
|
257
|
+
['https://api.coinbase.com/v2/prices/ETH-USD/spot', (o) => parseFloat(o && o.data && o.data.amount)],
|
|
258
|
+
['https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd', (o) => o && o.ethereum && o.ethereum.usd],
|
|
259
|
+
];
|
|
260
|
+
const vals = [];
|
|
261
|
+
await Promise.all(srcs.map(async ([url, pick]) => {
|
|
262
|
+
try {
|
|
263
|
+
const ac = new AbortController(); const tm = setTimeout(() => ac.abort(), 6000);
|
|
264
|
+
const v = pick(await fetch(url, { signal: ac.signal }).finally(() => clearTimeout(tm)).then((r) => r.json()));
|
|
265
|
+
if (v && isFinite(v) && v > 0) vals.push(v);
|
|
266
|
+
} catch {}
|
|
267
|
+
}));
|
|
268
|
+
const agree = (a, b) => Math.abs(a - b) <= Math.max(a, b) * 0.2;
|
|
269
|
+
let v = null;
|
|
270
|
+
if (vals.length >= 2) v = agree(vals[0], vals[1]) ? (vals[0] + vals[1]) / 2 : null;
|
|
271
|
+
else if (vals.length === 1) v = (!_usdGood || Date.now() - _usdGood.t >= 86400000 || agree(vals[0], _usdGood.v)) ? vals[0] : null;
|
|
272
|
+
if (v) _usdGood = { v, t: Date.now() };
|
|
273
|
+
return v;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// terminal fee for the pool path, decided on-chain by the router (launchpad 1%,
|
|
277
|
+
// external 0.15%). Unknown -> assume the HIGHER tier: never understate a fee.
|
|
278
|
+
async function feeBpsFor(token) {
|
|
279
|
+
try {
|
|
280
|
+
const hx = await rpc('eth_call', [{ to: V4_ROUTER, data: '0xe868ce52' + padA(token) }, 'latest']);
|
|
281
|
+
const v = Number(BigInt(hx)); if (v > 0 && v < 10000) return v;
|
|
282
|
+
} catch {}
|
|
283
|
+
return 100;
|
|
284
|
+
}
|
|
285
|
+
const qTier = async (tin, tout, amtIn, tier) => {
|
|
286
|
+
const hx = await rpc('eth_call', [{ to: QUOTER, data: '0xc6a5026a' + padA(tin) + padA(tout) + w256(amtIn) + w256(tier) + w256(0) }, 'latest']).catch(() => null);
|
|
287
|
+
return (hx && hx.length >= 66) ? BigInt('0x' + hx.slice(2, 66)) : 0n;
|
|
288
|
+
};
|
|
289
|
+
async function bestPoolOut(tin, tout, amtIn) {
|
|
290
|
+
const tiers = [10000, 3000, 500];
|
|
291
|
+
const outs = await Promise.all(tiers.map((t) => qTier(tin, tout, amtIn, t)));
|
|
292
|
+
let best = null;
|
|
293
|
+
for (let i = 0; i < tiers.length; i++) if (outs[i] > 0n && (!best || outs[i] > best.out)) best = { tier: tiers[i], out: outs[i] };
|
|
294
|
+
return best;
|
|
295
|
+
}
|
|
296
|
+
const fmtUnits = (x, dec) => { if (!dec) return x.toString(); const s = x.toString().padStart(dec + 1, '0'); const i = s.slice(0, -dec), f = s.slice(-dec).replace(/0+$/, ''); return (i || '0') + (f ? '.' + f : ''); };
|
|
297
|
+
|
|
298
|
+
async function quoteToken(args) {
|
|
299
|
+
if (!isAddr(args.address)) throw new Error('Need a 0x token contract address');
|
|
300
|
+
const token = args.address.toLowerCase();
|
|
301
|
+
const side = String(args.side || 'buy').toLowerCase();
|
|
302
|
+
if (side !== 'buy' && side !== 'sell') throw new Error('side must be "buy" or "sell"');
|
|
303
|
+
const num = (v, what) => { if (v == null || v === '') return null; const n = parseFloat(v); if (!isFinite(n) || n <= 0) throw new Error(what + ' must be a positive number'); return n; };
|
|
304
|
+
const amountEth = num(args.amountEth, 'amountEth'), amountUsd = num(args.amountUsd, 'amountUsd'), amountTokens = num(args.amountTokens, 'amountTokens');
|
|
305
|
+
if (amountTokens != null && amountTokens > 1e15) throw new Error('amountTokens too large');
|
|
306
|
+
|
|
307
|
+
const notes = [];
|
|
308
|
+
const [name, symbol] = await Promise.all([viewStr(token, '0x06fdde03'), viewStr(token, '0x95d89b41')]);
|
|
309
|
+
let dec = null;
|
|
310
|
+
try { const hx = await rpc('eth_call', [{ to: token, data: '0x313ce567' }, 'latest']); const v = Number(BigInt(hx)); if (v >= 0 && v <= 36) dec = v; } catch {}
|
|
311
|
+
if (dec == null) { dec = 18; notes.push('could not read decimals() from the token — amounts assume 18 decimals'); }
|
|
312
|
+
const unit = 10n ** BigInt(dec);
|
|
313
|
+
const toUnits = (n) => { const s = n.toFixed(dec); const [i, f = ''] = s.split('.'); return BigInt(i) * unit + BigInt(f || '0'); };
|
|
314
|
+
const toWei = (n) => BigInt(Math.round(n * 1e18));
|
|
315
|
+
|
|
316
|
+
const ethUsd = await ethUsdNow();
|
|
317
|
+
if (!ethUsd) notes.push('USD figures omitted: live ETH/USD sources were unavailable or disagreed by more than 20% — this tool never guesses a rate');
|
|
318
|
+
if (amountUsd != null && !ethUsd) throw new Error('cannot size a $ amount right now: ETH/USD price sources disagree or are unavailable. Pass amountEth instead.');
|
|
319
|
+
|
|
320
|
+
// venue: a live (un-graduated) bonding curve wins; graduated or no-curve -> V3 pool
|
|
321
|
+
const curveAddr = await resolveCurve(token);
|
|
322
|
+
let venue = 'pool', curveInfo = null;
|
|
323
|
+
if (curveAddr) {
|
|
324
|
+
const [g, e] = await Promise.all([CSEL.grad, CSEL.realEth].map((d) => rpc('eth_call', [{ to: curveAddr, data: d }, 'latest']).catch(() => null)));
|
|
325
|
+
if (!g) throw new Error('could not read the curve state — try again');
|
|
326
|
+
if (BigInt(g) === 0n) {
|
|
327
|
+
venue = 'curve';
|
|
328
|
+
const raised = BigInt(e || '0x0');
|
|
329
|
+
curveInfo = { curve: curveAddr, raisedEth: fmtEth(raised), graduatesAtEth: fmtEth(GRAD_WEI),
|
|
330
|
+
progressPct: Math.min(100, Math.round(Number(raised) / Number(GRAD_WEI) * 100)) };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const curveBuy = async (wei) => {
|
|
335
|
+
const hx = await rpc('eth_call', [{ to: curveAddr, data: CSEL.pBuy + w256(wei) }, 'latest']);
|
|
336
|
+
if (!hx || hx.length < 130) throw new Error('curve quote failed — try again');
|
|
337
|
+
return { out: BigInt('0x' + hx.slice(2, 66)), refund: BigInt('0x' + hx.slice(66, 130)) };
|
|
338
|
+
};
|
|
339
|
+
const curveSell = async (amt) => {
|
|
340
|
+
const hx = await rpc('eth_call', [{ to: curveAddr, data: CSEL.pSell + w256(amt) }, 'latest']);
|
|
341
|
+
if (!hx || hx.length < 66) throw new Error('curve quote failed — try again');
|
|
342
|
+
return BigInt('0x' + hx.slice(2, 66));
|
|
343
|
+
};
|
|
344
|
+
const bps = venue === 'pool' ? await feeBpsFor(token) : 100; // curve fee is fixed 1% inside preview*
|
|
345
|
+
const noPool = 'no Uniswap V3 pool with liquidity found for this token — it may not be tradable on yoinkai.fun';
|
|
346
|
+
const poolBuy = async (wei) => { // terminal fee comes off the ETH in, then the pool swap
|
|
347
|
+
const b = await bestPoolOut(WETH, token, wei - (wei * BigInt(bps)) / 10000n);
|
|
348
|
+
if (!b || b.out <= 0n) throw new Error(noPool);
|
|
349
|
+
return b;
|
|
350
|
+
};
|
|
351
|
+
const poolSell = async (amt) => { // pool swap, then the terminal fee off the ETH out
|
|
352
|
+
const b = await bestPoolOut(token, WETH, amt);
|
|
353
|
+
if (!b || b.out <= 0n) throw new Error(noPool);
|
|
354
|
+
return { tier: b.tier, out: b.out - (b.out * BigInt(bps)) / 10000n };
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// reference price from a small (0.001 ETH) buy on the live venue, fees included
|
|
358
|
+
const probeWei = 1000000000000000n;
|
|
359
|
+
const probeOut = venue === 'curve' ? (await curveBuy(probeWei)).out : (await poolBuy(probeWei)).out;
|
|
360
|
+
if (probeOut <= 0n) throw new Error('could not price this token — the venue returned a zero quote');
|
|
361
|
+
const priceEth = (Number(probeWei) / 1e18) / (Number(probeOut) / Number(unit));
|
|
362
|
+
const price = {
|
|
363
|
+
ethPerToken: Number(priceEth.toPrecision(6)),
|
|
364
|
+
usdPerToken: ethUsd ? Number((priceEth * ethUsd).toPrecision(6)) : null,
|
|
365
|
+
basis: 'effective price of a 0.001 ETH buy on the live venue, fees included — larger trades move the price',
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
let quote = null;
|
|
369
|
+
if (side === 'buy' && (amountEth != null || amountUsd != null)) {
|
|
370
|
+
const spendWei = amountEth != null ? toWei(amountEth) : toWei(amountUsd / ethUsd);
|
|
371
|
+
if (venue === 'curve') {
|
|
372
|
+
const q = await curveBuy(spendWei);
|
|
373
|
+
quote = { spendEth: fmtEth(spendWei), tokensOut: fmtUnits(q.out, dec) };
|
|
374
|
+
if (q.refund > 0n) {
|
|
375
|
+
quote.refundEth = fmtEth(q.refund);
|
|
376
|
+
notes.push('this buy fills the curve to graduation — the unused ' + fmtEth(q.refund) + ' ETH refunds to your wallet in the same transaction');
|
|
377
|
+
}
|
|
378
|
+
} else {
|
|
379
|
+
const q = await poolBuy(spendWei);
|
|
380
|
+
quote = { spendEth: fmtEth(spendWei), tokensOut: fmtUnits(q.out, dec), poolFeeTier: (q.tier / 10000) + '%' };
|
|
381
|
+
}
|
|
382
|
+
if (ethUsd) quote.spendUsd = Number((Number(spendWei) / 1e18 * ethUsd).toFixed(2));
|
|
383
|
+
} else if (side === 'sell' && (amountTokens != null || amountEth != null || amountUsd != null)) {
|
|
384
|
+
let tokAmt;
|
|
385
|
+
if (amountTokens != null) tokAmt = toUnits(amountTokens);
|
|
386
|
+
else { // size the sell from the probe price to hit a $/ETH target — approximate by design
|
|
387
|
+
const targetEth = amountEth != null ? amountEth : amountUsd / ethUsd;
|
|
388
|
+
tokAmt = toUnits(targetEth / priceEth);
|
|
389
|
+
notes.push('token amount sized from the reference price to receive about ' + targetEth.toFixed(6) + ' ETH — approximate; the receive amount below is the real quote for that token amount');
|
|
390
|
+
}
|
|
391
|
+
if (tokAmt <= 0n) throw new Error('sell amount rounds to zero tokens');
|
|
392
|
+
if (venue === 'curve') {
|
|
393
|
+
const net = await curveSell(tokAmt);
|
|
394
|
+
quote = { tokensIn: fmtUnits(tokAmt, dec), receiveEth: fmtEth(net) };
|
|
395
|
+
if (ethUsd) quote.receiveUsd = Number((Number(net) / 1e18 * ethUsd).toFixed(2));
|
|
396
|
+
} else {
|
|
397
|
+
const q = await poolSell(tokAmt);
|
|
398
|
+
quote = { tokensIn: fmtUnits(tokAmt, dec), receiveEth: fmtEth(q.out), poolFeeTier: (q.tier / 10000) + '%' };
|
|
399
|
+
if (ethUsd) quote.receiveUsd = Number((Number(q.out) / 1e18 * ethUsd).toFixed(2));
|
|
400
|
+
}
|
|
401
|
+
} else if (side === 'sell') {
|
|
402
|
+
notes.push('pass amountTokens (or a USD/ETH target) to size a sell — showing price only');
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
token, name: name || null, symbol: symbol || null, decimals: dec,
|
|
407
|
+
venue: venue === 'curve' ? 'bonding curve' : 'uniswap v3 pool',
|
|
408
|
+
venueDetail: venue === 'curve'
|
|
409
|
+
? 'still bonding — quotes come from the curve contract itself (previewBuy/previewSell) and INCLUDE the 1% terminal fee, excluding your slippage setting.'
|
|
410
|
+
: 'Uniswap V3 pool via QuoterV2 exact-in simulation — includes the pool fee and the ' + (bps / 100) + '% yoinkai.fun terminal fee, excludes your slippage setting.',
|
|
411
|
+
curve: curveInfo,
|
|
412
|
+
side, price,
|
|
413
|
+
ethUsd: ethUsd ? Number(ethUsd.toFixed(2)) : null,
|
|
414
|
+
quote,
|
|
415
|
+
notes,
|
|
416
|
+
note: 'Read-only quote from live on-chain calls. Not financial advice. Trades happen in your own wallet on yoinkai.fun.',
|
|
77
417
|
};
|
|
78
418
|
}
|
|
79
419
|
|
|
80
420
|
const TOOLS = [
|
|
81
421
|
{ name: 'top_launches', description: 'Most active recent token launches on Robinhood Chain by newest block. Returns name, symbol, address, deployer, source.',
|
|
82
422
|
inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'how many, default 20, max 100' } } } },
|
|
83
|
-
{ name: 'scan_launch', description: '
|
|
423
|
+
{ name: 'scan_launch', description: 'Trust Score scan of one token by contract address: is it sellable, taxed, owner-controlled, LP locked, and who deployed it.',
|
|
84
424
|
inputSchema: { type: 'object', properties: { address: { type: 'string', description: '0x contract address' } }, required: ['address'] } },
|
|
85
|
-
{ name: 'xray_token', description: 'Alias of scan_launch
|
|
425
|
+
{ name: 'xray_token', description: 'Alias of scan_launch, the same Trust Score scan.',
|
|
86
426
|
inputSchema: { type: 'object', properties: { address: { type: 'string' } }, required: ['address'] } },
|
|
87
427
|
{ name: 'deployer_history', description: 'Every launch from a wallet in the graph, with a serial-deployer flag. Reputation signal for a launch.',
|
|
88
428
|
inputSchema: { type: 'object', properties: { address: { type: 'string', description: '0x deployer wallet' } }, required: ['address'] } },
|
|
89
429
|
{ name: 'graph_stats', description: 'Totals for the YOINK launch graph: tokens indexed, unique deployers, top serial deployers.',
|
|
90
430
|
inputSchema: { type: 'object', properties: {} } },
|
|
431
|
+
{ name: 'quote_token', description: 'Current price and expected receive amount for a token on Robinhood Chain. Routes to the live venue automatically: tokens still bonding quote from their bonding curve contract (1% fee included); graduated or pool tokens quote the Uniswap V3 pool via QuoterV2 exact-in simulation plus the terminal fee. Give amountUsd or amountEth to size a buy; amountTokens (or a USD/ETH target) to size a sell; omit amounts for price only. USD figures use a sanity-checked ETH/USD rate and are omitted when price sources disagree. Read-only: never signs, sends, or executes anything.',
|
|
432
|
+
inputSchema: { type: 'object', properties: {
|
|
433
|
+
address: { type: 'string', description: '0x token contract address' },
|
|
434
|
+
side: { type: 'string', enum: ['buy', 'sell'], description: 'buy (default) or sell' },
|
|
435
|
+
amountUsd: { type: 'number', description: 'trade size in US dollars' },
|
|
436
|
+
amountEth: { type: 'number', description: 'trade size in ETH' },
|
|
437
|
+
amountTokens: { type: 'number', description: 'token amount, for the sell side' },
|
|
438
|
+
}, required: ['address'] } },
|
|
439
|
+
{ name: 'launch_token', description: 'Build the UNSIGNED transaction that launches a new token on Robinhood Chain via the YOINK bonding-curve factory: fixed 1B supply, no upfront liquidity needed, flat 0.0005 ETH fee (verified on-chain before every build), trading starts on the curve immediately, and at 4.2 ETH raised the token graduates to a Uniswap V3 pool with the LP position locked. Optional creator first buy. Returns to/value/data for the user to review and sign in their OWN wallet — this server never holds keys and cannot send anything. ALWAYS show the review block to the user before they sign.',
|
|
440
|
+
inputSchema: { type: 'object', properties: {
|
|
441
|
+
name: { type: 'string', description: 'token name, max 32 chars' },
|
|
442
|
+
symbol: { type: 'string', description: 'ticker symbol, letters/digits, max 10 chars' },
|
|
443
|
+
description: { type: 'string', description: 'optional, max 180 chars, stored on-chain in the launch event' },
|
|
444
|
+
logo: { type: 'string', description: 'optional ipfs:// or https:// image URI' },
|
|
445
|
+
x: { type: 'string', description: 'optional https:// X/Twitter link' },
|
|
446
|
+
telegram: { type: 'string', description: 'optional https:// Telegram link' },
|
|
447
|
+
website: { type: 'string', description: 'optional https:// website link' },
|
|
448
|
+
initialBuyEth: { type: 'number', description: 'optional creator first buy in ETH, same tx (max 1)' },
|
|
449
|
+
from: { type: 'string', description: 'optional 0x wallet address that will sign — enables an exact dry run against the chain before anyone signs' },
|
|
450
|
+
}, required: ['name', 'symbol'] } },
|
|
91
451
|
];
|
|
92
452
|
|
|
93
453
|
async function call(name, args) {
|
|
@@ -98,20 +458,26 @@ async function call(name, args) {
|
|
|
98
458
|
return (r.tokens || []).map((t) => ({ name: t.name, symbol: t.symbol, address: t.address, deployer: t.deployer, source: t.source, block: t.block }));
|
|
99
459
|
}
|
|
100
460
|
if (name === 'scan_launch' || name === 'xray_token') {
|
|
101
|
-
if (!isAddr(args.address)) throw new Error('
|
|
461
|
+
if (!isAddr(args.address)) throw new Error('Need a 0x contract address');
|
|
102
462
|
return xray(args.address.toLowerCase());
|
|
103
463
|
}
|
|
104
464
|
if (name === 'deployer_history') {
|
|
105
|
-
if (!isAddr(args.address)) throw new Error('
|
|
465
|
+
if (!isAddr(args.address)) throw new Error('Need a 0x wallet address');
|
|
106
466
|
const r = await j('/v0/deployer/' + args.address.toLowerCase());
|
|
107
467
|
const launches = r.launches || [];
|
|
108
468
|
return { deployer: args.address.toLowerCase(), count: launches.length, serialDeployer: launches.length >= 3,
|
|
109
469
|
launches: launches.map((t) => ({ name: t.name, symbol: t.symbol, address: t.address, block: t.block })) };
|
|
110
470
|
}
|
|
111
471
|
if (name === 'graph_stats') return j('/v0/stats');
|
|
112
|
-
|
|
472
|
+
if (name === 'quote_token') return quoteToken(args);
|
|
473
|
+
if (name === 'launch_token') return buildLaunch(args);
|
|
474
|
+
throw new Error('Unknown tool ' + name);
|
|
113
475
|
}
|
|
114
476
|
|
|
477
|
+
// exported for the audit/parity harness; running as a bin starts the stdio server below
|
|
478
|
+
module.exports = { encYoinkLaunch, ycExpectedOut, buildLaunch, quoteToken, call, TOOLS, FACTORY, CURVE_FACTORY, FACTORY_V1, LAUNCH_FEE };
|
|
479
|
+
if (require.main !== module) return;
|
|
480
|
+
|
|
115
481
|
// ---- MCP stdio transport ----
|
|
116
482
|
const send = (m) => process.stdout.write(JSON.stringify(m) + '\n');
|
|
117
483
|
let buf = '';
|
|
@@ -125,7 +491,7 @@ process.stdin.on('data', async (d) => {
|
|
|
125
491
|
const reply = (result) => send({ jsonrpc: '2.0', id: msg.id, result });
|
|
126
492
|
const fail = (message) => send({ jsonrpc: '2.0', id: msg.id, error: { code: -32000, message } });
|
|
127
493
|
try {
|
|
128
|
-
if (msg.method === 'initialize') reply({ protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'yoinkai-mcp', version: '0.
|
|
494
|
+
if (msg.method === 'initialize') reply({ protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'yoinkai-mcp', version: '0.3.0' } });
|
|
129
495
|
else if (msg.method === 'tools/list') reply({ tools: TOOLS });
|
|
130
496
|
else if (msg.method === 'tools/call') {
|
|
131
497
|
const out = await call(msg.params.name, msg.params.arguments);
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yoinkai-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "MCP server over the YOINK launch graph on Robinhood Chain: scan launches, X-ray tokens for safety, quote live prices, read deployer history, and build unsigned bonding-curve token launches you sign in your own wallet. No keys, no custody.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": { "yoinkai-mcp": "index.js" },
|
|
7
|
+
"scripts": { "test": "node test/smoke.js" },
|
|
7
8
|
"files": ["index.js", "README.md", "SKILL.md"],
|
|
8
9
|
"engines": { "node": ">=18" },
|
|
9
10
|
"keywords": ["mcp", "model-context-protocol", "robinhood-chain", "yoink", "token-launch", "agent", "crypto"],
|