@cloakely/sdk 0.3.1 → 0.4.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/cloak.d.ts CHANGED
@@ -117,6 +117,7 @@ export declare class Cloak {
117
117
  creatorAlloc: bigint;
118
118
  vesting: Address;
119
119
  factory: Address;
120
+ index: bigint;
120
121
  legacy?: boolean;
121
122
  }[]>;
122
123
  private listLegacy;
@@ -341,6 +342,28 @@ export declare class Cloak {
341
342
  }];
342
343
  readonly outputs: readonly [];
343
344
  readonly stateMutability: "nonpayable";
345
+ }, {
346
+ readonly type: "function";
347
+ readonly name: "claimPrivate";
348
+ readonly inputs: readonly [{
349
+ readonly name: "id";
350
+ readonly type: "uint256";
351
+ readonly internalType: "uint256";
352
+ }, {
353
+ readonly name: "pubKey";
354
+ readonly type: "uint256";
355
+ readonly internalType: "uint256";
356
+ }, {
357
+ readonly name: "blinding";
358
+ readonly type: "uint256";
359
+ readonly internalType: "uint256";
360
+ }, {
361
+ readonly name: "memo";
362
+ readonly type: "bytes";
363
+ readonly internalType: "bytes";
364
+ }];
365
+ readonly outputs: readonly [];
366
+ readonly stateMutability: "nonpayable";
344
367
  }, {
345
368
  readonly type: "function";
346
369
  readonly name: "claimedCount";
@@ -915,6 +938,16 @@ export declare class Cloak {
915
938
  readonly internalType: "uint256";
916
939
  }];
917
940
  readonly anonymous: false;
941
+ }, {
942
+ readonly type: "event";
943
+ readonly name: "ClaimedPrivately";
944
+ readonly inputs: readonly [{
945
+ readonly name: "id";
946
+ readonly type: "uint256";
947
+ readonly indexed: true;
948
+ readonly internalType: "uint256";
949
+ }];
950
+ readonly anonymous: false;
918
951
  }, {
919
952
  readonly type: "event";
920
953
  readonly name: "Committed";
package/dist/cloak.js CHANGED
@@ -132,7 +132,7 @@ export class Cloak {
132
132
  // a factory from v2 on returns a vesting contract too; the first one returns five fields
133
133
  const six = await this.publicClient.readContract({ address: factory, abi: cloakFactoryAbi, functionName: "launches", args: [i] }).catch(() => null);
134
134
  const [auction, token, creator, launchpad, creatorAlloc] = six ?? (await this.publicClient.readContract({ address: factory, abi: LEGACY_LAUNCHES_ABI, functionName: "launches", args: [i] }));
135
- out.push({ auction, token, creator, launchpad, creatorAlloc, vesting: (six?.[5] ?? zeroAddress), factory, legacy: true });
135
+ out.push({ auction, token, creator, launchpad, creatorAlloc, vesting: (six?.[5] ?? zeroAddress), factory, index: i, legacy: true });
136
136
  }
137
137
  return out;
138
138
  }
@@ -152,7 +152,7 @@ export class Cloak {
152
152
  functionName: "launches",
153
153
  args: [i],
154
154
  });
155
- out.push({ auction, token, creator, launchpad, creatorAlloc, vesting, factory: this.factory });
155
+ out.push({ auction, token, creator, launchpad, creatorAlloc, vesting, factory: this.factory, index: i });
156
156
  }
157
157
  return out;
158
158
  }
@@ -1,20 +1,30 @@
1
- import type { Address } from "viem";
2
- /** An earlier deployment on the same chain: its launches still list, and `migrator` moves its private balances here. */
1
+ import type { Address, PublicClient } from "viem";
2
+ /** An earlier deployment on the same chain: its launches still list, and the migrator moves its private balances here. */
3
3
  export interface PreviousDeployment {
4
4
  version: number;
5
5
  factory: Address;
6
6
  router: Address;
7
7
  routerBlock: bigint;
8
8
  batcher?: Address;
9
- /** Moves this deployment's private balances into the current one (`CloakPrivate.migrateFrom(…, { migrator })`). */
9
+ /** Moved its balances into v3 (v1 and v2 only; from v4 on, the current deployment's `migrator` moves every version). */
10
10
  migrator?: Address;
11
+ /** Standalone vaults outside its router (v2's USDC vault), moved too. */
12
+ pools?: {
13
+ pool: Address;
14
+ fromBlock: bigint;
15
+ }[];
11
16
  }
12
17
  /** Where Cloak lives on each chain, so integrators never copy addresses by hand. */
