@atomiqlabs/chain-evm 2.0.9 → 2.1.10

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.
@@ -22,6 +22,7 @@ export type AlpenOptions = {
22
22
  retryPolicy?: EVMRetryPolicy;
23
23
  chainType?: "MAINNET" | "TESTNET" | "TESTNET4";
24
24
  swapContract?: string;
25
+ swapContractDeploymentHeight?: number;
25
26
  btcRelayContract?: string;
26
27
  btcRelayDeploymentHeight?: number;
27
28
  spvVaultContract?: string;
@@ -35,7 +36,7 @@ export type AlpenOptions = {
35
36
  };
36
37
  };
37
38
  fees?: EVMFees;
38
- evmConfig?: Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag">;
39
+ evmConfig?: Partial<Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag" | "finalityCheckStrategy">>;
39
40
  };
40
41
  /**
41
42
  * Initialize Alpen chain integration
@@ -21,6 +21,7 @@ const AlpenContractAddresses = {
21
21
  MAINNET: {
22
22
  executionContract: "",
23
23
  swapContract: "",
24
+ swapContractDeploymentHeight: 0,
24
25
  btcRelayContract: "",
25
26
  btcRelayDeploymentHeight: 0,
26
27
  spvVaultContract: "",
@@ -40,6 +41,7 @@ const AlpenContractAddresses = {
40
41
  TESTNET: {
41
42
  executionContract: "0x32EB4DbDdC31e19ba908fecc7cae03F0d04F01Fa",
42
43
  swapContract: "0x2920EE496693A5027249a027A6FD3F643E743745",
44
+ swapContractDeploymentHeight: 532614,
43
45
  btcRelayContract: "0x59A54378B6bA9C21ba66487C6A701D702baDEabE",
44
46
  btcRelayDeploymentHeight: 532610,
45
47
  spvVaultContract: "0xaB2D14745362B26a732dD8B7F95daAE3D2914bBF",
@@ -59,6 +61,7 @@ const AlpenContractAddresses = {
59
61
  TESTNET4: {
60
62
  executionContract: "0xa2698D2fBE3f7c74cCca428a5fd968411644C641",
61
63
  swapContract: "0xb0226bAC3BD30179fb66A43cEA212AbBC988e004",
64
+ swapContractDeploymentHeight: 843614,
62
65
  btcRelayContract: "0xfFA842529977a40A3fdb988cdDC9CB5c39bAcF26",
63
66
  btcRelayDeploymentHeight: 843611,
64
67
  spvVaultContract: "0x62a718348081F9CF9a8E3dF4B4EA6d6349991ad9",
@@ -128,7 +131,7 @@ function initializeAlpen(options, bitcoinRpc, network) {
128
131
  ...defaultContractAddresses.handlerContracts.claim,
129
132
  ...options?.handlerContracts?.claim
130
133
  }
131
- });
134
+ }, options.swapContractDeploymentHeight ?? defaultContractAddresses.swapContractDeploymentHeight);
132
135
  const spvVaultContract = new EVMSpvVaultContract_1.EVMSpvVaultContract(chainInterface, btcRelay, bitcoinRpc, options.spvVaultContract ?? defaultContractAddresses.spvVaultContract, options.spvVaultDeploymentHeight ?? defaultContractAddresses.spvVaultDeploymentHeight);
133
136
  const chainEvents = new EVMChainEventsBrowser_1.EVMChainEventsBrowser(chainInterface, swapContract, spvVaultContract);
134
137
  return {
@@ -22,6 +22,7 @@ export type BotanixOptions = {
22
22
  retryPolicy?: EVMRetryPolicy;
23
23
  chainType?: "MAINNET" | "TESTNET";
24
24
  swapContract?: string;
25
+ swapContractDeploymentHeight?: number;
25
26
  btcRelayContract?: string;
26
27
  btcRelayDeploymentHeight?: number;
27
28
  spvVaultContract?: string;
@@ -35,7 +36,7 @@ export type BotanixOptions = {
35
36
  };
36
37
  };
37
38
  fees?: EVMFees;
38
- evmConfig?: Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag">;
39
+ evmConfig?: Partial<Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag" | "finalityCheckStrategy">>;
39
40
  };
40
41
  /**
41
42
  * Initialize Botanix chain integration
@@ -20,6 +20,7 @@ const BotanixContractAddresses = {
20
20
  MAINNET: {
21
21
  executionContract: "0x71Bc44F3F7203fC1279107D924e418F02b0d4029",
22
22
  swapContract: "0x9a027B5Bf43382Cc4A5134d9EFD389f61ece27B9",
23
+ swapContractDeploymentHeight: 2320403,
23
24
  btcRelayContract: "0xe8be24CF21341c9567664009a8a82C9Dc1eE90D6",
24
25
  btcRelayDeploymentHeight: 2320400,
25
26
  spvVaultContract: "0xe510D5781C6C849284Fb25Dc20b1684cEC445C8B",
@@ -39,6 +40,7 @@ const BotanixContractAddresses = {
39
40
  TESTNET: {
40
41
  executionContract: "0xe510D5781C6C849284Fb25Dc20b1684cEC445C8B",
41
42
  swapContract: "0xfFA842529977a40A3fdb988cdDC9CB5c39bAcF26",
43
+ swapContractDeploymentHeight: 4173454,
42
44
  btcRelayContract: "0xba7E78011909e3501027FBc226a04DCC837a555D",
43
45
  btcRelayDeploymentHeight: 3462466,
44
46
  spvVaultContract: "0x9a027B5Bf43382Cc4A5134d9EFD389f61ece27B9",
@@ -111,7 +113,7 @@ function initializeBotanix(options, bitcoinRpc, network) {
111
113
  ...defaultContractAddresses.handlerContracts.claim,
112
114
  ...options?.handlerContracts?.claim
113
115
  }
114
- });
116
+ }, options.swapContractDeploymentHeight ?? defaultContractAddresses.swapContractDeploymentHeight);
115
117
  const spvVaultContract = new EVMSpvVaultContract_1.EVMSpvVaultContract(chainInterface, btcRelay, bitcoinRpc, options.spvVaultContract ?? defaultContractAddresses.spvVaultContract, options.spvVaultDeploymentHeight ?? defaultContractAddresses.spvVaultDeploymentHeight);
116
118
  const chainEvents = new EVMChainEventsBrowser_1.EVMChainEventsBrowser(chainInterface, swapContract, spvVaultContract);
117
119
  return {
@@ -22,6 +22,7 @@ export type CitreaOptions = {
22
22
  retryPolicy?: EVMRetryPolicy;
23
23
  chainType?: "MAINNET" | "TESTNET4";
24
24
  swapContract?: string;
25
+ swapContractDeploymentHeight?: number;
25
26
  btcRelayContract?: string;
26
27
  btcRelayDeploymentHeight?: number;
27
28
  spvVaultContract?: string;
@@ -35,7 +36,7 @@ export type CitreaOptions = {
35
36
  };
36
37
  };
37
38
  fees?: CitreaFees;
38
- evmConfig?: Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag">;
39
+ evmConfig?: Partial<Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag" | "finalityCheckStrategy">>;
39
40
  };
40
41
  /**
41
42
  * Initialize Citrea chain integration
@@ -21,6 +21,7 @@ const CitreaContractAddresses = {
21
21
  MAINNET: {
22
22
  executionContract: "0x6a373b6Adad83964727bA0fa15E22Be05173fc12",
23
23
  swapContract: "0xc98Ef084d3911C8447DBbE4dDa18bC2c9bB0584e",
24
+ swapContractDeploymentHeight: 2452632,
24
25
  btcRelayContract: "0x11ac854A68830d61af975063c91602f878C36fA6",
25
26
  btcRelayDeploymentHeight: 2452629,
26
27
  spvVaultContract: "0x5bb0C725939cB825d1322A99a3FeB570097628c3",
@@ -40,6 +41,7 @@ const CitreaContractAddresses = {
40
41
  TESTNET4: {
41
42
  executionContract: "0x9e289512965A0842b342A6BB3F3c41F22a555Cfe",
42
43
  swapContract: "0xBbf7755b674dD107d59F0650D1A3fA9C60bf6Fe6",
44
+ swapContractDeploymentHeight: 12346224,
43
45
  btcRelayContract: "0x00D122E9f9766cd81a38D2dd44f9AFfb94c67Af7",
44
46
  btcRelayDeploymentHeight: 12346223,
45
47
  spvVaultContract: "0x9Bf990C6088F716279797a602b05941c40591533",
@@ -121,7 +123,7 @@ function initializeCitrea(options, bitcoinRpc, network) {
121
123
  ...defaultContractAddresses.handlerContracts.claim,
122
124
  ...options?.handlerContracts?.claim
123
125
  }
124
- });
126
+ }, options.swapContractDeploymentHeight ?? defaultContractAddresses.swapContractDeploymentHeight);
125
127
  const spvVaultContract = new CitreaSpvVaultContract_1.CitreaSpvVaultContract(chainInterface, btcRelay, bitcoinRpc, options.spvVaultContract ?? defaultContractAddresses.spvVaultContract, options.spvVaultDeploymentHeight ?? defaultContractAddresses.spvVaultDeploymentHeight);
126
128
  const chainEvents = new EVMChainEventsBrowser_1.EVMChainEventsBrowser(chainInterface, swapContract, spvVaultContract);
127
129
  return {
@@ -22,6 +22,7 @@ export type GoatOptions = {
22
22
  retryPolicy?: EVMRetryPolicy;
23
23
  chainType?: "MAINNET" | "TESTNET" | "TESTNET4";
24
24
  swapContract?: string;
25
+ swapContractDeploymentHeight?: number;
25
26
  btcRelayContract?: string;
26
27
  btcRelayDeploymentHeight?: number;
27
28
  spvVaultContract?: string;
@@ -35,7 +36,7 @@ export type GoatOptions = {
35
36
  };
36
37
  };
37
38
  fees?: EVMFees;
38
- evmConfig?: Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag">;
39
+ evmConfig?: Partial<Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag" | "finalityCheckStrategy">>;
39
40
  };
40
41
  /**
41
42
  * Initialize GOAT Network chain integration
@@ -21,6 +21,7 @@ const GoatContractAddresses = {
21
21
  MAINNET: {
22
22
  executionContract: "",
23
23
  swapContract: "",
24
+ swapContractDeploymentHeight: 0,
24
25
  btcRelayContract: "",
25
26
  btcRelayDeploymentHeight: 0,
26
27
  spvVaultContract: "",
@@ -40,6 +41,7 @@ const GoatContractAddresses = {
40
41
  TESTNET: {
41
42
  executionContract: "0xe8be24CF21341c9567664009a8a82C9Dc1eE90D6",
42
43
  swapContract: "0xe510D5781C6C849284Fb25Dc20b1684cEC445C8B",
44
+ swapContractDeploymentHeight: 9368978,
43
45
  btcRelayContract: "0x3887B02217726bB36958Dd595e57293fB63D5082",
44
46
  btcRelayDeploymentHeight: 9368975,
45
47
  spvVaultContract: "0x71Bc44F3F7203fC1279107D924e418F02b0d4029",
@@ -59,6 +61,7 @@ const GoatContractAddresses = {
59
61
  TESTNET4: {
60
62
  executionContract: "0x4f7d86C870F28ac30C8fa864Ee04264D7dD03847",
61
63
  swapContract: "0x3FbbA0eb82cf1247cbf92B3D51641226310F0Ca5",
64
+ swapContractDeploymentHeight: 10240371,
62
65
  btcRelayContract: "0xEeD58871C24d24C49554aF8B65Dd86eD8ed778D3",
63
66
  btcRelayDeploymentHeight: 10240368,
64
67
  spvVaultContract: "0x8a80A68f8bA1732015A821b5260fEF8040a844b7",
@@ -138,7 +141,7 @@ function initializeGoat(options, bitcoinRpc, network) {
138
141
  ...defaultContractAddresses.handlerContracts.claim,
139
142
  ...options?.handlerContracts?.claim
140
143
  }
141
- });
144
+ }, options.swapContractDeploymentHeight ?? defaultContractAddresses.swapContractDeploymentHeight);
142
145
  const spvVaultContract = new EVMSpvVaultContract_1.EVMSpvVaultContract(chainInterface, btcRelay, bitcoinRpc, options.spvVaultContract ?? defaultContractAddresses.spvVaultContract, options.spvVaultDeploymentHeight ?? defaultContractAddresses.spvVaultDeploymentHeight);
143
146
  const chainEvents = new EVMChainEventsBrowser_1.EVMChainEventsBrowser(chainInterface, swapContract, spvVaultContract);
144
147
  return {
@@ -2,6 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EVMContractEvents = void 0;
4
4
  const EVMEvents_1 = require("../../chain/modules/EVMEvents");
5
+ function normalizeTopic(topic) {
6
+ if (topic.length !== 66) {
7
+ return "0x" + topic.substring(2).padStart(64, "0");
8
+ }
9
+ return topic;
10
+ }
5
11
  class EVMContractEvents extends EVMEvents_1.EVMEvents {
6
12
  constructor(chainInterface, contract) {
7
13
  super(chainInterface);
@@ -17,7 +23,7 @@ class EVMContractEvents extends EVMEvents_1.EVMEvents {
17
23
  return this.baseContract.getEvent(name).fragment.topicHash;
18
24
  }));
19
25
  if (keys != null)
20
- keys.forEach(key => filterArray.push(key));
26
+ keys.forEach(key => filterArray.push(typeof (key) === "string" ? [normalizeTopic(key)] : Array.isArray(key) ? key.map(normalizeTopic) : key));
21
27
  return filterArray;
22
28
  }
23
29
  /**
@@ -1,5 +1,4 @@
1
1
  import { ChainEvents, ClaimEvent, EventListener, InitializeEvent, RefundEvent, SpvVaultClaimEvent, SpvVaultCloseEvent, SpvVaultDepositEvent, SpvVaultFrontEvent, SpvVaultOpenEvent } from "@atomiqlabs/base";
2
- import { IClaimHandler } from "../swaps/handlers/claim/ClaimHandlers";
3
2
  import { EVMSwapData } from "../swaps/EVMSwapData";
4
3
  import { Block, JsonRpcApiProvider, EventFilter, Log } from "ethers";
5
4
  import { EVMSwapContract } from "../swaps/EVMSwapContract";
@@ -8,7 +7,6 @@ import { EVMChainInterface } from "../chain/EVMChainInterface";
8
7
  import { TypedEventLog } from "../typechain/common";
9
8
  import { EscrowManager } from "../swaps/EscrowManagerTypechain";
10
9
  import { SpvVaultManager } from "../spv_swap/SpvVaultContractTypechain";
11
- import { EVMTxTrace } from "../chain/modules/EVMTransactions";
12
10
  export type EVMEventListenerState = {
13
11
  lastBlockNumber: number;
14
12
  lastEvent?: {
@@ -45,7 +43,6 @@ export declare class EVMChainEventsBrowser implements ChainEvents<EVMSwapData, E
45
43
  constructor(chainInterface: EVMChainInterface, evmSwapContract: EVMSwapContract, evmSpvVaultContract: EVMSpvVaultContract<any>, pollIntervalSeconds?: number);
46
44
  private addProcessedEvent;
47
45
  private isEventProcessed;
48
- findInitSwapData(call: EVMTxTrace, escrowHash: string, claimHandler: IClaimHandler<any, any>): EVMSwapData | null;
49
46
  /**
50
47
  * Returns async getter for fetching on-demand initialize event swap data
51
48
  *
@@ -2,8 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EVMChainEventsBrowser = void 0;
4
4
  const base_1 = require("@atomiqlabs/base");
5
- const EVMSwapData_1 = require("../swaps/EVMSwapData");
6
- const ethers_1 = require("ethers");
7
5
  const Utils_1 = require("../../utils/Utils");
8
6
  const EVMSpvVaultContract_1 = require("../spv_swap/EVMSpvVaultContract");
9
7
  const LOGS_SLIDING_WINDOW_LENGTH = 60;
@@ -45,30 +43,6 @@ class EVMChainEventsBrowser {
45
43
  isEventProcessed(event) {
46
44
  return this.processedEvents.includes(event.transactionHash + ":" + event.index);
47
45
  }
48
- findInitSwapData(call, escrowHash, claimHandler) {
49
- if (call.to.toLowerCase() === this.evmSwapContract.contractAddress.toLowerCase()) {
50
- const _result = this.evmSwapContract.parseCalldata(call.input);
51
- if (_result != null && _result.name === "initialize") {
52
- const result = _result;
53
- //Found, check correct escrow hash
54
- const [escrowData, signature, timeout, extraData] = result.args;
55
- const escrow = EVMSwapData_1.EVMSwapData.deserializeFromStruct(escrowData, claimHandler);
56
- if ("0x" + escrow.getEscrowHash() === escrowHash) {
57
- const extraDataHex = (0, ethers_1.hexlify)(extraData);
58
- if (extraDataHex.length > 2) {
59
- escrow.setExtraData(extraDataHex.substring(2));
60
- }
61
- return escrow;
62
- }
63
- }
64
- }
65
- for (let _call of call.calls) {
66
- const found = this.findInitSwapData(_call, escrowHash, claimHandler);
67
- if (found != null)
68
- return found;
69
- }
70
- return null;
71
- }
72
46
  /**
73
47
  * Returns async getter for fetching on-demand initialize event swap data
74
48
  *
@@ -82,7 +56,7 @@ class EVMChainEventsBrowser {
82
56
  const trace = await this.chainInterface.Transactions.traceTransaction(event.transactionHash);
83
57
  if (trace == null)
84
58
  return null;
85
- return this.findInitSwapData(trace, event.args.escrowHash, claimHandler);
59
+ return this.evmSwapContract.findInitSwapData(trace, event.args.escrowHash, claimHandler);
86
60
  };
87
61
  }
88
62
  parseInitializeEvent(event) {
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
- import { BitcoinRpc, BtcTx, RelaySynchronizer, SpvVaultContract, SpvVaultTokenData, SpvWithdrawalState, SpvWithdrawalTransactionData, TransactionConfirmationOptions } from "@atomiqlabs/base";
3
+ import { BitcoinRpc, BtcTx, RelaySynchronizer, SpvVaultContract, SpvVaultTokenData, SpvWithdrawalClaimedState, SpvWithdrawalFrontedState, SpvWithdrawalState, SpvWithdrawalTransactionData, TransactionConfirmationOptions } from "@atomiqlabs/base";
4
4
  import { Buffer } from "buffer";
5
5
  import { EVMTx } from "../chain/modules/EVMTransactions";
6
6
  import { EVMContractBase } from "../contract/EVMContractBase";
@@ -112,6 +112,12 @@ export declare class EVMSpvVaultContract<ChainId extends string> extends EVMCont
112
112
  }[]): Promise<{
113
113
  [btcTxId: string]: SpvWithdrawalState;
114
114
  }>;
115
+ getHistoricalWithdrawalStates(recipient: string, startBlockheight?: number): Promise<{
116
+ withdrawals: {
117
+ [btcTxId: string]: SpvWithdrawalClaimedState | SpvWithdrawalFrontedState;
118
+ };
119
+ latestBlockheight?: number;
120
+ }>;
115
121
  /**
116
122
  * @inheritDoc
117
123
  */
