@cloakely/sdk 0.2.2 → 0.2.5

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 CHANGED
@@ -72,6 +72,15 @@ await priv.unshieldHype(freshAddress, parseEther("0.5")); // back to any wallet
72
72
  console.log(await priv.balance(priv.hypeMarket.pool)); // only this account can see it
73
73
  ```
74
74
 
75
+ Private USDC lives in its own vault. Register it from the deployment, then shield, send and withdraw like any token:
76
+
77
+ ```ts
78
+ const usdc = priv.addAsset(d.assets![0]); // { symbol: "USDC", decimals: 6, … }
79
+ await priv.shieldToken(walletClient, usdc.token, parseUnits("50", 6)); // approve + deposit (public, once)
80
+ await priv.send(usdc.token, "cloak:…", parseUnits("20", 6)); // amount hidden
81
+ await priv.unshieldToken(usdc.token, freshAddress, parseUnits("10", 6)); // 0.25% fee kept
82
+ ```
83
+
75
84
  The private account comes from one EIP-712 signature bound to the chain and the router. The same wallet always restores it, and nothing is stored on a server. In Node, read the circuit files yourself and pass them to `createProver(wasm, zkey)`.
76
85
 
77
86
  ## Fees (testnet beta)
@@ -90,7 +99,7 @@ Rules live in the on-chain `CloakConfig`. Raising a fee or a cap waits a public
90
99
  | Export | What it is |
91
100
  |---|---|
92
101
  | `Cloak` | Launches: `createLaunch`, `bid`, `revealAll`, `settle`, `claimAll`, `payOut`, `getAuction`, `listLaunches` |
93
- | `CloakPrivate` | Private balances: `shieldHype`, `buy`, `sell`, `batch`, `send`, `unshieldHype`, `unshieldToken`, `balance`, `maxSpendable`, `migrateFrom` |
102
+ | `CloakPrivate` | Private balances: `shieldHype`, `shieldToken`, `buy`, `sell`, `batch`, `send`, `unshieldHype`, `unshieldToken`, `addAsset`, `balance`, `maxSpendable`, `migrateFrom` |
94
103
  | `relayerSubmitter`, `walletSubmitter` | How private calls are submitted: through a relayer (private), or from a wallet (for tests) |
95
104
  | `loadProver`, `createProver` | The Groth16 prover, running on the device |
96
105
  | `deployments`, `deploymentFor` | Addresses, start blocks, relayer and circuit URLs per chain |
@@ -15,6 +15,14 @@ export interface CloakDeployment {
15
15
  relayerUrl: string;
16
16
  /** Hosted circuit artifacts (transaction2x2.wasm and transaction2x2_final.zkey). */
17
17
  circuitsUrl: string;
18
+ /** Shielded pools for existing tokens (USDC): register them with `CloakPrivate.addAsset`. */
19
+ assets?: {
20
+ symbol: string;
21
+ token: Address;
22
+ pool: Address;
23
+ decimals: number;
24
+ fromBlock: bigint;
25
+ }[];
18
26
  /** The first deployment: its launches still list, and its private balances move here through `migrator`. */
19
27
  legacy?: {
20
28
  factory: Address;
@@ -11,6 +11,10 @@ export const deployments = {
11
11
  whype: "0xe44b27113D4b947c0B1bDf4a93878D0e115f4bdD",
12
12
  relayerUrl: "https://cloak-keeper.onrender.com",
13
13
  circuitsUrl: "https://cloak.boblabs.tech/circuits",
14
+ assets: [
15
+ // Elysium testnet USDC (bridged from Circle's USDC on HyperEVM): private sends and withdrawals
16
+ { symbol: "USDC", token: "0x7D29d8047B905000459c0E80c34a26CeedcB47b2", pool: "0x61de30970e3c53665ce5a89323284e7ab379fa08", decimals: 6, fromBlock: 18329466n },
17
+ ],
14
18
  legacy: {
15
19
  factory: "0x1dc1ea9837ecbd31ec74f0d558e1db1c303a7e1c",
16
20
  router: "0xfa2574cfc3afb8f7d3a0651fa2111ff4e3d2fc41",
@@ -26,6 +26,17 @@ export interface PrivateMarket {
26
26
  pool: Address;
27
27
  pair: Address | null;
28
28
  symbol?: string;
29
+ /** Token decimals (18 for HYPE and launch tokens; 6 for USDC). */
30
+ decimals?: number;
31
+ }
32
+ /** A shielded pool for an existing token (such as USDC) that lives next to the router, not in its launch registry. */
33
+ export interface PrivateAsset {
34
+ symbol: string;
35
+ token: Address;
36
+ pool: Address;
37
+ decimals: number;
38
+ /** Block the pool was deployed in: its events are scanned from here. */
39
+ fromBlock: bigint;
29
40
  }
30
41
  /** POST shielded calls to a Cloak relayer (`services/keeper` serves `/relay` and `/quote`). */
31
42
  export declare function relayerSubmitter(url: string): Submitter;
@@ -51,6 +62,10 @@ export declare class CloakPrivate {
51
62
  readonly migrator?: Address | undefined;
52
63
  private pools;
53
64
  markets: PrivateMarket[];
65
+ /** Shielded pools outside the launch registry (USDC). Send, unshieldToken and shieldToken work on these too. */
66
+ assets: (PrivateMarket & {
67
+ fromBlock: bigint;
68
+ })[];
54
69
  constructor(publicClient: PublicClient, router: Address, keys: CloakKeys, prover: () => Promise<Prover>, submit: Submitter, fromBlock?: bigint, batcher?: Address | undefined, migrator?: Address | undefined);
55
70
  /**
56
71
  * Derive the private keys from one EIP-712 signature bound to this chain and router, so a signature asked for by any
@@ -67,6 +82,8 @@ export declare class CloakPrivate {
67
82
  loadMarkets(): Promise<PrivateMarket[]>;
68
83
  get hypeMarket(): PrivateMarket;
69
84
  market(token: Address): PrivateMarket;
85
+ /** Register a shielded pool that isn't a launch market, such as USDC (`deploymentFor(chainId).assets`). */
86
+ addAsset(a: PrivateAsset): PrivateMarket;
70
87
  private state;
71
88
  /** Pull new leaves from the pool's events, trial-decrypt them, and refresh spent flags. */
72
89
  sync(pool: Address): Promise<OwnedNote[]>;
@@ -92,6 +109,11 @@ export declare class CloakPrivate {
92
109
  private inputsFor;
93
110
  /** Public HYPE → private balance. This one step is public (your wallet deposits); everything after is not. */
94
111
  shieldHype(wallet: Wallet, amount: bigint): Promise<Hex>;
112
+ /**
113
+ * Public tokens → private balance, for any pool this client knows (USDC, or a launch token). Like shieldHype, this
114
+ * one step is public and lands on a one-time key. Asks the wallet to approve the pool first if needed.
115
+ */
116
+ shieldToken(wallet: Wallet, token: Address, amount: bigint, onStatus?: Status): Promise<Hex>;
95
117
  roundInfo(): Promise<{
96
118
  round: bigint;
97
119
  endsAt: number;
@@ -143,10 +165,10 @@ export declare class CloakPrivate {
143
165
  address(): Promise<string>;
144
166
  /**
145
167
  * Private transfer inside a pool: sender, recipient and amount all stay hidden.
146
- * `token` = a launch token, or the WHYPE address / hypeMarket.token for HYPE.
168
+ * `token` = a launch token, an asset such as USDC, or the WHYPE address / hypeMarket.token for HYPE.
147
169
  */
148
170
  send(token: Address, to: string, amount: bigint, onStatus?: Status): Promise<Hex>;
149
- /** Private launch tokens → any address. */
171
+ /** Private tokens (a launch token, or an asset such as USDC) → any address. */
150
172
  unshieldToken(token: Address, to: Address, amount: bigint, onStatus?: Status): Promise<Hex>;
151
173
  /**
152
174
  * Move every private balance `legacy` (a client on the earlier router, with the earlier keys) can see into this
@@ -7,6 +7,10 @@ import { MOVE_DATA_ABI, ORDER_DATA_ABI, SWAP_DATA_ABI, buildTransaction, selectN
7
7
  const EPOCH_BITS = 20;
8
8
  const epochOf = (globalIndex) => Math.floor(globalIndex / 2 ** EPOCH_BITS);
9
9
  const positionOf = (globalIndex) => globalIndex % 2 ** EPOCH_BITS;
10
+ const ERC20_ABI = [
11
+ { type: "function", name: "allowance", stateMutability: "view", inputs: [{ type: "address" }, { type: "address" }], outputs: [{ type: "uint256" }] },
12
+ { type: "function", name: "approve", stateMutability: "nonpayable", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }] },
13
+ ];
10
14
  const json = (v) => JSON.stringify(v, (_, x) => (typeof x === "bigint" ? x.toString() : x));
11
15
  /** POST shielded calls to a Cloak relayer (`services/keeper` serves `/relay` and `/quote`). */
12
16
  export function relayerSubmitter(url) {
@@ -75,6 +79,8 @@ export class CloakPrivate {
75
79
  migrator;
76
80
  pools = new Map();
77
81
  markets = [];
82
+ /** Shielded pools outside the launch registry (USDC). Send, unshieldToken and shieldToken work on these too. */
83
+ assets = [];
78
84
  constructor(publicClient, router, keys, prover, submit, fromBlock = 0n, batcher, migrator) {
79
85
  this.publicClient = publicClient;
80
86
  this.router = router;
@@ -128,15 +134,26 @@ export class CloakPrivate {
128
134
  return m;
129
135
  }
130
136
  market(token) {
131
- const m = this.markets.find((x) => x.token.toLowerCase() === token.toLowerCase());
137
+ const t = token.toLowerCase();
138
+ const m = this.markets.find((x) => x.token.toLowerCase() === t) ?? this.assets.find((x) => x.token.toLowerCase() === t);
132
139
  if (!m)
133
140
  throw new Error("no private market for this token (createPool after settlement)");
134
141
  return m;
135
142
  }
143
+ /** Register a shielded pool that isn't a launch market, such as USDC (`deploymentFor(chainId).assets`). */
144
+ addAsset(a) {
145
+ const existing = this.assets.find((x) => x.pool.toLowerCase() === a.pool.toLowerCase());
146
+ if (existing)
147
+ return existing;
148
+ const m = { token: a.token, pool: a.pool, pair: null, symbol: a.symbol, decimals: a.decimals, fromBlock: a.fromBlock };
149
+ this.assets.push(m);
150
+ return m;
151
+ }
136
152
  state(pool) {
137
153
  let s = this.pools.get(pool.toLowerCase());
138
154
  if (!s) {
139
- s = { leaves: [], scannedUpTo: 0, fromBlock: this.fromBlock, notes: [] };
155
+ const asset = this.assets.find((x) => x.pool.toLowerCase() === pool.toLowerCase());
156
+ s = { leaves: [], scannedUpTo: 0, fromBlock: asset?.fromBlock ?? this.fromBlock, notes: [] };
140
157
  this.pools.set(pool.toLowerCase(), s);
141
158
  }
142
159
  return s;
@@ -188,8 +205,19 @@ export class CloakPrivate {
188
205
  }
189
206
  /** The most a single relayed call of this kind can move from `pool`: the balance minus the relayer's fee. */
190
207
  async maxSpendable(pool, kind = "transact") {
191
- const [bal, q] = await Promise.all([this.balance(pool), this.relayFee(kind, pool)]);
192
- return bal > q.fee ? bal - q.fee : 0n;
208
+ const [notes, q, m] = await Promise.all([this.sync(pool), this.relayFee(kind, pool), this.relayFee("transact", pool)]);
209
+ // a transaction takes at most two notes, so using the k largest notes costs k-2 combining steps first (dust notes
210
+ // aren't worth one); the fee is re-quoted when sending, so keep a 25% cushion on every fee
211
+ const sorted = notes.map((n) => n.amount).sort((a, b) => (a > b ? -1 : a < b ? 1 : 0));
212
+ let best = 0n;
213
+ let sum = 0n;
214
+ for (let k = 1; k <= sorted.length; k++) {
215
+ sum += sorted[k - 1];
216
+ const spendable = sum - (q.fee * 5n) / 4n - (BigInt(Math.max(0, k - 2)) * m.fee * 5n) / 4n;
217
+ if (spendable > best)
218
+ best = spendable;
219
+ }
220
+ return best;
193
221
  }
194
222
  /** The Merkle tree of one epoch (default: the epoch the given notes live in, else the newest). */
195
223
  async tree(pool, epoch) {
@@ -275,6 +303,38 @@ export class CloakPrivate {
275
303
  throw new Error("shield reverted");
276
304
  return hash;
277
305
  }
306
+ /**
307
+ * Public tokens → private balance, for any pool this client knows (USDC, or a launch token). Like shieldHype, this
308
+ * one step is public and lands on a one-time key. Asks the wallet to approve the pool first if needed.
309
+ */
310
+ async shieldToken(wallet, token, amount, onStatus = () => { }) {
311
+ if (amount <= 0n)
312
+ throw new Error("amount must be positive");
313
+ const pool = this.market(token).pool;
314
+ const owner = wallet.account.address;
315
+ const allowance = await this.publicClient.readContract({ address: token, abi: ERC20_ABI, functionName: "allowance", args: [owner, pool] });
316
+ if (allowance < amount) {
317
+ onStatus("Approve the deposit in your wallet…");
318
+ const a = await wallet.writeContract({ address: token, abi: ERC20_ABI, functionName: "approve", args: [pool, amount] });
319
+ const ar = await this.publicClient.waitForTransactionReceipt({ hash: a });
320
+ if (ar.status !== "success")
321
+ throw new Error("approve reverted");
322
+ }
323
+ const keyIndex = randomKeyIndex();
324
+ const k = await this.keys.key(keyIndex);
325
+ const blinding = randomField();
326
+ onStatus("Confirm the deposit in your wallet…");
327
+ const hash = await wallet.writeContract({
328
+ address: pool,
329
+ abi: shieldedPoolAbi,
330
+ functionName: "depositTo",
331
+ args: [k.pubKey, amount, blinding, encryptFullMemo(this.keys.encPub, amount, blinding, keyIndex)],
332
+ });
333
+ const r = await this.publicClient.waitForTransactionReceipt({ hash });
334
+ if (r.status !== "success")
335
+ throw new Error("shield reverted");
336
+ return hash;
337
+ }
278
338
  // ───────────── batch auctions (hide trade size) ─────────────
279
339
  async roundInfo() {
280
340
  if (!this.batcher)
@@ -449,7 +509,7 @@ export class CloakPrivate {
449
509
  }
450
510
  /**
451
511
  * Private transfer inside a pool: sender, recipient and amount all stay hidden.
452
- * `token` = a launch token, or the WHYPE address / hypeMarket.token for HYPE.
512
+ * `token` = a launch token, an asset such as USDC, or the WHYPE address / hypeMarket.token for HYPE.
453
513
  */
454
514
  async send(token, to, amount, onStatus = () => { }) {
455
515
  const rcpt = parseCloakAddress(to);
@@ -473,7 +533,7 @@ export class CloakPrivate {
473
533
  onStatus("Relaying private transfer…");
474
534
  return this.submit({ kind: "transact", pool, proof, extData });
475
535
  }
476
- /** Private launch tokens → any address. */
536
+ /** Private tokens (a launch token, or an asset such as USDC) → any address. */
477
537
  async unshieldToken(token, to, amount, onStatus = () => { }) {
478
538
  const pool = this.market(token).pool;
479
539
  const q = await this.relayFee("transact", pool);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloakely/sdk",
3
- "version": "0.2.2",
3
+ "version": "0.2.5",
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",
@@ -20,7 +20,7 @@
20
20
  "homepage": "https://cloak.boblabs.tech/sdk",
21
21
  "repository": {
22
22
  "type": "git",
23
- "url": "git+https://github.com/NECOKIZZ/cloak.git",
23
+ "url": "git+https://github.com/NECOKIZZ/Cloak.git",
24
24
  "directory": "packages/sdk"
25
25
  },
26
26
  "bugs": {