@cloakely/sdk 0.4.0 → 0.6.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
@@ -1,4 +1,6 @@
1
1
  import { type Account, type Address, type Chain, type Hex, type PublicClient, type Transport, type WalletClient } from "viem";
2
+ /** `lpLockUntil` for "locked forever, the creator earns the pool's trading fees". */
3
+ export declare const LOCK_FOREVER_FEES: bigint;
2
4
  import { type BidPreimage } from "./seal.js";
3
5
  export type Wallet = WalletClient<Transport, Chain, Account>;
4
6
  export interface LaunchParams {
@@ -21,8 +23,11 @@ export interface LaunchParams {
21
23
  revealEnd: number;
22
24
  /** Share of the raise that seeds the pool, in bps (default 5000). */
23
25
  lpBps?: number;
24
- /** Unix seconds until LP shares can be released to the creator; 0 / "burn" burns them (default). */
25
- lpLockUntil?: number | "burn";
26
+ /**
27
+ * What happens to the pool's LP: "burn" (default) burns it; "fees" locks it forever and pays the creator the pool's
28
+ * 0.3% trading fees (CloakFeeLocker); a unix time locks it until then, when the creator can take it.
29
+ */
30
+ lpLockUntil?: number | "burn" | "fees";
26
31
  noRevealPenaltyBps?: number;
27
32
  creator?: Address;
28
33
  metadataURI?: string;
@@ -53,7 +58,12 @@ export interface AuctionState {
53
58
  clearingPrice: bigint;
54
59
  sold: bigint;
55
60
  raised: bigint;
61
+ /** The market Cloak trades: a CloakV2Market in front of a Uniswap V2 pool, or (earlier launches) a CloakPair. */
56
62
  pair: Address;
63
+ /** The launch's Uniswap V2 pool (token / WHYPE), tradable and tracked everywhere. Zero for a CloakPair market. */
64
+ uniPair: Address;
65
+ /** The LP is locked forever and the creator earns its trading fees (see `creatorFees`). */
66
+ creatorFees: boolean;
57
67
  tokenName: string;
58
68
  tokenSymbol: string;
59
69
  /** Launch fee on a successful raise, fixed when the auction was created. */
@@ -123,6 +133,20 @@ export declare class Cloak {
123
133
  private listLegacy;
124
134
  private listCurrent;
125
135
  getAuction(auction: Address): Promise<AuctionState>;
136
+ /**
137
+ * The creator's trading fees for a launch whose LP is locked forever: what can be claimed now (in tokens and WHYPE),
138
+ * who receives it, and the locker position. `null` for launches without creator fees.
139
+ */
140
+ creatorFees(auction: Address): Promise<{
141
+ locker: Address;
142
+ position: bigint;
143
+ beneficiary: Address;
144
+ claimableLp: bigint;
145
+ tokens: bigint;
146
+ whype: bigint;
147
+ } | null>;
148
+ /** Pay a launch's earned trading fees to its creator (anyone can send this; the fees always go to the creator). */
149
+ claimCreatorFees(auction: Address): Promise<Hex>;
126
150
  /** Revealed demand per tick (empty until the reveal phase — that is the point). */
127
151
  demandCurve(auction: Address): Promise<{
128
152
  tick: number;
@@ -218,8 +242,19 @@ export declare class Cloak {
218
242
  readonly name: "config_";
219
243
  readonly type: "address";
220
244
  readonly internalType: "contract CloakConfig";
245
+ }, {
246
+ readonly name: "seeder_";
247
+ readonly type: "address";
248
+ readonly internalType: "contract IMarketSeeder";
249
+ }, {
250
+ readonly name: "feeLocker_";
251
+ readonly type: "address";
252
+ readonly internalType: "contract IFeeLocker";
221
253
  }];
222
254
  readonly stateMutability: "nonpayable";
255
+ }, {
256
+ readonly type: "receive";
257
+ readonly stateMutability: "payable";
223
258
  }, {
224
259
  readonly type: "function";
225
260
  readonly name: "DEAD";
@@ -250,6 +285,16 @@ export declare class Cloak {
250
285
  readonly internalType: "uint256";
251
286
  }];
252
287
  readonly stateMutability: "view";
288
+ }, {
289
+ readonly type: "function";
290
+ readonly name: "LOCK_FOREVER_FEES";
291
+ readonly inputs: readonly [];
292
+ readonly outputs: readonly [{
293
+ readonly name: "";
294
+ readonly type: "uint64";
295
+ readonly internalType: "uint64";
296
+ }];
297
+ readonly stateMutability: "view";
253
298
  }, {
254
299
  readonly type: "function";
255
300
  readonly name: "MAX_TICKS";
@@ -472,6 +517,16 @@ export declare class Cloak {
472
517
  readonly internalType: "uint256";
473
518
  }];
474
519
  readonly stateMutability: "view";
520
+ }, {
521
+ readonly type: "function";
522
+ readonly name: "creatorFees";
523
+ readonly inputs: readonly [];
524
+ readonly outputs: readonly [{
525
+ readonly name: "";
526
+ readonly type: "bool";
527
+ readonly internalType: "bool";
528
+ }];
529
+ readonly stateMutability: "view";
475
530
  }, {
476
531
  readonly type: "function";
477
532
  readonly name: "creatorOwed";
@@ -536,6 +591,26 @@ export declare class Cloak {
536
591
  readonly internalType: "bool";
537
592
  }];
538
593
  readonly stateMutability: "view";
594
+ }, {
595
+ readonly type: "function";
596
+ readonly name: "feeLocker";
597
+ readonly inputs: readonly [];
598
+ readonly outputs: readonly [{
599
+ readonly name: "";
600
+ readonly type: "address";
601
+ readonly internalType: "contract IFeeLocker";
602
+ }];
603
+ readonly stateMutability: "view";
604
+ }, {
605
+ readonly type: "function";
606
+ readonly name: "feePosition";
607
+ readonly inputs: readonly [];
608
+ readonly outputs: readonly [{
609
+ readonly name: "";
610
+ readonly type: "uint256";
611
+ readonly internalType: "uint256";
612
+ }];
613
+ readonly stateMutability: "view";
539
614
  }, {
540
615
  readonly type: "function";
541
616
  readonly name: "feesOwed";
@@ -830,6 +905,16 @@ export declare class Cloak {
830
905
  readonly internalType: "uint256";
831
906
  }];
