@kohaku-eth/tornado-cash 0.0.2-alpha.12 → 0.0.2-alpha.13

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 CHANGED
@@ -1,9 +1,10 @@
1
1
  import { Broadcaster } from '@kohaku-eth/plugins/broadcaster';
2
- import { Host, ChainId, Storage, PrivateOperation, PublicOperation, AssetAmount, ERC20AssetId, PluginInstance, AccountId } from '@kohaku-eth/plugins';
2
+ import { Host, ExternalRawEvent, ChainId, Storage, PrivateOperation, PublicOperation, AccountId, AssetAmount, ERC20AssetId, PluginInstance, UnshieldOptions } from '@kohaku-eth/plugins';
3
+ import { TxData } from '@kohaku-eth/provider';
3
4
  import { combineReducers } from '@reduxjs/toolkit';
4
5
  import { ParseAbiItem, SignedAuthorization, Hash, Address as Address$2 } from 'viem';
5
- import { TxData } from '@kohaku-eth/provider';
6
6
  import { Address as Address$1 } from 'ox/Address';
7
+ import { Hex } from 'ox/Hex';
7
8
 
8
9
  type Nullifier$1 = bigint & {};
9
10
  type NullifierHash = bigint & {};
@@ -190,7 +191,7 @@ declare const INSTANCE_REGISTRY_EVENT_SIGNATURES: {
190
191
  };
191
192
 
192
193
  type IEventsMap = Record<string, ParseAbiItem<string>>;
