@cloakely/sdk 0.2.4 → 0.2.6

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,11 @@ 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
+ { type: "function", name: "balanceOf", stateMutability: "view", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }] },
14
+ ];
10
15
  const json = (v) => JSON.stringify(v, (_, x) => (typeof x === "bigint" ? x.toString() : x));
11
16
  /** POST shielded calls to a Cloak relayer (`services/keeper` serves `/relay` and `/quote`). */
12
17
  export function relayerSubmitter(url) {
@@ -75,6 +80,8 @@ export class CloakPrivate {
75
80
  migrator;
76
81
  pools = new Map();
77
82
  markets = [];
83
+ /** Shielded pools outside the launch registry (USDC). Send, unshieldToken and shieldToken work on these too. */
84
+ assets = [];
78
85
  constructor(publicClient, router, keys, prover, submit, fromBlock = 0n, batcher, migrator) {
79
86
  this.publicClient = publicClient;
80
87
  this.router = router;
@@ -128,15 +135,26 @@ export class CloakPrivate {
128
135
  return m;
129
136
  }
130
137
  market(token) {
131
- const m = this.markets.find((x) => x.token.toLowerCase() === token.toLowerCase());
138
+ const t = token.toLowerCase();
139
+ const m = this.markets.find((x) => x.token.toLowerCase() === t) ?? this.assets.find((x) => x.token.toLowerCase() === t);
132
140
  if (!m)
133
141
  throw new Error("no private market for this token (createPool after settlement)");
134
142
  return m;
135
143
  }
144
+ /** Register a shielded pool that isn't a launch market, such as USDC (`deploymentFor(chainId).assets`). */
145
+ addAsset(a) {
146
+ const existing = this.assets.find((x) => x.pool.toLowerCase() === a.pool.toLowerCase());
147
+ if (existing)
148
+ return existing;
149
+ const m = { token: a.token, pool: a.pool, pair: null, symbol: a.symbol, decimals: a.decimals, fromBlock: a.fromBlock };
150
+ this.assets.push(m);
151
+ return m;
152
+ }
136
153
  state(pool) {
137
154
  let s = this.pools.get(pool.toLowerCase());
138
155
  if (!s) {
139
- s = { leaves: [], scannedUpTo: 0, fromBlock: this.fromBlock, notes: [] };
156
+ const asset = this.assets.find((x) => x.pool.toLowerCase() === pool.toLowerCase());
157
+ s = { leaves: [], scannedUpTo: 0, fromBlock: asset?.fromBlock ?? this.fromBlock, notes: [] };
140
158
  this.pools.set(pool.toLowerCase(), s);
141
159
  }
142
160
  return s;
@@ -286,6 +304,42 @@ export class CloakPrivate {
286
304
  throw new Error("shield reverted");
287
305
  return hash;
288
306
  }
307
+ /**
308
+ * Public tokens → private balance, for any pool this client knows (USDC, or a launch token). Like shieldHype, this
309
+ * one step is public and lands on a one-time key. Asks the wallet to approve the pool first if needed.
310
+ */
311
+ async shieldToken(wallet, token, amount, onStatus = () => { }) {
312
+ if (amount <= 0n)
313
+ throw new Error("amount must be positive");
314
+ const pool = this.market(token).pool;
315
+ const owner = wallet.account.address;
316
+ // check first, so a wallet without the tokens doesn't pay gas for an approval that leads nowhere
317
+ const held = await this.publicClient.readContract({ address: token, abi: ERC20_ABI, functionName: "balanceOf", args: [owner] });
318
+ if (held < amount)
319
+ throw new Error(`insufficient token balance in wallet: has ${held}, needs ${amount}`);
320
+ const allowance = await this.publicClient.readContract({ address: token, abi: ERC20_ABI, functionName: "allowance", args: [owner, pool] });
321
+ if (allowance < amount) {
322
+ onStatus("Approve the deposit in your wallet…");
323
+ const a = await wallet.writeContract({ address: token, abi: ERC20_ABI, functionName: "approve", args: [pool, amount] });
324
+ const ar = await this.publicClient.waitForTransactionReceipt({ hash: a });
325
+ if (ar.status !== "success")
326
+ throw new Error("approve reverted");
327
+ }
328
+ const keyIndex = randomKeyIndex();
329
+ const k = await this.keys.key(keyIndex);
330
+ const blinding = randomField();
331
+ onStatus("Confirm the deposit in your wallet…");
332
+ const hash = await wallet.writeContract({
333
+ address: pool,
334
+ abi: shieldedPoolAbi,
335
+ functionName: "depositTo",
336
+ args: [k.pubKey, amount, blinding, encryptFullMemo(this.keys.encPub, amount, blinding, keyIndex)],
337
+ });
338
+ const r = await this.publicClient.waitForTransactionReceipt({ hash });
339
+ if (r.status !== "success")
340
+ throw new Error("shield reverted");
341
+ return hash;
342
+ }
289
343
  // ───────────── batch auctions (hide trade size) ─────────────
290
344
  async roundInfo() {
291
345
  if (!this.batcher)
@@ -460,7 +514,7 @@ export class CloakPrivate {
460
514
  }
461
515
  /**
462
516
  * Private transfer inside a pool: sender, recipient and amount all stay hidden.
463
- * `token` = a launch token, or the WHYPE address / hypeMarket.token for HYPE.
517
+ * `token` = a launch token, an asset such as USDC, or the WHYPE address / hypeMarket.token for HYPE.
464
518
  */
465
519
  async send(token, to, amount, onStatus = () => { }) {
466
520
  const rcpt = parseCloakAddress(to);
@@ -484,7 +538,7 @@ export class CloakPrivate {
484
538
  onStatus("Relaying private transfer…");
485
539
  return this.submit({ kind: "transact", pool, proof, extData });
486
540
  }
487
- /** Private launch tokens → any address. */
541
+ /** Private tokens (a launch token, or an asset such as USDC) → any address. */
488
542
  async unshieldToken(token, to, amount, onStatus = () => { }) {
489
543
  const pool = this.market(token).pool;
490
544
  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.4",
3
+ "version": "0.2.6",
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",