832
907
  readonly stateMutability: "view";
908
+ }, {
909
+ readonly type: "function";
910
+ readonly name: "seeder";
911
+ readonly inputs: readonly [];
912
+ readonly outputs: readonly [{
913
+ readonly name: "";
914
+ readonly type: "address";
915
+ readonly internalType: "contract IMarketSeeder";
916
+ }];
917
+ readonly stateMutability: "view";
833
918
  }, {
834
919
  readonly type: "function";
835
920
  readonly name: "settle";
@@ -892,6 +977,16 @@ export declare class Cloak {
892
977
  readonly internalType: "uint256";
893
978
  }];
894
979
  readonly stateMutability: "view";
980
+ }, {
981
+ readonly type: "function";
982
+ readonly name: "uniPair";
983
+ readonly inputs: readonly [];
984
+ readonly outputs: readonly [{
985
+ readonly name: "";
986
+ readonly type: "address";
987
+ readonly internalType: "address";
988
+ }];
989
+ readonly stateMutability: "view";
895
990
  }, {
896
991
  readonly type: "function";
897
992
  readonly name: "withdrawProceeds";
@@ -978,6 +1073,21 @@ export declare class Cloak {
978
1073
  readonly internalType: "bytes";
979
1074
  }];
980
1075
  readonly anonymous: false;
1076
+ }, {
1077
+ readonly type: "event";
1078
+ readonly name: "CreatorFeesOn";
1079
+ readonly inputs: readonly [{
1080
+ readonly name: "locker";
1081
+ readonly type: "address";
1082
+ readonly indexed: false;
1083
+ readonly internalType: "address";
1084
+ }, {
1085
+ readonly name: "position";
1086
+ readonly type: "uint256";
1087
+ readonly indexed: false;
1088
+ readonly internalType: "uint256";
1089
+ }];
1090
+ readonly anonymous: false;
981
1091
  }, {
982
1092
  readonly type: "event";
983
1093
  readonly name: "DustSwept";
@@ -1183,6 +1293,26 @@ export declare class Cloak {
1183
1293
  readonly internalType: "uint256";
1184
1294
  }];
1185
1295
  readonly anonymous: false;
1296
+ }, {
1297
+ readonly type: "event";
1298
+ readonly name: "UniswapMarket";
1299
+ readonly inputs: readonly [{
1300
+ readonly name: "uniPair";
1301
+ readonly type: "address";
1302
+ readonly indexed: true;
1303
+ readonly internalType: "address";
1304
+ }, {
1305
+ readonly name: "market";
1306
+ readonly type: "address";
1307
+ readonly indexed: false;
1308
+ readonly internalType: "address";
1309
+ }, {
1310
+ readonly name: "lpShares";
1311
+ readonly type: "uint256";
1312
+ readonly indexed: false;
1313
+ readonly internalType: "uint256";
1314
+ }];
1315
+ readonly anonymous: false;
1186
1316
  }], "Committed", true, bigint, bigint>>;
