@cloakely/sdk 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.
@@ -42,6 +42,12 @@ export interface CloakDeployment {
42
42
  batcher: Address;
43
43
  /** Moves balances from every earlier deployment into the permanent vaults. */
44
44
  migrator?: Address;
45
+ /** SharedVault: one private vault for every new Uniswap launch token (testnet experiment). */
46
+ shared?: Address;
47
+ /** Block the shared vault was deployed in: scan its notes from here. */
48
+ sharedBlock?: bigint;
49
+ /** SharedRouter: private buys, sells and hidden sends for the shared vault. */
50
+ sharedRouter?: Address;
45
51
  hypePool: Address;
46
52
  whype: Address;
47
53
  /** Hosted relayer (POST /relay, GET /quote). */
@@ -17,6 +17,10 @@ export const deployments = {
17
17
  firstFactoryBlock: 19484794n,
18
18
  seeder: "0x633aa82b806dafc0ae4c84418d23f3726139a88d",
19
19
  feeLocker: "0x3b74062a72b16f9a221d63515518a635414501d8",
20
+ // the shared launch-token vault (Nov 2, testnet experiment): new Uniswap launches keep their private balances here
21
+ shared: "0x0ce2ecb0195e07899377e3d7b01e927ab3f87610",
22
+ sharedBlock: 21426599n,
23
+ sharedRouter: "0xe2a0769a5acbd987c1b6725247c56187a112dc7e",
20
24
  router: "0x343ab9a7aac93cee28321410510ea24417cc6386",
21
25
  routerBlock: 19485063n,
22
26
  batcher: "0xc69e4bca18020ae93bc9f17093985bfb139a3203",
@@ -96,6 +100,9 @@ export async function liveDeployment(publicClient, base) {
96
100
  routerBlock: reg.routerBlock,
97
101
  batcher: reg.batcher ?? base.batcher,
98
102
  migrator: reg.migrator ?? base.migrator,
103
+ shared: reg.shared ?? base.shared,
104
+ sharedBlock: reg.sharedBlock ?? base.sharedBlock,
105
+ sharedRouter: reg.sharedRouter ?? base.sharedRouter,
99
106
  hypePool,
100
107
  whype,
101
108
  previous: reg.previous.length
@@ -114,7 +114,11 @@ export declare class CloakPrivate {
114
114
  relayFee(kind: RelayKind, pool: Address): Promise<RelayQuote>;
115
115
  /** What a withdrawal of `amount` from `pool` delivers after the protocol's withdrawal fee. */
116
116
  afterFee(pool: Address, amount: bigint): Promise<bigint>;
117
- private changeOutput;
117
+ changeOutput(amount: bigint): Promise<{
118
+ amount: bigint;
119
+ pubKey: bigint;
120
+ memo: (b: bigint) => `0x${string}`;
121
+ }>;
118
122
  dummy(): Promise<{
119
123
  dummyPubKey: bigint;
120
124
  dummyMemo: (b: bigint) => `0x${string}`;
@@ -123,7 +127,7 @@ export declare class CloakPrivate {
123
127
  * Get 1–2 notes (from one tree epoch) covering `target`, merging notes privately first when the balance is spread
124
128
  * across many. Each merge is a relayed transaction and pays the relayer's fee from the merged notes.
125
129
  */
126
- private inputsFor;
130
+ inputsFor(pool: Address, target: bigint, onStatus: Status): Promise<OwnedNote[]>;
127
131
  /** Public HYPE → private balance. This one step is public (your wallet deposits); everything after is not. */
128
132
  shieldHype(wallet: Wallet, amount: bigint): Promise<Hex>;
129
133
  /**
@@ -178,7 +182,12 @@ export declare class CloakPrivate {
178
182
  * wallet's HYPE back into the private balance. Before settlement it only moves back HYPE from a one-time wallet whose
179
183
  * bid never went through. Safe to call again; it only does what's left.
180
184
  */
181
- finishPrivateBids(auction: Address, onStatus?: Status): Promise<{
185
+ finishPrivateBids(auction: Address, onStatus?: Status,
186
+ /** The shared launch-token vault, for launches whose token lives there (claimed to the bid wallet, then moved in). */
187
+ shared?: {
188
+ isAdmitted(token: Address): Promise<boolean>;
189
+ shield(wallet: Wallet, token: Address, amount: bigint): Promise<Hex>;
190
+ }): Promise<{
182
191
  claimed: number;
183
192
  movedHype: bigint;
184
193
  movedTokens: bigint;
@@ -479,13 +479,16 @@ export class CloakPrivate {
479
479
  * wallet's HYPE back into the private balance. Before settlement it only moves back HYPE from a one-time wallet whose
480
480
  * bid never went through. Safe to call again; it only does what's left.
481
481
  */
482
- async finishPrivateBids(auction, onStatus = () => { }) {
482
+ async finishPrivateBids(auction, onStatus = () => { },
483
+ /** The shared launch-token vault, for launches whose token lives there (claimed to the bid wallet, then moved in). */
484
+ shared) {
483
485
  const reader = new Cloak(this.publicClient);
484
486
  const s = await reader.getAuction(auction);
485
487
  let claimed = 0;
486
488
  let movedHype = 0n;
487
489
  let movedTokens = 0n;
488
490
  const vaultOpen = !s.failed && this.markets.some((m) => m.token.toLowerCase() === s.token.toLowerCase());
491
+ const inShared = !s.failed && !vaultOpen && !!shared && (await shared.isAdmitted(s.token));
489
492
  for (const bw of await this.bidWallets(auction)) {
490
493
  const c = new Cloak(this.publicClient, bw.wallet);
491
494
  const bids = await reader.bidsOf(auction, bw.address);
@@ -512,12 +515,12 @@ export class CloakPrivate {
512
515
  const h = await bw.wallet.writeContract({ address: auction, abi: cloakAuctionAbi, functionName: "withdrawRefund", args: [bw.address] });
513
516
  await this.publicClient.waitForTransactionReceipt({ hash: h });
514
517
  }
515
- if (vaultOpen) {
518
+ if (vaultOpen || inShared) {
516
519
  const t = await this.publicClient.readContract({ address: s.token, abi: ERC20_ABI, functionName: "balanceOf", args: [bw.address] });
517
520
  if (t > 0n) {
518
521
  onStatus("Moving tokens into your private balance…");
519
522
  try {
520
- await this.shieldToken(bw.wallet, s.token, t);
523
+ await (inShared ? shared.shield(bw.wallet, s.token, t) : this.shieldToken(bw.wallet, s.token, t));
521
524
  movedTokens += t;
522
525
  }
523
526
  catch { } // a cap may block it; the tokens stay in the one-time wallet, which nobody can tie to you
@@ -81,6 +81,8 @@ export interface Note {
81
81
  amount: bigint;
82
82
  pubKey: bigint;
83
83
  blinding: bigint;
84
+ /** Shared vault only: the token's address as a number. Notes of single-token vaults have none. */
85
+ asset?: bigint;
84
86
  }
85
87
  export declare const noteCommitment: (n: Note) => Promise<bigint>;
86
88
  export declare function nullifierOf(n: Note, leafIndex: number, privKey: bigint): Promise<bigint>;
@@ -86,7 +86,7 @@ export class CloakKeys {
86
86
  return keccak256(concat([toBytes("cloak-bidwallet-v1"), this.seed, toBytes(auction.toLowerCase()), toBytes(toHex(n, { size: 4 }))]));
87
87
  }
88
88
  }
89
- export const noteCommitment = (n) => poseidon([n.amount, n.pubKey, n.blinding]);
89
+ export const noteCommitment = (n) => n.asset === undefined ? poseidon([n.amount, n.pubKey, n.blinding]) : poseidon([n.amount, n.pubKey, n.blinding, n.asset]);
90
90
  export async function nullifierOf(n, leafIndex, privKey) {
91
91
  const c = await noteCommitment(n);
92
92
  const sig = await poseidon([privKey, c, BigInt(leafIndex)]);
@@ -3,3 +3,4 @@ export * from "./tree.js";
3
3
  export * from "./prover.js";
4
4
  export * from "./tx.js";
5
5
  export * from "./client.js";
6
+ export * from "./shared.js";
@@ -3,3 +3,4 @@ export * from "./tree.js";
3
3
  export * from "./prover.js";
4
4
  export * from "./tx.js";
5
5
  export * from "./client.js";
6
+ export * from "./shared.js";
@@ -7,10 +7,14 @@ export interface ProofArgs {
7
7
  extDataHash: bigint;
8
8
  inputNullifiers: [bigint, bigint];
9
9
  outputCommitments: [bigint, bigint];
10
+ /** Shared-vault proofs only: the token named publicly, or 0 for a send that hides it. */
11
+ publicAsset?: bigint;
10
12
  }
11
13
  export type Prover = (input: Record<string, unknown>) => Promise<ProofArgs>;
12
14
  /** Groth16 prover for the 2-in/2-out circuit (`transaction2x2`). Runs on the user's device. */
13
15
  export declare function createProver(wasm: Uint8Array, zkey: Uint8Array): Prover;
16
+ /** Groth16 prover for the shared launch-token vault (`transactionMA2x2`, 8 public signals). */
17
+ export declare function createSharedProver(wasm: Uint8Array, zkey: Uint8Array): Prover;
14
18
  /** Hosted circuit artifacts. */
15
19
  export declare const CIRCUITS_URL = "https://cloak.boblabs.tech/circuits";
16
20
  /** SHA-256 of the artifacts this SDK version was built and tested against. */
@@ -29,3 +33,18 @@ export declare function loadProver(baseUrl?: string, opts?: {
29
33
  zkey: string;
30
34
  } | null;
31
35
  }): Promise<Prover>;
36
+ /** SHA-256 of the shared-vault circuit artifacts this SDK version was built and tested against. */
37
+ export declare const SHARED_CIRCUIT_HASHES: {
38
+ readonly wasm: "55712f66e67fd9345daac5c881c2e73c546864dbd1e23481463b603b47aa53ed";
39
+ readonly zkey: "86ecee336f022ac1d2dbcc4db7e85ef838aee34c4d75fe8bc7446db86f3b97b3";
40
+ };
41
+ /**
42
+ * Fetch the shared launch-token vault's circuit (`transactionMA2x2`, 15 MB, once) and build its prover. Checked against
43
+ * `SHARED_CIRCUIT_HASHES` like `loadProver`; pass `hashes: null` to skip that when serving your own build.
44
+ */
45
+ export declare function loadSharedProver(baseUrl?: string, opts?: {
46
+ hashes?: {
47
+ wasm: string;
48
+ zkey: string;
49
+ } | null;
50
+ }): Promise<Prover>;
@@ -20,6 +20,28 @@ export function createProver(wasm, zkey) {
20
20
  };
21
21
  };
22
22
  }
23
+ /** Groth16 prover for the shared launch-token vault (`transactionMA2x2`, 8 public signals). */
24
+ export function createSharedProver(wasm, zkey) {
25
+ return async (input) => {
26
+ const snarkjs = await import("snarkjs");
27
+ const { proof, publicSignals } = await snarkjs.groth16.fullProve(Object.fromEntries(Object.entries(input).map(([k, v]) => [k, str(v)])), wasm, zkey);
28
+ const s = publicSignals.map(BigInt);
29
+ return {
30
+ a: [BigInt(proof.pi_a[0]), BigInt(proof.pi_a[1])],
31
+ b: [
32
+ [BigInt(proof.pi_b[0][1]), BigInt(proof.pi_b[0][0])],
33
+ [BigInt(proof.pi_b[1][1]), BigInt(proof.pi_b[1][0])],
34
+ ],
35
+ c: [BigInt(proof.pi_c[0]), BigInt(proof.pi_c[1])],
36
+ root: s[0],
37
+ publicAmount: s[1],
38
+ extDataHash: s[2],
39
+ publicAsset: s[3],
40
+ inputNullifiers: [s[4], s[5]],
41
+ outputCommitments: [s[6], s[7]],
42
+ };
43
+ };
44
+ }
23
45
  /** Hosted circuit artifacts. */
24
46
  export const CIRCUITS_URL = "https://cloak.boblabs.tech/circuits";
25
47
  /** SHA-256 of the artifacts this SDK version was built and tested against. */
@@ -53,3 +75,29 @@ export async function loadProver(baseUrl = CIRCUITS_URL, opts = {}) {
53
75
  }
54
76
  return createProver(wasm, zkey);
55
77
  }
78
+ /** SHA-256 of the shared-vault circuit artifacts this SDK version was built and tested against. */
79
+ export const SHARED_CIRCUIT_HASHES = {
80
+ wasm: "55712f66e67fd9345daac5c881c2e73c546864dbd1e23481463b603b47aa53ed",
81
+ zkey: "86ecee336f022ac1d2dbcc4db7e85ef838aee34c4d75fe8bc7446db86f3b97b3",
82
+ };
83
+ /**
84
+ * Fetch the shared launch-token vault's circuit (`transactionMA2x2`, 15 MB, once) and build its prover. Checked against
85
+ * `SHARED_CIRCUIT_HASHES` like `loadProver`; pass `hashes: null` to skip that when serving your own build.
86
+ */
87
+ export async function loadSharedProver(baseUrl = CIRCUITS_URL, opts = {}) {
88
+ const base = baseUrl.replace(/\/$/, "");
89
+ const get = async (name) => {
90
+ const res = await fetch(`${base}/${name}`);
91
+ if (!res.ok)
92
+ throw new Error(`could not download ${name} from ${base} (${res.status})`);
93
+ return new Uint8Array(await res.arrayBuffer());
94
+ };
95
+ const [wasm, zkey] = await Promise.all([get("transactionMA2x2.wasm"), get("transactionMA2x2_final.zkey")]);
96
+ const want = opts.hashes === undefined ? SHARED_CIRCUIT_HASHES : opts.hashes;
97
+ if (want) {
98
+ const [hw, hz] = await Promise.all([sha256(wasm), sha256(zkey)]);
99
+ if (hw !== want.wasm || hz !== want.zkey)
100
+ throw new Error("shared-vault circuit files don't match this SDK version's hashes; refusing to prove with them");
101
+ }
102
+ return createSharedProver(wasm, zkey);
103
+ }
@@ -0,0 +1,127 @@
1
+ import { type Address, type Hex } from "viem";
2
+ import { type Wallet } from "../cloak.js";
3
+ import type { ProofArgs, Prover } from "./prover.js";
4
+ import { type ExtData, type OwnedNote } from "./tx.js";
5
+ import type { CloakPrivate, RelayQuote, Status } from "./client.js";
6
+ /**
7
+ * The shared launch-token vault (testnet experiment): one private vault for every Cloak launch token that opens on
8
+ * Uniswap from now on, next to the single-token vaults of HYPE and USDC.
9
+ *
10
+ * shield wallet tokens → private note (one-time key)
11
+ * buy private HYPE → private token, through the launch's Uniswap pool
12
+ * sell private token → private HYPE
13
+ * send private token → any cloak: address, and nobody can tell WHICH token moved: the vault sees a send with no
14
+ * fee, and the relayer is paid from the sender's private HYPE in the same transaction
15
+ * unshield private token → any address
16
+ *
17
+ * Each note holds one token (Poseidon(amount, pubKey, blinding, token)), and every proof spends and creates notes of a
18
+ * single token only. Uses the account's keys, HYPE vault and relayer from `CloakPrivate`.
19
+ */
20
+ export declare const SHARED_SWAP_DATA_ABI: readonly [{
21
+ readonly type: "tuple";
22
+ readonly components: readonly [{
23
+ readonly name: "token";
24
+ readonly type: "address";
25
+ }, {
26
+ readonly name: "minOut";
27
+ readonly type: "uint256";
28
+ }, {
29
+ readonly name: "outPubKey";
30
+ readonly type: "uint256";
31
+ }, {
32
+ readonly name: "outBlinding";
33
+ readonly type: "uint256";
34
+ }, {
35
+ readonly name: "outMemo";
36
+ readonly type: "bytes";
37
+ }, {
38
+ readonly name: "deadline";
39
+ readonly type: "uint64";
40
+ }];
41
+ }];
42
+ export type SharedRelayKind = "sharedTransact" | "sharedBuy" | "sharedSell" | "sharedSend";
43
+ export interface SharedRelayCall {
44
+ kind: SharedRelayKind;
45
+ /** The shared-vault proof (or, for sharedBuy, the HYPE vault proof). */
46
+ proof: ProofArgs;
47
+ extData: ExtData;
48
+ /** sharedSend only: the HYPE vault proof that pays the relayer. */
49
+ feeProof?: ProofArgs;
50
+ feeExtData?: ExtData;
51
+ }
52
+ export type SharedSubmitter = ((call: SharedRelayCall) => Promise<Hex>) & {
53
+ /** The relayer's fee: in HYPE for sharedBuy and sharedSend, in the token for sharedTransact and sharedSell. */
54
+ quote?: (kind: SharedRelayKind, token: Address) => Promise<RelayQuote>;
55
+ };
56
+ /** POST shared-vault calls to a Cloak relayer (`services/keeper` serves `/relay-shared` and `/quote-shared`). */
57
+ export declare function sharedRelayerSubmitter(url: string): SharedSubmitter;
58
+ /** Submit shared-vault calls from a wallet (that wallet pays gas and appears as the sender). For tests and relayers. */
59
+ export declare function sharedWalletSubmitter(priv: CloakPrivate, wallet: Wallet, vault: Address, router: Address): SharedSubmitter;
60
+ export interface SharedToken {
61
+ token: Address;
62
+ market: Address;
63
+ symbol: string;
64
+ }
65
+ export declare class SharedVaultClient {
66
+ readonly priv: CloakPrivate;
67
+ readonly vault: Address;
68
+ readonly router: Address;
69
+ readonly prover: () => Promise<Prover>;
70
+ readonly submit: SharedSubmitter;
71
+ readonly fromBlock: bigint;
72
+ tokens: SharedToken[];
73
+ private leaves;
74
+ private scannedUpTo;
75
+ private notes;
76
+ private nextBlock;
77
+ constructor(priv: CloakPrivate, vault: Address, router: Address, prover: () => Promise<Prover>, submit: SharedSubmitter, fromBlock?: bigint);
78
+ private get pub();
79
+ /** Every token admitted to the vault, with its Uniswap market. */
80
+ loadTokens(): Promise<SharedToken[]>;
81
+ /** Whether `token` is in the shared vault (reads the vault's list if it isn't known yet). */
82
+ isAdmitted(token: Address): Promise<boolean>;
83
+ private info;
84
+ /** Let a settled Uniswap launch's token into the vault (anyone can; costs gas). */
85
+ admit(wallet: Wallet, factory: Address, launchIndex: bigint): Promise<Hex>;
86
+ /** Pull new notes, find which are ours and which token each holds, refresh spent flags. */
87
+ sync(): Promise<OwnedNote[]>;
88
+ /** Private balance of `token` in the shared vault. */
89
+ balance(token: Address): Promise<bigint>;
90
+ /** Every token this account holds privately in the shared vault. */
91
+ balances(): Promise<{
92
+ token: Address;
93
+ symbol: string;
94
+ amount: bigint;
95
+ }[]>;
96
+ /** The most one withdrawal (or sell) of `token` can take: the balance minus the relayer's fee (with a 25% cushion). */
97
+ maxWithdraw(token: Address, kind?: "sharedTransact" | "sharedSell"): Promise<bigint>;
98
+ private tree;
99
+ private quote;
100
+ /** 1–2 notes of `token` covering `target`, combining notes first (a relayed send to yourself) when needed. */
101
+ private inputsFor;
102
+ /** Expected output of a swap through `token`'s Uniswap market (0.3% fee), before slippage. */
103
+ quoteSwap(token: Address, side: "buy" | "sell", amountIn: bigint): Promise<bigint>;
104
+ /** Wallet tokens → a private note on a one-time key. This step is public (the wallet deposits). */
105
+ shield(wallet: Wallet, token: Address, amount: bigint, onStatus?: Status): Promise<Hex>;
106
+ /** Claim won bids of a shared-vault launch and move the tokens in. Two public steps from the bidder's wallet. */
107
+ claimInto(wallet: Wallet, auction: Address, bidIds: bigint[], onStatus?: Status): Promise<{
108
+ tokens: bigint;
109
+ }>;
110
+ /** Private HYPE → private `token`. The HYPE side, the swap and the output note are proven here, relayed elsewhere. */
111
+ buy(token: Address, hypeIn: bigint, opts?: {
112
+ slippageBps?: number;
113
+ onStatus?: Status;
114
+ }): Promise<Hex>;
115
+ /** Private `token` → private HYPE. */
116
+ sell(token: Address, amount: bigint, opts?: {
117
+ slippageBps?: number;
118
+ onStatus?: Status;
119
+ }): Promise<Hex>;
120
+ /**
121
+ * Send `amount` of `token` to a cloak: address without revealing which token: the shared-vault proof names no token
122
+ * and pays no fee, and a second proof pays the relayer from this account's private HYPE. Needs some private HYPE.
123
+ */
124
+ send(token: Address, amount: bigint, to: string, onStatus?: Status): Promise<Hex>;
125
+ /** Private `token` → `to` (any address). Names the token, as any withdrawal must. */
126
+ unshield(token: Address, amount: bigint, to: Address, onStatus?: Status): Promise<Hex>;
127
+ }