@atomiqlabs/chain-evm 1.0.0-dev.89 → 1.0.0-dev.92

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.
@@ -1,6 +1,6 @@
1
1
  import { BaseTokenType, BitcoinNetwork, BitcoinRpc, ChainData, ChainInitializer, ChainSwapType } from "@atomiqlabs/base";
2
2
  import { JsonRpcApiProvider } from "ethers";
3
- import { EVMRetryPolicy } from "../../evm/chain/EVMChainInterface";
3
+ import { EVMConfiguration, EVMRetryPolicy } from "../../evm/chain/EVMChainInterface";
4
4
  import { EVMFees } from "../../evm/chain/modules/EVMFees";
5
5
  import { BotanixChainType } from "./BotanixChainType";
6
6
  export type BotanixAssetsType = BaseTokenType<"BBTC">;
@@ -9,7 +9,6 @@ export type BotanixOptions = {
9
9
  rpcUrl: string | JsonRpcApiProvider;
10
10
  retryPolicy?: EVMRetryPolicy;
11
11
  chainType?: "MAINNET" | "TESTNET";
12
- maxLogsBlockRange?: number;
13
12
  swapContract?: string;
14
13
  btcRelayContract?: string;
15
14
  btcRelayDeploymentHeight?: number;
@@ -24,6 +23,7 @@ export type BotanixOptions = {
24
23
  };
25
24
  };
26
25
  fees?: EVMFees;
26
+ evmConfig?: Omit<EVMConfiguration, "safeBlockTag">;
27
27
  };
28
28
  export declare function initializeBotanix(options: BotanixOptions, bitcoinRpc: BitcoinRpc<any>, network: BitcoinNetwork): ChainData<BotanixChainType>;
29
29
  export type BotanixInitializerType = ChainInitializer<BotanixOptions, BotanixChainType, BotanixAssetsType>;