@@ -264,32 +264,47 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
264
264
  const [ownerFront, vaultIdFront] = unpackOwnerAndVaultId(frontedEvent.args.ownerAndVaultId);
265
265
  return {
266
266
  type: base_1.SpvWithdrawalStateType.FRONTED,
267
- txId: event.transactionHash,
267
+ btcTxId: buffer_1.Buffer.from(frontedEvent.args.btcTxHash.substring(2), "hex").reverse().toString("hex"),
268
268
  owner: ownerFront,
269
269
  vaultId: vaultIdFront,
270
270
  recipient: frontedEvent.args.recipient,
271
- fronter: frontedEvent.args.caller
271
+ fronter: frontedEvent.args.caller,
272
+ txId: event.transactionHash,
273
+ getTxBlock: async () => ({
274
+ blockHeight: event.blockNumber,
275
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
276
+ })
272
277
  };
273
278
  case "Claimed":
274
279
  const claimedEvent = event;
275
280
  const [ownerClaim, vaultIdClaim] = unpackOwnerAndVaultId(claimedEvent.args.ownerAndVaultId);
276
281
  return {
277
282
  type: base_1.SpvWithdrawalStateType.CLAIMED,
278
- txId: event.transactionHash,
283
+ btcTxId: buffer_1.Buffer.from(claimedEvent.args.btcTxHash.substring(2), "hex").reverse().toString("hex"),
279
284
  owner: ownerClaim,
280
285
  vaultId: vaultIdClaim,
281
286
  recipient: claimedEvent.args.recipient,
282
287
  claimer: claimedEvent.args.caller,
283
- fronter: claimedEvent.args.frontingAddress
288
+ fronter: claimedEvent.args.frontingAddress,
289
+ txId: event.transactionHash,
290
+ getTxBlock: async () => ({
291
+ blockHeight: event.blockNumber,
292
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
293
+ })
284
294
  };
