@cloakely/sdk 0.8.5 → 0.9.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 -0
- package/dist/abis.js +647 -0
- package/dist/bytecode.d.ts +17 -14
- package/dist/bytecode.js +1 -1
- package/dist/deployments.d.ts +2 -0
- package/dist/deployments.js +2 -0
- package/dist/drops.d.ts +11 -1
- package/dist/private/client.d.ts +75 -1
- package/dist/private/client.js +255 -1
- package/dist/private/crypto.d.ts +15 -0
- package/dist/private/crypto.js +20 -4
- package/dist/private/funds.d.ts +58 -0
- package/dist/private/funds.js +65 -0
- package/dist/private/fundsVk.d.ts +13 -0
- package/dist/private/fundsVk.js +121 -0
- package/dist/private/index.d.ts +3 -0
- package/dist/private/index.js +3 -0
- package/dist/private/prover.d.ts +29 -0
- package/dist/private/prover.js +22 -0
- package/dist/private/receive.d.ts +52 -0
- package/dist/private/receive.js +45 -0
- package/dist/private/shared.d.ts +12 -0
- package/dist/private/shared.js +70 -4
- package/dist/private/syncState.d.ts +28 -0
- package/dist/private/syncState.js +40 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +1 -0
- package/dist/spots.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Verifying key of the proof-of-funds circuit (fundsProof3), built into the SDK so no download can swap it. Generated
|
|
2
|
+
* by snarkjs (`zkey export verificationkey`); must match apps/web/public/circuits/fundsProof3_final.zkey. */
|
|
3
|
+
export declare const FUNDS_VERIFYING_KEY: {
|
|
4
|
+
readonly protocol: "groth16";
|
|
5
|
+
readonly curve: "bn128";
|
|
6
|
+
readonly nPublic: 6;
|
|
7
|
+
readonly vk_alpha_1: readonly ["16428432848801857252194528405604668803277877773566238944394625302971855135431", "16846502678714586896801519656441059708016666274385668027902869494772365009666", "1"];
|
|
8
|
+
readonly vk_beta_2: readonly [readonly ["16348171800823588416173124589066524623406261996681292662100840445103873053252", "3182164110458002340215786955198810119980427837186618912744689678939861918171"], readonly ["19687132236965066906216944365591810874384658708175106803089633851114028275753", "4920802715848186258981584729175884379674325733638798907835771393452862684714"], readonly ["1", "0"]];
|
|
9
|
+
readonly vk_gamma_2: readonly [readonly ["10857046999023057135944570762232829481370756359578518086990519993285655852781", "11559732032986387107991004021392285783925812861821192530917403151452391805634"], readonly ["8495653923123431417604973247489272438418190587263600148770280649306958101930", "4082367875863433681332203403145435568316851327593401208105741076214120093531"], readonly ["1", "0"]];
|
|
10
|
+
readonly vk_delta_2: readonly [readonly ["18266226936584141102298525449969355518905231162547171843332013239103312296001", "4608830916962325122577800770047480184405870923857658614655541326829471425080"], readonly ["11249194154404412529813650344391742555988497818201018073467031259599014123168", "13914512280164755779806285822909794056855533523214639854638450175467117157204"], readonly ["1", "0"]];
|
|
11
|
+
readonly vk_alphabeta_12: readonly [readonly [readonly ["5275725312362878540782176211860327475781113689246818544623830805017503247034", "700769043921060225711174322502145319612473365595920873303028146383045646735"], readonly ["16577533945604560505206253312979863148043263406037367789711279754781525822966", "9408338099405950952721388539539775335199747835458172188116297223654842340186"], readonly ["12663399896275491035004982800573482669934131767886952660443268164480899034271", "4432711152773877173921024337047412943791122852326272337530740732443732395954"]], readonly [readonly ["13121778684901402722679281862736806628725205381360313795132945954337708567513", "9534744673358550231812045647241180985734073058548683258847806241019905135720"], readonly ["21329152369227346659770815132468371951064045353268189088026893413117512652875", "17209195434408943681049655974234541356066884378594227002358272904159790622854"], readonly ["5346467096835895366917814311591075634165750361894629082277248282132405045579", "15508364027636868967189209273443690126627947943852338696115233789046842639684"]]];
|
|
12
|
+
readonly IC: readonly [readonly ["15721865684541064263775327890593609717066197085417368299033857075295607606836", "3649085456057800705385232234652618732288045527432498449499319231402629070287", "1"], readonly ["19115754470622874327254680090376640458468168063599461561255078642455456752436", "268352908916366244976924385725787738812432620063272109979171168398146539570", "1"], readonly ["13367241374203588791127524370615543794080582178103402583137031081521492676799", "6075251114291220999136126530229192647359235168426531430677724749887555440659", "1"], readonly ["12899883044790761759445681050714971056055170420962560870231759170060523486580", "132304697271888807017055212104280482514758688081530539577784696329754230011", "1"], readonly ["5690029548563104275791293554608531345222634194836540313233716266820964228742", "932633710232186900549426475808990580341173397726821371850203234208052200421", "1"], readonly ["10335982160444207962727295113015528447870994416465641412585067601211373725633", "7876523262068583666312816278471389215143528952414879583307520358979655103139", "1"], readonly ["16609775028617948028931502278564182714321144256052088546007480321369921357581", "14747851351181528909259712851703464531252723287386778525179881822951720765189", "1"]];
|
|
13
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** Verifying key of the proof-of-funds circuit (fundsProof3), built into the SDK so no download can swap it. Generated
|
|
2
|
+
* by snarkjs (`zkey export verificationkey`); must match apps/web/public/circuits/fundsProof3_final.zkey. */
|
|
3
|
+
export const FUNDS_VERIFYING_KEY = {
|
|
4
|
+
"protocol": "groth16",
|
|
5
|
+
"curve": "bn128",
|
|
6
|
+
"nPublic": 6,
|
|
7
|
+
"vk_alpha_1": [
|
|
8
|
+
"16428432848801857252194528405604668803277877773566238944394625302971855135431",
|
|
9
|
+
"16846502678714586896801519656441059708016666274385668027902869494772365009666",
|
|
10
|
+
"1"
|
|
11
|
+
],
|
|
12
|
+
"vk_beta_2": [
|
|
13
|
+
[
|
|
14
|
+
"16348171800823588416173124589066524623406261996681292662100840445103873053252",
|
|
15
|
+
"3182164110458002340215786955198810119980427837186618912744689678939861918171"
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
"19687132236965066906216944365591810874384658708175106803089633851114028275753",
|
|
19
|
+
"4920802715848186258981584729175884379674325733638798907835771393452862684714"
|
|
20
|
+
],
|
|
21
|
+
[
|
|
22
|
+
"1",
|
|
23
|
+
"0"
|
|
24
|
+
]
|
|
25
|
+
],
|
|
26
|
+
"vk_gamma_2": [
|
|
27
|
+
[
|
|
28
|
+
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
|
|
29
|
+
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
|
|
30
|
+
],
|
|
31
|
+
[
|
|
32
|
+
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
|
|
33
|
+
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"1",
|
|
37
|
+
"0"
|
|
38
|
+
]
|
|
39
|
+
],
|
|
40
|
+
"vk_delta_2": [
|
|
41
|
+
[
|
|
42
|
+
"18266226936584141102298525449969355518905231162547171843332013239103312296001",
|
|
43
|
+
"4608830916962325122577800770047480184405870923857658614655541326829471425080"
|
|
44
|
+
],
|
|
45
|
+
[
|
|
46
|
+
"11249194154404412529813650344391742555988497818201018073467031259599014123168",
|
|
47
|
+
"13914512280164755779806285822909794056855533523214639854638450175467117157204"
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
"1",
|
|
51
|
+
"0"
|
|
52
|
+
]
|
|
53
|
+
],
|
|
54
|
+
"vk_alphabeta_12": [
|
|
55
|
+
[
|
|
56
|
+
[
|
|
57
|
+
"5275725312362878540782176211860327475781113689246818544623830805017503247034",
|
|
58
|
+
"700769043921060225711174322502145319612473365595920873303028146383045646735"
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
"16577533945604560505206253312979863148043263406037367789711279754781525822966",
|
|
62
|
+
"9408338099405950952721388539539775335199747835458172188116297223654842340186"
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
"12663399896275491035004982800573482669934131767886952660443268164480899034271",
|
|
66
|
+
"4432711152773877173921024337047412943791122852326272337530740732443732395954"
|
|
67
|
+
]
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
[
|
|
71
|
+
"13121778684901402722679281862736806628725205381360313795132945954337708567513",
|
|
72
|
+
"9534744673358550231812045647241180985734073058548683258847806241019905135720"
|
|
73
|
+
],
|
|
74
|
+
[
|
|
75
|
+
"21329152369227346659770815132468371951064045353268189088026893413117512652875",
|
|
76
|
+
"17209195434408943681049655974234541356066884378594227002358272904159790622854"
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
"5346467096835895366917814311591075634165750361894629082277248282132405045579",
|
|
80
|
+
"15508364027636868967189209273443690126627947943852338696115233789046842639684"
|
|
81
|
+
]
|
|
82
|
+
]
|
|
83
|
+
],
|
|
84
|
+
"IC": [
|
|
85
|
+
[
|
|
86
|
+
"15721865684541064263775327890593609717066197085417368299033857075295607606836",
|
|
87
|
+
"3649085456057800705385232234652618732288045527432498449499319231402629070287",
|
|
88
|
+
"1"
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
"19115754470622874327254680090376640458468168063599461561255078642455456752436",
|
|
92
|
+
"268352908916366244976924385725787738812432620063272109979171168398146539570",
|
|
93
|
+
"1"
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
"13367241374203588791127524370615543794080582178103402583137031081521492676799",
|
|
97
|
+
"6075251114291220999136126530229192647359235168426531430677724749887555440659",
|
|
98
|
+
"1"
|
|
99
|
+
],
|
|
100
|
+
[
|
|
101
|
+
"12899883044790761759445681050714971056055170420962560870231759170060523486580",
|
|
102
|
+
"132304697271888807017055212104280482514758688081530539577784696329754230011",
|
|
103
|
+
"1"
|
|
104
|
+
],
|
|
105
|
+
[
|
|
106
|
+
"5690029548563104275791293554608531345222634194836540313233716266820964228742",
|
|
107
|
+
"932633710232186900549426475808990580341173397726821371850203234208052200421",
|
|
108
|
+
"1"
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
"10335982160444207962727295113015528447870994416465641412585067601211373725633",
|
|
112
|
+
"7876523262068583666312816278471389215143528952414879583307520358979655103139",
|
|
113
|
+
"1"
|
|
114
|
+
],
|
|
115
|
+
[
|
|
116
|
+
"16609775028617948028931502278564182714321144256052088546007480321369921357581",
|
|
117
|
+
"14747851351181528909259712851703464531252723287386778525179881822951720765189",
|
|
118
|
+
"1"
|
|
119
|
+
]
|
|
120
|
+
]
|
|
121
|
+
};
|
package/dist/private/index.d.ts
CHANGED
package/dist/private/index.js
CHANGED
package/dist/private/prover.d.ts
CHANGED
|
@@ -55,3 +55,32 @@ export declare function loadSharedProver(baseUrl?: string | string[], opts?: {
|
|
|
55
55
|
zkey: string;
|
|
56
56
|
} | null;
|
|
57
57
|
}): Promise<Prover>;
|
|
58
|
+
/** SHA-256 of the proof-of-funds circuit artifacts (`fundsProof3`: up to 3 notes) this SDK version was tested with. */
|
|
59
|
+
export declare const FUNDS_CIRCUIT_HASHES: {
|
|
60
|
+
readonly wasm: "28056a569b765c4ec62b43d4af005503000e5d7a797cf779f92e66411c3960ef";
|
|
61
|
+
readonly zkey: "5ae9a8b264b833b711a3bd3dfe64774c4944f363464592085cd564b7ca27e043";
|
|
62
|
+
};
|
|
63
|
+
/** A raw snarkjs Groth16 proof, as `snarkjs.groth16.verify` takes it. */
|
|
64
|
+
export interface RawGroth16 {
|
|
65
|
+
pi_a: string[];
|
|
66
|
+
pi_b: string[][];
|
|
67
|
+
pi_c: string[];
|
|
68
|
+
protocol?: string;
|
|
69
|
+
curve?: string;
|
|
70
|
+
}
|
|
71
|
+
export type FundsProver = (input: Record<string, unknown>) => Promise<{
|
|
72
|
+
proof: RawGroth16;
|
|
73
|
+
publicSignals: string[];
|
|
74
|
+
}>;
|
|
75
|
+
/** Where the proof-of-funds circuit is served: jsDelivr pinned to the commit that added it, then Cloak's site. */
|
|
76
|
+
export declare const FUNDS_CIRCUITS_CDN = "https://cdn.jsdelivr.net/gh/NECOKIZZ/Cloak@9c949fa0578ee20a78166f8fcfaf7633010b54d0/apps/web/public/circuits";
|
|
77
|
+
export declare const FUNDS_CIRCUITS_SOURCES: string[];
|
|
78
|
+
/** Fetch the proof-of-funds circuit (18 MB, once) and build its prover. Checked against `FUNDS_CIRCUIT_HASHES`. */
|
|
79
|
+
export declare function loadFundsProver(baseUrl?: string | string[], opts?: {
|
|
80
|
+
hashes?: {
|
|
81
|
+
wasm: string;
|
|
82
|
+
zkey: string;
|
|
83
|
+
} | null;
|
|
84
|
+
}): Promise<FundsProver>;
|
|
85
|
+
/** The proof-of-funds prover from files you already have (Node, or your own hosting). */
|
|
86
|
+
export declare function createFundsProver(wasm: Uint8Array, zkey: Uint8Array): FundsProver;
|
package/dist/private/prover.js
CHANGED
|
@@ -110,3 +110,25 @@ export async function loadSharedProver(baseUrl = CIRCUITS_SOURCES, opts = {}) {
|
|
|
110
110
|
const [wasm, zkey] = await fetchCircuit(baseUrl, ["transactionMA2x2.wasm", "transactionMA2x2_final.zkey"], want, "shared-vault circuit");
|
|
111
111
|
return createSharedProver(wasm, zkey);
|
|
112
112
|
}
|
|
113
|
+
/** SHA-256 of the proof-of-funds circuit artifacts (`fundsProof3`: up to 3 notes) this SDK version was tested with. */
|
|
114
|
+
export const FUNDS_CIRCUIT_HASHES = {
|
|
115
|
+
wasm: "28056a569b765c4ec62b43d4af005503000e5d7a797cf779f92e66411c3960ef",
|
|
116
|
+
zkey: "5ae9a8b264b833b711a3bd3dfe64774c4944f363464592085cd564b7ca27e043",
|
|
117
|
+
};
|
|
118
|
+
/** Where the proof-of-funds circuit is served: jsDelivr pinned to the commit that added it, then Cloak's site. */
|
|
119
|
+
export const FUNDS_CIRCUITS_CDN = "https://cdn.jsdelivr.net/gh/NECOKIZZ/Cloak@9c949fa0578ee20a78166f8fcfaf7633010b54d0/apps/web/public/circuits";
|
|
120
|
+
export const FUNDS_CIRCUITS_SOURCES = [FUNDS_CIRCUITS_CDN, CIRCUITS_URL];
|
|
121
|
+
/** Fetch the proof-of-funds circuit (18 MB, once) and build its prover. Checked against `FUNDS_CIRCUIT_HASHES`. */
|
|
122
|
+
export async function loadFundsProver(baseUrl = FUNDS_CIRCUITS_SOURCES, opts = {}) {
|
|
123
|
+
const want = opts.hashes === undefined ? FUNDS_CIRCUIT_HASHES : opts.hashes;
|
|
124
|
+
const [wasm, zkey] = await fetchCircuit(baseUrl, ["fundsProof3.wasm", "fundsProof3_final.zkey"], want, "proof-of-funds circuit");
|
|
125
|
+
return createFundsProver(wasm, zkey);
|
|
126
|
+
}
|
|
127
|
+
/** The proof-of-funds prover from files you already have (Node, or your own hosting). */
|
|
128
|
+
export function createFundsProver(wasm, zkey) {
|
|
129
|
+
return async (input) => {
|
|
130
|
+
const snarkjs = await import("snarkjs");
|
|
131
|
+
const { proof, publicSignals } = await snarkjs.groth16.fullProve(Object.fromEntries(Object.entries(input).map(([k, v]) => [k, str(v)])), wasm, zkey);
|
|
132
|
+
return { proof: proof, publicSignals: publicSignals };
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type Address, type Hex } from "viem";
|
|
2
|
+
import type { CloakKeys } from "./crypto.js";
|
|
3
|
+
/**
|
|
4
|
+
* Private receive addresses (CloakReceive). Each is an ordinary-looking address, derived offline from the account seed
|
|
5
|
+
* and a number n, that anyone can pay with a plain transfer: an exchange withdrawal, a bridge, any wallet. A relayer
|
|
6
|
+
* then delivers what arrived into the vault as this account's note (CloakReceive.deliver), keeping at most `maxFeeBps`.
|
|
7
|
+
* The address has no private key: only the vault (or the owner's rescue signature) can ever move what's there.
|
|
8
|
+
* Use each address once: payments to the same address are visibly linked to each other.
|
|
9
|
+
*/
|
|
10
|
+
export interface ReceiveParams {
|
|
11
|
+
pubKey: bigint;
|
|
12
|
+
blinding: bigint;
|
|
13
|
+
memo: Hex;
|
|
14
|
+
maxFeeBps: number;
|
|
15
|
+
rescuer: Address;
|
|
16
|
+
}
|
|
17
|
+
export interface ReceiveAddress {
|
|
18
|
+
n: number;
|
|
19
|
+
address: Address;
|
|
20
|
+
params: ReceiveParams;
|
|
21
|
+
}
|
|
22
|
+
/** The most a relayer may keep for delivering, unless the app asks for less (1%). */
|
|
23
|
+
export declare const RECEIVE_MAX_FEE_BPS = 100;
|
|
24
|
+
export declare function receiveSalt(p: ReceiveParams): Hex;
|
|
25
|
+
/** The address CloakReceive at `factory` gives these parameters (`boxCodeHash` = CloakReceive.boxCodeHash()). */
|
|
26
|
+
export declare function receiveAddressOf(factory: Address, boxCodeHash: Hex, p: ReceiveParams): Address;
|
|
27
|
+
/** Receive address `n` of this account (offline: nothing goes on chain until someone pays it). */
|
|
28
|
+
export declare function receiveAddress(keys: CloakKeys, factory: Address, boxCodeHash: Hex, n: number, maxFeeBps?: number): Promise<ReceiveAddress>;
|
|
29
|
+
/**
|
|
30
|
+
* The owner's signature for getting something out of receive address `n` that the vaults can't take (an NFT, a token
|
|
31
|
+
* without a vault): CloakReceive.rescue(params, target, value, data, signature). `nonce` = CloakReceive.nonces(address).
|
|
32
|
+
*/
|
|
33
|
+
export declare function signReceiveRescue(keys: CloakKeys, n: number, o: {
|
|
34
|
+
factory: Address;
|
|
35
|
+
chainId: number;
|
|
36
|
+
box: Address;
|
|
37
|
+
target: Address;
|
|
38
|
+
value?: bigint;
|
|
39
|
+
data: Hex;
|
|
40
|
+
nonce: bigint;
|
|
41
|
+
}): Promise<Hex>;
|
|
42
|
+
/** Ask a Cloak relayer to watch receive addresses and deliver what arrives (`POST /receive`). */
|
|
43
|
+
export declare function watchReceive(url: string, list: ReceiveAddress[]): Promise<void>;
|
|
44
|
+
export interface ReceiveDelivery {
|
|
45
|
+
token: Address;
|
|
46
|
+
amount: string;
|
|
47
|
+
fee: string;
|
|
48
|
+
txHash: Hex;
|
|
49
|
+
at: number;
|
|
50
|
+
}
|
|
51
|
+
/** What a relayer delivered from these receive addresses (`GET /receive?addresses=`). */
|
|
52
|
+
export declare function receiveDeliveries(url: string, addresses: Address[]): Promise<Record<string, ReceiveDelivery[]>>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { encodeAbiParameters, getContractAddress, keccak256 } from "viem";
|
|
2
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
3
|
+
/** The most a relayer may keep for delivering, unless the app asks for less (1%). */
|
|
4
|
+
export const RECEIVE_MAX_FEE_BPS = 100;
|
|
5
|
+
export function receiveSalt(p) {
|
|
6
|
+
return keccak256(encodeAbiParameters([{ type: "uint256" }, { type: "uint256" }, { type: "bytes32" }, { type: "uint16" }, { type: "address" }], [p.pubKey, p.blinding, keccak256(p.memo), p.maxFeeBps, p.rescuer]));
|
|
7
|
+
}
|
|
8
|
+
/** The address CloakReceive at `factory` gives these parameters (`boxCodeHash` = CloakReceive.boxCodeHash()). */
|
|
9
|
+
export function receiveAddressOf(factory, boxCodeHash, p) {
|
|
10
|
+
return getContractAddress({ opcode: "CREATE2", from: factory, salt: receiveSalt(p), bytecodeHash: boxCodeHash });
|
|
11
|
+
}
|
|
12
|
+
/** Receive address `n` of this account (offline: nothing goes on chain until someone pays it). */
|
|
13
|
+
export async function receiveAddress(keys, factory, boxCodeHash, n, maxFeeBps = RECEIVE_MAX_FEE_BPS) {
|
|
14
|
+
const r = await keys.receive(n);
|
|
15
|
+
const params = { pubKey: r.pubKey, blinding: r.blinding, memo: r.memo, maxFeeBps, rescuer: privateKeyToAccount(r.rescueKey).address };
|
|
16
|
+
return { n, address: receiveAddressOf(factory, boxCodeHash, params), params };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The owner's signature for getting something out of receive address `n` that the vaults can't take (an NFT, a token
|
|
20
|
+
* without a vault): CloakReceive.rescue(params, target, value, data, signature). `nonce` = CloakReceive.nonces(address).
|
|
21
|
+
*/
|
|
22
|
+
export async function signReceiveRescue(keys, n, o) {
|
|
23
|
+
const { rescueKey } = await keys.receive(n);
|
|
24
|
+
return privateKeyToAccount(rescueKey).signTypedData({
|
|
25
|
+
domain: { name: "CloakReceive", chainId: o.chainId, verifyingContract: o.factory },
|
|
26
|
+
types: { Rescue: [{ name: "box", type: "address" }, { name: "target", type: "address" }, { name: "value", type: "uint256" }, { name: "data", type: "bytes32" }, { name: "nonce", type: "uint256" }] },
|
|
27
|
+
primaryType: "Rescue",
|
|
28
|
+
message: { box: o.box, target: o.target, value: o.value ?? 0n, data: keccak256(o.data), nonce: o.nonce },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const json = (v) => JSON.stringify(v, (_, x) => (typeof x === "bigint" ? x.toString() : x));
|
|
32
|
+
/** Ask a Cloak relayer to watch receive addresses and deliver what arrives (`POST /receive`). */
|
|
33
|
+
export async function watchReceive(url, list) {
|
|
34
|
+
const res = await fetch(`${url.replace(/\/$/, "")}/receive`, { method: "POST", headers: { "content-type": "application/json" }, body: json({ addresses: list.map((x) => ({ address: x.address, params: x.params })) }), signal: AbortSignal.timeout(30_000) });
|
|
35
|
+
if (!res.ok)
|
|
36
|
+
throw new Error((await res.json().catch(() => ({}))).error ?? `watch failed (${res.status})`);
|
|
37
|
+
}
|
|
38
|
+
/** What a relayer delivered from these receive addresses (`GET /receive?addresses=`). */
|
|
39
|
+
export async function receiveDeliveries(url, addresses) {
|
|
40
|
+
const res = await fetch(`${url.replace(/\/$/, "")}/receive?addresses=${addresses.join(",")}`, { signal: AbortSignal.timeout(30_000) });
|
|
41
|
+
const out = (await res.json().catch(() => ({})));
|
|
42
|
+
if (!res.ok)
|
|
43
|
+
throw new Error(out.error ?? `status failed (${res.status})`);
|
|
44
|
+
return out.deliveries ?? {};
|
|
45
|
+
}
|
package/dist/private/shared.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type Address, type Hex } from "viem";
|
|
2
2
|
import { type Wallet } from "../cloak.js";
|
|
3
|
+
import { type PublicLeaves } from "./syncState.js";
|
|
3
4
|
import type { ProofArgs, Prover } from "./prover.js";
|
|
4
5
|
import { type ExtData, type OwnedNote } from "./tx.js";
|
|
5
6
|
import type { CloakPrivate, RelayQuote, Status } from "./client.js";
|
|
@@ -76,6 +77,8 @@ export declare class SharedVaultClient {
|
|
|
76
77
|
private scannedUpTo;
|
|
77
78
|
private notes;
|
|
78
79
|
private nextBlock;
|
|
80
|
+
/** Started from a cache rather than the chain: a gap means rescan. */
|
|
81
|
+
private seeded;
|
|
79
82
|
constructor(priv: CloakPrivate, vault: Address, router: Address, prover: () => Promise<Prover>, submit: SharedSubmitter, fromBlock?: bigint,
|
|
80
83
|
/** SharedBatcher: batch rounds that hide trade size for these tokens. */
|
|
81
84
|
batcher?: Address | undefined);
|
|
@@ -88,7 +91,16 @@ export declare class SharedVaultClient {
|
|
|
88
91
|
/** Let a settled Uniswap launch's token into the vault (anyone can; costs gas). */
|
|
89
92
|
admit(wallet: Wallet, factory: Address, launchIndex: bigint): Promise<Hex>;
|
|
90
93
|
/** Pull new notes, find which are ours and which token each holds, refresh spent flags. */
|
|
94
|
+
private inflight?;
|
|
95
|
+
/** One sync at a time (callers at the same moment share it), so a new note is never counted twice. */
|
|
91
96
|
sync(): Promise<OwnedNote[]>;
|
|
97
|
+
private syncOnce;
|
|
98
|
+
/** Start from public leaves (a keeper's GET /leaves) if nothing is synced yet and they hash to a root the vault knows. */
|
|
99
|
+
seedLeaves(src: PublicLeaves): Promise<boolean>;
|
|
100
|
+
/** What sync has learned, to keep on this device (encrypted) and hand back to `importSync`. No spending keys inside. */
|
|
101
|
+
exportSync(): string;
|
|
102
|
+
/** Load a state from `exportSync` (ignored if anything is synced already, or if a note doesn't match this account). */
|
|
103
|
+
importSync(data: string): Promise<boolean>;
|
|
92
104
|
/** Private balance of `token` in the shared vault. */
|
|
93
105
|
balance(token: Address): Promise<bigint>;
|
|
94
106
|
/** Every token this account holds privately in the shared vault. */
|
package/dist/private/shared.js
CHANGED
|
@@ -3,6 +3,7 @@ import { cloakAuctionAbi, cloakPairAbi, sharedBatcherAbi, sharedRouterAbi, share
|
|
|
3
3
|
import { Cloak } from "../cloak.js";
|
|
4
4
|
import { MAIN_KEY, encryptFullMemo, encryptRouterMemo, noteCommitment, nullifierOf, parseCloakAddress, randomField, randomKeyIndex, readMemo } from "./crypto.js";
|
|
5
5
|
import { MerkleTree } from "./tree.js";
|
|
6
|
+
import { fromJson, leavesRoot, toJson } from "./syncState.js";
|
|
6
7
|
import { ORDER_DATA_ABI, buildTransaction, selectNotes, splitStandard } from "./tx.js";
|
|
7
8
|
/**
|
|
8
9
|
* The shared launch-token vault (testnet experiment): one private vault for every Cloak launch token that opens on
|
|
@@ -100,6 +101,8 @@ export class SharedVaultClient {
|
|
|
100
101
|
scannedUpTo = 0;
|
|
101
102
|
notes = [];
|
|
102
103
|
nextBlock;
|
|
104
|
+
/** Started from a cache rather than the chain: a gap means rescan. */
|
|
105
|
+
seeded = false;
|
|
103
106
|
constructor(priv, vault, router, prover, submit, fromBlock = 0n,
|
|
104
107
|
/** SharedBatcher: batch rounds that hide trade size for these tokens. */
|
|
105
108
|
batcher) {
|
|
@@ -119,14 +122,17 @@ export class SharedVaultClient {
|
|
|
119
122
|
async loadTokens() {
|
|
120
123
|
const c = { address: this.vault, abi: sharedVaultAbi };
|
|
121
124
|
const n = Number(await this.pub.readContract({ ...c, functionName: "tokensCount" }));
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
const from = this.tokens.length;
|
|
126
|
+
const more = await Promise.all(Array.from({ length: Math.max(0, n - from) }, async (_, j) => {
|
|
127
|
+
const token = await this.pub.readContract({ ...c, functionName: "tokens", args: [BigInt(from + j)] });
|
|
124
128
|
const [market, symbol] = await Promise.all([
|
|
125
129
|
this.pub.readContract({ ...c, functionName: "marketOf", args: [token] }),
|
|
126
130
|
this.pub.readContract({ address: token, abi: ERC20, functionName: "symbol" }).catch(() => "?"),
|
|
127
131
|
]);
|
|
128
|
-
|
|
129
|
-
}
|
|
132
|
+
return { token, market, symbol };
|
|
133
|
+
}));
|
|
134
|
+
if (this.tokens.length === from)
|
|
135
|
+
this.tokens.push(...more); // a parallel call may have loaded them already
|
|
130
136
|
return this.tokens;
|
|
131
137
|
}
|
|
132
138
|
/** Whether `token` is in the shared vault (reads the vault's list if it isn't known yet). */
|
|
@@ -152,7 +158,13 @@ export class SharedVaultClient {
|
|
|
152
158
|
return h;
|
|
153
159
|
}
|
|
154
160
|
/** Pull new notes, find which are ours and which token each holds, refresh spent flags. */
|
|
161
|
+
inflight;
|
|
162
|
+
/** One sync at a time (callers at the same moment share it), so a new note is never counted twice. */
|
|
155
163
|
async sync() {
|
|
164
|
+
this.inflight ??= this.syncOnce().finally(() => (this.inflight = undefined));
|
|
165
|
+
return this.inflight;
|
|
166
|
+
}
|
|
167
|
+
async syncOnce() {
|
|
156
168
|
if (!this.tokens.length)
|
|
157
169
|
await this.loadTokens();
|
|
158
170
|
const latest = await this.pub.getBlockNumber({ cacheTime: 0 });
|
|
@@ -170,6 +182,15 @@ export class SharedVaultClient {
|
|
|
170
182
|
let n = 0;
|
|
171
183
|
while (n < this.leaves.length && this.leaves[n].index === n)
|
|
172
184
|
n++;
|
|
185
|
+
if (n < this.leaves.length && this.seeded) {
|
|
186
|
+
// the cache stopped short of where the chain picked up: forget it and scan from the start
|
|
187
|
+
this.leaves = [];
|
|
188
|
+
this.scannedUpTo = 0;
|
|
189
|
+
this.notes = [];
|
|
190
|
+
this.nextBlock = this.fromBlock;
|
|
191
|
+
this.seeded = false;
|
|
192
|
+
return this.syncOnce();
|
|
193
|
+
}
|
|
173
194
|
let reloaded = false;
|
|
174
195
|
for (const l of this.leaves.slice(this.scannedUpTo, n)) {
|
|
175
196
|
const m = readMemo(this.priv.keys.encPriv, l.memo);
|
|
@@ -203,6 +224,51 @@ export class SharedVaultClient {
|
|
|
203
224
|
this.notes.forEach((x, i) => (x.spent = Boolean(flags[i])));
|
|
204
225
|
return this.notes.filter((x) => !x.spent);
|
|
205
226
|
}
|
|
227
|
+
/** Start from public leaves (a keeper's GET /leaves) if nothing is synced yet and they hash to a root the vault knows. */
|
|
228
|
+
async seedLeaves(src) {
|
|
229
|
+
if (this.leaves.length || this.scannedUpTo)
|
|
230
|
+
return false;
|
|
231
|
+
const root = await leavesRoot(src.leaves);
|
|
232
|
+
if (root === null)
|
|
233
|
+
return false;
|
|
234
|
+
const known = await this.pub.readContract({ address: this.vault, abi: sharedVaultAbi, functionName: "isKnownRoot", args: [root] }).catch(() => false);
|
|
235
|
+
if (!known)
|
|
236
|
+
return false;
|
|
237
|
+
this.leaves = src.leaves.map((l) => ({ ...l }));
|
|
238
|
+
this.nextBlock = src.block + 1n;
|
|
239
|
+
this.seeded = true;
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
/** What sync has learned, to keep on this device (encrypted) and hand back to `importSync`. No spending keys inside. */
|
|
243
|
+
exportSync() {
|
|
244
|
+
return toJson({ v: 1, leaves: this.leaves.map((l, i) => (i < this.scannedUpTo ? { ...l, memo: "0x" } : l)), scannedUpTo: this.scannedUpTo, nextBlock: this.nextBlock, notes: this.notes.map(({ privKey: _, ...n }) => n) });
|
|
245
|
+
}
|
|
246
|
+
/** Load a state from `exportSync` (ignored if anything is synced already, or if a note doesn't match this account). */
|
|
247
|
+
async importSync(data) {
|
|
248
|
+
if (this.leaves.length || this.scannedUpTo)
|
|
249
|
+
return false;
|
|
250
|
+
try {
|
|
251
|
+
const o = fromJson(data);
|
|
252
|
+
if (o.v !== 1)
|
|
253
|
+
return false;
|
|
254
|
+
const notes = [];
|
|
255
|
+
for (const n of o.notes) {
|
|
256
|
+
const key = await this.priv.keys.key(n.keyIndex);
|
|
257
|
+
if (key.pubKey !== n.pubKey)
|
|
258
|
+
return false;
|
|
259
|
+
notes.push({ ...n, privKey: key.privKey });
|
|
260
|
+
}
|
|
261
|
+
this.leaves = o.leaves;
|
|
262
|
+
this.scannedUpTo = o.scannedUpTo;
|
|
263
|
+
this.nextBlock = o.nextBlock;
|
|
264
|
+
this.notes = notes;
|
|
265
|
+
this.seeded = true;
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
206
272
|
/** Private balance of `token` in the shared vault. */
|
|
207
273
|
async balance(token) {
|
|
208
274
|
const t = asNum(token);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Address, Hex } from "viem";
|
|
2
|
+
/** One leaf of a vault's tree, as its NewCommitment event has it. */
|
|
3
|
+
export interface Leaf {
|
|
4
|
+
index: number;
|
|
5
|
+
commitment: bigint;
|
|
6
|
+
memo: Hex;
|
|
7
|
+
}
|
|
8
|
+
/** A vault's public leaves up to `block` (from a keeper's GET /leaves). */
|
|
9
|
+
export interface PublicLeaves {
|
|
10
|
+
pool: Address;
|
|
11
|
+
block: bigint;
|
|
12
|
+
leaves: Leaf[];
|
|
13
|
+
}
|
|
14
|
+
/** Leaves per tree (each vault starts a new tree, an epoch, when one fills). */
|
|
15
|
+
export declare const LEAVES_PER_EPOCH: number;
|
|
16
|
+
/**
|
|
17
|
+
* Every vault's public leaves from a keeper's `GET /leaves`, keyed by lowercase vault address (empty on any error, so
|
|
18
|
+
* callers just scan the chain). Nothing in the request identifies the caller's notes: it is the same for everyone.
|
|
19
|
+
*/
|
|
20
|
+
export declare function fetchLeaves(keeperUrl: string): Promise<Map<string, PublicLeaves>>;
|
|
21
|
+
/**
|
|
22
|
+
* Whether `leaves` can stand in for scanning the chain: indexes 0, 1, 2… with no gap, all in the first tree, and
|
|
23
|
+
* hashing to `root` (the caller checks the vault knows that root). The memos aren't covered by the root: a bad memo
|
|
24
|
+
* can only hide one of your notes from the scan, never move money, and the next full rescan finds it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function leavesRoot(leaves: Leaf[]): Promise<bigint | null>;
|
|
27
|
+
export declare const toJson: (v: unknown) => string;
|
|
28
|
+
export declare const fromJson: <T>(s: string) => T;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { MerkleTree } from "./tree.js";
|
|
2
|
+
/** Leaves per tree (each vault starts a new tree, an epoch, when one fills). */
|
|
3
|
+
export const LEAVES_PER_EPOCH = 2 ** 20;
|
|
4
|
+
/**
|
|
5
|
+
* Every vault's public leaves from a keeper's `GET /leaves`, keyed by lowercase vault address (empty on any error, so
|
|
6
|
+
* callers just scan the chain). Nothing in the request identifies the caller's notes: it is the same for everyone.
|
|
7
|
+
*/
|
|
8
|
+
export async function fetchLeaves(keeperUrl) {
|
|
9
|
+
const out = new Map();
|
|
10
|
+
try {
|
|
11
|
+
const r = await fetch(`${keeperUrl.replace(/\/$/, "")}/leaves`);
|
|
12
|
+
if (!r.ok)
|
|
13
|
+
return out;
|
|
14
|
+
const body = (await r.json());
|
|
15
|
+
for (const v of body.vaults ?? []) {
|
|
16
|
+
out.set(v.pool.toLowerCase(), { pool: v.pool, block: BigInt(v.block), leaves: v.leaves.map(([index, c, memo]) => ({ index, commitment: BigInt(c), memo })) });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch { }
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Whether `leaves` can stand in for scanning the chain: indexes 0, 1, 2… with no gap, all in the first tree, and
|
|
24
|
+
* hashing to `root` (the caller checks the vault knows that root). The memos aren't covered by the root: a bad memo
|
|
25
|
+
* can only hide one of your notes from the scan, never move money, and the next full rescan finds it.
|
|
26
|
+
*/
|
|
27
|
+
export async function leavesRoot(leaves) {
|
|
28
|
+
if (!leaves.length || leaves.length > LEAVES_PER_EPOCH)
|
|
29
|
+
return null;
|
|
30
|
+
for (let i = 0; i < leaves.length; i++)
|
|
31
|
+
if (leaves[i].index !== i)
|
|
32
|
+
return null;
|
|
33
|
+
const t = new MerkleTree();
|
|
34
|
+
t.leaves = leaves.map((l) => l.commitment);
|
|
35
|
+
return t.root();
|
|
36
|
+
}
|
|
37
|
+
const revive = (_k, v) => (v && typeof v === "object" && "$n" in v ? BigInt(v.$n) : v);
|
|
38
|
+
const replace = (_k, v) => (typeof v === "bigint" ? { $n: v.toString() } : v);
|
|
39
|
+
export const toJson = (v) => JSON.stringify(v, replace);
|
|
40
|
+
export const fromJson = (s) => JSON.parse(s, revive);
|
package/dist/registry.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ export interface CloakRegistry {
|
|
|
44
44
|
sharedRouter?: Address;
|
|
45
45
|
/** SharedBatcher: batch rounds for the shared vault's tokens. */
|
|
46
46
|
sharedBatcher?: Address;
|
|
47
|
+
/** CloakReceive: private receive addresses (a plain transfer becomes the owner's note). */
|
|
48
|
+
receive?: Address;
|
|
47
49
|
factory: Address;
|
|
48
50
|
factoryBlock: bigint;
|
|
49
51
|
/** Earlier factories of the current version (a factory added next to the first), newest first. */
|
package/dist/registry.js
CHANGED
|
@@ -36,6 +36,7 @@ export async function readRegistry(publicClient, config) {
|
|
|
36
36
|
sharedBlock: newest("shared")?.startBlock,
|
|
37
37
|
sharedRouter: newest("sharedRouter")?.addr,
|
|
38
38
|
sharedBatcher: newest("sharedBatcher")?.addr,
|
|
39
|
+
receive: newest("receive")?.addr,
|
|
39
40
|
factory: factory.addr,
|
|
40
41
|
factoryBlock: factory.startBlock,
|
|
41
42
|
earlierFactories: entries.filter((e) => e.kind === "factory" && e.version === factory.version && e.addr !== factory.addr).reverse().map((e) => e.addr),
|
package/dist/spots.js
CHANGED
|
@@ -36,7 +36,7 @@ export class CloakSpots {
|
|
|
36
36
|
if (!partner || partner.length > 200)
|
|
37
37
|
throw new Error("Name the partner collection (up to 200 characters).");
|
|
38
38
|
const days = o.walletDays ?? SPOT_DEFAULT_WALLET_DAYS;
|
|
39
|
-
if (!(days
|
|
39
|
+
if (!(days >= 1 && days <= SPOT_MAX_WALLET_DAYS))
|
|
40
40
|
throw new Error(`Winners get 1 to ${SPOT_MAX_WALLET_DAYS} days to name wallets.`);
|
|
41
41
|
const { drop, txHash } = await this.drops.create({ ...o, baseURI: o.baseURI ?? "", maxPerBid: o.maxPerBid ?? 1, spots: { partner } });
|
|
42
42
|
const s = await this.drops.get(drop);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloakely/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Cloak on Elysium: sealed-bid token launches, sealed NFT drops and WL spot sales, and private trading (shielded balances, private swaps, batch auctions, private sends) with on-device zero-knowledge proofs.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloak",
|