@cloakely/mcp 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.d.ts +1 -1
- package/dist/server.js +73 -20
- package/package.json +2 -2
package/dist/server.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type Prover } from "@cloakely/sdk";
|
|
|
3
3
|
import type { Settings } from "./config.js";
|
|
4
4
|
export { loadSettings, type Settings, type Limits } from "./config.js";
|
|
5
5
|
export { Spending } from "./limits.js";
|
|
6
|
-
export declare const VERSION = "0.
|
|
6
|
+
export declare const VERSION = "0.7.0";
|
|
7
7
|
export declare const HOSTED_URL = "https://cloak.boblabs.tech/mcp";
|
|
8
8
|
/** Turn an SDK or chain error into one plain sentence an agent can act on. */
|
|
9
9
|
export declare function explain(e: unknown): string;
|
package/dist/server.js
CHANGED
|
@@ -4,11 +4,11 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { createPublicClient, createWalletClient, erc20Abi, formatEther, formatUnits, http, isAddress, parseEther, parseUnits, zeroAddress, } from "viem";
|
|
6
6
|
import { privateKeyToAccount } from "viem/accounts";
|
|
7
|
-
import { Cloak, CloakPrivate, cloakAuctionAbi, cloakPairAbi, cloakVaultsAbi, legacyFactories, createProver, loadProver, privateRouterAbi, relayerSubmitter, shieldedPoolAbi, } from "@cloakely/sdk";
|
|
7
|
+
import { Cloak, CloakPrivate, cloakAuctionAbi, cloakPairAbi, cloakVaultsAbi, legacyFactories, createProver, loadProver, createSharedProver, loadSharedProver, privateRouterAbi, relayerSubmitter, sharedRelayerSubmitter, sharedVaultAbi, SharedVaultClient, shieldedPoolAbi, } from "@cloakely/sdk";
|
|
8
8
|
import { Spending } from "./limits.js";
|
|
9
9
|
export { loadSettings } from "./config.js";
|
|
10
10
|
export { Spending } from "./limits.js";
|
|
11
|
-
export const VERSION = "0.
|
|
11
|
+
export const VERSION = "0.7.0";
|
|
12
12
|
export const HOSTED_URL = "https://cloak.boblabs.tech/mcp";
|
|
13
13
|
const INSTRUCTIONS = `Cloak: sealed-bid token launches and private trading on Kinetiq's Elysium (HYPE is the gas and base currency).
|
|
14
14
|
|
|
@@ -94,6 +94,26 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
94
94
|
(() => (proverP ??= settings.circuitsDir
|
|
95
95
|
? Promise.resolve(createProver(readFileSync(join(settings.circuitsDir, "transaction2x2.wasm")), readFileSync(join(settings.circuitsDir, "transaction2x2_final.zkey"))))
|
|
96
96
|
: loadProver(d.circuitsUrl)));
|
|
97
|
+
let sharedProverP;
|
|
98
|
+
const sharedProver = () => (sharedProverP ??= settings.circuitsDir
|
|
99
|
+
? Promise.resolve(createSharedProver(readFileSync(join(settings.circuitsDir, "transactionMA2x2.wasm")), readFileSync(join(settings.circuitsDir, "transactionMA2x2_final.zkey"))))
|
|
100
|
+
: loadSharedProver(d.circuitsUrl));
|
|
101
|
+
let sharedC;
|
|
102
|
+
/** The shared launch-token vault (new Uniswap launches keep their private balances there). */
|
|
103
|
+
async function shared() {
|
|
104
|
+
if (!d.shared || !d.sharedRouter)
|
|
105
|
+
throw new Error("This deployment has no shared launch-token vault.");
|
|
106
|
+
const p = await priv();
|
|
107
|
+
if (!sharedC || sharedC.priv !== p)
|
|
108
|
+
sharedC = new SharedVaultClient(p, d.shared, d.sharedRouter, sharedProver, sharedRelayerSubmitter(d.relayerUrl), d.sharedBlock ?? 0n);
|
|
109
|
+
return sharedC;
|
|
110
|
+
}
|
|
111
|
+
/** Private balance of one asset, wherever its vault is. */
|
|
112
|
+
async function balanceOf(a) {
|
|
113
|
+
if (a.kind === "token" && a.shared)
|
|
114
|
+
return (await shared()).balance(a.token);
|
|
115
|
+
return (await priv()).balance(a.pool);
|
|
116
|
+
}
|
|
97
117
|
let privP;
|
|
98
118
|
async function priv() {
|
|
99
119
|
if (!wallet)
|
|
@@ -157,6 +177,18 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
157
177
|
else
|
|
158
178
|
out.push({ kind: "token", symbol, token, pool, pair, decimals: 18 });
|
|
159
179
|
}
|
|
180
|
+
// tokens in the shared launch-token vault (every Uniswap launch from Nov 2 on)
|
|
181
|
+
if (d.shared) {
|
|
182
|
+
const v = { address: d.shared, abi: sharedVaultAbi };
|
|
183
|
+
const m = Number(await pub.readContract({ ...v, functionName: "tokensCount" }));
|
|
184
|
+
for (let i = 0; i < m; i++) {
|
|
185
|
+
const token = await pub.readContract({ ...v, functionName: "tokens", args: [BigInt(i)] });
|
|
186
|
+
if (out.some((a) => a.token.toLowerCase() === token.toLowerCase()))
|
|
187
|
+
continue;
|
|
188
|
+
const [pair, symbol] = await Promise.all([pub.readContract({ ...v, functionName: "marketOf", args: [token] }), symbolOf(token)]);
|
|
189
|
+
out.push({ kind: "token", symbol, token, pool: d.shared, pair, decimals: 18, shared: true });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
160
192
|
return out;
|
|
161
193
|
}
|
|
162
194
|
/** "HYPE", "USDC", a launch token's symbol, or any of their addresses. */
|
|
@@ -185,7 +217,9 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
185
217
|
pub.readContract({ address: t.pair, abi: cloakPairAbi, functionName: "reserveToken" }),
|
|
186
218
|
]);
|
|
187
219
|
const out = await pub.readContract({ address: t.pair, abi: cloakPairAbi, functionName: "getAmountOut", args: side === "buy" ? [net, rH, rT] : [net, rT, rH] });
|
|
188
|
-
const fee =
|
|
220
|
+
const fee = t.shared
|
|
221
|
+
? await (side === "buy" ? sharedRelayerSubmitter(d.relayerUrl).quote("sharedBuy", t.token) : sharedRelayerSubmitter(d.relayerUrl).quote("sharedSell", t.token)).then((q) => q.fee, () => 0n)
|
|
222
|
+
: await submit.quote("privateSwap", poolIn).then((q) => q.fee, () => 0n);
|
|
189
223
|
return { out, net, fee };
|
|
190
224
|
}
|
|
191
225
|
/** What an amount of an asset counts against: HYPE value (launch tokens at the pool's price), or USDC. */
|
|
@@ -227,7 +261,7 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
227
261
|
...(s.settled
|
|
228
262
|
? { clearingPrice: `${formatEther(s.clearingPrice)} HYPE per token`, sold: formatEther(s.sold), raised: hype(s.raised), pool: s.pair }
|
|
229
263
|
: {}),
|
|
230
|
-
liquidity: `${s.lpBps / 100}% of the raise seeds the pool at the clearing price; LP ${s.lpLockUntil === 0 ? "burned" : `locked until ${iso(s.lpLockUntil)}`}`,
|
|
264
|
+
liquidity: `${s.lpBps / 100}% of the raise seeds the pool at the clearing price; LP ${s.lpLockUntil === 0 ? "burned" : s.creatorFees || s.lpLockUntil >= 2 ** 63 ? "locked forever, trading fees to the creator" : `locked until ${iso(s.lpLockUntil)}`}`,
|
|
231
265
|
noRevealPenalty: `${s.noRevealPenaltyBps / 100}%`,
|
|
232
266
|
launchFee: `${s.launchFeeBps / 100}% of a successful raise`,
|
|
233
267
|
caps: { perBid: s.bidCap ? hype(s.bidCap) : "none", total: s.depositCap ? hype(s.depositCap) : "none" },
|
|
@@ -386,7 +420,7 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
386
420
|
ifCreatedNow: { biddingEnds: iso(commitEnd), revealsEnd: iso(revealEnd) },
|
|
387
421
|
priceRange: `${formatEther(min)} to ${formatEther(top)} HYPE per token, ${ticks} ticks`,
|
|
388
422
|
raiseIfFullySold: { atLowestPrice: hype(raise(min)), atHighestPrice: hype(raise(top)) },
|
|
389
|
-
toPool: `${lp}% of the raise, paired with tokens at the clearing price; the LP is
|
|
423
|
+
toPool: `${lp}% of the raise, paired with tokens at the clearing price in a Uniswap V2 pool; the LP is locked forever and its 0.3% trading fees go to you (or burned, or locked for a set time)`,
|
|
390
424
|
launchFee: "1.5% of a successful raise; nothing if the launch doesn't fill",
|
|
391
425
|
caps: "During the beta a bid can be at most 100 HYPE and an auction can take at most 10,000 HYPE.",
|
|
392
426
|
create: { onTheSite: "https://cloak.boblabs.tech/app/create", withAnAgent: "the local server's create_launch tool (see connect_full_agent)" },
|
|
@@ -420,7 +454,8 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
420
454
|
reveal_minutes: z.number().min(1).max(60 * 24).optional().describe("Time for bids to open after bidding closes (default 10)"),
|
|
421
455
|
lp_percent: z.number().min(0).max(100).optional().describe("Share of the raise that seeds the pool (default 50)"),
|
|
422
456
|
lp_tokens: amt("Tokens paired with that HYPE (default: same as sale_supply)").optional(),
|
|
423
|
-
lp_lock_days: z.number().min(0).optional().describe("Lock the LP for this many days
|
|
457
|
+
lp_lock_days: z.number().min(0).optional().describe("Lock the LP for this many days, after which the creator can take it (overrides creator_fees)"),
|
|
458
|
+
creator_fees: z.boolean().optional().describe("Lock the LP forever and pay the pool's 0.3% trading fees to the creator (default true; false burns the LP)"),
|
|
424
459
|
creator_share: amt("Tokens for the creator, at most 20% of supply, vesting linearly").optional(),
|
|
425
460
|
vest_days: z.number().min(30).optional().describe("Vesting length for the creator share (default 90)"),
|
|
426
461
|
metadata_uri: z.string().optional(),
|
|
@@ -445,7 +480,7 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
445
480
|
commitEnd,
|
|
446
481
|
revealEnd,
|
|
447
482
|
lpBps: Math.round((p.lp_percent ?? 50) * 100),
|
|
448
|
-
lpLockUntil: p.lp_lock_days ?
|
|
483
|
+
lpLockUntil: p.lp_lock_days ? revealEnd + Math.round(p.lp_lock_days * 86400) : p.creator_fees === false ? "burn" : "fees",
|
|
449
484
|
metadataURI: p.metadata_uri,
|
|
450
485
|
};
|
|
451
486
|
spending.check("launches", 1n);
|
|
@@ -574,7 +609,7 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
574
609
|
const out = {};
|
|
575
610
|
const tokens = {};
|
|
576
611
|
for (const a of await vaults()) {
|
|
577
|
-
const b = await
|
|
612
|
+
const b = await balanceOf(a);
|
|
578
613
|
if (b === 0n && !include_zero && a.kind === "token")
|
|
579
614
|
continue;
|
|
580
615
|
if (a.kind === "token")
|
|
@@ -586,13 +621,13 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
586
621
|
}));
|
|
587
622
|
server.registerTool("shield", {
|
|
588
623
|
title: "Shield into a private balance",
|
|
589
|
-
description: "Deposit HYPE or
|
|
590
|
-
inputSchema: { asset: z.string().describe('"HYPE"
|
|
624
|
+
description: "Deposit HYPE, USDC or a new launch token (one in the shared launch-token vault) from the wallet into the private balance. This is the one public step: it lands on a one-time key, so it can't be linked to what comes next.",
|
|
625
|
+
inputSchema: { asset: z.string().describe('"HYPE", "USDC" or a launch token in the shared vault'), amount: amt("Amount"), dry_run: dry },
|
|
591
626
|
annotations: WRITE,
|
|
592
627
|
}, safe(async ({ asset, amount, dry_run }) => {
|
|
593
628
|
const { wallet, spending } = needWallet();
|
|
594
629
|
const a = await resolve(asset);
|
|
595
|
-
if (a.kind === "token")
|
|
630
|
+
if (a.kind === "token" && !a.shared)
|
|
596
631
|
throw new Error("Launch tokens enter a private balance by buying privately (private_swap), not by shielding.");
|
|
597
632
|
const x = amountOf(amount, a.decimals);
|
|
598
633
|
if (x === null)
|
|
@@ -603,9 +638,9 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
603
638
|
return { dryRun: true, wouldShield: fmtAmt(a, x), ...(a.kind === "asset" ? { note: "The first time takes two transactions: an approval, then the deposit." } : {}) };
|
|
604
639
|
return exclusive(async () => {
|
|
605
640
|
const p = await priv();
|
|
606
|
-
const h = a.kind === "hype" ? await p.shieldHype(wallet, x) : await p.shieldToken(wallet, a.token, x);
|
|
641
|
+
const h = a.kind === "hype" ? await p.shieldHype(wallet, x) : a.kind === "token" ? await (await shared()).shield(wallet, a.token, x) : await p.shieldToken(wallet, a.token, x);
|
|
607
642
|
spending.record(c.budget, c.value);
|
|
608
|
-
return { shielded: fmtAmt(a, x), privateBalance: fmtAmt(a, await
|
|
643
|
+
return { shielded: fmtAmt(a, x), privateBalance: fmtAmt(a, await balanceOf(a)), tx: link(h) };
|
|
609
644
|
});
|
|
610
645
|
}));
|
|
611
646
|
server.registerTool("private_swap", {
|
|
@@ -620,7 +655,7 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
620
655
|
throw new Error(`${t.symbol} isn't a launch token: private swaps trade launch tokens against HYPE.`);
|
|
621
656
|
const p = await priv();
|
|
622
657
|
const poolIn = side === "buy" ? d.hypePool : t.pool;
|
|
623
|
-
const x = amountOf(amount, 18) ?? (await p.maxSpendable(poolIn, "privateSwap"));
|
|
658
|
+
const x = amountOf(amount, 18) ?? (t.shared && side === "sell" ? await (await shared()).maxWithdraw(t.token, "sharedSell") : await p.maxSpendable(d.hypePool === poolIn ? d.hypePool : poolIn, "privateSwap"));
|
|
624
659
|
if (x <= 0n)
|
|
625
660
|
throw new Error("Not enough private balance for this, counting the relayer's fee.");
|
|
626
661
|
const c = await cost(side === "buy" ? (await vaults())[0] : t, x);
|
|
@@ -631,6 +666,14 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
631
666
|
if (isDry(dry_run))
|
|
632
667
|
return { dryRun: true, wouldSwap: `${formatEther(x)} ${inSym} for about ${formatEther(q.out)} ${outSym}`, relayerFee: `${formatEther(q.fee)} ${inSym}`, slippage: `${(slippage_bps ?? 100) / 100}%` };
|
|
633
668
|
return exclusive(async () => {
|
|
669
|
+
if (t.shared) {
|
|
670
|
+
const sh = await shared();
|
|
671
|
+
const h = side === "buy" ? await sh.buy(t.token, x, { slippageBps: slippage_bps ?? 100 }) : await sh.sell(t.token, x, { slippageBps: slippage_bps ?? 100 });
|
|
672
|
+
await receipt(h);
|
|
673
|
+
spending.record(c.budget, c.value);
|
|
674
|
+
const hypeA = (await vaults())[0];
|
|
675
|
+
return { swapped: `${formatEther(x)} ${inSym}`, expected: `${formatEther(q.out)} ${outSym}`, relayerFee: `${formatEther(q.fee)} HYPE or ${t.symbol}`, privateBalance: { HYPE: formatEther(await balanceOf(hypeA)), [t.symbol]: formatEther(await balanceOf(t)) }, tx: link(h), sentBy: "the relayer, not this wallet" };
|
|
676
|
+
}
|
|
634
677
|
const r = await (side === "buy" ? p.buy(t.token, x, { slippageBps: slippage_bps }) : p.sell(t.token, x, { slippageBps: slippage_bps }));
|
|
635
678
|
await receipt(r.txHash);
|
|
636
679
|
spending.record(c.budget, c.value);
|
|
@@ -648,6 +691,8 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
648
691
|
const t = await resolve(token);
|
|
649
692
|
if (t.kind !== "token")
|
|
650
693
|
throw new Error(`${t.symbol} isn't a launch token: batch orders trade launch tokens against HYPE.`);
|
|
694
|
+
if (t.shared)
|
|
695
|
+
throw new Error(`${t.symbol} is in the shared launch-token vault, which has no batch rounds yet. Use private_swap.`);
|
|
651
696
|
const x = amountOf(amount, 18);
|
|
652
697
|
if (x === null)
|
|
653
698
|
throw new Error('Give an exact amount for a batch order ("max" works for private_swap).');
|
|
@@ -671,7 +716,7 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
671
716
|
}));
|
|
672
717
|
server.registerTool("private_send", {
|
|
673
718
|
title: "Send privately",
|
|
674
|
-
description: "Pay someone's cloak: address from the private balance. The chain sees that a private transaction happened, but not who sent it, who got it or how much.",
|
|
719
|
+
description: "Pay someone's cloak: address from the private balance. The chain sees that a private transaction happened, but not who sent it, who got it or how much. For tokens in the shared launch-token vault it can't even tell which token (the relayer is paid from private HYPE, so keep a little private HYPE).",
|
|
675
720
|
inputSchema: { asset: z.string().describe('"HYPE", "USDC" or a launch token'), to: z.string().describe("Recipient cloak: address"), amount: amt('Amount, or "max"'), dry_run: dry },
|
|
676
721
|
annotations: WRITE,
|
|
677
722
|
}, safe(async ({ asset, to, amount, dry_run }) => {
|
|
@@ -680,14 +725,21 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
680
725
|
throw new Error("The recipient must be a cloak: address (the receiver gets it from unlock or the Portfolio page).");
|
|
681
726
|
const a = await resolve(asset);
|
|
682
727
|
const p = await priv();
|
|
683
|
-
const
|
|
728
|
+
const sharedTok = a.kind === "token" && a.shared;
|
|
729
|
+
const x = amountOf(amount, a.decimals) ?? (sharedTok ? await balanceOf(a) : await p.maxSpendable(a.pool, "transact"));
|
|
684
730
|
if (x <= 0n)
|
|
685
731
|
throw new Error("Not enough private balance for this, counting the relayer's fee.");
|
|
686
732
|
const c = await cost(a, x);
|
|
687
733
|
spending.check(c.budget, c.value);
|
|
688
734
|
if (isDry(dry_run))
|
|
689
|
-
return { dryRun: true, wouldSend: fmtAmt(a, x), to };
|
|
735
|
+
return { dryRun: true, wouldSend: fmtAmt(a, x), to, ...(sharedTok ? { relayerFee: "paid from your private HYPE, so the send doesn't show which token moved" } : {}) };
|
|
690
736
|
return exclusive(async () => {
|
|
737
|
+
if (sharedTok) {
|
|
738
|
+
const h = await (await shared()).send(a.token, x, to);
|
|
739
|
+
await receipt(h);
|
|
740
|
+
spending.record(c.budget, c.value);
|
|
741
|
+
return { sent: fmtAmt(a, x), to, privateBalance: fmtAmt(a, await balanceOf(a)), tx: link(h), visibleOnChain: "a private transaction happened; no sender, recipient, amount, or even which token" };
|
|
742
|
+
}
|
|
691
743
|
const h = await p.send(a.kind === "hype" ? p.hypeMarket.token : a.token, to, x);
|
|
692
744
|
await receipt(h);
|
|
693
745
|
spending.record(c.budget, c.value);
|
|
@@ -706,7 +758,8 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
706
758
|
const a = await resolve(asset);
|
|
707
759
|
const p = await priv();
|
|
708
760
|
const kind = a.kind === "hype" ? "unshieldHype" : "transact";
|
|
709
|
-
const
|
|
761
|
+
const sharedTok = a.kind === "token" && a.shared;
|
|
762
|
+
const x = amountOf(amount, a.decimals) ?? (sharedTok ? await (await shared()).maxWithdraw(a.token) : await p.maxSpendable(a.pool, kind));
|
|
710
763
|
if (x <= 0n)
|
|
711
764
|
throw new Error("Not enough private balance for this, counting the relayer's fee.");
|
|
712
765
|
const c = await cost(a, x);
|
|
@@ -715,10 +768,10 @@ export function createCloakServer(settings, opts = {}) {
|
|
|
715
768
|
if (isDry(dry_run))
|
|
716
769
|
return { dryRun: true, wouldWithdraw: fmtAmt(a, x), arrives: fmtAmt(a, arrives), to };
|
|
717
770
|
return exclusive(async () => {
|
|
718
|
-
const h = a.kind === "hype" ? await p.unshieldHype(to, x) : await p.unshieldToken(a.token, to, x);
|
|
771
|
+
const h = a.kind === "hype" ? await p.unshieldHype(to, x) : sharedTok ? await (await shared()).unshield(a.token, x, to) : await p.unshieldToken(a.token, to, x);
|
|
719
772
|
await receipt(h);
|
|
720
773
|
spending.record(c.budget, c.value);
|
|
721
|
-
return { withdrew: fmtAmt(a, x), arrived: fmtAmt(a, arrives), to, privateBalance: fmtAmt(a, await
|
|
774
|
+
return { withdrew: fmtAmt(a, x), arrived: fmtAmt(a, arrives), to, privateBalance: fmtAmt(a, await balanceOf(a)), tx: link(h) };
|
|
722
775
|
});
|
|
723
776
|
}));
|
|
724
777
|
return server;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloakely/mcp",
|
|
3
3
|
"mcpName": "io.github.NECOKIZZ/cloak",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"description": "Cloak's MCP server: let an AI agent launch tokens with sealed bids and trade them privately on Elysium, with spending limits, testnet by default and a local signer.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mcp",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
48
48
|
"viem": "2.21.54",
|
|
49
49
|
"zod": "4.6.7",
|
|
50
|
-
"@cloakely/sdk": "^0.
|
|
50
|
+
"@cloakely/sdk": "^0.7.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/node": "22.10.2",
|