285
295
  case "Closed":
286
296
  const closedEvent = event;
287
297
  return {
288
298
  type: base_1.SpvWithdrawalStateType.CLOSED,
289
- txId: event.transactionHash,
299
+ btcTxId: buffer_1.Buffer.from(closedEvent.args.btcTxHash.substring(2), "hex").reverse().toString("hex"),
290
300
  owner: closedEvent.args.owner,
291
301
  vaultId: closedEvent.args.vaultId,
292
- error: closedEvent.args.error
302
+ error: closedEvent.args.error,
303
+ txId: event.transactionHash,
304
+ getTxBlock: async () => ({
305
+ blockHeight: event.blockNumber,
306
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
307
+ })
293
308
  };
294
309
  default:
295
310
  return null;
@@ -398,6 +413,20 @@ class EVMSpvVaultContract extends EVMContractBase_1.EVMContractBase {
398
413
  }
399
414
  return result;
400
415
  }
416
+ async getHistoricalWithdrawalStates(recipient, startBlockheight) {
417
+ const { height: latestBlockheight } = await this.Chain.getFinalizedBlock();
418
+ const withdrawals = {};
419
+ await this.Events.findInContractEventsForward(["Claimed", "Fronted"], [null, recipient], async (_event) => {
420
+ const eventResult = this.parseWithdrawalEvent(_event);
421
+ if (eventResult == null || eventResult.type === base_1.SpvWithdrawalStateType.CLOSED)
422
+ return null;
423
+ withdrawals[eventResult.btcTxId] = eventResult;
424
+ }, startBlockheight);
425
+ return {
426
+ withdrawals,
427
+ latestBlockheight
428
+ };
429
+ }
401
430
  /**
402
431
  * @inheritDoc
403
432
  */
@@ -7,7 +7,7 @@ import { IHandler } from "./handlers/IHandler";
7
7
  import { EVMContractBase } from "../contract/EVMContractBase";
8
8
  import { EscrowManager } from "./EscrowManagerTypechain";
9
9
  import { EVMSwapData } from "./EVMSwapData";
10
- import { EVMTx } from "../chain/modules/EVMTransactions";
10
+ import { EVMTx, EVMTxTrace } from "../chain/modules/EVMTransactions";
11
11
  import { EVMSigner } from "../wallet/EVMSigner";
12
12
  import { EVMChainInterface } from "../chain/EVMChainInterface";
13
13
  import { EVMBtcRelay } from "../btcrelay/EVMBtcRelay";
