@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 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.5.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.5.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 = await submit.quote("privateSwap", poolIn).then((q) => q.fee, () => 0n);
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 burned unless you lock it`,
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 instead of burning it (default: burn)"),
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 ? now + Math.round(p.lp_lock_days * 86400) : "burn",
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 p.balance(a.pool);
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 USDC 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.",
590
- inputSchema: { asset: z.string().describe('"HYPE" or "USDC"'), amount: amt("Amount"), dry_run: dry },
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 p.balance(a.pool)), tx: link(h) };
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 x = amountOf(amount, a.decimals) ?? (await p.maxSpendable(a.pool, "transact"));
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 x = amountOf(amount, a.decimals) ?? (await p.maxSpendable(a.pool, kind));
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 p.balance(a.pool)), tx: link(h) };
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.5.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.5.0"
50
+ "@cloakely/sdk": "^0.7.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "22.10.2",