@cloakely/sdk 0.3.0 → 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/abis.d.ts +498 -135
- package/dist/abis.js +636 -158
- package/dist/bytecode.d.ts +8 -7
- package/dist/bytecode.js +1 -1
- package/dist/cloak.d.ts +33 -0
- package/dist/cloak.js +2 -2
- package/dist/deployments.d.ts +20 -5
- package/dist/deployments.js +71 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/private/client.d.ts +28 -4
- package/dist/private/client.js +88 -22
- package/dist/private/crypto.d.ts +23 -5
- package/dist/private/crypto.js +14 -5
- package/dist/registry.d.ts +46 -0
- package/dist/registry.js +53 -0
- package/package.json +1 -1
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
|
}
|
package/dist/deployments.d.ts
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
|
-
import type { Address } from "viem";
|
|
2
|
-
/** An earlier deployment on the same chain: its launches still list, and
|
|
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
|
-
/**
|
|
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 (
|
|
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
|
-
/**
|
|
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>;
|
package/dist/deployments.js
CHANGED
|
@@ -1,31 +1,48 @@
|
|
|
1
|
+
import { privateRouterAbi } from "./abis.js";
|
|
2
|
+
import { readRegistry } from "./registry.js";
|
|
1
3
|
export const deployments = {
|
|
2
|
-
// Cloak
|
|
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:
|
|
6
|
-
config: "
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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: "
|
|
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
|
-
|
|
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
package/dist/index.js
CHANGED
package/dist/private/client.d.ts
CHANGED
|
@@ -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
|
|
74
|
-
*
|
|
75
|
-
*
|
|
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;
|
package/dist/private/client.js
CHANGED
|
@@ -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`). */
|
|
@@ -51,9 +53,11 @@ export function walletSubmitter(publicClient, wallet, router, batcher, migrator)
|
|
|
51
53
|
if (call.kind === "placeOrder" && !batcher)
|
|
52
54
|
throw new Error("no batcher configured");
|
|
53
55
|
// Pad the estimate: state can change between estimation and inclusion (e.g. an order landing in a new batch round
|
|
54
|
-
// writes fresh storage, ~60k more gas). Unused gas is refunded.
|
|
56
|
+
// writes fresh storage, ~60k more gas). Unused gas is refunded. Estimate from the address, not the local account:
|
|
57
|
+
// with a local account viem estimates twice, the second time capped at the first result, so a round turning over
|
|
58
|
+
// in between made that second estimate run out of gas.
|
|
55
59
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
-
const est = await publicClient.estimateContractGas({ ...req, account: wallet.account });
|
|
60
|
+
const est = await publicClient.estimateContractGas({ ...req, account: wallet.account.address });
|
|
57
61
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
62
|
const hash = await wallet.writeContract({ ...req, gas: (est * 13n) / 10n + 100000n });
|
|
59
63
|
const r = await publicClient.waitForTransactionReceipt({ hash });
|
|
@@ -94,14 +98,30 @@ export class CloakPrivate {
|
|
|
94
98
|
this.migrator = migrator;
|
|
95
99
|
}
|
|
96
100
|
/**
|
|
97
|
-
* Derive the private
|
|
98
|
-
*
|
|
99
|
-
*
|
|
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).
|
|
100
104
|
*/
|
|
101
105
|
static async keysFromWallet(wallet, opts) {
|
|
102
|
-
|
|
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 };
|
|
103
123
|
const sig = await wallet.signTypedData(req);
|
|
104
|
-
if (
|
|
124
|
+
if (verify) {
|
|
105
125
|
const again = await wallet.signTypedData(req);
|
|
106
126
|
if (again.toLowerCase() !== sig.toLowerCase()) {
|
|
107
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.");
|
|
@@ -114,20 +134,42 @@ export class CloakPrivate {
|
|
|
114
134
|
const sig = await wallet.signMessage({ account: wallet.account, message: ACCOUNT_MESSAGE });
|
|
115
135
|
return CloakKeys.fromSignature(sig);
|
|
116
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
|
+
*/
|
|
117
141
|
async loadMarkets() {
|
|
118
142
|
const c = { address: this.router, abi: privateRouterAbi };
|
|
119
|
-
const n = await
|
|
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
|
+
])));
|
|
120
152
|
const out = [];
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const [pool, pair] =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
}
|
|
128
168
|
}
|
|
129
|
-
|
|
130
|
-
|
|
169
|
+
if (!hype)
|
|
170
|
+
throw new Error("the router has no HYPE vault");
|
|
171
|
+
this.markets = [hype, ...out];
|
|
172
|
+
return this.markets;
|
|
131
173
|
}
|
|
132
174
|
get hypeMarket() {
|
|
133
175
|
const m = this.markets[0];
|
|
@@ -341,6 +383,29 @@ export class CloakPrivate {
|
|
|
341
383
|
throw new Error("shield reverted");
|
|
342
384
|
return hash;
|
|
343
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
|
+
}
|
|
344
409
|
// ───────────── batch auctions (hide trade size) ─────────────
|
|
345
410
|
async roundInfo() {
|
|
346
411
|
if (!this.batcher)
|
|
@@ -569,11 +634,12 @@ export class CloakPrivate {
|
|
|
569
634
|
async migrateFrom(legacy, onStatus = () => { }, opts = {}) {
|
|
570
635
|
const migrator = opts.migrator ?? this.migratorAddress();
|
|
571
636
|
const moved = [];
|
|
572
|
-
const markets = await legacy.loadMarkets();
|
|
637
|
+
const markets = [...(await legacy.loadMarkets()), ...legacy.assets];
|
|
573
638
|
await this.loadMarkets();
|
|
639
|
+
const here = [...this.markets, ...this.assets];
|
|
574
640
|
for (const m of markets) {
|
|
575
|
-
if (!
|
|
576
|
-
continue; // no
|
|
641
|
+
if (!here.some((x) => x.token.toLowerCase() === m.token.toLowerCase()))
|
|
642
|
+
continue; // no vault here yet
|
|
577
643
|
for (;;) {
|
|
578
644
|
const live = (await legacy.sync(m.pool)).sort((a, b) => (a.amount > b.amount ? -1 : 1));
|
|
579
645
|
if (!live.length)
|
package/dist/private/crypto.d.ts
CHANGED
|
@@ -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
|
|
14
|
-
*
|
|
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
|
|
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";
|
package/dist/private/crypto.js
CHANGED
|
@@ -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
|
|
25
|
-
*
|
|
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
|
|
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>;
|
package/dist/registry.js
ADDED
|
@@ -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
|
+
}
|