@@ -50,7 +50,7 @@ export declare class EVMSwapContract<ChainId extends string = string> extends EV
50
50
  claim: {
51
51
  [type in ChainSwapType]: string;
52
52
  };
53
- });
53
+ }, contractDeploymentHeight?: number);
54
54
  /**
55
55
  * @inheritDoc
56
56
  */
@@ -136,10 +136,27 @@ export declare class EVMSwapContract<ChainId extends string = string> extends EV
136
136
  }[]): Promise<{
137
137
  [p: string]: SwapCommitState;
138
138
  }>;
139
+ getHistoricalSwaps(signer: string, startBlockheight?: number): Promise<{
140
+ swaps: {
141
+ [escrowHash: string]: {
142
+ init?: {
143
+ data: EVMSwapData;
144
+ getInitTxId: () => Promise<string>;
145
+ getTxBlock: () => Promise<{
146
+ blockTime: number;
147
+ blockHeight: number;
148
+ }>;
149
+ };
150
+ state: SwapCommitState;
151
+ };
152
+ };
153
+ latestBlockheight?: number;
154
+ }>;
139
155
  /**
140
156
  * @inheritDoc
141
157
  */
142
158
  createSwapData(type: ChainSwapType, offerer: string, claimer: string, token: string, amount: bigint, paymentHash: string, sequence: bigint, expiry: bigint, payIn: boolean, payOut: boolean, securityDeposit: bigint, claimerBounty: bigint, depositToken?: string): Promise<EVMSwapData>;
159
+ findInitSwapData(call: EVMTxTrace, escrowHash: string, claimHandler: IClaimHandler<any, any>): EVMSwapData | null;
143
160
  /**
144
161
  * @inheritDoc
145
162
  */
@@ -14,15 +14,17 @@ const EVMLpVault_1 = require("./modules/EVMLpVault");
14
14
  const EVMSwapInit_1 = require("./modules/EVMSwapInit");
15
15
  const EVMSwapRefund_1 = require("./modules/EVMSwapRefund");
16
16
  const EVMSwapClaim_1 = require("./modules/EVMSwapClaim");
17
+ const Utils_1 = require("../../utils/Utils");
17
18
  const ESCROW_STATE_COMMITTED = 1;
18
19
  const ESCROW_STATE_CLAIMED = 2;
19
20
  const ESCROW_STATE_REFUNDED = 3;
21
+ const logger = (0, Utils_1.getLogger)("EVMSwapContract: ");
20
22
  /**
21
23
  * @category Swaps
22
24
  */