193
- interface IGetEventsParams<T> {
194
+ interface IGetEventsParams$1<T> {
194
195
  events: T | T[];
195
196
  fromBlock: bigint;
196
197
  toBlock?: bigint;
@@ -211,7 +212,7 @@ type IGroupedEvents<NamesTable extends Record<string, unknown>> = {
211
212
  };
212
213
  type GetEventsFn<EventsMap extends IEventsMap, ParsedEvents extends {
213
214
  [key in keyof EventsMap]: unknown;
214
- }> = <const T extends keyof ParsedEvents = never>(params: IGetEventsParams<T>) => Promise<Pick<IGroupedEvents<ParsedEvents>, T> & {
215
+ }> = <const T extends keyof ParsedEvents = never>(params: IGetEventsParams$1<T>) => Promise<Pick<IGroupedEvents<ParsedEvents>, T> & {
215
216
  fromBlock: bigint;
216
217
  toBlock: bigint;
217
218
  }>;
@@ -220,7 +221,6 @@ interface IPoolConfig {
220
221
  isERC20: boolean;
221
222
  token: Address;
222
223
  state: 0 | 1;
223
- uniswapPoolSwappingFee: number;
224
224
  protocolFeePercentage: number;
225
225
  denomination: bigint;
226
226
  rootHistorySize: number;
@@ -233,6 +233,14 @@ interface IRelayerAggregatorData {
233
233
  }
234
234
  interface IDataService {
235
235
  getPoolEvents: GetEventsFn<typeof POOL_EVENTS_SIGNATURES, IPoolEvents>;
236
+ getBlockNumber(): Promise<bigint>;
237
+ parsePoolEvents(events: ExternalRawEvent[]): {
238
+ Deposited: IRawDepositEvent[];
239
+ Withdrawn: IRawWithdrawalEvent[];
240
+ };
241
+ parseRelayerRegistryEvents(events: ExternalRawEvent[]): {
242
+ RelayerRegistered: IRelayerRegisteredEvent[];
243
+ };
236
244
  getRelayerRegistryEvents: GetEventsFn<typeof RELAYER_REGISTRY_EVENTS_SIGNATURES, IRelayerRegistryEvents>;
237
245
  getInstanceRegistryEvents: GetEventsFn<typeof INSTANCE_REGISTRY_EVENT_SIGNATURES, IInstanceRegistryEvents>;
238
246
  getAsset(assetAddress: Address): Promise<IAsset>;
@@ -249,7 +257,38 @@ interface IDataService {
249
257
  getLatestBlockTimestamp(): Promise<bigint>;
250
258
  getRelayerData(aggregatorAddress: Address, relayerNameHashes: bigint[], subdomains: string[]): Promise<IRelayerAggregatorData[]>;
251
259
  getAccountNonce(accountAddress: Address): Promise<number>;
252
- quoteEthToToken(amountInWei: bigint, tokenAddress: Address, poolFee: number): Promise<bigint>;
260
+ quoteWeiInToken(paymasterAddress: Address, feeToken: Address, weiAmount: bigint): Promise<bigint>;
261
+ }
262
+
263
+ /** Identifies one contract to sync from a given block. */
264
+ interface IGetEventsParams {
265
+ chainId: bigint;
266
+ address: Address;
267
+ fromBlock: bigint;
268
+ }
269
+ interface IGetPoolEventsResult {
270
+ Deposited: IRawDepositEvent[];
271
+ Withdrawn: IRawWithdrawalEvent[];
272
+ /** Highest block the returned events are synced up to. */
273
+ toBlock: bigint;
274
+ }
275
+ interface IGetRelayerRegistryEventsResult {
276
+ RelayerRegistered: IRelayerRegisteredEvent[];
277
+ /** Highest block the returned events are synced up to. */
278
+ toBlock: bigint;
279
+ }
280
+ interface ISyncService {
281
+ /**
282
+ * Fetches a single pool's `Deposited`/`Withdrawn` events from `fromBlock`.
283
+ * Uses the external sync provider for the bulk of a large range and the chain
284
+ * for the remaining tail; falls back to a chain-only fetch otherwise.
285
+ */
286
+ getPoolEvents(params: IGetEventsParams): Promise<IGetPoolEventsResult>;
287
+ /**
288
+ * Fetches the relayer registry's `RelayerRegistered` events from `fromBlock`,
289
+ * with the same external-then-chain strategy as {@link getPoolEvents}.
290
+ */
291
+ getRelayerRegistryEvents(params: IGetEventsParams): Promise<IGetRelayerRegistryEventsResult>;
253
292
  }
254
293
 
255
294
  type SpecificAssetBalanceFn<isAsync extends boolean = false> = <const Adresses extends Address | Address[] = Address[], ReturnType = Adresses extends unknown[] ? Map<Address, bigint> : bigint>(assetAddress?: Adresses) => isAsync extends true ? Promise<ReturnType> : ReturnType;
@@ -289,6 +328,7 @@ interface IRelayerFeeConfig {
289
328
  interface StoreFactoryParams {
290
329
  secretManagerFactory: () => Promise<ISecretManager>;
291
330
  dataService: IDataService;
331
+ syncService: ISyncService;
292
332
  relayerClient: IRelayerClient;
293
333
  paymasterConfig: IChainsPaymastersConfig;
294
334
  storageToSyncTo?: Storage;
@@ -303,6 +343,36 @@ declare enum DepositStrategy {
303
343
  MinFee = 1
304
344
  }
305
345
 
346
+ interface SerializedAuth {
347
+ address: Address$1;
348
+ chainId: Hex;
349
+ nonce: Hex;
350
+ r: Hex;
351
+ s: Hex;
352
+ yParity: Hex;
353
+ }
354
+ /**
355
+ * A userOp serialized to the hex shape expected by `eth_sendUserOperation`, so
356
+ * it can be carried as plain (JSON-serializable) data from the prepare phase
357
+ * (thunk) to the broadcast phase.
358
+ */
359
+ interface SerializedUserOperation {
360
+ sender: `0x${string}`;
361
+ nonce: `0x${string}`;
362
+ callData: `0x${string}`;
363
+ callGasLimit: `0x${string}`;
364
+ verificationGasLimit: `0x${string}`;
365
+ preVerificationGas: `0x${string}`;
366
+ maxFeePerGas: `0x${string}`;
367
+ maxPriorityFeePerGas: `0x${string}`;
368
+ paymaster?: `0x${string}`;
369
+ paymasterVerificationGasLimit?: `0x${string}`;
370
+ paymasterPostOpGasLimit?: `0x${string}`;
371
+ paymasterData?: `0x${string}`;
372
+ signature: `0x${string}`;
373
+ eip7702Auth?: SerializedAuth;
374
+ }
375
+
306
376
  interface SignedDelegation {
307
377
  senderAddress: `0x${string}`;
308
378
  authorization: SignedAuthorization;
@@ -322,8 +392,7 @@ interface IGenericPaymasterWithdrawalPayload {
322
392
  paymasterAddress: `0x${string}`;
323
393
  entryPointAddress: `0x${string}`;
324
394
  bundlerUrl: string;
325
- accountAddress: `0x${string}`;
326
- delegation?: SignedDelegation;
395
+ userOperation: SerializedUserOperation;
327
396
  }
328
397
  interface PaymasterBroadcastResult {
329
398
  userOpHash: Hash;
@@ -377,6 +446,12 @@ interface TCProtocolParams {
377
446
  artifactsLoader?: () => Promise<ITornadoArtifacts>;
378
447
  initialState?: () => Promise<Record<string, PublicRootState>>;
379
448
  stateManagerWorkerUrl?: string;
449
+ /**
450
+ * When a pool is at least this many blocks behind, use the host's
451
+ * `externalSyncProvider` (if any) for the bulk of the sync instead of the
452
+ * chain. Omit to always sync from the chain.
453
+ */
454
+ minExternalSyncBlocksAmount?: number;
380
455
  }
381
456
  interface IBaseOperationParams {
382
457
  }
@@ -396,6 +471,7 @@ interface IRelayerWithdrawParams extends IWithdrawBaseParams {
396
471
  interface IPaymasterWithdrawParams extends IWithdrawBaseParams {
397
472
  mode: 'paymaster';
398
473
  delegation?: DelegationConfig;
474
+ tailCalls?: (address: AccountId) => Promise<TxData[]>;
399
475
  }
400
476
  type IWithdrawapOperationParams = IRelayerWithdrawParams | IPaymasterWithdrawParams;
401
477
  interface IGetNotesParams extends IBaseOperationParams {
@@ -445,7 +521,7 @@ type TCBroadcaster = Broadcaster<TCPrivateOperation, ITornadoWithdrawResponse[]>
445
521
  interface TCBaseCredential {
446
522
  accountIndex: number;
447
523
  }
448
- interface TCPluginParameters extends TCBroadcasterParameters, TCBaseCredential, Pick<TCProtocolParams, 'initialState'> {
524
+ interface TCPluginParameters extends TCBroadcasterParameters, TCBaseCredential, Pick<TCProtocolParams, 'initialState' | 'minExternalSyncBlocksAmount'> {
449
525
  protocolConfig: TCProtocolConfig;
450
526
  relayerConfig?: IRelayerFeeConfig;
451
527
  stateManagerWorkerUrl?: string;
@@ -454,11 +530,11 @@ interface TCPluginParameters extends TCBroadcasterParameters, TCBaseCredential,
454
530
  type TCAddress = Address$1;
455
531
  type TCAssetAmount<Tag extends string | undefined = undefined> = AssetAmount<ERC20AssetId, bigint, Tag>;
456
532
  type TCAssetBalance = TCAssetAmount;
457
- interface TCRelayerUnshieldOptions {
533
+ interface TCRelayerUnshieldOptions extends UnshieldOptions {
458
534
  mode: 'relayer';
459
535
  preferredRelayersEns?: string[];
460
536
  }
461
- interface TCPaymasterUnshieldOptions {
537
+ interface TCPaymasterUnshieldOptions extends UnshieldOptions {
462
538
  mode: 'paymaster';
463
539
  delegation?: DelegationConfig;
464
540
  }
@@ -495,7 +571,7 @@ type RequireOnly<T, Keys extends keyof T> = Partial<T> & Pick<T, Keys>;
495
571
  declare class TornadoCashProtocol implements TCInstance {
496
572
  readonly host: Host;
497
573
  private stateManager;
498
- constructor(host: Host, { accountIndex, initialState, protocolConfig, artifactsLoader, stateManagerWorkerUrl, relayerConfig, paymasterConfig, relayerClientFactory, }: RequireOnly<TCProtocolParams, 'protocolConfig'>);
574
+ constructor(host: Host, { accountIndex, initialState, protocolConfig, artifactsLoader, stateManagerWorkerUrl, relayerConfig, paymasterConfig, relayerClientFactory, minExternalSyncBlocksAmount, }: RequireOnly<TCProtocolParams, 'protocolConfig'>);
499
575
  instanceId: () => Promise<"0x1">;
500
576
  /**
501
577
  * Only process supported assets or error out?
@@ -510,6 +586,7 @@ declare class TornadoCashProtocol implements TCInstance {
510
586
  */
511
587
  notes(assets?: ERC20AssetId[], includeSpent?: boolean): Promise<TCNote[]>;
512
588
  prepareShield(assets: TCAssetAmount, options?: TCPrepareShieldOptions | `0x${string}`): Promise<TCPublicOperation>;
589
+ prepareUnshield(assets: AssetAmount, to: AccountId): Promise<TCPrivateOperation<'relayer'>>;
513
590
  prepareUnshield(assets: AssetAmount, to: AccountId, options?: TCRelayerUnshieldOptions): Promise<TCPrivateOperation<'relayer'>>;
514
591
  prepareUnshield(assets: AssetAmount, to: AccountId, options?: TCPaymasterUnshieldOptions): Promise<TCPrivateOperation<'paymaster'>>;
515
592
  sync(): Promise<void>;
@@ -534,11 +611,11 @@ declare const TornadoPaymasterConfigs: {
534
611
  readonly 11155111: {
535
612
  readonly bundlerUrl: "https://public.pimlico.io/v2/11155111/rpc";
536
613
  readonly entryPointAddress: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108";
537
- readonly paymasterAddress: "0xA0e2667AcdECb9bCDa6Fcf87638a962e2E9dB20D";
614
+ readonly paymasterAddress: "0x1c5aCCb9c09D72945b79EC986776136bE01d7B2F";
538
615
  readonly poolsAccountsMap: {
539
- readonly '0x8c4a04d872a6c1be37964a21ba3a138525dff50b': "0x8A70E3dE493B8599447013f08d8b35719C8ED0ec";
540
- readonly '0x8cc930096b4df705a007c4a039bdfa1320ed2508': "0x191e3De3cF7f61de7EbEf4Bf45cfBA4f652d1C6e";
541
- readonly '0x6921fd1a97441dd603a997ed6ddf388658daf754': "0x060d053A6595630851ab873d6C95401b0C964d26";
616
+ readonly '0x8c4a04d872a6c1be37964a21ba3a138525dff50b': "0xa616aAE443FCCABfc2F1EA2Afe001E5046FFDCe0";
617
+ readonly '0x8cc930096b4df705a007c4a039bdfa1320ed2508': "0x67a898343F32641206d0f30CB3367944a8919A3A";
618
+ readonly '0x6921fd1a97441dd603a997ed6ddf388658daf754': "0xbF0a7969dacF8337716d0F283df0574dF56b0479";
542
619
  };
543
620
  };
544
621
  readonly 1: never;
@@ -558,7 +635,6 @@ declare const TornadoCashConfigs: {
558
635
  readonly address: 1329937909414098670091808248270266272594403334985n;
559
636
  };
560
637
  readonly weth: 1097077688018008265106216665536940668749033598146n;
561
- readonly uniswapQuoterV2: 559478315000592720989748010348495519536007655966n;
562
638
  };
563
639
  readonly 11155111: {
564
640
  readonly ensSubdomainKey: "sepolia-tornado";
@@ -574,7 +650,6 @@ declare const TornadoCashConfigs: {
574
650
  readonly address: 368418168928372787409224158240737522620871644738n;
575
651
  };
576
652
  readonly weth: 1461358721292675304620367034492138750323989506836n;
577
- readonly uniswapQuoterV2: 1353730886217084326490181093487957540173111046067n;
578
653
  };
579
654
  };
580
655
 
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { createSelector, createAsyncThunk } from '@reduxjs/toolkit';
2
2
  import { createTx } from '@kohaku-eth/provider';
3
- import { encodeFunctionData, toHex, erc20Abi } from 'viem';
3
+ import { encodeFunctionData, toHex, erc20Abi, http } from 'viem';
4
4
  import { proxy } from 'comlink';
5
5
  import { loadStateManagerWorker } from '#worker-loader';
6
- import { BundlerClient, TornadoBuilder } from '@privacy-paymasters/sdk';
7
- import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
6
+ import 'viem/accounts';
7
+ import { createBundlerClient } from 'viem/account-abstraction';
8
8
  import { numberToBytesLE, bytesToNumberLE, concatBytes } from '@noble/curves/utils.js';
9
9
  import { keccak_256 } from '@noble/hashes/sha3.js';
10
10
  import { pedersenHash as pedersenHash$1, Point } from 'micro-zk-proofs/pedersen.js';
@@ -348,11 +348,12 @@ var TornadoPaymasterConfigs = {
348
348
  11155111: {
349
349
  bundlerUrl: "https://public.pimlico.io/v2/11155111/rpc",
350
350
  entryPointAddress: "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108",
351
- paymasterAddress: "0xA0e2667AcdECb9bCDa6Fcf87638a962e2E9dB20D",
351
+ paymasterAddress: "0x1c5aCCb9c09D72945b79EC986776136bE01d7B2F",
352
352
  poolsAccountsMap: {
353
- "0x8c4a04d872a6c1be37964a21ba3a138525dff50b": "0x8A70E3dE493B8599447013f08d8b35719C8ED0ec",
354
- "0x8cc930096b4df705a007c4a039bdfa1320ed2508": "0x191e3De3cF7f61de7EbEf4Bf45cfBA4f652d1C6e",
355
- "0x6921fd1a97441dd603a997ed6ddf388658daf754": "0x060d053A6595630851ab873d6C95401b0C964d26"
353
+ // pool -> per-pool TornadoFeeAdapter (eth_0_1, eth_1, dai_100)
354
+ "0x8c4a04d872a6c1be37964a21ba3a138525dff50b": "0xa616aAE443FCCABfc2F1EA2Afe001E5046FFDCe0",
355
+ "0x8cc930096b4df705a007c4a039bdfa1320ed2508": "0x67a898343F32641206d0f30CB3367944a8919A3A",
356
+ "0x6921fd1a97441dd603a997ed6ddf388658daf754": "0xbF0a7969dacF8337716d0F283df0574dF56b0479"
356
357
  }
357
358
  },
358
359
  1: {}
@@ -371,8 +372,7 @@ var TornadoCashConfigs = {
371
372
  aggregator: {
372
373
  address: 0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49n
373
374
  },
374
- weth: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2n,
375
- uniswapQuoterV2: 0x61fFE014bA17989E743c5F6cB21bF9697530B21en
375
+ weth: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2n
376
376
  },
377
377
  11155111: {
378
378
  ensSubdomainKey: "sepolia-tornado",
@@ -387,8 +387,7 @@ var TornadoCashConfigs = {
387
387
  aggregator: {
388
388
  address: 0x4088712AC9fad39ea133cdb9130E465d235e9642n
389
389
  },
390
- weth: 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14n,
391
- uniswapQuoterV2: 0xEd1f6473345F45b75F8179591dd5bA1888cf2FB3n
390
+ weth: 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14n
392
391
  }
393
392
  };
394
393
 
@@ -420,7 +419,8 @@ var TornadoCashProtocol = class {
420
419
  stateManagerWorkerUrl,
421
420
  relayerConfig,
422
421
  paymasterConfig = TornadoPaymasterConfigs,
423
- relayerClientFactory = () => new RelayerClient(host)
422
+ relayerClientFactory = () => new RelayerClient(host),
423
+ minExternalSyncBlocksAmount
424
424
  }) {
425
425
  this.host = host;
426
426
  this.stateManager = (async () => {
@@ -431,6 +431,17 @@ var TornadoCashProtocol = class {
431
431
  reject(err);
432
432
  });
433
433
  });
434
+ const { externalSyncProvider } = host;
435
+ const externalSyncClient = externalSyncProvider && {
436
+ getEvents: async (params) => {
437
+ const events = [];
438
+ for await (const event of externalSyncProvider.streamEvents(params)) {
439
+ events.push(event);
440
+ }
441
+ return events;
442
+ },
443
+ lastCoveredBlock: (params) => externalSyncProvider.lastCoveredBlock(params)
444
+ };
434
445
  await Promise.race([
435
446
  remote.init(
436
447
  proxy(host.provider),
@@ -439,7 +450,8 @@ var TornadoCashProtocol = class {
439
450
  proxy(host.storage),
440
451
  proxy(initialState),
441
452
  proxy(artifactsLoader),
442
- { protocolConfig, accountIndex, relayerConfig, paymasterConfig }
453
+ externalSyncClient ? proxy(externalSyncClient) : void 0,
454
+ { protocolConfig, accountIndex, relayerConfig, paymasterConfig, minExternalSyncBlocksAmount }
443
455
  ),
444
456
  workerReady
445
457
  ]);
@@ -448,7 +460,16 @@ var TornadoCashProtocol = class {
448
460
  getBalances: ((assets) => remote.getBalances(assets)),
449
461
  getNotes: (params) => remote.getNotes(params),
450
462
  getDepositPayload: (params) => remote.getDepositPayload(params),
451
- getWithdrawalPayloads: (params) => remote.getWithdrawalPayloads(params),
463
+ getWithdrawalPayloads: (params) => {
464
+ if (params.mode === "paymaster") {
465
+ const { tailCalls, ...rest } = params;
466
+ return remote.getWithdrawalPayloads(
467
+ rest,
468
+ tailCalls ? proxy(tailCalls) : void 0
469
+ );
470
+ }
471
+ return remote.getWithdrawalPayloads(params);
472
+ },
452
473
  dumpState: (() => remote.dumpState())
453
474
  };
454
475
  })();
@@ -515,7 +536,10 @@ var TornadoCashProtocol = class {
515
536
  });
516
537
  return { txns: tx };
517
538
  }
518
- async prepareUnshield(assets, to, options) {
539
+ async prepareUnshield(assets, to, options = { mode: "relayer" }) {
540
+ if (options.mode === "relayer" && options.tailCalls !== void 0) {
541
+ throw new Error("Tail Calls are only supported when using paymaster mode.");
542
+ }
519
543
  const { asset, amount } = assets;
520
544
  const parsedAsset = BigInt(asset.contract || E_ADDRESS_BIGINT);
521
545
  const stateManager = await this.stateManager;
@@ -530,7 +554,8 @@ var TornadoCashProtocol = class {
530
554
  withdrawals = await stateManager.getWithdrawalPayloads({
531
555
  ...baseParams,
532
556
  mode: "paymaster",
533
- delegation: options.delegation
557
+ delegation: options.delegation,
558
+ tailCalls: options.tailCalls
534
559
  });
535
560
  } else {
536
561
  withdrawals = await stateManager.getWithdrawalPayloads({
@@ -553,120 +578,51 @@ var TornadoCashProtocol = class {
553
578
  return stateManager.dumpState();
554
579
  }
555
580
  };
556
-
557
- // src/paymaster/fee.ts
558
- var ERC20_TRANSFER_GAS = 100000n;
559
- var baseGasUnits = {
560
- preVerificationGas: 80000n,
561
- verificationGasLimit: 50000n,
562
- callGasLimit: 300000n,
563
- paymasterVerificationGasLimit: 350000n,
564
- paymasterPostOpGasLimit: 10000n
565
- };
566
- function reasonableGasUnits(isERC20) {
567
- if (!isERC20) return baseGasUnits;
568
- return {
569
- ...baseGasUnits,
570
- callGasLimit: baseGasUnits.callGasLimit + ERC20_TRANSFER_GAS
571
- };
581
+ function createPaymasterBundlerClient(bundlerUrl) {
582
+ return createBundlerClient({ transport: http(bundlerUrl) });
572
583
  }
573
- async function signDelegationAuthorization({
574
- privateKey,
575
- accountAddress,
576
- chainId,
577
- nonce
578
- }) {
579
- const account = privateKeyToAccount(privateKey);
580
- const authorization = await account.signAuthorization({
581
- contractAddress: accountAddress,
582
- chainId,
583
- nonce
584
+ async function sendSerializedUserOperation(client, op, entryPoint) {
585
+ return client.request({
586
+ method: "eth_sendUserOperation",
587
+ params: [op, entryPoint]
584
588
  });
585
- return { authorization, senderAddress: account.address };
586
589
  }
587
590
 
588
591
  // src/paymaster/paymaster-broadcaster.ts
589
592
  var PaymasterBroadcaster = class _PaymasterBroadcaster {
590
- constructor(provider, options) {
591
- this.provider = provider;
592
- this.options = options;
593
- }
594
- provider;
595
- options;
596
593
  async broadcast(withdrawals) {
597
- const chainId = Number(await this.provider.getChainId());
598
- const {
599
- poolsAccountsMap: rawPoolsAccountsMap
600
- } = this.options[chainId];
601
- const poolAcountsMap = new Map(
602
- Object.entries(rawPoolsAccountsMap).map(([poolAccount, tornadoAccount]) => [
603
- BigInt(poolAccount),
604
- tornadoAccount
605
- ])
606
- );
607
- const results = await Promise.allSettled(
608
- withdrawals.map((w) => _PaymasterBroadcaster.broadcastOne({
609
- ...w,
610
- accountAddress: poolAcountsMap.get(w.poolAddress)
611
- }, this.provider))
594
+ const bySender = /* @__PURE__ */ new Map();
595
+ for (const w of withdrawals) {
596
+ const key = w.userOperation.sender.toLowerCase();
597
+ const group = bySender.get(key) ?? [];
598
+ group.push(w);
599
+ bySender.set(key, group);
600
+ }
601
+ const groupResults = await Promise.allSettled(
602
+ [...bySender.values()].map(async (group) => {
603
+ const out = [];
604
+ for (const w of group) {
605
+ out.push(await _PaymasterBroadcaster.broadcastOne(w));
606
+ }
607
+ return out;
608
+ })
612
609
  );
613
- const failed = results.filter((r) => r.status === "rejected");
610
+ const failed = groupResults.filter((r) => r.status === "rejected");
614
611
  if (failed.length > 0) {
615
612
  console.warn(
616
613
  `Some paymaster withdrawals failed.`,
617
614
  failed.map((e) => e.reason).join("\n")
618
615
  );
619
616
  }
620
- return results.filter((r) => r.status === "fulfilled").map((r) => r.value);
617
+ return groupResults.filter((r) => r.status === "fulfilled").flatMap((r) => r.value);
621
618
  }
622
- static async broadcastOne(withdrawal, provider) {
623
- const {
624
- proof: { proof: proofHex, args: proofArgs },
625
- paymasterAddress,
626
- entryPointAddress,
627
- bundlerUrl,
628
- accountAddress,
629
- isERC20
630
- } = withdrawal;
631
- const [root, nullifierHash, recipient, _paymasterAddress, feeHex] = proofArgs;
632
- if (BigInt(paymasterAddress) !== BigInt(_paymasterAddress)) {
633
- throw new Error(`relayer must be paymaster when using the 4337 paymaster flow: ${paymasterAddress} != ${_paymasterAddress}`);
634
- }
635
- const delegation = withdrawal.delegation ?? await _PaymasterBroadcaster.generateRandomDelegation(accountAddress, provider);
636
- const { senderAddress, authorization } = delegation;
637
- const bundlerClient = new BundlerClient(bundlerUrl, entryPointAddress);
638
- const { standard: { maxFeePerGas, maxPriorityFeePerGas } } = await bundlerClient.getUserOperationGasPrice();
639
- let gas;
640
- {
641
- gas = {
642
- type: "manual",
643
- ...reasonableGasUnits(isERC20),
644
- maxFeePerGas,
645
- maxPriorityFeePerGas
646
- };
647
- }
648
- const op = await new TornadoBuilder(senderAddress).withPaymaster(paymasterAddress).withAuthorization(authorization).withWithdraw(
649
- proofHex,
650
- root,
651
- nullifierHash,
652
- recipient,
653
- _paymasterAddress,
654
- BigInt(feeHex)
655
- ).withGas(gas).build(provider, bundlerClient);
656
- const userOpHash = await bundlerClient.sendUserOperation(op);
657
- await bundlerClient.waitForUserOperationReceipt(userOpHash);
619
+ static async broadcastOne(withdrawal) {
620
+ const { bundlerUrl, entryPointAddress, userOperation } = withdrawal;
621
+ const bundlerClient = createPaymasterBundlerClient(bundlerUrl);
622
+ const userOpHash = await sendSerializedUserOperation(bundlerClient, userOperation, entryPointAddress);
623
+ await bundlerClient.waitForUserOperationReceipt({ hash: userOpHash });
658
624
  return { userOpHash };
659
625
  }
660
- static async generateRandomDelegation(accountAddress, provider) {
661
- const privateKey = generatePrivateKey();
662
- const chainId = Number(await provider.getChainId());
663
- return signDelegationAuthorization({
664
- privateKey,
665
- accountAddress,
666
- chainId,
667
- nonce: 0
668
- });
669
- }
670
626
  };
671
627
 
672
628
  // src/plugin/broadcaster.ts
@@ -678,7 +634,7 @@ var TornadoCashBroadcaster = class {
678
634
  host,
679
635
  relayerClientFactory = () => new RelayerClient({ network: host.network }),
680
636
  paymasterConfig,
681
- paymasterClientFactory = () => new PaymasterBroadcaster(host.provider, paymasterConfig)
637
+ paymasterClientFactory = () => new PaymasterBroadcaster()
682
638
  }) {
683
639
  this.provider = host.provider;
684
640
  this.relayerClient = relayerClientFactory();