@cloakely/mcp 0.6.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.6.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.6.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. */
@@ -575,7 +609,7 @@ export function createCloakServer(settings, opts = {}) {
575
609
  const out = {};
576
610
  const tokens = {};
577
611
  for (const a of await vaults()) {
578
- const b = await p.balance(a.pool);
612
+ const b = await balanceOf(a);
579
613
  if (b === 0n && !include_zero && a.kind === "token")
580
614
  continue;
581
615
  if (a.kind === "token")
@@ -587,13 +621,13 @@ export function createCloakServer(settings, opts = {}) {
587
621
  }));
588
622
  server.registerTool("shield", {
589
623
  title: "Shield into a private balance",
590
- 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.",
591
- 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 },
592
626
  annotations: WRITE,
593
627
  }, safe(async ({ asset, amount, dry_run }) => {
594
628
  const { wallet, spending } = needWallet();
595
629
  const a = await resolve(asset);
596
- if (a.kind === "token")
630
+ if (a.kind === "token" && !a.shared)
597
631
  throw new Error("Launch tokens enter a private balance by buying privately (private_swap), not by shielding.");
598
632
  const x = amountOf(amount, a.decimals);
599
633
  if (x === null)
@@ -604,9 +638,9 @@ export function createCloakServer(settings, opts = {}) {
604
638
  return { dryRun: true, wouldShield: fmtAmt(a, x), ...(a.kind === "asset" ? { note: "The first time takes two transactions: an approval, then the deposit." } : {}) };
605
639
  return exclusive(async () => {
606
640
  const p = await priv();
607
- 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);
608
642
  spending.record(c.budget, c.value);
609
- 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) };
610
644
  });
611
645
  }));
612
646
  server.registerTool("private_swap", {
@@ -621,7 +655,7 @@ export function createCloakServer(settings, opts = {}) {
621
655
  throw new Error(`${t.symbol} isn't a launch token: private swaps trade launch tokens against HYPE.`);
622
656
  const p = await priv();
623
657
  const poolIn = side === "buy" ? d.hypePool : t.pool;
624
- 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"));
625
659
  if (x <= 0n)
626
660
  throw new Error("Not enough private balance for this, counting the relayer's fee.");
627
661
  const c = await cost(side === "buy" ? (await vaults())[0] : t, x);
@@ -632,6 +666,14 @@ export function createCloakServer(settings, opts = {}) {
632
666
  if (isDry(dry_run))
633
667
  return { dryRun: true, wouldSwap: `${formatEther(x)} ${inSym} for about ${formatEther(q.out)} ${outSym}`, relayerFee: `${formatEther(q.fee)} ${inSym}`, slippage: `${(slippage_bps ?? 100) / 100}%` };
634
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
+ }
635
677
  const r = await (side === "buy" ? p.buy(t.token, x, { slippageBps: slippage_bps }) : p.sell(t.token, x, { slippageBps: slippage_bps }));
636
678
  await receipt(r.txHash);
637
679
  spending.record(c.budget, c.value);
@@ -649,6 +691,8 @@ export function createCloakServer(settings, opts = {}) {
649
691
  const t = await resolve(token);
650
692
  if (t.kind !== "token")
651
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.`);
652
696
  const x = amountOf(amount, 18);
653
697
  if (x === null)
654
698
  throw new Error('Give an exact amount for a batch order ("max" works for private_swap).');
@@ -672,7 +716,7 @@ export function createCloakServer(settings, opts = {}) {
672
716
  }));
673
717
  server.registerTool("private_send", {
674
718
  title: "Send privately",
675
- 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).",
676
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 },
677
721
  annotations: WRITE,
678
722
  }, safe(async ({ asset, to, amount, dry_run }) => {
@@ -681,14 +725,21 @@ export function createCloakServer(settings, opts = {}) {
681
725
  throw new Error("The recipient must be a cloak: address (the receiver gets it from unlock or the Portfolio page).");
682
726
  const a = await resolve(asset);
683
727
  const p = await priv();
684
- 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"));
685
730
  if (x <= 0n)
686
731
  throw new Error("Not enough private balance for this, counting the relayer's fee.");
687
732
  const c = await cost(a, x);
688
733
  spending.check(c.budget, c.value);
689
734
  if (isDry(dry_run))
690
- 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" } : {}) };
691
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
+ }
692
743
  const h = await p.send(a.kind === "hype" ? p.hypeMarket.token : a.token, to, x);
693
744
  await receipt(h);
694
745
  spending.record(c.budget, c.value);
@@ -707,7 +758,8 @@ export function createCloakServer(settings, opts = {}) {
707
758
  const a = await resolve(asset);
708
759
  const p = await priv();
709
760
  const kind = a.kind === "hype" ? "unshieldHype" : "transact";
710
- 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));
711
763
  if (x <= 0n)
712
764
  throw new Error("Not enough private balance for this, counting the relayer's fee.");
713
765
  const c = await cost(a, x);
@@ -716,10 +768,10 @@ export function createCloakServer(settings, opts = {}) {
716
768
  if (isDry(dry_run))
717
769
  return { dryRun: true, wouldWithdraw: fmtAmt(a, x), arrives: fmtAmt(a, arrives), to };
718
770
  return exclusive(async () => {
719
- 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);
720
772
  await receipt(h);
721
773
  spending.record(c.budget, c.value);
722
- 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) };
723
775
  });
724
776
  }));
725
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.6.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.6.0"
50
+ "@cloakely/sdk": "^0.7.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "22.10.2",