13
18
  export interface CloakDeployment {
14
19
  chainId: number;
15
- /** Which Cloak deployment this is (3 on Elysium testnet). */
20
+ /** Which Cloak deployment this is (4 on Elysium testnet). */
16
21
  version: number;
22
+ /** CloakConfig: the rules and the registry of every Cloak contract. `liveDeployment` reads the current ones from it. */
17
23
  config: Address;
24
+ /** CloakVaults: the permanent vaults (v4 on). */
25
+ vaults?: Address;
26
+ /** First block of the permanent vaults: scan private notes from here, whichever router is current. */
27
+ vaultsBlock?: bigint;
18
28
  factory: Address;
19
29
  /** Block the factory was deployed in: scan launch events from here. */
20
30
  factoryBlock: bigint;
@@ -22,7 +32,7 @@ export interface CloakDeployment {
22
32
  /** Block the router was deployed in: scan private-pool events from here. */
23
33
  routerBlock: bigint;
24
34
  batcher: Address;
25
- /** The migrator from the newest earlier deployment (each one in `previous` has its own). */
35
+ /** Moves balances from every earlier deployment into the permanent vaults. */
26
36
  migrator?: Address;
27
37
  hypePool: Address;
28
38
  whype: Address;
@@ -44,3 +54,8 @@ export interface CloakDeployment {
44
54
  export declare const deployments: Record<number, CloakDeployment>;
45
55
  /** The Cloak deployment on `chainId`, or an error naming the chains Cloak is on. */
46
56
  export declare function deploymentFor(chainId: number): CloakDeployment;
57
+ /**
58
+ * `base` with the current factory, router, batcher, migrator and vaults read from CloakConfig's registry, so a router or
59
+ * batcher added on-chain is picked up without a new SDK. Earlier deployments keep what `base` knows about them.
60
+ */
61
+ export declare function liveDeployment(publicClient: PublicClient, base: CloakDeployment): Promise<CloakDeployment>;
@@ -1,31 +1,48 @@
1
+ import { privateRouterAbi } from "./abis.js";
2
+ import { readRegistry } from "./registry.js";
1
3
  export const deployments = {
2
- // Cloak v3 (Oct 30): deployments/elysium-testnet-v3.json
4
+ // Cloak v4 (Oct 31), the permanent vaults: deployments/elysium-testnet-v4.json. CloakConfig's registry lists all of it;
5
+ // liveDeployment() reads the current contracts from there.
3
6
  99801: {
4
7
  chainId: 99801,
5
- version: 3,
6
- config: "0x9d30c7b23fa2d5c40779d3db3bb4292e18c94373",
7
- factory: "0xd0f4aacf339f07211a9155194925b2f94e250e29",
8
- factoryBlock: 19096456n,
9
- router: "0x78319bee7c21ca411166acc67c750aca10280e86",
10
- routerBlock: 19096485n,
11
- batcher: "0x9902d86c5be81f6c6ce059acf4c89193d06069c4",
12
- migrator: "0x295f816aa6dac1901bd793120248d8f9cbbd5eec",
13
- hypePool: "0x786886a61Dd77581ef03787c5939fC44B004064d",
8
+ version: 4,
9
+ config: "0x9fdf166f0671166148ea98cdd3ef0118d256167f",
10
+ vaults: "0x24bc1d369aab3a2d741a48f8cac1af5dfd9f3d43",
11
+ vaultsBlock: 19485039n,
12
+ factory: "0xa88df63d7d44acc66052e5cbe5ba9503bf940993",
13
+ factoryBlock: 19484794n,
14
+ router: "0x343ab9a7aac93cee28321410510ea24417cc6386",
15
+ routerBlock: 19485063n,
16
+ batcher: "0xc69e4bca18020ae93bc9f17093985bfb139a3203",
17
+ migrator: "0x86c5d8035224493f10938aaa07b179be316dadf4",
18
+ hypePool: "0x3ACB22829bb422446caaDd8546e65ab530A53031",
14
19
  whype: "0xe44b27113D4b947c0B1bDf4a93878D0e115f4bdD",
15
20
  relayerUrl: "https://cloak-keeper.onrender.com",
16
21
  circuitsUrl: "https://cloak.boblabs.tech/circuits",
17
22
  assets: [
18
23
  // Elysium testnet USDC (bridged from Circle's USDC on HyperEVM): private sends and withdrawals
19
- { symbol: "USDC", token: "0x7D29d8047B905000459c0E80c34a26CeedcB47b2", pool: "0x61de30970e3c53665ce5a89323284e7ab379fa08", decimals: 6, fromBlock: 18329466n },
24
+ { symbol: "USDC", token: "0x7D29d8047B905000459c0E80c34a26CeedcB47b2", pool: "0x3d5cB722490D9908a118f8B3151f83fcE943E4F0", decimals: 6, fromBlock: 19485039n },
20
25
  ],
21
26
  previous: [
27
+ {
28
+ version: 3,
29
+ factory: "0xd0f4aacf339f07211a9155194925b2f94e250e29",
30
+ router: "0x78319bee7c21ca411166acc67c750aca10280e86",
31
+ routerBlock: 19096485n,
32
+ batcher: "0x9902d86c5be81f6c6ce059acf4c89193d06069c4",
33
+ pools: [
34
+ { pool: "0x61de30970e3c53665ce5a89323284e7ab379fa08", fromBlock: 18329466n },
35
+ ],
36
+ },
22
37
  {
23
38
  version: 2,
24
39
  factory: "0xc1fc14e84cbbd10f75dad9d91bf50f47c48757fc",
25
40
  router: "0xd51e731467c49991496329c2b7979cb1aa220ee2",
26
41
  routerBlock: 15856031n,
27
42
  batcher: "0xa13ffd763a8cccfea1b868c616291723579ddb77",
28
- migrator: "0x295f816aa6dac1901bd793120248d8f9cbbd5eec",
43
+ pools: [
44
+ { pool: "0x61de30970e3c53665ce5a89323284e7ab379fa08", fromBlock: 18329466n },
45
+ ],
29
46
  },
30
47
  {
31
48
  version: 1,
@@ -33,7 +50,6 @@ export const deployments = {
33
50
  router: "0xfa2574cfc3afb8f7d3a0651fa2111ff4e3d2fc41",
34
51
  routerBlock: 10973723n,
35
52
  batcher: "0xf6582cc5a84fccf74e571e652fb7b603a2c25812",
36
- migrator: "0x5f3d82a6aa1b20ba9d0f58a98d1c4933c115a0da",
37
53
  },
38
54
  ],
39
55
  },
@@ -45,3 +61,45 @@ export function deploymentFor(chainId) {
45
61
  throw new Error(`Cloak isn't deployed on chain ${chainId} (available: ${Object.keys(deployments).join(", ")})`);
46
62
  return d;
47
63
  }
64
+ /**
65
+ * `base` with the current factory, router, batcher, migrator and vaults read from CloakConfig's registry, so a router or
66
+ * batcher added on-chain is picked up without a new SDK. Earlier deployments keep what `base` knows about them.
67
+ */
68
+ export async function liveDeployment(publicClient, base) {
69
+ const reg = await readRegistry(publicClient, base.config).catch(() => undefined);
70
+ if (!reg)
71
+ return base;
72
+ const [hypePool, whype] = await Promise.all([
73
+ publicClient.readContract({ address: reg.router, abi: privateRouterAbi, functionName: "hypePool" }),
74
+ publicClient.readContract({ address: reg.router, abi: privateRouterAbi, functionName: "whype" }),
75
+ ]);
76
+ const known = new Map((base.previous ?? []).map((p) => [p.version, p]));
77
+ return {
78
+ ...base,
79
+ version: reg.version,
80
+ vaults: reg.vaults ?? base.vaults,
81
+ vaultsBlock: reg.vaultsBlock ?? base.vaultsBlock,
82
+ factory: reg.factory,
83
+ factoryBlock: reg.factoryBlock,
84
+ router: reg.router,
85
+ routerBlock: reg.routerBlock,
86
+ batcher: reg.batcher ?? base.batcher,
87
+ migrator: reg.migrator ?? base.migrator,
88
+ hypePool,
89
+ whype,
90
+ previous: reg.previous.length
91
+ ? reg.previous.map((p) => {
92
+ const k = known.get(p.version);
93
+ return {
94
+ version: p.version,
95
+ factory: p.factory ?? k?.factory ?? ("0x" + "0".repeat(40)),
96
+ router: p.router ?? k?.router ?? ("0x" + "0".repeat(40)),
97
+ routerBlock: p.routerBlock ?? k?.routerBlock ?? 0n,
98
+ batcher: p.batcher ?? k?.batcher,
99
+ migrator: k?.migrator,
100
+ pools: p.pools.length ? p.pools.map((x) => ({ pool: x.addr, fromBlock: x.startBlock })) : k?.pools,
101
+ };
102
+ })
103
+ : base.previous,
104
+ };
105
+ }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./abis.js";
2
2
  export * from "./chains.js";
3
3
  export * from "./deployments.js";
4
+ export * from "./registry.js";
4
5
  export * from "./seal.js";
5
6
  export * from "./cloak.js";
6
7
  export * from "./private/index.js";
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "./abis.js";
2
2
  export * from "./chains.js";
3
3
  export * from "./deployments.js";
4
+ export * from "./registry.js";
4
5
  export * from "./seal.js";
5
6
  export * from "./cloak.js";
6
7
  export * from "./private/index.js";
@@ -70,17 +70,32 @@ export declare class CloakPrivate {
70
70
  })[];
71
71
  constructor(publicClient: PublicClient, router: Address, keys: CloakKeys, prover: () => Promise<Prover>, submit: Submitter, fromBlock?: bigint, batcher?: Address | undefined, migrator?: Address | undefined);
72
72
  /**
73
- * Derive the private keys from one EIP-712 signature bound to this chain and router, so a signature asked for by any
74
- * other site or contract can't reproduce them. With `verify`, the wallet signs twice and the keys are refused if the
75
- * two signatures differ (some smart and MPC wallets sign differently each time, which would hide earlier notes).
73
+ * Derive the wallet's Cloak private account from one EIP-712 signature bound to this chain (never to a contract, so
74
+ * the account survives every upgrade). With `verify`, the wallet signs twice and the keys are refused if the two
75
+ * signatures differ (some smart and MPC wallets sign differently each time, which would hide earlier notes).
76
76
  */
77
77
  static keysFromWallet(wallet: Wallet, opts: {
78
78
  chainId: number;
79
- router: Address;
80
79
  verify?: boolean;
81
80
  }): Promise<CloakKeys>;
81
+ /** Keys of a v2 or v3 account, bound to that deployment's router. Only needed to move an old balance across. */
82
+ static routerKeysFromWallet(wallet: Wallet, opts: {
83
+ chainId: number;
84
+ router: Address;
85
+ }): Promise<CloakKeys>;
86
+ /** The keys an earlier deployment's notes were made for: v1 a plain message, v2 and v3 their router, v4 on the account. */
87
+ static keysForVersion(wallet: Wallet, opts: {
88
+ chainId: number;
89
+ version: number;
90
+ router: Address;
91
+ }): Promise<CloakKeys>;
92
+ private static signedKeys;
82
93
  /** Keys of the first Cloak deployment (plain signed message). Only needed to move an old balance across. */
83
94
  static legacyKeysFromWallet(wallet: Wallet): Promise<CloakKeys>;
95
+ /**
96
+ * Read the router's vaults: HYPE first, then every launch market. A vault without a launch pair (USDC) is an asset,
97
+ * not a market, and is added to `assets` instead.
98
+ */
84
99
  loadMarkets(): Promise<PrivateMarket[]>;
85
100
  get hypeMarket(): PrivateMarket;
86
101
  market(token: Address): PrivateMarket;
@@ -116,6 +131,15 @@ export declare class CloakPrivate {
116
131
  * one step is public and lands on a one-time key. Asks the wallet to approve the pool first if needed.
117
132
  */
118
133
  shieldToken(wallet: Wallet, token: Address, amount: bigint, onStatus?: Status): Promise<Hex>;
134
+ /**
135
+ * Claim a won sealed bid straight into the private vault (v4 launches): the tokens become a note for a one-time key,
136
+ * so later trades can't be linked to the bid. Sent from the bidder's wallet (only the bidder can claim this way); the
137
+ * HYPE refund, if any, still goes to the wallet. The token's vault must exist (the keeper opens it at settlement).
138
+ */
139
+ claimPrivately(wallet: Wallet, auction: Address, bidId: bigint): Promise<{
140
+ txHash: Hex;
141
+ tokens: bigint;
142
+ }>;
119
143
  roundInfo(): Promise<{
120
144
  round: bigint;
121
145
  endsAt: number;
@@ -1,6 +1,6 @@
1
1
  import { encodeAbiParameters, parseEventLogs, zeroAddress } from "viem";
2
- import { cloakMigratorAbi, cloakPairAbi, privateBatcherAbi, privateRouterAbi, shieldedPoolAbi } from "../abis.js";
3
- import { ACCOUNT_MESSAGE, ACCOUNT_TYPED_DATA, CloakKeys, MAIN_KEY, noteCommitment, encryptFullMemo, encryptRouterMemo, formatCloakAddress, nullifierOf, parseCloakAddress, randomField, randomKeyIndex, readMemo, } from "./crypto.js";
2
+ import { cloakAuctionAbi, cloakMigratorAbi, cloakPairAbi, privateBatcherAbi, privateRouterAbi, shieldedPoolAbi } from "../abis.js";
3
+ import { ACCOUNT_MESSAGE, ACCOUNT_TYPED_DATA, ROUTER_ACCOUNT_TYPED_DATA, CloakKeys, MAIN_KEY, noteCommitment, encryptFullMemo, encryptRouterMemo, formatCloakAddress, nullifierOf, parseCloakAddress, randomField, randomKeyIndex, readMemo, } from "./crypto.js";
4
4
  import { MerkleTree } from "./tree.js";
5
5
  import { MOVE_DATA_ABI, ORDER_DATA_ABI, SWAP_DATA_ABI, buildTransaction, selectNotes, splitStandard } from "./tx.js";
6
6
  /** Leaf positions per tree epoch (a pool starts a new tree after 2^20 leaves). */
@@ -11,6 +11,8 @@ const ERC20_ABI = [
11
11
  { type: "function", name: "allowance", stateMutability: "view", inputs: [{ type: "address" }, { type: "address" }], outputs: [{ type: "uint256" }] },
12
12
  { type: "function", name: "approve", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] },
13
13
  { type: "function", name: "balanceOf", stateMutability: "view", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }] },
14
+ { type: "function", name: "symbol", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] },
15
+ { type: "function", name: "decimals", stateMutability: "view", inputs: [], outputs: [{ type: "uint8" }] },
14
16
  ];
15
17
  const json = (v) => JSON.stringify(v, (_, x) => (typeof x === "bigint" ? x.toString() : x));
16
18
  /** POST shielded calls to a Cloak relayer (`services/keeper` serves `/relay` and `/quote`). */
@@ -96,14 +98,30 @@ export class CloakPrivate {
96
98
  this.migrator = migrator;
97
99
  }
98
100
  /**
99
- * Derive the private keys from one EIP-712 signature bound to this chain and router, so a signature asked for by any
100
- * other site or contract can't reproduce them. With `verify`, the wallet signs twice and the keys are refused if the
101
- * two signatures differ (some smart and MPC wallets sign differently each time, which would hide earlier notes).
101
+ * Derive the wallet's Cloak private account from one EIP-712 signature bound to this chain (never to a contract, so
102
+ * the account survives every upgrade). With `verify`, the wallet signs twice and the keys are refused if the two
103
+ * signatures differ (some smart and MPC wallets sign differently each time, which would hide earlier notes).
102
104
  */
103
105
  static async keysFromWallet(wallet, opts) {
104
- const req = { account: wallet.account, ...ACCOUNT_TYPED_DATA(opts.chainId, opts.router) };
106
+ return CloakPrivate.signedKeys(wallet, ACCOUNT_TYPED_DATA(opts.chainId), opts.verify);
107
+ }
108
+ /** Keys of a v2 or v3 account, bound to that deployment's router. Only needed to move an old balance across. */
109
+ static async routerKeysFromWallet(wallet, opts) {
110
+ return CloakPrivate.signedKeys(wallet, ROUTER_ACCOUNT_TYPED_DATA(opts.chainId, opts.router), false);
111
+ }
112
+ /** The keys an earlier deployment's notes were made for: v1 a plain message, v2 and v3 their router, v4 on the account. */
113
+ static async keysForVersion(wallet, opts) {
114
+ if (opts.version <= 1)
115
+ return CloakPrivate.legacyKeysFromWallet(wallet);
116
+ if (opts.version <= 3)
117
+ return CloakPrivate.routerKeysFromWallet(wallet, opts);
118
+ return CloakPrivate.keysFromWallet(wallet, opts);
119
+ }
120
+ static async signedKeys(wallet, typed, verify) {
121
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
+ const req = { account: wallet.account, ...typed };
105
123
  const sig = await wallet.signTypedData(req);
106
- if (opts.verify) {
124
+ if (verify) {
107
125
  const again = await wallet.signTypedData(req);
108
126
  if (again.toLowerCase() !== sig.toLowerCase()) {
109
127
  throw new Error("This wallet gives a different signature each time, so it can't hold a Cloak private account. Use a standard wallet such as MetaMask or Rabby.");
@@ -116,20 +134,42 @@ export class CloakPrivate {
116
134
  const sig = await wallet.signMessage({ account: wallet.account, message: ACCOUNT_MESSAGE });
117
135
  return CloakKeys.fromSignature(sig);
118
136
  }
137
+ /**
138
+ * Read the router's vaults: HYPE first, then every launch market. A vault without a launch pair (USDC) is an asset,
139
+ * not a market, and is added to `assets` instead.
140
+ */
119
141
  async loadMarkets() {
120
142
  const c = { address: this.router, abi: privateRouterAbi };
121
- const n = await this.publicClient.readContract({ ...c, functionName: "tokensCount" });
143
+ const [n, whype] = await Promise.all([
144
+ this.publicClient.readContract({ ...c, functionName: "tokensCount" }),
145
+ this.publicClient.readContract({ ...c, functionName: "whype" }),
146
+ ]);
147
+ const tokens = await Promise.all(Array.from({ length: Number(n) }, (_, i) => this.publicClient.readContract({ ...c, functionName: "tokens", args: [BigInt(i)] })));
148
+ const rows = await Promise.all(tokens.map((token) => Promise.all([
149
+ this.publicClient.readContract({ ...c, functionName: "poolOf", args: [token] }),
150
+ this.publicClient.readContract({ ...c, functionName: "pairOf", args: [token] }),
151
+ ])));
122
152
  const out = [];
123
- for (let i = 0n; i < n; i++) {
124
- const token = await this.publicClient.readContract({ ...c, functionName: "tokens", args: [i] });
125
- const [pool, pair] = await Promise.all([
126
- this.publicClient.readContract({ ...c, functionName: "poolOf", args: [token] }),
127
- this.publicClient.readContract({ ...c, functionName: "pairOf", args: [token] }),
128
- ]);
129
- out.push({ token, pool, pair: i === 0n ? null : pair });
153
+ let hype;
154
+ for (let i = 0; i < tokens.length; i++) {
155
+ const [pool, pair] = rows[i];
156
+ const token = tokens[i];
157
+ if (token.toLowerCase() === whype.toLowerCase())
158
+ hype = { token, pool, pair: null };
159
+ else if (pair !== zeroAddress)
160
+ out.push({ token, pool, pair });
161
+ else if (!this.assets.some((a) => a.pool.toLowerCase() === pool.toLowerCase())) {
162
+ const [symbol, decimals] = await Promise.all([
163
+ this.publicClient.readContract({ address: token, abi: ERC20_ABI, functionName: "symbol" }).catch(() => "?"),
164
+ this.publicClient.readContract({ address: token, abi: ERC20_ABI, functionName: "decimals" }).catch(() => 18),
165
+ ]);
166
+ this.addAsset({ symbol, token, pool, decimals: Number(decimals), fromBlock: this.fromBlock });
167
+ }
130
168
  }
131
- this.markets = out;
132
- return out;
169
+ if (!hype)
170
+ throw new Error("the router has no HYPE vault");
171
+ this.markets = [hype, ...out];
172
+ return this.markets;
133
173
  }
134
174
  get hypeMarket() {
135
175
  const m = this.markets[0];
@@ -343,6 +383,29 @@ export class CloakPrivate {
343
383
  throw new Error("shield reverted");
344
384
  return hash;
345
385
  }
386
+ /**
387
+ * Claim a won sealed bid straight into the private vault (v4 launches): the tokens become a note for a one-time key,
388
+ * so later trades can't be linked to the bid. Sent from the bidder's wallet (only the bidder can claim this way); the
389
+ * HYPE refund, if any, still goes to the wallet. The token's vault must exist (the keeper opens it at settlement).
390
+ */
391
+ async claimPrivately(wallet, auction, bidId) {
392
+ const [tokens] = await this.publicClient.readContract({ address: auction, abi: cloakAuctionAbi, functionName: "allocationOf", args: [bidId] });
393
+ if (tokens === 0n)
394
+ throw new Error("this bid won no tokens");
395
+ const keyIndex = randomKeyIndex();
396
+ const k = await this.keys.key(keyIndex);
397
+ const blinding = randomField();
398
+ const hash = await wallet.writeContract({
399
+ address: auction,
400
+ abi: cloakAuctionAbi,
401
+ functionName: "claimPrivate",
402
+ args: [bidId, k.pubKey, blinding, encryptFullMemo(this.keys.encPub, tokens, blinding, keyIndex)],
403
+ });
404
+ const r = await this.publicClient.waitForTransactionReceipt({ hash });
405
+ if (r.status !== "success")
406
+ throw new Error("private claim reverted");
407
+ return { txHash: hash, tokens };
408
+ }
346
409
  // ───────────── batch auctions (hide trade size) ─────────────
347
410
  async roundInfo() {
348
411
  if (!this.batcher)
@@ -571,11 +634,12 @@ export class CloakPrivate {
571
634
  async migrateFrom(legacy, onStatus = () => { }, opts = {}) {
572
635
  const migrator = opts.migrator ?? this.migratorAddress();
573
636
  const moved = [];
574
- const markets = await legacy.loadMarkets();
637
+ const markets = [...(await legacy.loadMarkets()), ...legacy.assets];
575
638
  await this.loadMarkets();
639
+ const here = [...this.markets, ...this.assets];
576
640
  for (const m of markets) {
577
- if (!this.markets.some((x) => x.token.toLowerCase() === m.token.toLowerCase()))
578
- continue; // no pool here yet
641
+ if (!here.some((x) => x.token.toLowerCase() === m.token.toLowerCase()))
642
+ continue; // no vault here yet
579
643
  for (;;) {
580
644
  const live = (await legacy.sync(m.pool)).sort((a, b) => (a.amount > b.amount ? -1 : 1));
581
645
  if (!live.length)
@@ -7,13 +7,31 @@ export declare const MAIN_KEY = 4294967295;
7
7
  export declare const randomField: () => bigint;
8
8
  export declare const randomKeyIndex: () => number;
9
9
  export declare function poseidon(inputs: bigint[]): Promise<bigint>;
10
- /** The message a wallet signs to derive its Cloak private account (deterministic for standard EOAs). */
11
- export declare const ACCOUNT_MESSAGE = "Cloak private account v1\n\nSigning this derives your private trading keys on Elysium. It costs nothing and does not move funds.";
12
10
  /**
13
- * EIP-712 request whose signature derives the private keys of a Cloak v2 account. It is bound to the chain and the
14
- * router, so a signature collected by another site or for another contract can't reproduce the keys.
11
+ * EIP-712 request whose signature derives a wallet's Cloak private account (v4 on). It is bound to the chain only, never
12
+ * to a contract, so the account and its `cloak:` address stay the same through every upgrade.
15
13
  */
16
- export declare const ACCOUNT_TYPED_DATA: (chainId: number, router: `0x${string}`) => {
14
+ export declare const ACCOUNT_TYPED_DATA: (chainId: number) => {
15
+ readonly domain: {
16
+ readonly name: "Cloak";
17
+ readonly version: "4";
18
+ readonly chainId: number;
19
+ };
20
+ readonly types: {
21
+ readonly PrivateAccount: readonly [{
22
+ readonly name: "purpose";
23
+ readonly type: "string";
24
+ }];
25
+ };
26
+ readonly primaryType: "PrivateAccount";
27
+ readonly message: {
28
+ readonly purpose: "Derive my Cloak private account. This costs nothing and moves no funds. Only sign this on cloak.boblabs.tech.";
29
+ };
30
+ };
31
+ /** The plain message v1 accounts were derived from. Only used to move an old v1 balance. */
32
+ export declare const ACCOUNT_MESSAGE = "Cloak private account v1\n\nSigning this derives your private trading keys on Elysium. It costs nothing and does not move funds.";
33
+ /** The EIP-712 request v2 and v3 accounts were derived from, bound to that deployment's router. Only used to move an old balance. */
34
+ export declare const ROUTER_ACCOUNT_TYPED_DATA: (chainId: number, router: `0x${string}`) => {
17
35
  readonly domain: {
18
36
  readonly name: "Cloak";
19
37
  readonly version: "2";
@@ -18,13 +18,22 @@ export async function poseidon(inputs) {
18
18
  _poseidon ??= await (await import("circomlibjs")).buildPoseidon(); // loaded on first use
19
19
  return _poseidon.F.toObject(_poseidon(inputs.map((x) => BigInt(x))));
20
20
  }
21
- /** The message a wallet signs to derive its Cloak private account (deterministic for standard EOAs). */
22
- export const ACCOUNT_MESSAGE = "Cloak private account v1\n\nSigning this derives your private trading keys on Elysium. It costs nothing and does not move funds.";
23
21
  /**
24
- * EIP-712 request whose signature derives the private keys of a Cloak v2 account. It is bound to the chain and the
25
- * router, so a signature collected by another site or for another contract can't reproduce the keys.
22
+ * EIP-712 request whose signature derives a wallet's Cloak private account (v4 on). It is bound to the chain only, never
23
+ * to a contract, so the account and its `cloak:` address stay the same through every upgrade.
26
24
  */
27
- export const ACCOUNT_TYPED_DATA = (chainId, router) => ({
25
+ export const ACCOUNT_TYPED_DATA = (chainId) => ({
26
+ domain: { name: "Cloak", version: "4", chainId },
27
+ types: { PrivateAccount: [{ name: "purpose", type: "string" }] },
28
+ primaryType: "PrivateAccount",
29
+ message: {
30
+ purpose: "Derive my Cloak private account. This costs nothing and moves no funds. Only sign this on cloak.boblabs.tech.",
31
+ },
32
+ });
33
+ /** The plain message v1 accounts were derived from. Only used to move an old v1 balance. */
34
+ export const ACCOUNT_MESSAGE = "Cloak private account v1\n\nSigning this derives your private trading keys on Elysium. It costs nothing and does not move funds.";
35
+ /** The EIP-712 request v2 and v3 accounts were derived from, bound to that deployment's router. Only used to move an old balance. */
36
+ export const ROUTER_ACCOUNT_TYPED_DATA = (chainId, router) => ({
28
37
  domain: { name: "Cloak", version: "2", chainId, verifyingContract: router },
29
38
  types: { PrivateAccount: [{ name: "purpose", type: "string" }] },
30
39
  primaryType: "PrivateAccount",
@@ -0,0 +1,46 @@
1
+ import { type Address, type PublicClient } from "viem";
2
+ /** One contract in CloakConfig's registry. */
3
+ export interface RegistryEntry {
4
+ kind: string;
5
+ addr: Address;
6
+ version: number;
7
+ startBlock: bigint;
8
+ }
9
+ /** An earlier deployment, rebuilt from the registry: what's needed to list its launches and move its balances. */
10
+ export interface RegistryVersion {
11
+ version: number;
12
+ router?: Address;
13
+ routerBlock?: bigint;
14
+ batcher?: Address;
15
+ batcherBlock?: bigint;
16
+ factory?: Address;
17
+ factoryBlock?: bigint;
18
+ /** Standalone vaults of that deployment, outside its router (v2's USDC vault). */
19
+ pools: {
20
+ addr: Address;
21
+ startBlock: bigint;
22
+ }[];
23
+ }
24
+ /** Everything Cloak has deployed on a chain, read from CloakConfig, so apps only need that one address. */
25
+ export interface CloakRegistry {
26
+ config: Address;
27
+ entries: RegistryEntry[];
28
+ /** Factories whose launches the vaults may list, oldest first. */
29
+ launchFactories: Address[];
30
+ /** The current version (the newest router's). */
31
+ version: number;
32
+ vaults?: Address;
33
+ /** First block of the permanent vaults: scan private notes from here, whichever router is current. */
34
+ vaultsBlock?: bigint;
35
+ router: Address;
36
+ routerBlock: bigint;
37
+ batcher?: Address;
38
+ batcherBlock?: bigint;
39
+ migrator?: Address;
40
+ factory: Address;
41
+ factoryBlock: bigint;
42
+ /** Earlier deployments, newest first. */
43
+ previous: RegistryVersion[];
44
+ }
45
+ /** Read CloakConfig's registry. Throws if it has no router or factory yet. */
46
+ export declare function readRegistry(publicClient: PublicClient, config: Address): Promise<CloakRegistry>;
@@ -0,0 +1,53 @@
1
+ import { hexToString } from "viem";
2
+ import { cloakConfigAbi } from "./abis.js";
3
+ const kindOf = (k) => hexToString(k, { size: 32 }).replace(/\0+$/, "");
4
+ /** Read CloakConfig's registry. Throws if it has no router or factory yet. */
5
+ export async function readRegistry(publicClient, config) {
6
+ const c = { address: config, abi: cloakConfigAbi };
7
+ const [n, nf] = await Promise.all([
8
+ publicClient.readContract({ ...c, functionName: "registryCount" }),
9
+ publicClient.readContract({ ...c, functionName: "launchFactoriesCount" }),
10
+ ]);
11
+ const [rows, launchFactories] = await Promise.all([
12
+ Promise.all(Array.from({ length: Number(n) }, (_, i) => publicClient.readContract({ ...c, functionName: "registry", args: [BigInt(i)] }))),
13
+ Promise.all(Array.from({ length: Number(nf) }, (_, i) => publicClient.readContract({ ...c, functionName: "launchFactories", args: [BigInt(i)] }))),
14
+ ]);
15
+ const entries = rows.map(([kind, addr, version, startBlock]) => ({ kind: kindOf(kind), addr, version: Number(version), startBlock: BigInt(startBlock) }));
16
+ const newest = (kind, version) => [...entries].reverse().find((e) => e.kind === kind && (version === undefined || e.version === version));
17
+ const router = newest("router");
18
+ const factory = newest("factory");
19
+ if (!router || !factory)
20
+ throw new Error(`CloakConfig ${config} has no router or factory registered yet`);
21
+ const batcher = newest("batcher", router.version);
22
+ const versions = [...new Set(entries.map((e) => e.version))].filter((v) => v < router.version).sort((a, b) => b - a);
23
+ return {
24
+ config,
25
+ entries,
26
+ launchFactories,
27
+ version: router.version,
28
+ vaults: newest("vaults")?.addr,
29
+ vaultsBlock: newest("vaults")?.startBlock,
30
+ router: router.addr,
31
+ routerBlock: router.startBlock,
32
+ batcher: batcher?.addr,
33
+ batcherBlock: batcher?.startBlock,
34
+ migrator: newest("migrator", router.version)?.addr,
35
+ factory: factory.addr,
36
+ factoryBlock: factory.startBlock,
37
+ previous: versions.map((v) => {
38
+ const r = newest("router", v);
39
+ const b = newest("batcher", v);
40
+ const f = newest("factory", v);
41
+ return {
42
+ version: v,
43
+ router: r?.addr,
44
+ routerBlock: r?.startBlock,
45
+ batcher: b?.addr,
46
+ batcherBlock: b?.startBlock,
47
+ factory: f?.addr,
48
+ factoryBlock: f?.startBlock,
49
+ pools: entries.filter((e) => e.kind === "pool" && e.version === v).map((e) => ({ addr: e.addr, startBlock: e.startBlock })),
50
+ };
51
+ }),
52
+ };
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloakely/sdk",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Cloak on Elysium: sealed-bid token launches and private trading (shielded balances, private swaps, batch auctions, private sends) with on-device zero-knowledge proofs.",
5
5
  "keywords": [
6
6
  "cloak",