23
25
  class EVMSwapContract extends EVMContractBase_1.EVMContractBase {
24
- constructor(chainInterface, btcRelay, contractAddress, handlerAddresses) {
25
- super(chainInterface, contractAddress, EscrowManagerAbi_1.EscrowManagerAbi);
26
+ constructor(chainInterface, btcRelay, contractAddress, handlerAddresses, contractDeploymentHeight) {
27
+ super(chainInterface, contractAddress, EscrowManagerAbi_1.EscrowManagerAbi, contractDeploymentHeight);
26
28
  this.supportsInitWithoutClaimer = true;
27
29
  ////////////////////////
28
30
  //// Timeouts
@@ -299,6 +301,95 @@ class EVMSwapContract extends EVMContractBase_1.EVMContractBase {
299
301
  await Promise.all(promises);
300
302
  return result;
301
303
  }
304
+ async getHistoricalSwaps(signer, startBlockheight) {
305
+ const { height: latestBlockheight } = await this.Chain.getFinalizedBlock();
306
+ const swapsOpened = {};
307
+ const resultingSwaps = {};
308
+ const processor = async (_event) => {
309
+ const escrowHash = _event.args.escrowHash.substring(2);
310
+ if (_event.eventName === "Initialize") {
311
+ const event = _event;
312
+ const claimHandlerHex = event.args.claimHandler;
313
+ const claimHandler = this.claimHandlersByAddress[claimHandlerHex.toLowerCase()];
314
+ if (claimHandler == null) {
315
+ logger.warn(`getHistoricalSwaps(): Unknown claim handler in tx ${event.transactionHash} with claim handler: ` + claimHandlerHex);
316
+ return null;
317
+ }
318
+ const txTrace = await this.Chain.Transactions.traceTransaction(event.transactionHash);
319
+ const data = this.findInitSwapData(txTrace, event.args.escrowHash, claimHandler);
320
+ if (data == null) {
321
+ logger.warn(`getHistoricalSwaps(): Cannot parse swap data from tx ${event.transactionHash} with escrow hash: ` + escrowHash);
322
+ return null;
323
+ }
324
+ swapsOpened[escrowHash] = {
325
+ data,
326
+ getInitTxId: () => Promise.resolve(event.transactionHash),
327
+ getTxBlock: async () => {
328
+ return {
329
+ blockHeight: event.blockNumber,
330
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
331
+ };
332
+ }
333
+ };
334
+ }
335
+ if (_event.eventName === "Claim") {
336
+ const event = _event;
337
+ const foundSwapData = swapsOpened[escrowHash];
338
+ delete swapsOpened[escrowHash];
339
+ resultingSwaps[escrowHash] = {
340
+ init: foundSwapData,
341
+ state: {
342
+ type: base_1.SwapCommitStateType.PAID,
343
+ getClaimTxId: () => Promise.resolve(event.transactionHash),
344
+ getClaimResult: () => Promise.resolve(event.args.witnessResult.substring(2)),
345
+ getTxBlock: async () => {
346
+ return {
347
+ blockHeight: event.blockNumber,
348
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
349
+ };
350
+ }
351
+ }
352
+ };
353
+ }
354
+ if (_event.eventName === "Refund") {
355
+ const event = _event;
356
+ const foundSwapData = swapsOpened[escrowHash];
357
+ delete swapsOpened[escrowHash];
358
+ const isExpired = foundSwapData != null && await this.isExpired(signer, foundSwapData.data);
359
+ resultingSwaps[escrowHash] = {
360
+ init: foundSwapData,
361
+ state: {
362
+ type: isExpired ? base_1.SwapCommitStateType.EXPIRED : base_1.SwapCommitStateType.NOT_COMMITED,
363
+ getRefundTxId: () => Promise.resolve(event.transactionHash),
364
+ getTxBlock: async () => {
365
+ return {
366
+ blockHeight: event.blockNumber,
367
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
368
+ };
369
+ }
370
+ }
371
+ };
372
+ }
373
+ };
374
+ //We have to fetch separately the different directions
375
+ await this.Events.findInContractEventsForward(["Initialize", "Claim", "Refund"], [signer, null], processor, startBlockheight);
376
+ await this.Events.findInContractEventsForward(["Initialize", "Claim", "Refund"], [null, signer], processor, startBlockheight);
377
+ logger.debug(`getHistoricalSwaps(): Found ${Object.keys(resultingSwaps).length} settled swaps!`);
378
+ logger.debug(`getHistoricalSwaps(): Found ${Object.keys(swapsOpened).length} unsettled swaps!`);
379
+ for (let escrowHash in swapsOpened) {
380
+ const foundSwapData = swapsOpened[escrowHash];
381
+ resultingSwaps[escrowHash] = {
382
+ init: foundSwapData,
383
+ state: foundSwapData.data.isOfferer(signer) && await this.isExpired(signer, foundSwapData.data)
384
+ ? { type: base_1.SwapCommitStateType.REFUNDABLE }
385
+ : { type: base_1.SwapCommitStateType.COMMITED }
386
+ };
387
+ }
388
+ return {
389
+ swaps: resultingSwaps,
390
+ latestBlockheight: latestBlockheight ?? startBlockheight
391
+ };
392
+ }
302
393
  ////////////////////////////////////////////
303
394
  //// Swap data initializer
304
395
  /**
@@ -311,6 +402,30 @@ class EVMSwapContract extends EVMContractBase_1.EVMContractBase {
311
402
  return Promise.resolve(new EVMSwapData_1.EVMSwapData(offerer, claimer, token, this.timelockRefundHandler.address, claimHandler.address, payOut, payIn, payIn, //For now track reputation for all payIn swaps
312
403
  sequence, "0x" + paymentHash, (0, ethers_1.hexlify)(base_1.BigIntBufferUtils.toBuffer(expiry, "be", 32)), amount, depositToken, securityDeposit, claimerBounty, type));
313
404
  }
405
+ findInitSwapData(call, escrowHash, claimHandler) {
406
+ if (call.to.toLowerCase() === this.contractAddress.toLowerCase()) {
407
+ const _result = this.parseCalldata(call.input);
408
+ if (_result != null && _result.name === "initialize") {
409
+ const result = _result;
410
+ //Found, check correct escrow hash
411
+ const [escrowData, signature, timeout, extraData] = result.args;
412
+ const escrow = EVMSwapData_1.EVMSwapData.deserializeFromStruct(escrowData, claimHandler);
413
+ if ("0x" + escrow.getEscrowHash() === escrowHash) {
414
+ const extraDataHex = (0, ethers_1.hexlify)(extraData);
415
+ if (extraDataHex.length > 2) {
416
+ escrow.setExtraData(extraDataHex.substring(2));
417
+ }
418
+ return escrow;
419
+ }
420
+ }
421
+ }
422
+ for (let _call of call.calls) {
423
+ const found = this.findInitSwapData(_call, escrowHash, claimHandler);
424
+ if (found != null)
425
+ return found;
426
+ }
427
+ return null;
428
+ }
314
429
  ////////////////////////////////////////////
315
430
  //// Utils
316
431
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atomiqlabs/chain-evm",
3
- "version": "2.0.9",
3
+ "version": "2.1.10",
4
4
  "description": "EVM specific base implementation",
5
5
  "main": "./dist/index.js",
6
6
  "types:": "./dist/index.d.ts",
@@ -25,7 +25,7 @@
25
25
  "author": "adambor",
26
26
  "license": "Apache-2.0",
27
27
  "dependencies": {
28
- "@atomiqlabs/base": "^13.0.4",
28
+ "@atomiqlabs/base": "^13.1.9",
29
29
  "@noble/hashes": "^1.8.0",
30
30
  "@scure/btc-signer": "^1.6.0",
31
31
  "buffer": "6.0.3",
@@ -21,6 +21,7 @@ const AlpenContractAddresses = {
21
21
  MAINNET: {
22
22
  executionContract: "",
23
23
  swapContract: "",
24
+ swapContractDeploymentHeight: 0,
24
25
  btcRelayContract: "",
25
26
  btcRelayDeploymentHeight: 0,
26
27
  spvVaultContract: "",
@@ -40,6 +41,7 @@ const AlpenContractAddresses = {
40
41
  TESTNET: {
41
42
  executionContract: "0x32EB4DbDdC31e19ba908fecc7cae03F0d04F01Fa",
42
43
  swapContract: "0x2920EE496693A5027249a027A6FD3F643E743745",
44
+ swapContractDeploymentHeight: 532614,
43
45
  btcRelayContract: "0x59A54378B6bA9C21ba66487C6A701D702baDEabE",
44
46
  btcRelayDeploymentHeight: 532610,
45
47
  spvVaultContract: "0xaB2D14745362B26a732dD8B7F95daAE3D2914bBF",
@@ -59,6 +61,7 @@ const AlpenContractAddresses = {
59
61
  TESTNET4: {
60
62
  executionContract: "0xa2698D2fBE3f7c74cCca428a5fd968411644C641",
61
63
  swapContract: "0xb0226bAC3BD30179fb66A43cEA212AbBC988e004",
64
+ swapContractDeploymentHeight: 843614,
62
65
  btcRelayContract: "0xfFA842529977a40A3fdb988cdDC9CB5c39bAcF26",
63
66
  btcRelayDeploymentHeight: 843611,
64
67
  spvVaultContract: "0x62a718348081F9CF9a8E3dF4B4EA6d6349991ad9",
@@ -111,6 +114,7 @@ export type AlpenOptions = {
111
114
  chainType?: "MAINNET" | "TESTNET" | "TESTNET4",
112
115
 
113
116
  swapContract?: string,
117
+ swapContractDeploymentHeight?: number,
114
118
  btcRelayContract?: string,
115
119
  btcRelayDeploymentHeight?: number,
116
120
  spvVaultContract?: string,
@@ -126,7 +130,7 @@ export type AlpenOptions = {
126
130
 
127
131
  fees?: EVMFees,
128
132
 
129
- evmConfig?: Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag">
133
+ evmConfig?: Partial<Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag" | "finalityCheckStrategy">>
130
134
  }
131
135
 
132
136
  /**
@@ -180,7 +184,8 @@ export function initializeAlpen(
180
184
  ...defaultContractAddresses.handlerContracts.claim,
181
185
  ...options?.handlerContracts?.claim
182
186
  }
183
- }
187
+ },
188
+ options.swapContractDeploymentHeight ?? defaultContractAddresses.swapContractDeploymentHeight
184
189
  );
185
190
 
186
191
  const spvVaultContract = new EVMSpvVaultContract(
@@ -20,6 +20,7 @@ const BotanixContractAddresses = {
20
20
  MAINNET: {
21
21
  executionContract: "0x71Bc44F3F7203fC1279107D924e418F02b0d4029",
22
22
  swapContract: "0x9a027B5Bf43382Cc4A5134d9EFD389f61ece27B9",
23
+ swapContractDeploymentHeight: 2320403,
23
24
  btcRelayContract: "0xe8be24CF21341c9567664009a8a82C9Dc1eE90D6",
24
25
  btcRelayDeploymentHeight: 2320400,
25
26
  spvVaultContract: "0xe510D5781C6C849284Fb25Dc20b1684cEC445C8B",
@@ -39,6 +40,7 @@ const BotanixContractAddresses = {
39
40
  TESTNET: {
40
41
  executionContract: "0xe510D5781C6C849284Fb25Dc20b1684cEC445C8B",
41
42
  swapContract: "0xfFA842529977a40A3fdb988cdDC9CB5c39bAcF26",
43
+ swapContractDeploymentHeight: 4173454,
42
44
  btcRelayContract: "0xba7E78011909e3501027FBc226a04DCC837a555D",
43
45
  btcRelayDeploymentHeight: 3462466,
44
46
  spvVaultContract: "0x9a027B5Bf43382Cc4A5134d9EFD389f61ece27B9",
@@ -90,6 +92,7 @@ export type BotanixOptions = {
90
92
  chainType?: "MAINNET" | "TESTNET",
91
93
 
92
94
  swapContract?: string,
95
+ swapContractDeploymentHeight?: number,
93
96
  btcRelayContract?: string,
94
97
  btcRelayDeploymentHeight?: number,
95
98
  spvVaultContract?: string,
@@ -105,7 +108,7 @@ export type BotanixOptions = {
105
108
 
106
109
  fees?: EVMFees,
107
110
 
108
- evmConfig?: Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag">
111
+ evmConfig?: Partial<Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag" | "finalityCheckStrategy">>
109
112
  }
110
113
 
111
114
  /**
@@ -163,7 +166,8 @@ export function initializeBotanix(
163
166
  ...defaultContractAddresses.handlerContracts.claim,
164
167
  ...options?.handlerContracts?.claim
165
168
  }
166
- }
169
+ },
170
+ options.swapContractDeploymentHeight ?? defaultContractAddresses.swapContractDeploymentHeight
167
171
  );
168
172
 
169
173
  const spvVaultContract = new EVMSpvVaultContract(
@@ -21,6 +21,7 @@ const CitreaContractAddresses = {
21
21
  MAINNET: {
22
22
  executionContract: "0x6a373b6Adad83964727bA0fa15E22Be05173fc12",
23
23
  swapContract: "0xc98Ef084d3911C8447DBbE4dDa18bC2c9bB0584e",
24
+ swapContractDeploymentHeight: 2452632,
24
25
  btcRelayContract: "0x11ac854A68830d61af975063c91602f878C36fA6",
25
26
  btcRelayDeploymentHeight: 2452629,
26
27
  spvVaultContract: "0x5bb0C725939cB825d1322A99a3FeB570097628c3",
@@ -40,6 +41,7 @@ const CitreaContractAddresses = {
40
41
  TESTNET4: {
41
42
  executionContract: "0x9e289512965A0842b342A6BB3F3c41F22a555Cfe",
42
43
  swapContract: "0xBbf7755b674dD107d59F0650D1A3fA9C60bf6Fe6",
44
+ swapContractDeploymentHeight: 12346224,
43
45
  btcRelayContract: "0x00D122E9f9766cd81a38D2dd44f9AFfb94c67Af7",
44
46
  btcRelayDeploymentHeight: 12346223,
45
47
  spvVaultContract: "0x9Bf990C6088F716279797a602b05941c40591533",
@@ -101,6 +103,7 @@ export type CitreaOptions = {
101
103
  chainType?: "MAINNET" | "TESTNET4",
102
104
 
103
105
  swapContract?: string,
106
+ swapContractDeploymentHeight?: number,
104
107
  btcRelayContract?: string,
105
108
  btcRelayDeploymentHeight?: number,
106
109
  spvVaultContract?: string,
@@ -116,7 +119,7 @@ export type CitreaOptions = {
116
119
 
117
120
  fees?: CitreaFees,
118
121
 
119
- evmConfig?: Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag">
122
+ evmConfig?: Partial<Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag" | "finalityCheckStrategy">>
120
123
  }
121
124
 
122
125
  /**
@@ -172,7 +175,8 @@ export function initializeCitrea(
172
175
  ...defaultContractAddresses.handlerContracts.claim,
173
176
  ...options?.handlerContracts?.claim
174
177
  }
175
- }
178
+ },
179
+ options.swapContractDeploymentHeight ?? defaultContractAddresses.swapContractDeploymentHeight
176
180
  );
177
181
 
178
182
  const spvVaultContract = new CitreaSpvVaultContract(
@@ -21,6 +21,7 @@ const GoatContractAddresses = {
21
21
  MAINNET: {
22
22
  executionContract: "",
23
23
  swapContract: "",
24
+ swapContractDeploymentHeight: 0,
24
25
  btcRelayContract: "",
25
26
  btcRelayDeploymentHeight: 0,
26
27
  spvVaultContract: "",
@@ -40,6 +41,7 @@ const GoatContractAddresses = {
40
41
  TESTNET: {
41
42
  executionContract: "0xe8be24CF21341c9567664009a8a82C9Dc1eE90D6",
42
43
  swapContract: "0xe510D5781C6C849284Fb25Dc20b1684cEC445C8B",
44
+ swapContractDeploymentHeight: 9368978,
43
45
  btcRelayContract: "0x3887B02217726bB36958Dd595e57293fB63D5082",
44
46
  btcRelayDeploymentHeight: 9368975,
45
47
  spvVaultContract: "0x71Bc44F3F7203fC1279107D924e418F02b0d4029",
@@ -59,6 +61,7 @@ const GoatContractAddresses = {
59
61
  TESTNET4: {
60
62
  executionContract: "0x4f7d86C870F28ac30C8fa864Ee04264D7dD03847",
61
63
  swapContract: "0x3FbbA0eb82cf1247cbf92B3D51641226310F0Ca5",
64
+ swapContractDeploymentHeight: 10240371,
62
65
  btcRelayContract: "0xEeD58871C24d24C49554aF8B65Dd86eD8ed778D3",
63
66
  btcRelayDeploymentHeight: 10240368,
64
67
  spvVaultContract: "0x8a80A68f8bA1732015A821b5260fEF8040a844b7",
@@ -121,6 +124,7 @@ export type GoatOptions = {
121
124
  chainType?: "MAINNET" | "TESTNET" | "TESTNET4",
122
125
 
123
126
  swapContract?: string,
127
+ swapContractDeploymentHeight?: number,
124
128
  btcRelayContract?: string,
125
129
  btcRelayDeploymentHeight?: number,
126
130
  spvVaultContract?: string,
@@ -136,7 +140,7 @@ export type GoatOptions = {
136
140
 
137
141
  fees?: EVMFees,
138
142
 
139
- evmConfig?: Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag">
143
+ evmConfig?: Partial<Omit<EVMConfiguration, "safeBlockTag" | "finalizedBlockTag" | "finalityCheckStrategy">>
140
144
  }
141
145
 
142
146
  /**
@@ -190,7 +194,8 @@ export function initializeGoat(
190
194
  ...defaultContractAddresses.handlerContracts.claim,
191
195
  ...options?.handlerContracts?.claim
192
196
  }
193
- }
197
+ },
198
+ options.swapContractDeploymentHeight?? defaultContractAddresses.swapContractDeploymentHeight
194
199
  );
195
200
 
196
201
  const spvVaultContract = new EVMSpvVaultContract(
@@ -4,6 +4,14 @@ import {EVMContractBase} from "../EVMContractBase";
4
4
  import {EVMChainInterface} from "../../chain/EVMChainInterface";
5
5
  import {TypedEventLog} from "../../typechain/common";
6
6
 
7
+ function normalizeTopic(topic: string) {
8
+ if(topic.length!==66) {
9
+ return "0x"+topic.substring(2).padStart(64, "0");
10
+ }
11
+ return topic;
12
+ }
13
+
14
+
7
15
  export class EVMContractEvents<T extends BaseContract> extends EVMEvents {
8
16
 
9
17
  readonly contract: EVMContractBase<T>;
@@ -22,12 +30,12 @@ export class EVMContractEvents<T extends BaseContract> extends EVMEvents {
22
30
  private toFilter<TEventName extends keyof T["filters"]>(
23
31
  events: TEventName[],
24
32
  keys: null | (null | string | string[])[],
25
- ): (null | string | string[])[] {
26
- const filterArray: (null | string | string[])[] = [];
33
+ ): (null | string[])[] {
34
+ const filterArray: (null | string[])[] = [];
27
35
  filterArray.push(events.map(name => {
28
36
  return this.baseContract.getEvent(name as string).fragment.topicHash;
29
37
  }));
30
- if(keys!=null) keys.forEach(key => filterArray.push(key));
38
+ if(keys!=null) keys.forEach(key => filterArray.push(typeof(key)==="string" ? [normalizeTopic(key)] : Array.isArray(key) ? key.map(normalizeTopic) : key));
31
39
  return filterArray;
32
40
  }
33
41
 
@@ -93,33 +93,6 @@ export class EVMChainEventsBrowser implements ChainEvents<EVMSwapData, EVMEventL
93
93
  return this.processedEvents.includes(event.transactionHash+":"+event.index);
94
94
  }
95
95
 
96
- findInitSwapData(call: EVMTxTrace, escrowHash: string, claimHandler: IClaimHandler<any, any>): EVMSwapData | null {
97
- if(call.to.toLowerCase() === this.evmSwapContract.contractAddress.toLowerCase()) {
98
- const _result = this.evmSwapContract.parseCalldata(call.input);
99
- if(_result!=null && _result.name==="initialize") {
100
- const result = _result as TypedFunctionCall<
101
- // @ts-ignore
102
- typeof this.evmSwapContract.contract.initialize
103
- >;
104
- //Found, check correct escrow hash
105
- const [escrowData, signature, timeout, extraData] = result.args;
106
- const escrow = EVMSwapData.deserializeFromStruct(escrowData, claimHandler);
107
- if("0x"+escrow.getEscrowHash()===escrowHash) {
108
- const extraDataHex = hexlify(extraData);
109
- if(extraDataHex.length>2) {
110
- escrow.setExtraData(extraDataHex.substring(2));
111
- }
112
- return escrow;
113
- }
114
- }
115
- }
116
- for(let _call of call.calls) {
117
- const found = this.findInitSwapData(_call, escrowHash, claimHandler);
118
- if(found!=null) return found;
119
- }
120
- return null;
121
- }
122
-
123
96
  /**
124
97
  * Returns async getter for fetching on-demand initialize event swap data
125
98
  *
@@ -135,7 +108,7 @@ export class EVMChainEventsBrowser implements ChainEvents<EVMSwapData, EVMEventL
135
108
  return async () => {
136
109
  const trace = await this.chainInterface.Transactions.traceTransaction(event.transactionHash);
137
110
  if(trace==null) return null;
138
- return this.findInitSwapData(trace, event.args.escrowHash, claimHandler);
111
+ return this.evmSwapContract.findInitSwapData(trace, event.args.escrowHash, claimHandler);
139
112
  }
140
113
  }
141
114
 
@@ -5,6 +5,9 @@ import {
5
5
  RelaySynchronizer,
6
6
  SpvVaultContract,
7
7
  SpvVaultTokenData,
8
+ SpvWithdrawalClaimedState,
9
+ SpvWithdrawalClosedState,
10
+ SpvWithdrawalFrontedState,
8
11
  SpvWithdrawalState,
9
12
  SpvWithdrawalStateType,
10
13
  SpvWithdrawalTransactionData,
@@ -364,39 +367,56 @@ export class EVMSpvVaultContract<ChainId extends string>
364
367
  return vaults;
365
368
  }
366
369
 
367
- private parseWithdrawalEvent(event: TypedEventLog<SpvVaultManager["filters"][keyof SpvVaultManager["filters"]]>): SpvWithdrawalState | null {
370
+ private parseWithdrawalEvent(
371
+ event: TypedEventLog<SpvVaultManager["filters"][keyof SpvVaultManager["filters"]]>
372
+ ): ((SpvWithdrawalFrontedState | SpvWithdrawalClaimedState | SpvWithdrawalClosedState) & {btcTxId: string}) | null {
368
373
  switch(event.eventName) {
369
374
  case "Fronted":
370
375
  const frontedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Fronted"]>;
371
376
  const [ownerFront, vaultIdFront] = unpackOwnerAndVaultId(frontedEvent.args.ownerAndVaultId);
372
377
  return {
373
378
  type: SpvWithdrawalStateType.FRONTED,
374
- txId: event.transactionHash,
379
+ btcTxId: Buffer.from(frontedEvent.args.btcTxHash.substring(2), "hex").reverse().toString("hex"),
375
380
  owner: ownerFront,
376
381
  vaultId: vaultIdFront,
377
382
  recipient: frontedEvent.args.recipient,
378
- fronter: frontedEvent.args.caller
383
+ fronter: frontedEvent.args.caller,
384
+ txId: event.transactionHash,
385
+ getTxBlock: async () => ({
386
+ blockHeight: event.blockNumber,
387
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
388
+ })
379
389
  };
380
390
  case "Claimed":
381
391
  const claimedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Claimed"]>;
382
392
  const [ownerClaim, vaultIdClaim] = unpackOwnerAndVaultId(claimedEvent.args.ownerAndVaultId);
383
393
  return {
384
394
  type: SpvWithdrawalStateType.CLAIMED,
385
- txId: event.transactionHash,
395
+ btcTxId: Buffer.from(claimedEvent.args.btcTxHash.substring(2), "hex").reverse().toString("hex"),
386
396
  owner: ownerClaim,
387
397
  vaultId: vaultIdClaim,
388
398
  recipient: claimedEvent.args.recipient,
389
399
  claimer: claimedEvent.args.caller,
390
- fronter: claimedEvent.args.frontingAddress
400
+ fronter: claimedEvent.args.frontingAddress,
401
+ txId: event.transactionHash,
402
+ getTxBlock: async () => ({
403
+ blockHeight: event.blockNumber,
404
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
405
+ })
391
406
  };
392
407
  case "Closed":
393
408
  const closedEvent = event as TypedEventLog<SpvVaultManager["filters"]["Closed"]>;
394
409
  return {
395
410
  type: SpvWithdrawalStateType.CLOSED,
396
- txId: event.transactionHash,
411
+ btcTxId: Buffer.from(closedEvent.args.btcTxHash.substring(2), "hex").reverse().toString("hex"),
397
412
  owner: closedEvent.args.owner,
398
413
  vaultId: closedEvent.args.vaultId,
399
- error: closedEvent.args.error
414
+ error: closedEvent.args.error,
415
+ txId: event.transactionHash,
416
+ getTxBlock: async () => ({
417
+ blockHeight: event.blockNumber,
418
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
419
+ })
400
420
  };
401
421
  default:
402
422
  return null;
@@ -523,6 +543,30 @@ export class EVMSpvVaultContract<ChainId extends string>
523
543
  return result;
524
544
  }
525
545
 
546
+ async getHistoricalWithdrawalStates(recipient: string, startBlockheight?: number): Promise<{
547
+ withdrawals: { [btcTxId: string]: SpvWithdrawalClaimedState | SpvWithdrawalFrontedState };
548
+ latestBlockheight?: number
549
+ }> {
550
+ const {height: latestBlockheight} = await this.Chain.getFinalizedBlock();
551
+ const withdrawals: { [btcTxId: string]: SpvWithdrawalClaimedState | SpvWithdrawalFrontedState } = {};
552
+
553
+ await this.Events.findInContractEventsForward(
554
+ ["Claimed", "Fronted"],
555
+ [null, recipient],
556
+ async (_event) => {
557
+ const eventResult = this.parseWithdrawalEvent(_event);
558
+ if(eventResult==null || eventResult.type===SpvWithdrawalStateType.CLOSED) return null;
559
+ withdrawals[eventResult.btcTxId] = eventResult;
560
+ },
561
+ startBlockheight
562
+ );
563
+
564
+ return {
565
+ withdrawals,
566
+ latestBlockheight
567
+ }
568
+ }
569
+
526
570
  /**
527
571
  * @inheritDoc
528
572
  */
@@ -12,10 +12,10 @@ import {TimelockRefundHandler} from "./handlers/refund/TimelockRefundHandler";
12
12
  import {claimHandlersList, IClaimHandler} from "./handlers/claim/ClaimHandlers"
13
13
  import {IHandler} from "./handlers/IHandler";
14
14
  import {sha256} from "@noble/hashes/sha2";
15
- import { EVMContractBase } from "../contract/EVMContractBase";
15
+ import {EVMContractBase, TypedFunctionCall} from "../contract/EVMContractBase";
16
16
  import {EscrowManager} from "./EscrowManagerTypechain";
17
17
  import { EVMSwapData } from "./EVMSwapData";
18
- import {EVMTx} from "../chain/modules/EVMTransactions";
18
+ import {EVMTx, EVMTxTrace} from "../chain/modules/EVMTransactions";
19
19
  import { EVMSigner } from "../wallet/EVMSigner";
20
20
  import {EVMChainInterface} from "../chain/EVMChainInterface";
21
21
  import { EVMBtcRelay } from "../btcrelay/EVMBtcRelay";
@@ -26,11 +26,15 @@ import {EVMLpVault} from "./modules/EVMLpVault";
26
26
  import {EVMPreFetchVerification, EVMSwapInit} from "./modules/EVMSwapInit";
27
27
  import { EVMSwapRefund } from "./modules/EVMSwapRefund";
28
28
  import {EVMSwapClaim} from "./modules/EVMSwapClaim";
29
+ import {TypedEventLog} from "../typechain/common";
30
+ import {getLogger} from "../../utils/Utils";
29
31
 
30
32
  const ESCROW_STATE_COMMITTED = 1;
31
33
  const ESCROW_STATE_CLAIMED = 2;
32
34
  const ESCROW_STATE_REFUNDED = 3;
33
35
 
36
+ const logger = getLogger("EVMSwapContract: ");
37
+
34
38
  /**
35
39
  * @category Swaps
36
40
  */
@@ -88,9 +92,10 @@ export class EVMSwapContract<ChainId extends string = string>
88
92
  claim: {
89
93
  [type in ChainSwapType]: string
90
94
  }
91
- }
95
+ },
96
+ contractDeploymentHeight?: number
92
97
  ) {
93
- super(chainInterface, contractAddress, EscrowManagerAbi);
98
+ super(chainInterface, contractAddress, EscrowManagerAbi, contractDeploymentHeight);
94
99
  this.chainId = chainInterface.chainId;
95
100
  this.Init = new EVMSwapInit(chainInterface, this);
96
101
  this.Refund = new EVMSwapRefund(chainInterface, this);
@@ -378,6 +383,145 @@ export class EVMSwapContract<ChainId extends string = string>
378
383
  return result;
379
384
  }