@@ -84,7 +84,11 @@ function initializeBotanix(options, bitcoinRpc, network) {
84
84
  const Fees = options.fees ?? new EVMFees_1.EVMFees(provider, 2n * 1000000000n, 1000000n);
85
85
  const chainInterface = new EVMChainInterface_1.EVMChainInterface("BOTANIX", chainId, provider, {
86
86
  safeBlockTag: "finalized",
87
- maxLogsBlockRange: options.maxLogsBlockRange ?? 500
87
+ maxLogsBlockRange: 950,
88
+ maxLogTopics: 64,
89
+ maxParallelLogRequests: 5,
90
+ maxParallelCalls: 5,
91
+ ...options?.evmConfig
88
92
  }, options.retryPolicy, Fees);
89
93
  const btcRelay = new EVMBtcRelay_1.EVMBtcRelay(chainInterface, bitcoinRpc, network, options.btcRelayContract ?? defaultContractAddresses.btcRelayContract, options.btcRelayDeploymentHeight ?? defaultContractAddresses.btcRelayDeploymentHeight);
90
94
  const swapContract = new EVMSwapContract_1.EVMSwapContract(chainInterface, btcRelay, options.swapContract ?? defaultContractAddresses.swapContract, {
@@ -1,6 +1,6 @@
1
1
  import { BaseTokenType, BitcoinNetwork, BitcoinRpc, ChainData, ChainInitializer, ChainSwapType } from "@atomiqlabs/base";
2
2
  import { JsonRpcApiProvider } from "ethers";
3
- import { EVMRetryPolicy } from "../../evm/chain/EVMChainInterface";
3
+ import { EVMConfiguration, EVMRetryPolicy } from "../../evm/chain/EVMChainInterface";
4
4
  import { CitreaChainType } from "./CitreaChainType";
5
5
  import { CitreaFees } from "./CitreaFees";
6
6
  export type CitreaAssetsType = BaseTokenType<"CBTC" | "USDC">;
@@ -9,7 +9,6 @@ export type CitreaOptions = {
9
9
  rpcUrl: string | JsonRpcApiProvider;
10
10
  retryPolicy?: EVMRetryPolicy;
11
11
  chainType?: "MAINNET" | "TESTNET4";
12
- maxLogsBlockRange?: number;
13
12
  swapContract?: string;
14
13
  btcRelayContract?: string;
15
14
  btcRelayDeploymentHeight?: number;
@@ -24,6 +23,7 @@ export type CitreaOptions = {
24
23
  };
25
24
  };
26
25
  fees?: CitreaFees;
26
+ evmConfig?: Omit<EVMConfiguration, "safeBlockTag">;
27
27
  };
28
28
  export declare function initializeCitrea(options: CitreaOptions, bitcoinRpc: BitcoinRpc<any>, network: BitcoinNetwork): ChainData<CitreaChainType>;
29
29
  export type CitreaInitializerType = ChainInitializer<CitreaOptions, CitreaChainType, CitreaAssetsType>;
@@ -90,7 +90,11 @@ function initializeCitrea(options, bitcoinRpc, network) {
90
90
  const Fees = options.fees ?? new CitreaFees_1.CitreaFees(provider, 2n * 1000000000n, 1000000n);
91
91
  const chainInterface = new EVMChainInterface_1.EVMChainInterface("CITREA", chainId, provider, {
92
92
  safeBlockTag: "latest",
93
- maxLogsBlockRange: options.maxLogsBlockRange ?? 500
93
+ maxLogsBlockRange: 950,
94
+ maxLogTopics: 64,
95
+ maxParallelLogRequests: 5,
96
+ maxParallelCalls: 5,
97
+ ...options?.evmConfig
94
98
  }, options.retryPolicy, Fees);
95
99
  chainInterface.Tokens = new CitreaTokens_1.CitreaTokens(chainInterface); //Override with custom token module allowing l1 state diff based fee calculation
96
100
  const btcRelay = new CitreaBtcRelay_1.CitreaBtcRelay(chainInterface, bitcoinRpc, network, options.btcRelayContract ?? defaultContractAddresses.btcRelayContract, options.btcRelayDeploymentHeight ?? defaultContractAddresses.btcRelayDeploymentHeight);
@@ -16,6 +16,9 @@ export type EVMRetryPolicy = {
16
16
  export type EVMConfiguration = {
17
17
  safeBlockTag: EVMBlockTag;
18
18
  maxLogsBlockRange: number;
19
+ maxParallelLogRequests: number;
20
+ maxParallelCalls: number;
21
+ maxLogTopics: number;
19
22
  };
20
23
  export declare class EVMChainInterface<ChainId extends string = string> implements ChainInterface {
21
24
  readonly chainId: ChainId;
@@ -91,19 +91,27 @@ class EVMEvents extends EVMModule_1.EVMModule {
91
91
  */
92
92
  async findInEvents(contract, topics, processor, abortSignal, genesisHeight) {
93
93
  const { number: latestBlockNumber } = await this.provider.getBlock(this.root.config.safeBlockTag);
94
+ let promises = [];
94
95
  for (let blockNumber = latestBlockNumber; blockNumber >= (genesisHeight ?? 0); blockNumber -= this.root.config.maxLogsBlockRange) {
95
- const eventsResult = await this.provider.getLogs({
96
- address: contract,
97
- topics,
98
- fromBlock: Math.max(blockNumber - this.root.config.maxLogsBlockRange, 0),
99
- toBlock: blockNumber === latestBlockNumber ? this.root.config.safeBlockTag : blockNumber
100
- });
101
- if (abortSignal != null)
102
- abortSignal.throwIfAborted();
103
- const result = await processor(eventsResult.reverse()); //Newest events first
104
- if (result != null)
105
- return result;
96
+ promises.push(this.getLogs(contract, topics, Math.max(blockNumber - this.root.config.maxLogsBlockRange, 0), blockNumber));
97
+ if (promises.length >= this.root.config.maxParallelLogRequests) {
98
+ const eventsResult = (await Promise.all(promises)).map(arr => arr.reverse() //Oldest events first
99
+ ).flat();
100
+ promises = [];
101
+ if (abortSignal != null)
102
+ abortSignal.throwIfAborted();
103
+ const result = await processor(eventsResult);
104
+ if (result != null)
105
+ return result;
106
+ }
106
107
  }
108
+ const eventsResult = (await Promise.all(promises)).map(arr => arr.reverse() //Oldest events first
109
+ ).flat();
110
+ if (abortSignal != null)
111
+ abortSignal.throwIfAborted();
112
+ const result = await processor(eventsResult); //Oldest events first
113
+ if (result != null)
114
+ return result;
107
115
  return null;
108
116
  }
109
117
  /**
@@ -118,19 +126,25 @@ class EVMEvents extends EVMModule_1.EVMModule {
118
126
  */
119
127
  async findInEventsForward(contract, topics, processor, abortSignal, startHeight) {
120
128
  const { number: latestBlockNumber } = await this.provider.getBlock(this.root.config.safeBlockTag);
129
+ let promises = [];
121
130
  for (let blockNumber = startHeight ?? 0; blockNumber < latestBlockNumber; blockNumber += this.root.config.maxLogsBlockRange) {
122
- const eventsResult = await this.provider.getLogs({
123
- address: contract,
124
- topics,
125
- fromBlock: blockNumber,
126
- toBlock: (blockNumber + this.root.config.maxLogsBlockRange) > latestBlockNumber ? this.root.config.safeBlockTag : blockNumber + this.root.config.maxLogsBlockRange
127
- });
128
- if (abortSignal != null)
129
- abortSignal.throwIfAborted();
130
- const result = await processor(eventsResult); //Oldest events first
131
- if (result != null)
132
- return result;
131
+ promises.push(this.getLogs(contract, topics, blockNumber, Math.min(blockNumber + this.root.config.maxLogsBlockRange, latestBlockNumber)));
132
+ if (promises.length >= this.root.config.maxParallelLogRequests) {
133
+ const eventsResult = (await Promise.all(promises)).flat();
134
+ promises = [];
135
+ if (abortSignal != null)
136
+ abortSignal.throwIfAborted();
137
+ const result = await processor(eventsResult); //Oldest events first
138
+ if (result != null)
139
+ return result;
140
+ }
133
141
  }
142
+ const eventsResult = (await Promise.all(promises)).flat();
143
+ if (abortSignal != null)
144
+ abortSignal.throwIfAborted();
145
+ const result = await processor(eventsResult); //Oldest events first
146
+ if (result != null)
147
+ return result;
134
148
  return null;
135
149
  }
136
150
  }
@@ -28,7 +28,7 @@ export declare class EVMContractEvents<T extends BaseContract> extends EVMEvents
28
28
  * if the search should continue
29
29
  * @param abortSignal
30
30
  */
31
- findInContractEvents<TResult, TEventName extends keyof T["filters"]>(events: TEventName[], keys: string[], processor: (event: TypedEventLog<T["filters"][TEventName]>) => Promise<TResult>, abortSignal?: AbortSignal): Promise<TResult>;
31
+ findInContractEvents<TResult, TEventName extends keyof T["filters"]>(events: TEventName[], keys: (string | string[])[], processor: (event: TypedEventLog<T["filters"][TEventName]>) => Promise<TResult>, abortSignal?: AbortSignal): Promise<TResult>;
32
32
  /**
33
33
  * Runs a search forwards in time, processing the events for a specific topic
34
34
  *
@@ -38,5 +38,5 @@ export declare class EVMContractEvents<T extends BaseContract> extends EVMEvents
38
38
  * if the search should continue
39
39
  * @param abortSignal
40
40
  */
41
- findInContractEventsForward<TResult, TEventName extends keyof T["filters"]>(events: TEventName[], keys: string[], processor: (event: TypedEventLog<T["filters"][TEventName]>) => Promise<TResult>, abortSignal?: AbortSignal): Promise<TResult>;
41
+ findInContractEventsForward<TResult, TEventName extends keyof T["filters"]>(events: TEventName[], keys: (string | string[])[], processor: (event: TypedEventLog<T["filters"][TEventName]>) => Promise<TResult>, abortSignal?: AbortSignal): Promise<TResult>;
42
42
  }
@@ -41,10 +41,38 @@ export declare class EVMSpvVaultContract<ChainId extends string> extends EVMCont
41
41
  checkWithdrawalTx(tx: SpvWithdrawalTransactionData): Promise<void>;
42
42
  createVaultData(owner: string, vaultId: bigint, utxo: string, confirmations: number, tokenData: SpvVaultTokenData[]): Promise<EVMSpvVaultData>;
43
43
  getFronterAddress(owner: string, vaultId: bigint, withdrawal: EVMSpvWithdrawalData): Promise<string>;
44
+ getFronterAddresses(withdrawals: {
45
+ owner: string;
46
+ vaultId: bigint;
47
+ withdrawal: EVMSpvWithdrawalData;
48
+ }[]): Promise<{
49
+ [btcTxId: string]: string | null;
50
+ }>;
44
51
  private vaultParamsCache;
45
52
  getVaultData(owner: string, vaultId: bigint): Promise<EVMSpvVaultData>;
53
+ getMultipleVaultData(vaults: {
54
+ owner: string;
55
+ vaultId: bigint;
56
+ }[]): Promise<{
57
+ [owner: string]: {
58
+ [vaultId: string]: EVMSpvVaultData;
59
+ };
60
+ }>;
61
+ getVaultLatestUtxo(owner: string, vaultId: bigint): Promise<string | null>;
62
+ getVaultLatestUtxos(vaults: {
63
+ owner: string;
64
+ vaultId: bigint;
65
+ }[]): Promise<{
66
+ [owner: string]: {
67
+ [vaultId: string]: string | null;
68
+ };
69
+ }>;
46
70
  getAllVaults(owner?: string): Promise<EVMSpvVaultData[]>;
71
+ private parseWithdrawalEvent;
47
72
  getWithdrawalState(btcTxId: string): Promise<SpvWithdrawalState>;
73
+ getWithdrawalStates(btcTxIds: string[]): Promise<{
74
+ [btcTxId: string]: SpvWithdrawalState;
75
+ }>;
48
76
  getWithdrawalData(btcTx: BtcTx): Promise<EVMSpvWithdrawalData>;
49
77
  fromOpReturnData(data: Buffer): {
50
78
  recipient: string;
@@ -125,6 +125,22 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
125
125
  return null;
126
126
  return frontingAddress;
127
127
  }
128
+ async getFronterAddresses(withdrawals) {
129
+ const result = {};
130
+ let promises = [];
131
+ //TODO: We can upgrade this to use multicall
132
+ for (let { owner, vaultId, withdrawal } of withdrawals) {
133
+ promises.push(this.getFronterAddress(owner, vaultId, withdrawal).then(val => {
134
+ result[withdrawal.getTxId()] = val;
135
+ }));
136
+ if (promises.length >= this.Chain.config.maxParallelCalls) {
137
+ await Promise.all(promises);
138
+ promises = [];
139
+ }
140
+ }
141
+ await Promise.all(promises);
142
+ return result;
143
+ }
128
144
  async getVaultData(owner, vaultId) {
129
145
  const vaultState = await this.contract.getVault(owner, vaultId);
130
146
  const vaultParams = await this.vaultParamsCache.getOrComputeAsync(vaultState.spvVaultParametersCommitment, async () => {
@@ -142,6 +158,47 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
142
158
  return null;
143
159
  return new EVMSpvVaultData_1.EVMSpvVaultData(owner, vaultId, vaultState, vaultParams);
144
160
  }
161
+ async getMultipleVaultData(vaults) {
162
+ const result = {};
163
+ let promises = [];
164
+ //TODO: We can upgrade this to use multicall
165
+ for (let { owner, vaultId } of vaults) {
166
+ promises.push(this.getVaultData(owner, vaultId).then(val => {
167
+ result[owner] ?? (result[owner] = {});
168
+ result[owner][vaultId.toString(10)] = val;
169
+ }));
170
+ if (promises.length >= this.Chain.config.maxParallelCalls) {
171
+ await Promise.all(promises);
172
+ promises = [];
173
+ }
174
+ }
175
+ await Promise.all(promises);
176
+ return result;
177
+ }
178
+ async getVaultLatestUtxo(owner, vaultId) {
179
+ const vaultState = await this.contract.getVault(owner, vaultId);
180
+ const utxo = (0, EVMSpvVaultData_1.getVaultUtxoFromState)(vaultState);
181
+ if (utxo === "0000000000000000000000000000000000000000000000000000000000000000:0")
182
+ return null;
183
+ return utxo;
184
+ }
185
+ async getVaultLatestUtxos(vaults) {
186
+ const result = {};
187
+ let promises = [];
188
+ //TODO: We can upgrade this to use multicall
189
+ for (let { owner, vaultId } of vaults) {
190
+ promises.push(this.getVaultLatestUtxo(owner, vaultId).then(val => {
191
+ result[owner] ?? (result[owner] = {});
192
+ result[owner][vaultId.toString(10)] = val;
193
+ }));
194
+ if (promises.length >= this.Chain.config.maxParallelCalls) {
195
+ await Promise.all(promises);
196
+ promises = [];
197
+ }
198
+ }
199
+ await Promise.all(promises);
200
+ return result;
201
+ }
145
202
  async getAllVaults(owner) {
146
203
  const openedVaults = new Map();
147
204
  await this.Events.findInContractEventsForward(["Opened", "Closed"], owner == null ? null : [
@@ -167,6 +224,44 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
167
224
  }
168
225
  return vaults;
169
226
  }
227
+ parseWithdrawalEvent(event) {
228
+ switch (event.eventName) {
229
+ case "Fronted":
230
+ const frontedEvent = event;
231
+ const [ownerFront, vaultIdFront] = unpackOwnerAndVaultId(frontedEvent.args.ownerAndVaultId);
232
+ return {
233
+ type: base_1.SpvWithdrawalStateType.FRONTED,
234
+ txId: event.transactionHash,
235
+ owner: ownerFront,
236
+ vaultId: vaultIdFront,
237
+ recipient: frontedEvent.args.recipient,
238
+ fronter: frontedEvent.args.caller
239
+ };
240
+ case "Claimed":
241
+ const claimedEvent = event;
242
+ const [ownerClaim, vaultIdClaim] = unpackOwnerAndVaultId(claimedEvent.args.ownerAndVaultId);
243
+ return {
244
+ type: base_1.SpvWithdrawalStateType.CLAIMED,
245
+ txId: event.transactionHash,
246
+ owner: ownerClaim,
247
+ vaultId: vaultIdClaim,
248
+ recipient: claimedEvent.args.recipient,
249
+ claimer: claimedEvent.args.caller,
250
+ fronter: claimedEvent.args.frontingAddress
251
+ };
252
+ case "Closed":
253
+ const closedEvent = event;
254
+ return {
255
+ type: base_1.SpvWithdrawalStateType.CLOSED,
256
+ txId: event.transactionHash,
257
+ owner: closedEvent.args.owner,
258
+ vaultId: closedEvent.args.vaultId,
259
+ error: closedEvent.args.error
260
+ };
261
+ default:
262
+ return null;
263
+ }
264
+ }
170
265
  async getWithdrawalState(btcTxId) {
171
266
  const txHash = buffer_1.Buffer.from(btcTxId, "hex").reverse();
172
267
  let result = await this.Events.findInContractEvents(["Fronted", "Claimed", "Closed"], [
@@ -174,46 +269,47 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
174
269
  null,
175
270
  (0, ethers_1.hexlify)(txHash)
176
271
  ], async (event) => {
177
- switch (event.eventName) {
178
- case "Fronted":
179
- const frontedEvent = event;
180
- const [ownerFront, vaultIdFront] = unpackOwnerAndVaultId(frontedEvent.args.ownerAndVaultId);
181
- return {
182
- type: base_1.SpvWithdrawalStateType.FRONTED,
183
- txId: event.transactionHash,
184
- owner: ownerFront,
185
- vaultId: vaultIdFront,
186
- recipient: frontedEvent.args.recipient,
187
- fronter: frontedEvent.args.caller
188
- };
189
- case "Claimed":
190
- const claimedEvent = event;
191
- const [ownerClaim, vaultIdClaim] = unpackOwnerAndVaultId(claimedEvent.args.ownerAndVaultId);
192
- return {
193
- type: base_1.SpvWithdrawalStateType.CLAIMED,
194
- txId: event.transactionHash,
195
- owner: ownerClaim,
196
- vaultId: vaultIdClaim,
197
- recipient: claimedEvent.args.recipient,
198
- claimer: claimedEvent.args.caller,
199
- fronter: claimedEvent.args.frontingAddress
200
- };
201
- case "Closed":
202
- const closedEvent = event;
203
- return {
204
- type: base_1.SpvWithdrawalStateType.CLOSED,
205
- txId: event.transactionHash,
206
- owner: closedEvent.args.owner,
207
- vaultId: closedEvent.args.vaultId,
208
- error: closedEvent.args.error
209
- };
210
- }
272
+ const result = this.parseWithdrawalEvent(event);
273
+ if (result != null)
274
+ return result;
211
275
  });
212
276
  result ?? (result = {
213
277
  type: base_1.SpvWithdrawalStateType.NOT_FOUND
214
278
  });
215
279
  return result;
216
280
  }
281
+ async getWithdrawalStates(btcTxIds) {
282
+ const result = {};
283
+ for (let i = 0; i < btcTxIds.length; i += this.Chain.config.maxLogTopics) {
284
+ const checkBtcTxIds = btcTxIds.slice(i, i + this.Chain.config.maxLogTopics);
285
+ const checkBtcTxIdsSet = new Set(checkBtcTxIds);
286
+ await this.Events.findInContractEvents(["Fronted", "Claimed", "Closed"], [
287
+ null,
288
+ null,
289
+ checkBtcTxIds.map(btcTxId => (0, ethers_1.hexlify)(buffer_1.Buffer.from(btcTxId, "hex").reverse()))
290
+ ], async (event) => {
291
+ const _event = event;
292
+ const btcTxId = buffer_1.Buffer.from(_event.args.btcTxHash.substring(2), "hex").reverse().toString("hex");
293
+ if (!checkBtcTxIdsSet.has(btcTxId)) {
294
+ this.logger.warn(`getWithdrawalStates(): findInContractEvents-callback: loaded event for ${btcTxId}, but transaction not found in input params!`);
295
+ return null;
296
+ }
297
+ const eventResult = this.parseWithdrawalEvent(event);
298
+ if (eventResult == null)
299
+ return null;
300
+ checkBtcTxIdsSet.delete(btcTxId);
301
+ result[btcTxId] = eventResult;
302
+ if (checkBtcTxIdsSet.size === 0)
303
+ return true; //All processed
304
+ });
305
+ }
306
+ for (let btcTxId of btcTxIds) {
307
+ result[btcTxId] ?? (result[btcTxId] = {
308
+ type: base_1.SpvWithdrawalStateType.NOT_FOUND
309
+ });
310
+ }
311
+ return result;
312
+ }
217
313
  getWithdrawalData(btcTx) {
218
314
  return Promise.resolve(new EVMSpvWithdrawalData_1.EVMSpvWithdrawalData(btcTx));
219
315
  }
@@ -2,6 +2,7 @@ import { SpvVaultClaimEvent, SpvVaultCloseEvent, SpvVaultData, SpvVaultDepositEv
2
2
  import { EVMSpvWithdrawalData } from "./EVMSpvWithdrawalData";
3
3
  import { SpvVaultParametersStruct, SpvVaultStateStruct } from "./SpvVaultContractTypechain";
4
4
  export declare function getVaultParamsCommitment(vaultParams: SpvVaultParametersStruct): string;
5
+ export declare function getVaultUtxoFromState(state: SpvVaultStateStruct): string;
5
6
  export declare class EVMSpvVaultData extends SpvVaultData<EVMSpvWithdrawalData> {
6
7
  readonly owner: string;
7
8
  readonly vaultId: bigint;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EVMSpvVaultData = exports.getVaultParamsCommitment = void 0;
3
+ exports.EVMSpvVaultData = exports.getVaultUtxoFromState = exports.getVaultParamsCommitment = void 0;
4
4
  const base_1 = require("@atomiqlabs/base");
5
5
  const buffer_1 = require("buffer");
6
6
  const EVMSpvWithdrawalData_1 = require("./EVMSpvWithdrawalData");
@@ -11,6 +11,11 @@ function getVaultParamsCommitment(vaultParams) {
11
11
  return (0, ethers_2.keccak256)(ethers_2.AbiCoder.defaultAbiCoder().encode(["address", "address", "address", "uint192", "uint192", "uint256"], [vaultParams.btcRelayContract, vaultParams.token0, vaultParams.token1, vaultParams.token0Multiplier, vaultParams.token1Multiplier, vaultParams.confirmations]));
12
12
  }
13
13
  exports.getVaultParamsCommitment = getVaultParamsCommitment;
14
+ function getVaultUtxoFromState(state) {
15
+ const txHash = buffer_1.Buffer.from((0, ethers_1.hexlify)(state.utxoTxHash).substring(2), "hex");
16
+ return txHash.reverse().toString("hex") + ":" + BigInt(state.utxoVout).toString(10);
17
+ }
18
+ exports.getVaultUtxoFromState = getVaultUtxoFromState;
14
19
  class EVMSpvVaultData extends base_1.SpvVaultData {
15
20
  constructor(ownerOrObj, vaultId, state, params, initialUtxo) {
16
21
  super();
@@ -28,8 +33,7 @@ class EVMSpvVaultData extends base_1.SpvVaultData {
28
33
  multiplier: BigInt(params.token1Multiplier),
29
34
  rawAmount: BigInt(state.token1Amount)
30
35
  };
31
- const txHash = buffer_1.Buffer.from((0, ethers_1.hexlify)(state.utxoTxHash).substring(2), "hex");
32
- this.utxo = txHash.reverse().toString("hex") + ":" + BigInt(state.utxoVout).toString(10);
36
+ this.utxo = getVaultUtxoFromState(state);
33
37
  this.confirmations = Number(params.confirmations);
34
38
  this.withdrawCount = Number(state.withdrawCount);
35
39
  this.depositCount = Number(state.depositCount);
@@ -128,6 +128,12 @@ export declare class EVMSwapContract<ChainId extends string = string> extends EV
128
128
  * @param data
129
129
  */
130
130
  getCommitStatus(signer: string, data: EVMSwapData): Promise<SwapCommitState>;
131
+ getCommitStatuses(request: {
132
+ signer: string;
133
+ swapData: EVMSwapData;
134
+ }[]): Promise<{
135
+ [p: string]: SwapCommitState;
136
+ }>;
131
137
  /**
132
138
  * Returns the data committed for a specific payment hash, or null if no data is currently commited for
133
139
  * the specific swap
@@ -243,6 +243,22 @@ class EVMSwapContract extends EVMContractBase_1.EVMContractBase {
243
243
  };
244
244
  }
245
245
  }
246
+ async getCommitStatuses(request) {
247
+ const result = {};
248
+ let promises = [];
249
+ //TODO: We can upgrade this to use multicall
250
+ for (let { signer, swapData } of request) {
251
+ promises.push(this.getCommitStatus(signer, swapData).then(val => {
252
+ result[swapData.getEscrowHash()] = val;
253
+ }));
254
+ if (promises.length >= this.Chain.config.maxParallelCalls) {
255
+ await Promise.all(promises);
256
+ promises = [];
257
+ }
258
+ }
259
+ await Promise.all(promises);
260
+ return result;
261
+ }
246
262
  /**
247
263
  * Returns the data committed for a specific payment hash, or null if no data is currently commited for
248
264
  * the specific swap
@@ -83,7 +83,7 @@ exports.allowedEthersErrorNumbers = new Set([
83
83
  -32700,
84
84
  -32600,
85
85
  -32601,
86
- // -32602, //Invalid params
86
+ -32602,
87
87
  // -32603, //Internal error
88
88
  -32000,
89
89
  // -32001, //Resource not found
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/chain-evm",
3
- "version": "1.0.0-dev.89",
3
+ "version": "1.0.0-dev.92",
4
4
  "description": "EVM specific base implementation",
5
5
  "main": "./dist/index.js",
6
6
  "types:": "./dist/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "author": "adambor",
24
24
  "license": "Apache-2.0",
25
25
  "dependencies": {
26
- "@atomiqlabs/base": "^10.0.0-dev.10",
26
+ "@atomiqlabs/base": "^10.0.0-dev.13",
27
27
  "@noble/hashes": "^1.8.0",
28
28
  "@scure/btc-signer": "^1.6.0",
29
29
  "buffer": "6.0.3",
@@ -1,6 +1,6 @@
1
1
  import {BaseTokenType, BitcoinNetwork, BitcoinRpc, ChainData, ChainInitializer, ChainSwapType} from "@atomiqlabs/base";
2
2
  import {JsonRpcApiProvider, JsonRpcProvider, WebSocketProvider} from "ethers";
3
- import {EVMChainInterface, EVMRetryPolicy} from "../../evm/chain/EVMChainInterface";
3
+ import {EVMChainInterface, EVMConfiguration, EVMRetryPolicy} from "../../evm/chain/EVMChainInterface";
4
4
  import {EVMFees} from "../../evm/chain/modules/EVMFees";
5
5
  import {EVMBtcRelay} from "../../evm/btcrelay/EVMBtcRelay";
6
6
  import {EVMSwapContract} from "../../evm/swaps/EVMSwapContract";
@@ -70,7 +70,6 @@ export type BotanixOptions = {
70
70
  rpcUrl: string | JsonRpcApiProvider,
71
71
  retryPolicy?: EVMRetryPolicy,
72
72
  chainType?: "MAINNET" | "TESTNET",
73
- maxLogsBlockRange?: number,
74
73
 
75
74
  swapContract?: string,
76
75
  btcRelayContract?: string,
@@ -86,7 +85,9 @@ export type BotanixOptions = {
86
85
  }
87
86
  }
88
87
 
89
- fees?: EVMFees
88
+ fees?: EVMFees,
89
+
90
+ evmConfig?: Omit<EVMConfiguration, "safeBlockTag">
90
91
  }
91
92
 
92
93
  export function initializeBotanix(
@@ -120,7 +121,11 @@ export function initializeBotanix(
120
121
 
121
122
  const chainInterface = new EVMChainInterface("BOTANIX", chainId, provider, {
122
123
  safeBlockTag: "finalized",
123
- maxLogsBlockRange: options.maxLogsBlockRange ?? 500
124
+ maxLogsBlockRange: 950,
125
+ maxLogTopics: 64,
126
+ maxParallelLogRequests: 5,
127
+ maxParallelCalls: 5,
128
+ ...options?.evmConfig
124
129
  }, options.retryPolicy, Fees);
125
130
 
126
131
  const btcRelay = new EVMBtcRelay(
@@ -1,6 +1,6 @@
1
1
  import {BaseTokenType, BitcoinNetwork, BitcoinRpc, ChainData, ChainInitializer, ChainSwapType} from "@atomiqlabs/base";
2
2
  import {JsonRpcApiProvider, JsonRpcProvider, WebSocketProvider} from "ethers";
3
- import {EVMChainInterface, EVMRetryPolicy} from "../../evm/chain/EVMChainInterface";
3
+ import {EVMChainInterface, EVMConfiguration, EVMRetryPolicy} from "../../evm/chain/EVMChainInterface";
4
4
  import {CitreaChainType} from "./CitreaChainType";
5
5
  import {EVMChainEventsBrowser} from "../../evm/events/EVMChainEventsBrowser";
6
6
  import {EVMSwapData} from "../../evm/swaps/EVMSwapData";
@@ -76,7 +76,6 @@ export type CitreaOptions = {
76
76
  rpcUrl: string | JsonRpcApiProvider,
77
77
  retryPolicy?: EVMRetryPolicy,
78
78
  chainType?: "MAINNET" | "TESTNET4",
79
- maxLogsBlockRange?: number,
80
79
 
81
80
  swapContract?: string,
82
81
  btcRelayContract?: string,
@@ -92,7 +91,9 @@ export type CitreaOptions = {
92
91
  }
93
92
  }
94
93
 
95
- fees?: CitreaFees
94
+ fees?: CitreaFees,
95
+
96
+ evmConfig?: Omit<EVMConfiguration, "safeBlockTag">
96
97
  }
97
98
 
98
99
  export function initializeCitrea(
@@ -126,7 +127,11 @@ export function initializeCitrea(
126
127
 
127
128
  const chainInterface = new EVMChainInterface("CITREA", chainId, provider, {
128
129
  safeBlockTag: "latest",
129
- maxLogsBlockRange: options.maxLogsBlockRange ?? 500
130
+ maxLogsBlockRange: 950,
131
+ maxLogTopics: 64,
132
+ maxParallelLogRequests: 5,
133
+ maxParallelCalls: 5,
134
+ ...options?.evmConfig
130
135
  }, options.retryPolicy, Fees);
131
136
  chainInterface.Tokens = new CitreaTokens(chainInterface); //Override with custom token module allowing l1 state diff based fee calculation
132
137
 
@@ -18,7 +18,10 @@ export type EVMRetryPolicy = {
18
18
 
19
19
  export type EVMConfiguration = {
20
20
  safeBlockTag: EVMBlockTag,
21
- maxLogsBlockRange: number
21
+ maxLogsBlockRange: number,
22
+ maxParallelLogRequests: number,
23
+ maxParallelCalls: number,
24
+ maxLogTopics: number
22
25
  };
23
26
 
24
27
  export class EVMChainInterface<ChainId extends string = string> implements ChainInterface {
@@ -102,19 +102,35 @@ export class EVMEvents extends EVMModule<any> {
102
102
  ): Promise<T> {
103
103
  const {number: latestBlockNumber} = await this.provider.getBlock(this.root.config.safeBlockTag);
104
104
 
105
+ let promises: Promise<Log[]>[] = [];
105
106
  for(let blockNumber = latestBlockNumber; blockNumber >= (genesisHeight ?? 0); blockNumber-=this.root.config.maxLogsBlockRange) {
106
- const eventsResult = await this.provider.getLogs({
107
- address: contract,
107
+ promises.push(this.getLogs(
108
+ contract,
108
109
  topics,
109
- fromBlock: Math.max(blockNumber-this.root.config.maxLogsBlockRange, 0),
110
- toBlock: blockNumber===latestBlockNumber ? this.root.config.safeBlockTag : blockNumber
111
- });
110
+ Math.max(blockNumber-this.root.config.maxLogsBlockRange, 0),
111
+ blockNumber
112
+ ));
112
113
 
113
- if(abortSignal!=null) abortSignal.throwIfAborted();
114
+ if(promises.length>=this.root.config.maxParallelLogRequests) {
115
+ const eventsResult = (await Promise.all(promises)).map(
116
+ arr => arr.reverse() //Oldest events first
117
+ ).flat();
118
+ promises = [];
119
+ if(abortSignal!=null) abortSignal.throwIfAborted();
114
120
 
115
- const result: T = await processor(eventsResult.reverse()); //Newest events first
116
- if(result!=null) return result;
121
+ const result: T = await processor(eventsResult);
122
+ if(result!=null) return result;
123
+ }
117
124
  }
125
+
126
+ const eventsResult = (await Promise.all(promises)).map(
127
+ arr => arr.reverse() //Oldest events first
128
+ ).flat();
129
+ if(abortSignal!=null) abortSignal.throwIfAborted();
130
+
131
+ const result: T = await processor(eventsResult); //Oldest events first
132
+ if(result!=null) return result;
133
+
118
134
  return null;
119
135
  }
120
136
 
@@ -136,20 +152,30 @@ export class EVMEvents extends EVMModule<any> {
136
152
  ): Promise<T> {
137
153
  const {number: latestBlockNumber} = await this.provider.getBlock(this.root.config.safeBlockTag);
138
154
 
155
+ let promises: Promise<Log[]>[] = [];
139
156
  for(let blockNumber = startHeight ?? 0; blockNumber < latestBlockNumber; blockNumber += this.root.config.maxLogsBlockRange) {
140
- const eventsResult = await this.provider.getLogs({
141
- address: contract,
157
+ promises.push(this.getLogs(
158
+ contract,
142
159
  topics,
143
- fromBlock: blockNumber,
144
- toBlock: (blockNumber + this.root.config.maxLogsBlockRange) > latestBlockNumber ? this.root.config.safeBlockTag : blockNumber + this.root.config.maxLogsBlockRange
145
- });
146
-
147
- if(abortSignal!=null) abortSignal.throwIfAborted();
160
+ blockNumber,
161
+ Math.min(blockNumber + this.root.config.maxLogsBlockRange, latestBlockNumber)
162
+ ));
163
+ if(promises.length>=this.root.config.maxParallelLogRequests) {
164
+ const eventsResult = (await Promise.all(promises)).flat();
165
+ promises = [];
166
+ if(abortSignal!=null) abortSignal.throwIfAborted();
148
167
 
149
- const result: T = await processor(eventsResult); //Oldest events first
150
- if(result!=null) return result;
168
+ const result: T = await processor(eventsResult); //Oldest events first
169
+ if(result!=null) return result;
170
+ }
151
171
  }
152
172
 
173
+ const eventsResult = (await Promise.all(promises)).flat();
174
+ if(abortSignal!=null) abortSignal.throwIfAborted();
175
+
176
+ const result: T = await processor(eventsResult); //Oldest events first
177
+ if(result!=null) return result;
178
+
153
179
  return null;
154
180
  }
155
181
 
@@ -21,7 +21,7 @@ export class EVMContractEvents<T extends BaseContract> extends EVMEvents {
21
21
 
22
22
  private toFilter<TEventName extends keyof T["filters"]>(
23
23
  events: TEventName[],
24
- keys: string[],
24
+ keys: (string | string[])[],
25
25
  ): (string | string[])[] {
26
26
  const filterArray: (string | string[])[] = [];
27
27
  filterArray.push(events.map(name => {
@@ -61,7 +61,7 @@ export class EVMContractEvents<T extends BaseContract> extends EVMEvents {
61
61
  */
62
62
  public async findInContractEvents<TResult, TEventName extends keyof T["filters"]>(
63
63
  events: TEventName[],
64
- keys: string[],
64
+ keys: (string | string[])[],
65
65
  processor: (event: TypedEventLog<T["filters"][TEventName]>) => Promise<TResult>,
66
66
  abortSignal?: AbortSignal
67
67
  ): Promise<TResult> {
@@ -85,7 +85,7 @@ export class EVMContractEvents<T extends BaseContract> extends EVMEvents {
85
85
  */
86
86
  public async findInContractEventsForward<TResult, TEventName extends keyof T["filters"]>(
87
87
  events: TEventName[],
88
- keys: string[],
88
+ keys: (string | string[])[],
89
89
  processor: (event: TypedEventLog<T["filters"][TEventName]>) => Promise<TResult>,
90
90
  abortSignal?: AbortSignal
91
91
  ): Promise<TResult> {
@@ -21,7 +21,7 @@ import {getLogger} from "../../utils/Utils";
21
21
  import {EVMChainInterface} from "../chain/EVMChainInterface";
22
22
  import {AbiCoder, getAddress, hexlify, keccak256, TransactionRequest, ZeroAddress, ZeroHash} from "ethers";
23
23
  import {EVMAddresses} from "../chain/modules/EVMAddresses";
24
- import {EVMSpvVaultData, getVaultParamsCommitment} from "./EVMSpvVaultData";
24
+ import {EVMSpvVaultData, getVaultParamsCommitment, getVaultUtxoFromState} from "./EVMSpvVaultData";
25
25
  import {EVMSpvWithdrawalData} from "./EVMSpvWithdrawalData";
26
26
  import {EVMFees} from "../chain/modules/EVMFees";
27
27
  import {EVMBtcStoredHeader} from "../btcrelay/headers/EVMBtcStoredHeader";
@@ -210,6 +210,25 @@ export class EVMSpvVaultContract<ChainId extends string>
210
210
  return frontingAddress;
211
211
  }
212
212
 
213
+ async getFronterAddresses(withdrawals: {owner: string, vaultId: bigint, withdrawal: EVMSpvWithdrawalData}[]): Promise<{[btcTxId: string]: string | null}> {
214
+ const result: {
215
+ [btcTxId: string]: string | null
216
+ } = {};
217
+ let promises: Promise<void>[] = [];
218
+ //TODO: We can upgrade this to use multicall
219
+ for(let {owner, vaultId, withdrawal} of withdrawals) {
220
+ promises.push(this.getFronterAddress(owner, vaultId, withdrawal).then(val => {
221
+ result[withdrawal.getTxId()] = val;
222
+ }));
223
+ if(promises.length>=this.Chain.config.maxParallelCalls) {
224
+ await Promise.all(promises);
225
+ promises = [];
226
+ }
227
+ }
228
+ await Promise.all(promises);
229
+ return result;
230
+ }
231
+
213
232
  private vaultParamsCache: PromiseLruCache<string, SpvVaultParametersStructOutput> = new PromiseLruCache<string, SpvVaultParametersStructOutput>(5000);
214
233
 
215
234
  async getVaultData(owner: string, vaultId: bigint): Promise<EVMSpvVaultData> {
@@ -239,6 +258,49 @@ export class EVMSpvVaultContract<ChainId extends string>
239
258
  return new EVMSpvVaultData(owner, vaultId, vaultState, vaultParams);
240
259
  }
241
260
 
261
+ async getMultipleVaultData(vaults: {owner: string, vaultId: bigint}[]): Promise<{[owner: string]: {[vaultId: string]: EVMSpvVaultData}}> {
262
+ const result: {[owner: string]: {[vaultId: string]: EVMSpvVaultData}} = {};
263
+ let promises: Promise<void>[] = [];
264
+ //TODO: We can upgrade this to use multicall
265
+ for(let {owner, vaultId} of vaults) {
266
+ promises.push(this.getVaultData(owner, vaultId).then(val => {
267
+ result[owner] ??= {};
268
+ result[owner][vaultId.toString(10)] = val;
269
+ }));
270
+ if(promises.length>=this.Chain.config.maxParallelCalls) {
271
+ await Promise.all(promises);
272
+ promises = [];
273
+ }
274
+ }
275
+ await Promise.all(promises);
276
+ return result;
277
+ }
278
+
279
+ async getVaultLatestUtxo(owner: string, vaultId: bigint): Promise<string | null> {
280
+ const vaultState = await this.contract.getVault(owner, vaultId);
281
+ const utxo = getVaultUtxoFromState(vaultState);
282
+ if(utxo==="0000000000000000000000000000000000000000000000000000000000000000:0") return null;
283
+ return utxo;
284
+ }
285
+
286
+ async getVaultLatestUtxos(vaults: {owner: string, vaultId: bigint}[]): Promise<{[owner: string]: {[vaultId: string]: string | null}}> {
287
+ const result: {[owner: string]: {[vaultId: string]: string | null}} = {};
288
+ let promises: Promise<void>[] = [];
289
+ //TODO: We can upgrade this to use multicall
290
+ for(let {owner, vaultId} of vaults) {
291
+ promises.push(this.getVaultLatestUtxo(owner, vaultId).then(val => {
292
+ result[owner] ??= {};
293
+ result[owner][vaultId.toString(10)] = val;
294
+ }));
295
+ if(promises.length>=this.Chain.config.maxParallelCalls) {
296
+ await Promise.all(promises);
297
+ promises = [];
298
+ }
299
+ }
300
+ await Promise.all(promises);
301
+ return result;
302
+ }
303
+
242
304
  async getAllVaults(owner?: string): Promise<EVMSpvVaultData[]> {
243
305
  const openedVaults = new Map<string, SpvVaultParametersStructOutput>();
244
306
  await this.Events.findInContractEventsForward(
@@ -269,6 +331,45 @@ export class EVMSpvVaultContract<ChainId extends string>
269
331
  return vaults;
270
332
  }
271
333
 
334
+ private parseWithdrawalEvent(event: TypedEventLog<SpvVaultManager["filters"][keyof SpvVaultManager["filters"]]>): SpvWithdrawalState | null {
335
+ switch(event.eventName) {
336
+ case "Fronted":
337
+ const frontedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Fronted"]>;
338
+ const [ownerFront, vaultIdFront] = unpackOwnerAndVaultId(frontedEvent.args.ownerAndVaultId);
339
+ return {
340
+ type: SpvWithdrawalStateType.FRONTED,
341
+ txId: event.transactionHash,
342
+ owner: ownerFront,
343
+ vaultId: vaultIdFront,
344
+ recipient: frontedEvent.args.recipient,
345
+ fronter: frontedEvent.args.caller
346
+ };
347
+ case "Claimed":
348
+ const claimedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Claimed"]>;
349
+ const [ownerClaim, vaultIdClaim] = unpackOwnerAndVaultId(claimedEvent.args.ownerAndVaultId);
350
+ return {
351
+ type: SpvWithdrawalStateType.CLAIMED,
352
+ txId: event.transactionHash,
353
+ owner: ownerClaim,
354
+ vaultId: vaultIdClaim,
355
+ recipient: claimedEvent.args.recipient,
356
+ claimer: claimedEvent.args.caller,
357
+ fronter: claimedEvent.args.frontingAddress
358
+ };
359
+ case "Closed":
360
+ const closedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Closed"]>;
361
+ return {
362
+ type: SpvWithdrawalStateType.CLOSED,
363
+ txId: event.transactionHash,
364
+ owner: closedEvent.args.owner,
365
+ vaultId: closedEvent.args.vaultId,
366
+ error: closedEvent.args.error
367
+ };
368
+ default:
369
+ return null;
370
+ }
371
+ }
372
+
272
373
  async getWithdrawalState(btcTxId: string): Promise<SpvWithdrawalState> {
273
374
  const txHash = Buffer.from(btcTxId, "hex").reverse();
274
375
  let result: SpvWithdrawalState = await this.Events.findInContractEvents(
@@ -279,40 +380,8 @@ export class EVMSpvVaultContract<ChainId extends string>
279
380
  hexlify(txHash)
280
381
  ],
281
382
  async (event) => {
282
- switch(event.eventName) {
283
- case "Fronted":
284
- const frontedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Fronted"]>;
285
- const [ownerFront, vaultIdFront] = unpackOwnerAndVaultId(frontedEvent.args.ownerAndVaultId);
286
- return {
287
- type: SpvWithdrawalStateType.FRONTED,
288
- txId: event.transactionHash,
289
- owner: ownerFront,
290
- vaultId: vaultIdFront,
291
- recipient: frontedEvent.args.recipient,
292
- fronter: frontedEvent.args.caller
293
- };
294
- case "Claimed":
295
- const claimedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Claimed"]>;
296
- const [ownerClaim, vaultIdClaim] = unpackOwnerAndVaultId(claimedEvent.args.ownerAndVaultId);
297
- return {
298
- type: SpvWithdrawalStateType.CLAIMED,
299
- txId: event.transactionHash,
300
- owner: ownerClaim,
301
- vaultId: vaultIdClaim,
302
- recipient: claimedEvent.args.recipient,
303
- claimer: claimedEvent.args.caller,
304
- fronter: claimedEvent.args.frontingAddress
305
- };
306
- case "Closed":
307
- const closedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Closed"]>;
308
- return {
309
- type: SpvWithdrawalStateType.CLOSED,
310
- txId: event.transactionHash,
311
- owner: closedEvent.args.owner,
312
- vaultId: closedEvent.args.vaultId,
313
- error: closedEvent.args.error
314
- };
315
- }
383
+ const result = this.parseWithdrawalEvent(event);
384
+ if(result!=null) return result;
316
385
  }
317
386
  );
318
387
  result ??= {
@@ -321,6 +390,45 @@ export class EVMSpvVaultContract<ChainId extends string>
321
390
  return result;
322
391
  }
323
392
 
393
+ async getWithdrawalStates(btcTxIds: string[]): Promise<{[btcTxId: string]: SpvWithdrawalState}> {
394
+ const result: {[btcTxId: string]: SpvWithdrawalState} = {};
395
+
396
+ for(let i=0;i<btcTxIds.length;i+=this.Chain.config.maxLogTopics) {
397
+ const checkBtcTxIds = btcTxIds.slice(i, i+this.Chain.config.maxLogTopics);
398
+ const checkBtcTxIdsSet = new Set(checkBtcTxIds);
399
+
400
+ await this.Events.findInContractEvents(
401
+ ["Fronted", "Claimed", "Closed"],
402
+ [
403
+ null,
404
+ null,
405
+ checkBtcTxIds.map(btcTxId => hexlify(Buffer.from(btcTxId, "hex").reverse()))
406
+ ],
407
+ async (event) => {
408
+ const _event = event as TypedEventLog<SpvVaultManager["filters"]["Fronted" | "Claimed" | "Closed"]>;
409
+ const btcTxId = Buffer.from(_event.args.btcTxHash.substring(2), "hex").reverse().toString("hex");
410
+ if(!checkBtcTxIdsSet.has(btcTxId)) {
411
+ this.logger.warn(`getWithdrawalStates(): findInContractEvents-callback: loaded event for ${btcTxId}, but transaction not found in input params!`)
412
+ return null;
413
+ }
414
+ const eventResult = this.parseWithdrawalEvent(event);
415
+ if(eventResult==null) return null;
416
+ checkBtcTxIdsSet.delete(btcTxId);
417
+ result[btcTxId] = eventResult;
418
+ if(checkBtcTxIdsSet.size===0) return true; //All processed
419
+ }
420
+ );
421
+ }
422
+
423
+ for(let btcTxId of btcTxIds) {
424
+ result[btcTxId] ??= {
425
+ type: SpvWithdrawalStateType.NOT_FOUND
426
+ };
427
+ }
428
+
429
+ return result;
430
+ }
431
+
324
432
  getWithdrawalData(btcTx: BtcTx): Promise<EVMSpvWithdrawalData> {
325
433
  return Promise.resolve(new EVMSpvWithdrawalData(btcTx));
326
434
  }
@@ -23,6 +23,11 @@ export function getVaultParamsCommitment(vaultParams: SpvVaultParametersStruct)
23
23
  ));
24
24
  }
25
25
 
26
+ export function getVaultUtxoFromState(state: SpvVaultStateStruct): string {
27
+ const txHash = Buffer.from(hexlify(state.utxoTxHash).substring(2), "hex");
28
+ return txHash.reverse().toString("hex")+":"+BigInt(state.utxoVout).toString(10);
29
+ }
30
+
26
31
  export class EVMSpvVaultData extends SpvVaultData<EVMSpvWithdrawalData> {
27
32
 
28
33
  readonly owner: string;
@@ -63,8 +68,7 @@ export class EVMSpvVaultData extends SpvVaultData<EVMSpvWithdrawalData> {
63
68
  multiplier: BigInt(params.token1Multiplier),
64
69
  rawAmount: BigInt(state.token1Amount)
65
70
  };
66
- const txHash = Buffer.from(hexlify(state.utxoTxHash).substring(2), "hex");
67
- this.utxo = txHash.reverse().toString("hex")+":"+BigInt(state.utxoVout).toString(10);
71
+ this.utxo = getVaultUtxoFromState(state);
68
72
  this.confirmations = Number(params.confirmations);
69
73
  this.withdrawCount = Number(state.withdrawCount);
70
74
  this.depositCount = Number(state.depositCount);
@@ -324,6 +324,27 @@ export class EVMSwapContract<ChainId extends string = string>
324
324
  }
325
325
  }
326
326
 
327
+ async getCommitStatuses(request: { signer: string; swapData: EVMSwapData }[]): Promise<{
328
+ [p: string]: SwapCommitState
329
+ }> {
330
+ const result: {
331
+ [p: string]: SwapCommitState
332
+ } = {};
333
+ let promises: Promise<void>[] = [];
334
+ //TODO: We can upgrade this to use multicall
335
+ for(let {signer, swapData} of request) {
336
+ promises.push(this.getCommitStatus(signer, swapData).then(val => {
337
+ result[swapData.getEscrowHash()] = val;
338
+ }));
339
+ if(promises.length>=this.Chain.config.maxParallelCalls) {
340
+ await Promise.all(promises);
341
+ promises = [];
342
+ }
343
+ }
344
+ await Promise.all(promises);
345
+ return result;
346
+ }
347
+
327
348
  /**
328
349
  * Returns the data committed for a specific payment hash, or null if no data is currently commited for
329
350
  * the specific swap
@@ -95,7 +95,7 @@ export const allowedEthersErrorNumbers: Set<number> = new Set([
95
95
  -32700, //Invalid JSON
96
96
  -32600, //Invalid request
97
97
  -32601, //Method not found
98
- // -32602, //Invalid params
98
+ -32602, //Invalid params
99
99
  // -32603, //Internal error
100
100
  -32000, //Invalid input
101
101
  // -32001, //Resource not found