@kohaku-eth/tornado-cash 0.0.1 → 0.0.2-alpha.9
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/index.d.ts +528 -0
- package/dist/index.js +778 -0
- package/dist/state-manager.worker.js +28508 -0
- package/dist/state-manager.worker.node.cjs +26326 -0
- package/dist/worker-loader.browser.d.ts +152 -0
- package/dist/worker-loader.browser.js +13 -0
- package/dist/worker-loader.node.d.ts +153 -0
- package/dist/worker-loader.node.js +22 -0
- package/package.json +109 -11
- package/index.js +0 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,778 @@
|
|
|
1
|
+
import { createSelector, createAsyncThunk } from '@reduxjs/toolkit';
|
|
2
|
+
import { createTx } from '@kohaku-eth/provider';
|
|
3
|
+
import { encodeFunctionData, toHex, erc20Abi } from 'viem';
|
|
4
|
+
import { proxy } from 'comlink';
|
|
5
|
+
import { loadStateManagerWorker } from '#worker-loader';
|
|
6
|
+
import { BundlerClient, TornadoBuilder } from '@privacy-paymasters/sdk';
|
|
7
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
8
|
+
import { numberToBytesLE, bytesToNumberLE, concatBytes } from '@noble/curves/utils.js';
|
|
9
|
+
import { keccak_256 } from '@noble/hashes/sha3.js';
|
|
10
|
+
import { pedersenHash as pedersenHash$1, Point } from 'micro-zk-proofs/pedersen.js';
|
|
11
|
+
import 'micro-zk-proofs/mimcsponge.js';
|
|
12
|
+
import 'fixed-merkle-tree';
|
|
13
|
+
|
|
14
|
+
// src/state/thunks/getDepositPayloadThunk.ts
|
|
15
|
+
|
|
16
|
+
// src/data/abis/pool.abi.ts
|
|
17
|
+
var poolAbi = [
|
|
18
|
+
{
|
|
19
|
+
type: "function",
|
|
20
|
+
name: "getLastRoot",
|
|
21
|
+
inputs: [],
|
|
22
|
+
outputs: [
|
|
23
|
+
{
|
|
24
|
+
name: "",
|
|
25
|
+
type: "bytes32",
|
|
26
|
+
internalType: "bytes32"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
stateMutability: "view"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
type: "function",
|
|
33
|
+
name: "currentRootIndex",
|
|
34
|
+
inputs: [],
|
|
35
|
+
outputs: [
|
|
36
|
+
{
|
|
37
|
+
name: "",
|
|
38
|
+
type: "uint32",
|
|
39
|
+
internalType: "uint32"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
stateMutability: "view"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: "function",
|
|
46
|
+
name: "roots",
|
|
47
|
+
inputs: [{
|
|
48
|
+
type: "uint256"
|
|
49
|
+
}],
|
|
50
|
+
outputs: [
|
|
51
|
+
{
|
|
52
|
+
name: "",
|
|
53
|
+
type: "bytes32",
|
|
54
|
+
internalType: "bytes32"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
stateMutability: "view"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: "function",
|
|
61
|
+
name: "denomination",
|
|
62
|
+
inputs: [],
|
|
63
|
+
outputs: [
|
|
64
|
+
{
|
|
65
|
+
type: "uint256",
|
|
66
|
+
internalType: "uint256"
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
stateMutability: "view"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: "function",
|
|
73
|
+
name: "deposit",
|
|
74
|
+
inputs: [
|
|
75
|
+
{
|
|
76
|
+
name: "_commitment",
|
|
77
|
+
type: "bytes32",
|
|
78
|
+
internalType: "bytes32"
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
outputs: [],
|
|
82
|
+
stateMutability: "payable"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "function",
|
|
86
|
+
name: "withdraw",
|
|
87
|
+
inputs: [
|
|
88
|
+
{ name: "_proof", type: "bytes", internalType: "bytes" },
|
|
89
|
+
{ name: "_root", type: "bytes32", internalType: "bytes32" },
|
|
90
|
+
{ name: "_nullifierHash", type: "bytes32", internalType: "bytes32" },
|
|
91
|
+
{ name: "_recipient", type: "address", internalType: "address" },
|
|
92
|
+
{ name: "_relayer", type: "address", internalType: "address" },
|
|
93
|
+
{ name: "_fee", type: "uint256", internalType: "uint256" },
|
|
94
|
+
{ name: "_refund", type: "uint256", internalType: "uint256" }
|
|
95
|
+
],
|
|
96
|
+
outputs: [],
|
|
97
|
+
stateMutability: "payable"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
type: "function",
|
|
101
|
+
name: "ROOT_HISTORY_SIZE",
|
|
102
|
+
inputs: [],
|
|
103
|
+
outputs: [{
|
|
104
|
+
type: "uint32"
|
|
105
|
+
}],
|
|
106
|
+
stateMutability: "view"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
type: "function",
|
|
110
|
+
name: "isKnownRoot",
|
|
111
|
+
inputs: [{
|
|
112
|
+
name: "_root",
|
|
113
|
+
type: "bytes32"
|
|
114
|
+
}],
|
|
115
|
+
outputs: [{
|
|
116
|
+
type: "bool"
|
|
117
|
+
}],
|
|
118
|
+
stateMutability: "view"
|
|
119
|
+
}
|
|
120
|
+
];
|
|
121
|
+
function prepareNativeShield({ commitment, poolAddress, poolDenomination }) {
|
|
122
|
+
const data = encodeFunctionData({
|
|
123
|
+
abi: poolAbi,
|
|
124
|
+
functionName: "deposit",
|
|
125
|
+
args: [toHex(commitment, { size: 32 })]
|
|
126
|
+
});
|
|
127
|
+
return createTx(poolAddress, data, poolDenomination);
|
|
128
|
+
}
|
|
129
|
+
function prepareErc20Shield({ commitment, tokenAddress, poolAddress, denomination }) {
|
|
130
|
+
const approveData = encodeFunctionData({
|
|
131
|
+
abi: erc20Abi,
|
|
132
|
+
functionName: "approve",
|
|
133
|
+
args: [poolAddress, denomination]
|
|
134
|
+
});
|
|
135
|
+
const depositData = encodeFunctionData({
|
|
136
|
+
abi: poolAbi,
|
|
137
|
+
functionName: "deposit",
|
|
138
|
+
args: [toHex(commitment, { size: 32 })]
|
|
139
|
+
});
|
|
140
|
+
return [
|
|
141
|
+
createTx(tokenAddress, approveData),
|
|
142
|
+
createTx(poolAddress, depositData)
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
function addressToHex(address, padding = 40) {
|
|
146
|
+
return `0x${address.toString(16).padStart(padding, "0")}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// src/state/utils/serialize.utils.ts
|
|
150
|
+
var deserialize = (data, strings) => {
|
|
151
|
+
switch (typeof data) {
|
|
152
|
+
case "object": {
|
|
153
|
+
if (data === null) {
|
|
154
|
+
return data;
|
|
155
|
+
}
|
|
156
|
+
if (data instanceof Array) {
|
|
157
|
+
if (data.length === 2) {
|
|
158
|
+
const stringsData = strings || [];
|
|
159
|
+
return [
|
|
160
|
+
deserialize(data[0], stringsData[0]),
|
|
161
|
+
deserialize(data[1], stringsData[1])
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
return data.map(
|
|
165
|
+
(d) => deserialize(d, strings)
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return Object.entries(data).reduce(
|
|
169
|
+
(deserializedObject, [key, value]) => {
|
|
170
|
+
deserializedObject[key] = deserialize(
|
|
171
|
+
value,
|
|
172
|
+
(strings || {})[key]
|
|
173
|
+
);
|
|
174
|
+
return deserializedObject;
|
|
175
|
+
},
|
|
176
|
+
{}
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
case "string":
|
|
180
|
+
return strings === true ? data : BigInt(data);
|
|
181
|
+
default:
|
|
182
|
+
return data;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// src/state/utils/selectors.utils.ts
|
|
187
|
+
var selectEntityMap = (mapFn, transform = deserialize) => createSelector([mapFn], (tuple) => new Map(tuple.map(transform)));
|
|
188
|
+
createSelector(
|
|
189
|
+
[(s) => s.deposits.depositsTuples],
|
|
190
|
+
(tuples) => {
|
|
191
|
+
const deserialized = deserialize(tuples);
|
|
192
|
+
return new Map(
|
|
193
|
+
deserialized.map(
|
|
194
|
+
([poolAddress, innerTuples]) => [poolAddress, new Map(innerTuples)]
|
|
195
|
+
)
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
var instanceRegistryInfoSelector = createSelector(
|
|
200
|
+
[(state) => state.instanceRegistryInfo],
|
|
201
|
+
(poolInfo) => deserialize(poolInfo, { ensSubdomainKey: true })
|
|
202
|
+
);
|
|
203
|
+
var poolsSelector = selectEntityMap(
|
|
204
|
+
(s) => s.pools.poolsTuples,
|
|
205
|
+
deserialize
|
|
206
|
+
);
|
|
207
|
+
selectEntityMap(
|
|
208
|
+
(s) => s.withdrawals.withdrawalsTuples,
|
|
209
|
+
deserialize
|
|
210
|
+
);
|
|
211
|
+
selectEntityMap(
|
|
212
|
+
(s) => s.assets.assetsTuples,
|
|
213
|
+
(assetsTuple) => deserialize(assetsTuple, [void 0, { name: true, symbol: true }])
|
|
214
|
+
);
|
|
215
|
+
createSelector(
|
|
216
|
+
[(state) => state.relayers.relayersTuples],
|
|
217
|
+
(tuples) => tuples.map(
|
|
218
|
+
([, relayer]) => deserialize(relayer, { ensName: true, hostname: true })
|
|
219
|
+
)
|
|
220
|
+
);
|
|
221
|
+
var userSecretsSelector = selectEntityMap(
|
|
222
|
+
(s) => s.userSecrets.byPool,
|
|
223
|
+
deserialize
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// src/state/thunks/getDepositPayloadThunk.ts
|
|
227
|
+
var DepositStrategy = /* @__PURE__ */ ((DepositStrategy2) => {
|
|
228
|
+
DepositStrategy2[DepositStrategy2["MaxAnonimitySet"] = 0] = "MaxAnonimitySet";
|
|
229
|
+
DepositStrategy2[DepositStrategy2["MinFee"] = 1] = "MinFee";
|
|
230
|
+
return DepositStrategy2;
|
|
231
|
+
})(DepositStrategy || {});
|
|
232
|
+
var calculatePoolsDeposits = (pools, value, strategy) => {
|
|
233
|
+
if (!pools.length) {
|
|
234
|
+
throw new Error("No pools to deposit into");
|
|
235
|
+
}
|
|
236
|
+
const poolsToDepositInto = [];
|
|
237
|
+
switch (strategy) {
|
|
238
|
+
case 0 /* MaxAnonimitySet */: {
|
|
239
|
+
const lowToHighDenominationPools = pools.sort((a, b) => Number(a.denomination - b.denomination));
|
|
240
|
+
const lowestDenominationPool = lowToHighDenominationPools[0];
|
|
241
|
+
const depositsCount = Number(value / lowestDenominationPool.denomination);
|
|
242
|
+
poolsToDepositInto.push({
|
|
243
|
+
...lowestDenominationPool,
|
|
244
|
+
depositsCount
|
|
245
|
+
});
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
case 1 /* MinFee */: {
|
|
249
|
+
const highToLowDenominationPools = pools.sort((a, b) => Number(b.denomination - a.denomination));
|
|
250
|
+
highToLowDenominationPools.reduce((remainingAmount, pool) => {
|
|
251
|
+
const depositsForThisPool = remainingAmount / pool.denomination;
|
|
252
|
+
if (depositsForThisPool > 0n) {
|
|
253
|
+
poolsToDepositInto.push({
|
|
254
|
+
...pool,
|
|
255
|
+
depositsCount: Number(depositsForThisPool)
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return remainingAmount - depositsForThisPool * pool.denomination;
|
|
259
|
+
}, value);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return poolsToDepositInto;
|
|
264
|
+
};
|
|
265
|
+
createAsyncThunk(
|
|
266
|
+
"deposits/getPayload",
|
|
267
|
+
async ({ secretManager, asset, amount, strategy }, { getState }) => {
|
|
268
|
+
const state = getState();
|
|
269
|
+
const pools = poolsSelector(state);
|
|
270
|
+
const { chainId } = instanceRegistryInfoSelector(state);
|
|
271
|
+
const userSecrets = userSecretsSelector(state);
|
|
272
|
+
const poolsCandidates = Array.from(pools.values()).filter((p) => p.asset === asset);
|
|
273
|
+
if (poolsCandidates.length === 0) throw new Error(`No pools available for asset ${addressToHex(asset)}`);
|
|
274
|
+
const poolsToDepositInto = calculatePoolsDeposits(poolsCandidates, amount, strategy);
|
|
275
|
+
const depositInfo = await Promise.all(poolsToDepositInto.map(async (pool) => {
|
|
276
|
+
const startIndex = userSecrets.get(pool.address)?.length ?? 0;
|
|
277
|
+
const count = pool.depositsCount;
|
|
278
|
+
const secrets = await Promise.all(
|
|
279
|
+
Array.from(
|
|
280
|
+
{ length: count },
|
|
281
|
+
(_, i) => secretManager.getDepositSecrets({
|
|
282
|
+
chainId,
|
|
283
|
+
depositIndex: startIndex + i,
|
|
284
|
+
poolAddress: pool.address
|
|
285
|
+
})
|
|
286
|
+
)
|
|
287
|
+
);
|
|
288
|
+
return {
|
|
289
|
+
...pool,
|
|
290
|
+
secrets
|
|
291
|
+
};
|
|
292
|
+
}));
|
|
293
|
+
const newDeposits = depositInfo.reduce((txData, { secrets, ...pool }) => {
|
|
294
|
+
const poolAddressHex = addressToHex(pool.address);
|
|
295
|
+
if (!pool.isERC20) {
|
|
296
|
+
const newDeposits3 = secrets.map(
|
|
297
|
+
({ commitment }) => prepareNativeShield({
|
|
298
|
+
commitment,
|
|
299
|
+
poolAddress: poolAddressHex,
|
|
300
|
+
poolDenomination: pool.denomination
|
|
301
|
+
})
|
|
302
|
+
);
|
|
303
|
+
return txData.concat(newDeposits3);
|
|
304
|
+
}
|
|
305
|
+
const assetHex = addressToHex(pool.asset);
|
|
306
|
+
const newDeposits2 = secrets.flatMap(
|
|
307
|
+
({ commitment }) => prepareErc20Shield({
|
|
308
|
+
commitment,
|
|
309
|
+
tokenAddress: assetHex,
|
|
310
|
+
poolAddress: poolAddressHex,
|
|
311
|
+
denomination: pool.denomination
|
|
312
|
+
})
|
|
313
|
+
);
|
|
314
|
+
return txData.concat(newDeposits2);
|
|
315
|
+
}, []);
|
|
316
|
+
return newDeposits;
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// src/relayer/relayer-client.ts
|
|
321
|
+
var RelayerError = class extends Error {
|
|
322
|
+
};
|
|
323
|
+
var RelayerClient = class {
|
|
324
|
+
fetch;
|
|
325
|
+
constructor({ network: { fetch: fetch2 } }) {
|
|
326
|
+
this.fetch = fetch2;
|
|
327
|
+
}
|
|
328
|
+
async getStatus(hostname) {
|
|
329
|
+
const statusResponse = await this.fetch(`https://${hostname}/status`);
|
|
330
|
+
return statusResponse.json();
|
|
331
|
+
}
|
|
332
|
+
async withdraw(relayerUrl, body) {
|
|
333
|
+
const response = await this.fetch(`${relayerUrl}v1/tornadoWithdraw`, {
|
|
334
|
+
method: "POST",
|
|
335
|
+
headers: { "Content-Type": "application/json" },
|
|
336
|
+
body: JSON.stringify(body)
|
|
337
|
+
});
|
|
338
|
+
if (!response.ok) {
|
|
339
|
+
const { error } = await response.json();
|
|
340
|
+
throw new RelayerError(error);
|
|
341
|
+
}
|
|
342
|
+
return response.json();
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// src/config.ts
|
|
347
|
+
var E_ADDRESS = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
|
|
348
|
+
var E_ADDRESS_BIGINT = BigInt(E_ADDRESS);
|
|
349
|
+
var TornadoPaymasterConfigs = {
|
|
350
|
+
11155111: {
|
|
351
|
+
bundlerUrl: "https://public.pimlico.io/v2/11155111/rpc",
|
|
352
|
+
entryPointAddress: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
|
|
353
|
+
paymasterAddress: "0xA0e2667AcdECb9bCDa6Fcf87638a962e2E9dB20D",
|
|
354
|
+
poolsAccountsMap: {
|
|
355
|
+
"0x8c4a04d872a6c1be37964a21ba3a138525dff50b": "0x8A70E3dE493B8599447013f08d8b35719C8ED0ec",
|
|
356
|
+
"0x8cc930096b4df705a007c4a039bdfa1320ed2508": "0x191e3De3cF7f61de7EbEf4Bf45cfBA4f652d1C6e",
|
|
357
|
+
"0x6921fd1a97441dd603a997ed6ddf388658daf754": "0x060d053A6595630851ab873d6C95401b0C964d26"
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
1: {}
|
|
361
|
+
};
|
|
362
|
+
var TornadoCashConfigs = {
|
|
363
|
+
1: {
|
|
364
|
+
ensSubdomainKey: "mainnet-tornado",
|
|
365
|
+
instanceRegistry: {
|
|
366
|
+
address: 0xB20c66C4DE72433F3cE747b58B86830c459CA911n,
|
|
367
|
+
deploymentBlock: 14173395n
|
|
368
|
+
},
|
|
369
|
+
relayerRegistry: {
|
|
370
|
+
address: 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2n,
|
|
371
|
+
deploymentBlock: 14173129n
|
|
372
|
+
},
|
|
373
|
+
aggregator: {
|
|
374
|
+
address: 0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49n
|
|
375
|
+
},
|
|
376
|
+
weth: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2n,
|
|
377
|
+
uniswapQuoterV2: 0x61fFE014bA17989E743c5F6cB21bF9697530B21en
|
|
378
|
+
},
|
|
379
|
+
11155111: {
|
|
380
|
+
ensSubdomainKey: "sepolia-tornado",
|
|
381
|
+
instanceRegistry: {
|
|
382
|
+
address: 0x4e69fD587118dFb64957d18654E3894118E9B1BFn,
|
|
383
|
+
deploymentBlock: 5594611n
|
|
384
|
+
},
|
|
385
|
+
relayerRegistry: {
|
|
386
|
+
address: 0xD6663593E71e4916eCb6f6606e1A6FbfA1634ffAn,
|
|
387
|
+
deploymentBlock: 5594660n
|
|
388
|
+
},
|
|
389
|
+
aggregator: {
|
|
390
|
+
address: 0x4088712AC9fad39ea133cdb9130E465d235e9642n
|
|
391
|
+
},
|
|
392
|
+
weth: 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14n,
|
|
393
|
+
uniswapQuoterV2: 0xEd1f6473345F45b75F8179591dd5bA1888cf2FB3n
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// src/utils/circuit-loader.ts
|
|
398
|
+
async function loadCircuitFiles(circuitUrl, provingKeyUrl) {
|
|
399
|
+
const [circuitRes, provingKeyRes] = await Promise.all([
|
|
400
|
+
fetch(circuitUrl),
|
|
401
|
+
fetch(provingKeyUrl)
|
|
402
|
+
]);
|
|
403
|
+
const [circuitText, provingKey] = await Promise.all([
|
|
404
|
+
circuitRes.text(),
|
|
405
|
+
provingKeyRes.arrayBuffer()
|
|
406
|
+
]);
|
|
407
|
+
return { circuitText, provingKey };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/utils/default-artifacts-loader.ts
|
|
411
|
+
var TC_CIRCUIT_URL = "https://raw.githubusercontent.com/tornadocash/tornado-cli/refs/heads/master/build/circuits/tornado.json";
|
|
412
|
+
var TC_PROVING_KEY_URL = "https://github.com/tornadocash/tornado-cli/raw/refs/heads/master/build/circuits/tornadoProvingKey.bin";
|
|
413
|
+
var defaultArtifactsLoader = () => loadCircuitFiles(TC_CIRCUIT_URL, TC_PROVING_KEY_URL);
|
|
414
|
+
|
|
415
|
+
// src/plugin/base.ts
|
|
416
|
+
var TornadoCashProtocol = class {
|
|
417
|
+
constructor(host, {
|
|
418
|
+
accountIndex = 0,
|
|
419
|
+
initialState = async () => ({}),
|
|
420
|
+
protocolConfig,
|
|
421
|
+
artifactsLoader = defaultArtifactsLoader,
|
|
422
|
+
stateManagerWorkerUrl,
|
|
423
|
+
relayerConfig,
|
|
424
|
+
paymasterConfig = TornadoPaymasterConfigs,
|
|
425
|
+
relayerClientFactory = () => new RelayerClient(host)
|
|
426
|
+
}) {
|
|
427
|
+
this.host = host;
|
|
428
|
+
this.stateManager = (async () => {
|
|
429
|
+
const { remote, onError } = loadStateManagerWorker(stateManagerWorkerUrl);
|
|
430
|
+
const workerReady = new Promise((_resolve, reject) => {
|
|
431
|
+
onError((err) => {
|
|
432
|
+
console.error("[worker crash]", err);
|
|
433
|
+
reject(err);
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
await Promise.race([
|
|
437
|
+
remote.init(
|
|
438
|
+
proxy(host.provider),
|
|
439
|
+
proxy(relayerClientFactory()),
|
|
440
|
+
proxy(host.keystore),
|
|
441
|
+
proxy(host.storage),
|
|
442
|
+
proxy(initialState),
|
|
443
|
+
proxy(artifactsLoader),
|
|
444
|
+
{ protocolConfig, accountIndex, relayerConfig, paymasterConfig }
|
|
445
|
+
),
|
|
446
|
+
workerReady
|
|
447
|
+
]);
|
|
448
|
+
return {
|
|
449
|
+
sync: () => remote.sync(),
|
|
450
|
+
getBalances: ((assets) => remote.getBalances(assets)),
|
|
451
|
+
getDepositPayload: (params) => remote.getDepositPayload(params),
|
|
452
|
+
getWithdrawalPayloads: (params) => remote.getWithdrawalPayloads(params),
|
|
453
|
+
dumpState: (() => remote.dumpState())
|
|
454
|
+
};
|
|
455
|
+
})();
|
|
456
|
+
}
|
|
457
|
+
host;
|
|
458
|
+
stateManager;
|
|
459
|
+
instanceId = () => Promise.resolve("0x1");
|
|
460
|
+
/**
|
|
461
|
+
* Only process supported assets or error out?
|
|
462
|
+
* Returns the balances of the requested assets.
|
|
463
|
+
* The assets retain the provided order. If an asset is not supported its balance will be 0
|
|
464
|
+
*/
|
|
465
|
+
async balance(assets = []) {
|
|
466
|
+
const stateManager = await this.stateManager;
|
|
467
|
+
await stateManager.sync();
|
|
468
|
+
const parsedDesiredAssets = assets.map(({ contract }) => {
|
|
469
|
+
const parsedAddress = BigInt(contract);
|
|
470
|
+
return parsedAddress === E_ADDRESS_BIGINT ? 0n : parsedAddress;
|
|
471
|
+
});
|
|
472
|
+
const balances = await stateManager.getBalances(
|
|
473
|
+
assets.length > 0 ? parsedDesiredAssets : void 0
|
|
474
|
+
);
|
|
475
|
+
const actuallySelectedAssets = assets.length > 0 ? assets.map((a) => a.contract) : [...balances.keys()].map((a) => addressToHex(a));
|
|
476
|
+
return actuallySelectedAssets.map((assetAddress, index) => {
|
|
477
|
+
const parsedSelectedAsset = BigInt(actuallySelectedAssets[index]);
|
|
478
|
+
const balance = balances.get(parsedSelectedAsset === E_ADDRESS_BIGINT ? 0n : parsedSelectedAsset) || 0n;
|
|
479
|
+
const asset = {
|
|
480
|
+
contract: assetAddress,
|
|
481
|
+
__type: "erc20"
|
|
482
|
+
};
|
|
483
|
+
return {
|
|
484
|
+
asset,
|
|
485
|
+
amount: balance
|
|
486
|
+
};
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
async prepareShield(assets, options) {
|
|
490
|
+
const { asset, amount } = assets;
|
|
491
|
+
const strategy = typeof options === "string" ? 1 /* MinFee */ : options?.strategy || 1 /* MinFee */;
|
|
492
|
+
const stateManager = await this.stateManager;
|
|
493
|
+
await stateManager.sync();
|
|
494
|
+
const parsedAsset = BigInt(asset.contract);
|
|
495
|
+
const tx = await stateManager.getDepositPayload({
|
|
496
|
+
asset: parsedAsset === E_ADDRESS_BIGINT ? 0n : parsedAsset,
|
|
497
|
+
amount,
|
|
498
|
+
strategy
|
|
499
|
+
});
|
|
500
|
+
return { txns: tx };
|
|
501
|
+
}
|
|
502
|
+
async prepareUnshield(assets, to, options) {
|
|
503
|
+
const { asset, amount } = assets;
|
|
504
|
+
const parsedAsset = BigInt(asset.contract || E_ADDRESS_BIGINT);
|
|
505
|
+
const stateManager = await this.stateManager;
|
|
506
|
+
await stateManager.sync();
|
|
507
|
+
const baseParams = {
|
|
508
|
+
asset: parsedAsset === E_ADDRESS_BIGINT ? 0n : parsedAsset,
|
|
509
|
+
amount,
|
|
510
|
+
recipient: BigInt(to)
|
|
511
|
+
};
|
|
512
|
+
let withdrawals;
|
|
513
|
+
if (options && options.mode === "paymaster") {
|
|
514
|
+
withdrawals = await stateManager.getWithdrawalPayloads({
|
|
515
|
+
...baseParams,
|
|
516
|
+
mode: "paymaster",
|
|
517
|
+
delegation: options.delegation
|
|
518
|
+
});
|
|
519
|
+
} else {
|
|
520
|
+
withdrawals = await stateManager.getWithdrawalPayloads({
|
|
521
|
+
...baseParams,
|
|
522
|
+
mode: "relayer",
|
|
523
|
+
preferredRelayersEns: options?.preferredRelayersEns
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
__type: "privateOperation",
|
|
528
|
+
withdrawals
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
async sync() {
|
|
532
|
+
const stateManager = await this.stateManager;
|
|
533
|
+
return stateManager.sync();
|
|
534
|
+
}
|
|
535
|
+
async dumpState() {
|
|
536
|
+
const stateManager = await this.stateManager;
|
|
537
|
+
return stateManager.dumpState();
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
// src/paymaster/fee.ts
|
|
542
|
+
var ERC20_TRANSFER_GAS = 100000n;
|
|
543
|
+
var baseGasUnits = {
|
|
544
|
+
preVerificationGas: 80000n,
|
|
545
|
+
verificationGasLimit: 50000n,
|
|
546
|
+
callGasLimit: 300000n,
|
|
547
|
+
paymasterVerificationGasLimit: 350000n,
|
|
548
|
+
paymasterPostOpGasLimit: 10000n
|
|
549
|
+
};
|
|
550
|
+
function reasonableGasUnits(isERC20) {
|
|
551
|
+
if (!isERC20) return baseGasUnits;
|
|
552
|
+
return {
|
|
553
|
+
...baseGasUnits,
|
|
554
|
+
callGasLimit: baseGasUnits.callGasLimit + ERC20_TRANSFER_GAS
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
async function signDelegationAuthorization({
|
|
558
|
+
privateKey,
|
|
559
|
+
accountAddress,
|
|
560
|
+
chainId,
|
|
561
|
+
nonce
|
|
562
|
+
}) {
|
|
563
|
+
const account = privateKeyToAccount(privateKey);
|
|
564
|
+
const authorization = await account.signAuthorization({
|
|
565
|
+
contractAddress: accountAddress,
|
|
566
|
+
chainId,
|
|
567
|
+
nonce
|
|
568
|
+
});
|
|
569
|
+
return { authorization, senderAddress: account.address };
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/paymaster/paymaster-broadcaster.ts
|
|
573
|
+
var PaymasterBroadcaster = class _PaymasterBroadcaster {
|
|
574
|
+
constructor(provider, options) {
|
|
575
|
+
this.provider = provider;
|
|
576
|
+
this.options = options;
|
|
577
|
+
}
|
|
578
|
+
provider;
|
|
579
|
+
options;
|
|
580
|
+
async broadcast(withdrawals) {
|
|
581
|
+
const chainId = Number(await this.provider.getChainId());
|
|
582
|
+
const {
|
|
583
|
+
poolsAccountsMap: rawPoolsAccountsMap,
|
|
584
|
+
bundlerUrl,
|
|
585
|
+
paymasterAddress,
|
|
586
|
+
entryPointAddress
|
|
587
|
+
} = this.options[chainId];
|
|
588
|
+
const poolAcountsMap = new Map(
|
|
589
|
+
Object.entries(rawPoolsAccountsMap).map(([poolAccount, tornadoAccount]) => [
|
|
590
|
+
BigInt(poolAccount),
|
|
591
|
+
tornadoAccount
|
|
592
|
+
])
|
|
593
|
+
);
|
|
594
|
+
const results = await Promise.allSettled(
|
|
595
|
+
withdrawals.map((w) => _PaymasterBroadcaster.broadcastOne({
|
|
596
|
+
...w,
|
|
597
|
+
bundlerUrl,
|
|
598
|
+
paymasterAddress,
|
|
599
|
+
entryPointAddress,
|
|
600
|
+
accountAddress: poolAcountsMap.get(w.poolAddress)
|
|
601
|
+
}, this.provider))
|
|
602
|
+
);
|
|
603
|
+
const failed = results.filter((r) => r.status === "rejected");
|
|
604
|
+
if (failed.length > 0) {
|
|
605
|
+
console.warn(
|
|
606
|
+
`Some paymaster withdrawals failed.`,
|
|
607
|
+
failed.map((e) => e.reason).join("\n")
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
return results.filter((r) => r.status === "fulfilled").map((r) => r.value);
|
|
611
|
+
}
|
|
612
|
+
static async broadcastOne(withdrawal, provider) {
|
|
613
|
+
const {
|
|
614
|
+
proof: { proof: proofHex, args: proofArgs },
|
|
615
|
+
paymasterAddress,
|
|
616
|
+
entryPointAddress,
|
|
617
|
+
bundlerUrl,
|
|
618
|
+
accountAddress,
|
|
619
|
+
isERC20
|
|
620
|
+
} = withdrawal;
|
|
621
|
+
const [root, nullifierHash, recipient, _paymasterAddress, feeHex] = proofArgs;
|
|
622
|
+
if (BigInt(paymasterAddress) !== BigInt(_paymasterAddress)) {
|
|
623
|
+
throw new Error(`relayer must be paymaster when using the 4337 paymaster flow: ${paymasterAddress} != ${_paymasterAddress}`);
|
|
624
|
+
}
|
|
625
|
+
const delegation = withdrawal.delegation ?? await _PaymasterBroadcaster.generateRandomDelegation(accountAddress, provider);
|
|
626
|
+
const { senderAddress, authorization } = delegation;
|
|
627
|
+
const bundlerClient = new BundlerClient(bundlerUrl, entryPointAddress);
|
|
628
|
+
const { standard: { maxFeePerGas, maxPriorityFeePerGas } } = await bundlerClient.getUserOperationGasPrice();
|
|
629
|
+
let gas;
|
|
630
|
+
{
|
|
631
|
+
gas = {
|
|
632
|
+
type: "manual",
|
|
633
|
+
...reasonableGasUnits(isERC20),
|
|
634
|
+
maxFeePerGas,
|
|
635
|
+
maxPriorityFeePerGas
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
const op = await new TornadoBuilder(senderAddress).withPaymaster(paymasterAddress).withAuthorization(authorization).withWithdraw(
|
|
639
|
+
proofHex,
|
|
640
|
+
root,
|
|
641
|
+
nullifierHash,
|
|
642
|
+
recipient,
|
|
643
|
+
_paymasterAddress,
|
|
644
|
+
BigInt(feeHex)
|
|
645
|
+
).withGas(gas).build(provider, bundlerClient);
|
|
646
|
+
const userOpHash = await bundlerClient.sendUserOperation(op);
|
|
647
|
+
await bundlerClient.waitForUserOperationReceipt(userOpHash);
|
|
648
|
+
return { userOpHash };
|
|
649
|
+
}
|
|
650
|
+
static async generateRandomDelegation(accountAddress, provider) {
|
|
651
|
+
const privateKey = generatePrivateKey();
|
|
652
|
+
const chainId = Number(await provider.getChainId());
|
|
653
|
+
return signDelegationAuthorization({
|
|
654
|
+
privateKey,
|
|
655
|
+
accountAddress,
|
|
656
|
+
chainId,
|
|
657
|
+
nonce: 0
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
|
|
662
|
+
// src/plugin/broadcaster.ts
|
|
663
|
+
var TornadoCashBroadcaster = class {
|
|
664
|
+
relayerClient;
|
|
665
|
+
provider;
|
|
666
|
+
paymasterBroadcaster;
|
|
667
|
+
constructor({
|
|
668
|
+
host,
|
|
669
|
+
relayerClientFactory = () => new RelayerClient({ network: host.network }),
|
|
670
|
+
paymasterConfig,
|
|
671
|
+
paymasterClientFactory = () => new PaymasterBroadcaster(host.provider, paymasterConfig)
|
|
672
|
+
}) {
|
|
673
|
+
this.provider = host.provider;
|
|
674
|
+
this.relayerClient = relayerClientFactory();
|
|
675
|
+
this.paymasterBroadcaster = paymasterClientFactory();
|
|
676
|
+
}
|
|
677
|
+
async broadcast({
|
|
678
|
+
withdrawals
|
|
679
|
+
}) {
|
|
680
|
+
const relayerWithdrawals = withdrawals.filter((w) => w.mode === "relayer");
|
|
681
|
+
const paymasterWithdrawals = withdrawals.filter((w) => w.mode === "paymaster");
|
|
682
|
+
const [relayerResults, paymasterResults] = await Promise.all([
|
|
683
|
+
this.broadcastViaRelayer(relayerWithdrawals),
|
|
684
|
+
paymasterWithdrawals.length > 0 ? this.broadcastViaPaymaster(paymasterWithdrawals) : Promise.resolve([])
|
|
685
|
+
]);
|
|
686
|
+
const normalizedPaymasterResults = paymasterResults.map(
|
|
687
|
+
({ userOpHash }) => ({ id: userOpHash })
|
|
688
|
+
);
|
|
689
|
+
return [...relayerResults, ...normalizedPaymasterResults];
|
|
690
|
+
}
|
|
691
|
+
async broadcastViaRelayer(withdrawals) {
|
|
692
|
+
if (withdrawals.length === 0) return [];
|
|
693
|
+
const successes = [];
|
|
694
|
+
const failures = [];
|
|
695
|
+
for (const { proof: { args, proof }, poolAddress, relayerUrl } of withdrawals) {
|
|
696
|
+
try {
|
|
697
|
+
successes.push(await this.relayerClient.withdraw(relayerUrl, {
|
|
698
|
+
proof,
|
|
699
|
+
args,
|
|
700
|
+
contract: addressToHex(poolAddress)
|
|
701
|
+
}));
|
|
702
|
+
} catch (error) {
|
|
703
|
+
failures.push(error);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (failures.length > 0) {
|
|
707
|
+
console.warn(`Some withdrawals failed.`, failures.map((e) => e.message).join("\n"));
|
|
708
|
+
}
|
|
709
|
+
return successes;
|
|
710
|
+
}
|
|
711
|
+
async broadcastViaPaymaster(withdrawals) {
|
|
712
|
+
return this.paymasterBroadcaster.broadcast(withdrawals);
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
// src/v1/factory.ts
|
|
717
|
+
var createTCBroadcaster = (host, params) => {
|
|
718
|
+
const paymasterConfig = params?.paymasterConfig || TornadoPaymasterConfigs;
|
|
719
|
+
return new TornadoCashBroadcaster({ host, ...params, paymasterConfig });
|
|
720
|
+
};
|
|
721
|
+
var createTCPlugin = ((host, params) => new TornadoCashProtocol(host, params));
|
|
722
|
+
function pedersenHash(msg) {
|
|
723
|
+
const hash = pedersenHash$1(msg);
|
|
724
|
+
return Point.decode(hash).x;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// src/account/keys.ts
|
|
728
|
+
var TORNADO_CASH_PATH = "m/29795'/1'";
|
|
729
|
+
var MASK_248 = (1n << 248n) - 1n;
|
|
730
|
+
function coalesceSecret({ secret, chainId, poolAddress }) {
|
|
731
|
+
return bytesToNumberLE(
|
|
732
|
+
keccak_256(
|
|
733
|
+
concatBytes(
|
|
734
|
+
numberToBytesLE(BigInt(secret), 32),
|
|
735
|
+
numberToBytesLE(chainId, 8),
|
|
736
|
+
numberToBytesLE(poolAddress, 20)
|
|
737
|
+
)
|
|
738
|
+
)
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
async function SecretManager({
|
|
742
|
+
host: { keystore },
|
|
743
|
+
accountIndex = 0
|
|
744
|
+
}) {
|
|
745
|
+
const deriveSecrets = async ({ chainId, poolAddress, depositIndex }) => {
|
|
746
|
+
const saltSecretRaw = await Promise.resolve(keystore.deriveAt(tcPath({ accountIndex, secretType: "salt", depositIndex })));
|
|
747
|
+
const nullifierSecretRaw = await Promise.resolve(keystore.deriveAt(tcPath({ accountIndex, secretType: "nullifier", depositIndex })));
|
|
748
|
+
const salt = coalesceSecret({ secret: saltSecretRaw, chainId, poolAddress }) & MASK_248;
|
|
749
|
+
const nullifier = coalesceSecret({ secret: nullifierSecretRaw, chainId, poolAddress }) & MASK_248;
|
|
750
|
+
const nullifierBytes = numberToBytesLE(nullifier, 31);
|
|
751
|
+
const preimage = new Uint8Array(62);
|
|
752
|
+
preimage.set(nullifierBytes, 0);
|
|
753
|
+
preimage.set(numberToBytesLE(salt, 31), 31);
|
|
754
|
+
const commitment = pedersenHash(preimage);
|
|
755
|
+
const nullifierHash = pedersenHash(nullifierBytes);
|
|
756
|
+
return { nullifier, salt, commitment, nullifierHash };
|
|
757
|
+
};
|
|
758
|
+
const deriveEphemeralSigner = async ({ chainId, poolAddress, depositIndex }) => {
|
|
759
|
+
const path = tcPath({ accountIndex, secretType: "signer", depositIndex });
|
|
760
|
+
const raw = await Promise.resolve(keystore.deriveAt(path));
|
|
761
|
+
const coalesced = coalesceSecret({ secret: raw, chainId, poolAddress });
|
|
762
|
+
return `0x${coalesced.toString(16).padStart(64, "0")}`;
|
|
763
|
+
};
|
|
764
|
+
return {
|
|
765
|
+
getDepositSecrets: (params) => deriveSecrets(params),
|
|
766
|
+
deriveEphemeralSigner
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function tcPath({ accountIndex, secretType, depositIndex }) {
|
|
770
|
+
const _secretType = {
|
|
771
|
+
"nullifier": 0,
|
|
772
|
+
"salt": 1,
|
|
773
|
+
"signer": 2
|
|
774
|
+
}[secretType];
|
|
775
|
+
return `${TORNADO_CASH_PATH}/${accountIndex}'/${_secretType}'/${depositIndex}'`;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
export { DepositStrategy, E_ADDRESS, SecretManager, TornadoCashConfigs, TornadoCashProtocol, createTCBroadcaster, createTCPlugin };
|