380
385
 
386
+ async getHistoricalSwaps(signer: string, startBlockheight?: number): Promise<{
387
+ swaps: {
388
+ [escrowHash: string]: {
389
+ init?: {
390
+ data: EVMSwapData;
391
+ getInitTxId: () => Promise<string>;
392
+ getTxBlock: () => Promise<{ blockTime: number; blockHeight: number }>
393
+ };
394
+ state: SwapCommitState
395
+ }
396
+ };
397
+ latestBlockheight?: number
398
+ }> {
399
+ const {height: latestBlockheight} = await this.Chain.getFinalizedBlock();
400
+
401
+ const swapsOpened: {
402
+ [escrowHash: string]: {
403
+ data: EVMSwapData,
404
+ getInitTxId: () => Promise<string>,
405
+ getTxBlock: () => Promise<{
406
+ blockTime: number,
407
+ blockHeight: number
408
+ }>
409
+ }
410
+ } = {};
411
+ const resultingSwaps: {
412
+ [escrowHash: string]: {
413
+ init?: {
414
+ data: EVMSwapData;
415
+ getInitTxId: () => Promise<string>;
416
+ getTxBlock: () => Promise<{ blockTime: number; blockHeight: number }>
417
+ };
418
+ state: SwapCommitState
419
+ }
420
+ } = {};
421
+
422
+ const processor = async (_event: TypedEventLog<EscrowManager["filters"]["Initialize" | "Claim" | "Refund"]>) => {
423
+ const escrowHash = _event.args.escrowHash.substring(2);
424
+ if(_event.eventName==="Initialize") {
425
+ const event = _event as TypedEventLog<EscrowManager["filters"]["Initialize"]>;
426
+ const claimHandlerHex = event.args.claimHandler;
427
+
428
+ const claimHandler = this.claimHandlersByAddress[claimHandlerHex.toLowerCase()];
429
+ if(claimHandler==null) {
430
+ logger.warn(`getHistoricalSwaps(): Unknown claim handler in tx ${event.transactionHash} with claim handler: `+claimHandlerHex);
431
+ return null;
432
+ }
433
+
434
+ const txTrace = await this.Chain.Transactions.traceTransaction(event.transactionHash);
435
+ const data = this.findInitSwapData(txTrace, event.args.escrowHash, claimHandler);
436
+ if(data==null) {
437
+ logger.warn(`getHistoricalSwaps(): Cannot parse swap data from tx ${event.transactionHash} with escrow hash: `+escrowHash);
438
+ return null;
439
+ }
440
+
441
+ swapsOpened[escrowHash] = {
442
+ data,
443
+ getInitTxId: () => Promise.resolve(event.transactionHash),
444
+ getTxBlock: async () => {
445
+ return {
446
+ blockHeight: event.blockNumber,
447
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
448
+ }
449
+ }
450
+ }
451
+ }
452
+ if(_event.eventName==="Claim") {
453
+ const event = _event as TypedEventLog<EscrowManager["filters"]["Claim"]>;
454
+ const foundSwapData = swapsOpened[escrowHash];
455
+ delete swapsOpened[escrowHash];
456
+ resultingSwaps[escrowHash] = {
457
+ init: foundSwapData,
458
+ state: {
459
+ type: SwapCommitStateType.PAID,
460
+ getClaimTxId: () => Promise.resolve(event.transactionHash),
461
+ getClaimResult: () => Promise.resolve(event.args.witnessResult.substring(2)),
462
+ getTxBlock: async () => {
463
+ return {
464
+ blockHeight: event.blockNumber,
465
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
466
+ }
467
+ }
468
+ }
469
+ }
470
+ }
471
+ if(_event.eventName==="Refund") {
472
+ const event = _event as TypedEventLog<EscrowManager["filters"]["Refund"]>;
473
+ const foundSwapData = swapsOpened[escrowHash];
474
+ delete swapsOpened[escrowHash];
475
+ const isExpired = foundSwapData!=null && await this.isExpired(signer, foundSwapData.data);
476
+ resultingSwaps[escrowHash] = {
477
+ init: foundSwapData,
478
+ state: {
479
+ type: isExpired ? SwapCommitStateType.EXPIRED : SwapCommitStateType.NOT_COMMITED,
480
+ getRefundTxId: () => Promise.resolve(event.transactionHash),
481
+ getTxBlock: async () => {
482
+ return {
483
+ blockHeight: event.blockNumber,
484
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber)
485
+ }
486
+ }
487
+ }
488
+ }
489
+ }
490
+ };
491
+
492
+ //We have to fetch separately the different directions
493
+ await this.Events.findInContractEventsForward(
494
+ ["Initialize", "Claim", "Refund"],
495
+ [signer, null],
496
+ processor,
497
+ startBlockheight
498
+ );
499
+ await this.Events.findInContractEventsForward(
500
+ ["Initialize", "Claim", "Refund"],
501
+ [null, signer],
502
+ processor,
503
+ startBlockheight
504
+ );
505
+
506
+ logger.debug(`getHistoricalSwaps(): Found ${Object.keys(resultingSwaps).length} settled swaps!`);
507
+ logger.debug(`getHistoricalSwaps(): Found ${Object.keys(swapsOpened).length} unsettled swaps!`);
508
+
509
+ for(let escrowHash in swapsOpened) {
510
+ const foundSwapData = swapsOpened[escrowHash];
511
+ resultingSwaps[escrowHash] = {
512
+ init: foundSwapData,
513
+ state: foundSwapData.data.isOfferer(signer) && await this.isExpired(signer, foundSwapData.data)
514
+ ? {type: SwapCommitStateType.REFUNDABLE}
515
+ : {type: SwapCommitStateType.COMMITED}
516
+ }
517
+ }
518
+
519
+ return {
520
+ swaps: resultingSwaps,
521
+ latestBlockheight: latestBlockheight ?? startBlockheight
522
+ }
523
+ }
524
+
381
525
  ////////////////////////////////////////////