1187
1317
  /**
1188
1318
  * Decrypt every sealed bid whose drand round has passed and reveal them in batches. Anyone can run this — it needs no
package/dist/cloak.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { zeroAddress, encodeAbiParameters, keccak256, parseEventLogs, toHex, } from "viem";
2
- import { cloakAuctionAbi, cloakFactoryAbi, cloakPairAbi, cloakTokenAbi } from "./abis.js";
2
+ import { cloakAuctionAbi, cloakFactoryAbi, cloakFeeLockerAbi, cloakPairAbi, cloakTokenAbi } from "./abis.js";
3
+ /** `lpLockUntil` for "locked forever, the creator earns the pool's trading fees". */
4
+ export const LOCK_FOREVER_FEES = 2n ** 64n - 1n;
3
5
  import { drandRoundAt, sealBid, unsealBid } from "./seal.js";
4
6
  const WAD = 10n ** 18n;
5
7
  const PHASES = ["commit", "reveal", "settle", "settled"];
@@ -88,7 +90,7 @@ export class Cloak {
88
90
  if (!this.factory)
89
91
  throw new Error("Cloak: factory address required");
90
92
  const w = this.wallet();
91
- const lock = p.lpLockUntil === undefined || p.lpLockUntil === "burn" ? 0 : p.lpLockUntil;
93
+ const lock = p.lpLockUntil === undefined || p.lpLockUntil === "burn" ? 0n : p.lpLockUntil === "fees" ? LOCK_FOREVER_FEES : BigInt(p.lpLockUntil);
92
94
  const params = {
93
95
  creator: p.creator ?? w.account.address,
94
96
  saleSupply: p.saleSupply,
@@ -100,7 +102,7 @@ export class Cloak {
100
102
  revealEnd: BigInt(p.revealEnd),
101
103
  lpBps: p.lpBps ?? 5000,
102
104
  noRevealPenaltyBps: p.noRevealPenaltyBps ?? 1000,
103
- lpLockUntil: BigInt(lock),
105
+ lpLockUntil: lock,
104
106
  tlockRound: BigInt(drandRoundAt(p.commitEnd)),
105
107
  };
106
108
  const txHash = await w.writeContract({
@@ -120,47 +122,34 @@ export class Cloak {
120
122
  async listLaunches() {
121
123
  if (!this.factory)
122
124
  throw new Error("Cloak: factory address required");
123
- const legacy = [];
124
- for (const f of this.legacyFactories)
125
- legacy.push(...(await this.listLegacy(f)));
126
- return [...(await this.listCurrent()), ...legacy];
125
+ // every factory, and every launch in it, read at once (a multicall client folds them into a few requests)
126
+ const [current, ...legacy] = await Promise.all([this.listCurrent(), ...this.legacyFactories.map((f) => this.listLegacy(f))]);
127
+ return [...current, ...legacy.flat()];
127
128
  }
128
129
  async listLegacy(factory) {
129
130
  const n = await this.publicClient.readContract({ address: factory, abi: cloakFactoryAbi, functionName: "launchesCount" });
130
- const out = [];
131
- for (let i = 0n; i < n; i++) {
131
+ return Promise.all(Array.from({ length: Number(n) }, async (_, k) => {
132
+ const i = BigInt(k);
132
133
  // a factory from v2 on returns a vesting contract too; the first one returns five fields
133
134
  const six = await this.publicClient.readContract({ address: factory, abi: cloakFactoryAbi, functionName: "launches", args: [i] }).catch(() => null);
134
135
  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, index: i, legacy: true });
136
- }
137
- return out;
136
+ return { auction, token, creator, launchpad, creatorAlloc, vesting: (six?.[5] ?? zeroAddress), factory, index: i, legacy: true };
137
+ }));
138
138
  }
139
139
  async listCurrent() {
140
- if (!this.factory)
141
- throw new Error("Cloak: factory address required");
142
- const n = await this.publicClient.readContract({
143
- address: this.factory,
144
- abi: cloakFactoryAbi,
145
- functionName: "launchesCount",
146
- });
147
- const out = [];
148
- for (let i = 0n; i < n; i++) {
149
- const [auction, token, creator, launchpad, creatorAlloc, vesting] = await this.publicClient.readContract({
150
- address: this.factory,
151
- abi: cloakFactoryAbi,
152
- functionName: "launches",
153
- args: [i],
154
- });
155
- out.push({ auction, token, creator, launchpad, creatorAlloc, vesting, factory: this.factory, index: i });
156
- }
157
- return out;
140
+ const factory = this.factory;
141
+ const n = await this.publicClient.readContract({ address: factory, abi: cloakFactoryAbi, functionName: "launchesCount" });
142
+ return Promise.all(Array.from({ length: Number(n) }, async (_, k) => {
143
+ const index = BigInt(k);
144
+ const [auction, token, creator, launchpad, creatorAlloc, vesting] = await this.publicClient.readContract({ address: factory, abi: cloakFactoryAbi, functionName: "launches", args: [index] });
145
+ return { auction, token, creator, launchpad, creatorAlloc, vesting, factory, index };
146
+ }));
158
147
  }
159
148
  // ───────────── reads ─────────────
160
149
  async getAuction(auction) {
161
150
  const c = { address: auction, abi: cloakAuctionAbi };
162
151
  const r = this.publicClient;
163
- const [params, token, phase, commits, reveals, totalDeposits, totalDemand, settled, failed, clearingTick, clearingPrice, sold, raised, pair, launchFeeBps, bidCap, depositCap, creatorOwed, feesOwed] = await Promise.all([
152
+ const [params, token, phase, commits, reveals, totalDeposits, totalDemand, settled, failed, clearingTick, clearingPrice, sold, raised, pair, launchFeeBps, bidCap, depositCap, creatorOwed, feesOwed, uniPair, creatorFees] = await Promise.all([
164
153
  r.readContract({ ...c, functionName: "params" }),
165
154
  r.readContract({ ...c, functionName: "token" }),
166
155
  r.readContract({ ...c, functionName: "phase" }),
@@ -181,6 +170,9 @@ export class Cloak {
181
170
  r.readContract({ ...c, functionName: "depositCap" }).catch(() => 0n),
182
171
  r.readContract({ ...c, functionName: "creatorOwed" }).catch(() => 0n),
183
172
  r.readContract({ ...c, functionName: "feesOwed" }).catch(() => 0n),
173
+ // launches from the Uniswap factory on
174
+ r.readContract({ ...c, functionName: "uniPair" }).catch(() => zeroAddress),
175
+ r.readContract({ ...c, functionName: "creatorFees" }).catch(() => false),
184
176
  ]);
185
177
  const [creator, saleSupply, lpReserve, minPrice, tickSize, numTicks, commitEnd, revealEnd, lpBps, noRevealPenaltyBps, lpLockUntil, tlockRound] = params;
186
178
  const [tokenName, tokenSymbol] = await Promise.all([
@@ -214,6 +206,8 @@ export class Cloak {
214
206
  sold,
215
207
  raised,
216
208
  pair,
209
+ uniPair,
210
+ creatorFees,
217
211
  tokenName,
218
212
  tokenSymbol,
219
213
  launchFeeBps,
@@ -223,6 +217,40 @@ export class Cloak {
223
217
  feesOwed,
224
218
  };
225
219
  }
220
+ /**
221
+ * The creator's trading fees for a launch whose LP is locked forever: what can be claimed now (in tokens and WHYPE),
222
+ * who receives it, and the locker position. `null` for launches without creator fees.
223
+ */
224
+ async creatorFees(auction) {
225
+ const c = { address: auction, abi: cloakAuctionAbi };
226
+ const on = await this.publicClient.readContract({ ...c, functionName: "creatorFees" }).catch(() => false);
227
+ if (!on)
228
+ return null;
229
+ const [locker, position, token, uniPair] = await Promise.all([
230
+ this.publicClient.readContract({ ...c, functionName: "feeLocker" }),
231
+ this.publicClient.readContract({ ...c, functionName: "feePosition" }),
232
+ this.publicClient.readContract({ ...c, functionName: "token" }),
233
+ this.publicClient.readContract({ ...c, functionName: "uniPair" }),
234
+ ]);
235
+ const l = { address: locker, abi: cloakFeeLockerAbi };
236
+ const [pos, claimableLp, [a0, a1], token0] = await Promise.all([
237
+ this.publicClient.readContract({ ...l, functionName: "positions", args: [position] }),
238
+ this.publicClient.readContract({ ...l, functionName: "claimable", args: [position] }),
239
+ this.publicClient.readContract({ ...l, functionName: "claimableAmounts", args: [position] }),
240
+ this.publicClient.readContract({ address: uniPair, abi: [{ type: "function", name: "token0", stateMutability: "view", inputs: [], outputs: [{ type: "address" }] }], functionName: "token0" }),
241
+ ]);
242
+ const tokenIs0 = token0.toLowerCase() === token.toLowerCase();
243
+ return { locker, position, beneficiary: pos.beneficiary, claimableLp, tokens: tokenIs0 ? a0 : a1, whype: tokenIs0 ? a1 : a0 };
244
+ }
245
+ /** Pay a launch's earned trading fees to its creator (anyone can send this; the fees always go to the creator). */
246
+ async claimCreatorFees(auction) {
247
+ const f = await this.creatorFees(auction);
248
+ if (!f)
249
+ throw new Error("this launch has no creator fees");
250
+ if (f.claimableLp === 0n)
251
+ throw new Error("no fees earned yet");
252
+ return this.send(await this.wallet().writeContract({ address: f.locker, abi: cloakFeeLockerAbi, functionName: "claim", args: [f.position] })).then((r) => r.transactionHash);
253
+ }
226
254
  /** Revealed demand per tick (empty until the reveal phase — that is the point). */
227
255
  async demandCurve(auction) {
228
256
  const s = await this.getAuction(auction);
@@ -28,6 +28,14 @@ export interface CloakDeployment {
28
28
  factory: Address;
29
29
  /** Block the factory was deployed in: scan launch events from here. */
30
30
  factoryBlock: bigint;
31
+ /** Earlier factories of this same deployment (newest first): their launches still list, settle and trade. */
32
+ earlierFactories?: Address[];
33
+ /** Block of this deployment's first factory: scan its launches' bids from here (defaults to `factoryBlock`). */
34
+ firstFactoryBlock?: bigint;
35
+ /** CloakSeeder: opens each settled launch's market (a Uniswap V2 pool where the chain has one). */
36
+ seeder?: Address;
37
+ /** CloakFeeLocker: holds "locked forever" LP and pays its trading fees to the creator. */
38
+ feeLocker?: Address;
31
39
  router: Address;
32
40
  /** Block the router was deployed in: scan private-pool events from here. */
33
41
  routerBlock: bigint;
@@ -52,6 +60,8 @@ export interface CloakDeployment {
52
60
  previous?: PreviousDeployment[];
53
61
  }
54
62
  export declare const deployments: Record<number, CloakDeployment>;
63
+ /** Every factory before `d.factory` whose launches should still list, newest first: this deployment's, then earlier ones'. */
64
+ export declare function legacyFactories(d: CloakDeployment): Address[];
55
65
  /** The Cloak deployment on `chainId`, or an error naming the chains Cloak is on. */
56
66
  export declare function deploymentFor(chainId: number): CloakDeployment;
57
67
  /**
@@ -1,7 +1,7 @@
1
1
  import { privateRouterAbi } from "./abis.js";
2
2
  import { readRegistry } from "./registry.js";
3
3
  export const deployments = {
4
- // Cloak v4 (Oct 31), the permanent vaults: deployments/elysium-testnet-v4.json. CloakConfig's registry lists all of it;
4
+ // Cloak v4 (Oct 31), the permanent vaults, plus the Uniswap launch factory (Nov 2): deployments/elysium-testnet-v4.json. CloakConfig's registry lists all of it;
5
5
  // liveDeployment() reads the current contracts from there.
6
6
  99801: {
7
7
  chainId: 99801,
@@ -9,8 +9,14 @@ export const deployments = {
9
9
  config: "0x9fdf166f0671166148ea98cdd3ef0118d256167f",
10
10
  vaults: "0x24bc1d369aab3a2d741a48f8cac1af5dfd9f3d43",
11
11
  vaultsBlock: 19485039n,
12
- factory: "0xa88df63d7d44acc66052e5cbe5ba9503bf940993",
13
- factoryBlock: 19484794n,
12
+ // the creator-fee launch factory (Nov 2): every launch opens a Uniswap V2 pool at its clearing price, and its LP can
13
+ // be locked forever with the trading fees going to the creator
14
+ factory: "0xd179bad69715f4bd4796dd7a4103be143fba02ad",
15
+ factoryBlock: 21312271n,
16
+ earlierFactories: ["0xeac84944f5a8343edf49cde8ef74d71f4908a656", "0xa88df63d7d44acc66052e5cbe5ba9503bf940993"],
17
+ firstFactoryBlock: 19484794n,
18
+ seeder: "0x633aa82b806dafc0ae4c84418d23f3726139a88d",
19
+ feeLocker: "0x3b74062a72b16f9a221d63515518a635414501d8",
14
20
  router: "0x343ab9a7aac93cee28321410510ea24417cc6386",
15
21
  routerBlock: 19485063n,
16
22
  batcher: "0xc69e4bca18020ae93bc9f17093985bfb139a3203",
@@ -54,6 +60,10 @@ export const deployments = {
54
60
  ],
55
61
  },
56
62
  };
63
+ /** Every factory before `d.factory` whose launches should still list, newest first: this deployment's, then earlier ones'. */
64
+ export function legacyFactories(d) {
65
+ return [...(d.earlierFactories ?? []), ...(d.previous ?? []).map((p) => p.factory)].filter((f) => f && !/^0x0{40}$/i.test(f));
66
+ }
57
67
  /** The Cloak deployment on `chainId`, or an error naming the chains Cloak is on. */
58
68
  export function deploymentFor(chainId) {
59
69
  const d = deployments[chainId];
@@ -81,6 +91,7 @@ export async function liveDeployment(publicClient, base) {
81
91
  vaultsBlock: reg.vaultsBlock ?? base.vaultsBlock,
82
92
  factory: reg.factory,
83
93
  factoryBlock: reg.factoryBlock,
94
+ earlierFactories: reg.earlierFactories.length ? reg.earlierFactories : base.earlierFactories,
84
95
  router: reg.router,
85
96
  routerBlock: reg.routerBlock,
86
97
  batcher: reg.batcher ?? base.batcher,
@@ -1,5 +1,5 @@
1
1
  import { type Address, type Hex, type PublicClient } from "viem";
2
- import type { Wallet } from "../cloak.js";
2
+ import { type Wallet } from "../cloak.js";
3
3
  import { CloakKeys } from "./crypto.js";
4
4
  import { MerkleTree } from "./tree.js";
5
5
  import type { ProofArgs, Prover } from "./prover.js";
@@ -140,6 +140,49 @@ export declare class CloakPrivate {
140
140
  txHash: Hex;
141
141
  tokens: bigint;
142
142
  }>;
143
+ /**
144
+ * One-time bid wallet `n` for `auction`. The private balance pays its deposit and a little gas through a relayed
145
+ * withdrawal, so on-chain the bid comes from a fresh address nobody can tie to this account's wallet.
146
+ */
147
+ bidWallet(auction: Address, n: number): {
148
+ n: number;
149
+ address: Address;
150
+ wallet: Wallet;
151
+ };
152
+ /** Gas a bid wallet keeps for its own transactions: the bid, a private claim, and moving what's left back in. */
153
+ bidGasReserve(): Promise<bigint>;
154
+ /** This account's bid wallets for `auction` that have been used (newest last), found from the seed alone. */
155
+ bidWallets(auction: Address): Promise<{
156
+ n: number;
157
+ address: Address;
158
+ wallet: Wallet;
159
+ }[]>;
160
+ /**
161
+ * Place a sealed bid paid from the private HYPE balance: a relayed private withdrawal funds a fresh one-time wallet
162
+ * with the deposit plus gas, and that wallet seals the bid. Keep the returned secret like any bid's (auto-reveal makes
163
+ * losing it harmless). After settlement, `finishPrivateBids` claims the tokens privately and moves the refund back.
164
+ */
165
+ bidPrivately(auction: Address, opts: {
166
+ maxPrice: bigint;
167
+ quantity: bigint;
168
+ deposit?: bigint;
169
+ }, onStatus?: Status): Promise<{
170
+ secret: import("../cloak.js").BidSecret;
171
+ bidWallet: `0x${string}`;
172
+ n: number;
173
+ fundTx: `0x${string}`;
174
+ }>;
175
+ /**
176
+ * After a launch settles: for every bid this account placed from its private balance, claim won tokens straight into
177
+ * the private vault (or, if a cap blocks that, to the bid wallet and then in), collect refunds, and move the bid
178
+ * wallet's HYPE back into the private balance. Before settlement it only moves back HYPE from a one-time wallet whose
179
+ * bid never went through. Safe to call again; it only does what's left.
180
+ */
181
+ finishPrivateBids(auction: Address, onStatus?: Status): Promise<{
182
+ claimed: number;
183
+ movedHype: bigint;
184
+ movedTokens: bigint;
185
+ }>;
143
186
  roundInfo(): Promise<{
144
187
  round: bigint;
145
188
  endsAt: number;
@@ -1,5 +1,7 @@
1
- import { encodeAbiParameters, parseEventLogs, zeroAddress } from "viem";
2
- import { cloakAuctionAbi, cloakMigratorAbi, cloakPairAbi, privateBatcherAbi, privateRouterAbi, shieldedPoolAbi } from "../abis.js";
1
+ import { createWalletClient, custom, encodeAbiParameters, parseEventLogs, zeroAddress } from "viem";
2
+ import { privateKeyToAccount } from "viem/accounts";
3
+ import { cloakAuctionAbi, cloakConfigAbi, cloakMigratorAbi, cloakPairAbi, privateBatcherAbi, privateRouterAbi, shieldedPoolAbi } from "../abis.js";
4
+ import { Cloak } from "../cloak.js";
3
5
  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
6
  import { MerkleTree } from "./tree.js";
5
7
  import { MOVE_DATA_ABI, ORDER_DATA_ABI, SWAP_DATA_ABI, buildTransaction, selectNotes, splitStandard } from "./tx.js";
@@ -406,6 +408,131 @@ export class CloakPrivate {
406
408
  throw new Error("private claim reverted");
407
409
  return { txHash: hash, tokens };
408
410
  }
411
+ // ───────────── sealed bids paid from the private balance ─────────────
412
+ /**
413
+ * One-time bid wallet `n` for `auction`. The private balance pays its deposit and a little gas through a relayed
414
+ * withdrawal, so on-chain the bid comes from a fresh address nobody can tie to this account's wallet.
415
+ */
416
+ bidWallet(auction, n) {
417
+ const account = privateKeyToAccount(this.keys.bidWalletKey(auction, n));
418
+ const chain = this.publicClient.chain;
419
+ if (!chain)
420
+ throw new Error("the public client needs a chain");
421
+ const wallet = createWalletClient({ account, chain, transport: custom({ request: (a) => this.publicClient.request(a) }) });
422
+ return { n, address: account.address, wallet };
423
+ }
424
+ /** Gas a bid wallet keeps for its own transactions: the bid, a private claim, and moving what's left back in. */
425
+ async bidGasReserve() {
426
+ const gp = await this.publicClient.getGasPrice();
427
+ return (gp < 100000000n ? 100000000n : gp) * 6000000n;
428
+ }
429
+ /** This account's bid wallets for `auction` that have been used (newest last), found from the seed alone. */
430
+ async bidWallets(auction) {
431
+ const out = [];
432
+ for (let n = 0; n < 64; n++) {
433
+ const w = this.bidWallet(auction, n);
434
+ const [nonce, bal] = await Promise.all([this.publicClient.getTransactionCount({ address: w.address }), this.publicClient.getBalance({ address: w.address })]);
435
+ if (nonce === 0 && bal === 0n)
436
+ break;
437
+ out.push(w);
438
+ }
439
+ return out;
440
+ }
441
+ /**
442
+ * Place a sealed bid paid from the private HYPE balance: a relayed private withdrawal funds a fresh one-time wallet
443
+ * with the deposit plus gas, and that wallet seals the bid. Keep the returned secret like any bid's (auto-reveal makes
444
+ * losing it harmless). After settlement, `finishPrivateBids` claims the tokens privately and moves the refund back.
445
+ */
446
+ async bidPrivately(auction, opts, onStatus = () => { }) {
447
+ const reader = new Cloak(this.publicClient);
448
+ const s = await reader.getAuction(auction);
449
+ if (s.phase !== "commit")
450
+ throw new Error("commit phase is over");
451
+ const q = reader.quoteBid(s, opts.maxPrice, opts.quantity);
452
+ const deposit = opts.deposit ?? q.deposit;
453
+ if (deposit < q.cost)
454
+ throw new Error("deposit is below the bid's cost");
455
+ const used = await this.bidWallets(auction);
456
+ const bw = this.bidWallet(auction, used.length);
457
+ const gas = await this.bidGasReserve();
458
+ // the vault keeps its withdrawal fee from what arrives, so send enough that deposit + gas land in full
459
+ const config = await this.publicClient.readContract({ address: this.router, abi: privateRouterAbi, functionName: "config" });
460
+ const feeBps = BigInt(await this.publicClient.readContract({ address: config, abi: cloakConfigAbi, functionName: "withdrawFeeBps" }));
461
+ const want = deposit + gas;
462
+ const send = (want * 10000n + (10000n - feeBps) - 1n) / (10000n - feeBps);
463
+ onStatus("Paying the deposit from your private balance…");
464
+ const fund = await this.unshieldHype(bw.address, send, onStatus);
465
+ const r = await this.publicClient.waitForTransactionReceipt({ hash: fund });
466
+ if (r.status !== "success")
467
+ throw new Error("funding the bid wallet reverted");
468
+ for (let i = 0; i < 30 && (await this.publicClient.getBalance({ address: bw.address })) < want; i++)
469
+ await new Promise((x) => setTimeout(x, 1000));
470
+ if ((await this.publicClient.getBalance({ address: bw.address })) < deposit)
471
+ throw new Error("the bid wallet wasn't funded; nothing was bid, and the HYPE is in the one-time wallet (finishPrivateBids moves it back)");
472
+ onStatus("Sealing the bid from a one-time wallet…");
473
+ const secret = await new Cloak(this.publicClient, bw.wallet).bid(auction, { maxPrice: opts.maxPrice, quantity: opts.quantity, deposit });
474
+ return { secret, bidWallet: bw.address, n: bw.n, fundTx: fund };
475
+ }
476
+ /**
477
+ * After a launch settles: for every bid this account placed from its private balance, claim won tokens straight into
478
+ * the private vault (or, if a cap blocks that, to the bid wallet and then in), collect refunds, and move the bid
479
+ * wallet's HYPE back into the private balance. Before settlement it only moves back HYPE from a one-time wallet whose
480
+ * bid never went through. Safe to call again; it only does what's left.
481
+ */
482
+ async finishPrivateBids(auction, onStatus = () => { }) {
483
+ const reader = new Cloak(this.publicClient);
484
+ const s = await reader.getAuction(auction);
485
+ let claimed = 0;
486
+ let movedHype = 0n;
487
+ let movedTokens = 0n;
488
+ const vaultOpen = !s.failed && this.markets.some((m) => m.token.toLowerCase() === s.token.toLowerCase());
489
+ for (const bw of await this.bidWallets(auction)) {
490
+ const c = new Cloak(this.publicClient, bw.wallet);
491
+ const bids = await reader.bidsOf(auction, bw.address);
492
+ // before settlement only a wallet whose bid never went through has anything to move back
493
+ if (!s.settled && bids.length > 0)
494
+ continue;
495
+ for (const b of bids) {
496
+ if (b.claimed)
497
+ continue;
498
+ onStatus(`Claiming bid #${b.id}…`);
499
+ if (b.tokens > 0n && vaultOpen) {
500
+ try {
501
+ await this.claimPrivately(bw.wallet, auction, b.id);
502
+ claimed++;
503
+ continue;
504
+ }
505
+ catch { } // over the vault's deposit cap: claim to the bid wallet instead
506
+ }
507
+ await c.claim(auction, [b.id]);
508
+ claimed++;
509
+ }
510
+ const owed = await this.publicClient.readContract({ address: auction, abi: cloakAuctionAbi, functionName: "refundOwed", args: [bw.address] }).catch(() => 0n);
511
+ if (owed > 0n) {
512
+ const h = await bw.wallet.writeContract({ address: auction, abi: cloakAuctionAbi, functionName: "withdrawRefund", args: [bw.address] });
513
+ await this.publicClient.waitForTransactionReceipt({ hash: h });
514
+ }
515
+ if (vaultOpen) {
516
+ const t = await this.publicClient.readContract({ address: s.token, abi: ERC20_ABI, functionName: "balanceOf", args: [bw.address] });
517
+ if (t > 0n) {
518
+ onStatus("Moving tokens into your private balance…");
519
+ try {
520
+ await this.shieldToken(bw.wallet, s.token, t);
521
+ movedTokens += t;
522
+ }
523
+ catch { } // a cap may block it; the tokens stay in the one-time wallet, which nobody can tie to you
524
+ }
525
+ }
526
+ const bal = await this.publicClient.getBalance({ address: bw.address });
527
+ const keep = (await this.bidGasReserve()) / 3n; // gas for this last shield
528
+ if (bal > keep * 2n) {
529
+ onStatus("Moving the refund back into your private balance…");
530
+ await this.shieldHype(bw.wallet, bal - keep);
531
+ movedHype += bal - keep;
532
+ }
533
+ }
534
+ return { claimed, movedHype, movedTokens };
535
+ }
409
536
  // ───────────── batch auctions (hide trade size) ─────────────
410
537
  async roundInfo() {
411
538
  if (!this.batcher)
@@ -70,6 +70,12 @@ export declare class CloakKeys {
70
70
  static fromSignature(sig: Hex): CloakKeys;
71
71
  key(index: number): Promise<SpendKey>;
72
72
  main(): Promise<SpendKey>;
73
+ /**
74
+ * The private key of one-time bid wallet `n` for `auction`: a fresh address that bids from the private balance, so the
75
+ * bid can't be linked to the account's wallet. Derived from this account's seed, so the same signature always finds
76
+ * it again (on any device).
77
+ */
78
+ bidWalletKey(auction: Hex, n: number): Hex;
73
79
  }
74
80
  export interface Note {
75
81
  amount: bigint;
@@ -77,6 +77,14 @@ export class CloakKeys {
77
77
  main() {
78
78
  return this.key(MAIN_KEY);
79
79
  }
80
+ /**
81
+ * The private key of one-time bid wallet `n` for `auction`: a fresh address that bids from the private balance, so the
82
+ * bid can't be linked to the account's wallet. Derived from this account's seed, so the same signature always finds
83
+ * it again (on any device).
84
+ */
85
+ bidWalletKey(auction, n) {
86
+ return keccak256(concat([toBytes("cloak-bidwallet-v1"), this.seed, toBytes(auction.toLowerCase()), toBytes(toHex(n, { size: 4 }))]));
87
+ }
80
88
  }
81
89
  export const noteCommitment = (n) => poseidon([n.amount, n.pubKey, n.blinding]);
82
90
  export async function nullifierOf(n, leafIndex, privKey) {