382
526
  //// Swap data initializer
383
527
  /**
@@ -421,6 +565,33 @@ export class EVMSwapContract<ChainId extends string = string>
421
565
  ));
422
566
  }
423
567
 
568
+ findInitSwapData(call: EVMTxTrace, escrowHash: string, claimHandler: IClaimHandler<any, any>): EVMSwapData | null {
569
+ if(call.to.toLowerCase() === this.contractAddress.toLowerCase()) {
570
+ const _result = this.parseCalldata(call.input);
571
+ if(_result!=null && _result.name==="initialize") {
572
+ const result = _result as TypedFunctionCall<
573
+ // @ts-ignore
574
+ typeof this.evmSwapContract.contract.initialize
575
+ >;
576
+ //Found, check correct escrow hash
577
+ const [escrowData, signature, timeout, extraData] = result.args;
578
+ const escrow = EVMSwapData.deserializeFromStruct(escrowData, claimHandler);
579
+ if("0x"+escrow.getEscrowHash()===escrowHash) {
580
+ const extraDataHex = hexlify(extraData);
581
+ if(extraDataHex.length>2) {
582
+ escrow.setExtraData(extraDataHex.substring(2));
583
+ }
584
+ return escrow;
585
+ }
586
+ }
587
+ }
588
+ for(let _call of call.calls) {
589
+ const found = this.findInitSwapData(_call, escrowHash, claimHandler);
590
+ if(found!=null) return found;
591
+ }
592
+ return null;
593
+ }
594
+
424
595
  ////////////////////////////////////////////
425
596
  //// Utils
426
597
  /**