@atomiqlabs/chain-evm 1.0.0-dev.22

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.
Files changed (146) hide show
  1. package/LICENSE +201 -0
  2. package/dist/chains/citrea/CitreaChainType.d.ts +13 -0
  3. package/dist/chains/citrea/CitreaChainType.js +2 -0
  4. package/dist/chains/citrea/CitreaInitializer.d.ts +30 -0
  5. package/dist/chains/citrea/CitreaInitializer.js +120 -0
  6. package/dist/evm/btcrelay/BtcRelayAbi.d.ts +198 -0
  7. package/dist/evm/btcrelay/BtcRelayAbi.js +261 -0
  8. package/dist/evm/btcrelay/BtcRelayTypechain.d.ts +172 -0
  9. package/dist/evm/btcrelay/BtcRelayTypechain.js +2 -0
  10. package/dist/evm/btcrelay/EVMBtcRelay.d.ts +188 -0
  11. package/dist/evm/btcrelay/EVMBtcRelay.js +419 -0
  12. package/dist/evm/btcrelay/headers/EVMBtcHeader.d.ts +33 -0
  13. package/dist/evm/btcrelay/headers/EVMBtcHeader.js +84 -0
  14. package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.d.ts +56 -0
  15. package/dist/evm/btcrelay/headers/EVMBtcStoredHeader.js +123 -0
  16. package/dist/evm/chain/EVMChainInterface.d.ts +51 -0
  17. package/dist/evm/chain/EVMChainInterface.js +90 -0
  18. package/dist/evm/chain/EVMModule.d.ts +9 -0
  19. package/dist/evm/chain/EVMModule.js +13 -0
  20. package/dist/evm/chain/modules/ERC20Abi.d.ts +168 -0
  21. package/dist/evm/chain/modules/ERC20Abi.js +225 -0
  22. package/dist/evm/chain/modules/EVMAddresses.d.ts +9 -0
  23. package/dist/evm/chain/modules/EVMAddresses.js +26 -0
  24. package/dist/evm/chain/modules/EVMBlocks.d.ts +20 -0
  25. package/dist/evm/chain/modules/EVMBlocks.js +64 -0
  26. package/dist/evm/chain/modules/EVMEvents.d.ts +36 -0
  27. package/dist/evm/chain/modules/EVMEvents.js +122 -0
  28. package/dist/evm/chain/modules/EVMFees.d.ts +35 -0
  29. package/dist/evm/chain/modules/EVMFees.js +73 -0
  30. package/dist/evm/chain/modules/EVMSignatures.d.ts +29 -0
  31. package/dist/evm/chain/modules/EVMSignatures.js +68 -0
  32. package/dist/evm/chain/modules/EVMTokens.d.ts +49 -0
  33. package/dist/evm/chain/modules/EVMTokens.js +105 -0
  34. package/dist/evm/chain/modules/EVMTransactions.d.ts +89 -0
  35. package/dist/evm/chain/modules/EVMTransactions.js +216 -0
  36. package/dist/evm/contract/EVMContractBase.d.ts +22 -0
  37. package/dist/evm/contract/EVMContractBase.js +34 -0
  38. package/dist/evm/contract/EVMContractModule.d.ts +8 -0
  39. package/dist/evm/contract/EVMContractModule.js +11 -0
  40. package/dist/evm/contract/modules/EVMContractEvents.d.ts +42 -0
  41. package/dist/evm/contract/modules/EVMContractEvents.js +75 -0
  42. package/dist/evm/events/EVMChainEvents.d.ts +22 -0
  43. package/dist/evm/events/EVMChainEvents.js +67 -0
  44. package/dist/evm/events/EVMChainEventsBrowser.d.ts +86 -0
  45. package/dist/evm/events/EVMChainEventsBrowser.js +294 -0
  46. package/dist/evm/spv_swap/EVMSpvVaultContract.d.ts +64 -0
  47. package/dist/evm/spv_swap/EVMSpvVaultContract.js +410 -0
  48. package/dist/evm/spv_swap/EVMSpvVaultData.d.ts +38 -0
  49. package/dist/evm/spv_swap/EVMSpvVaultData.js +159 -0
  50. package/dist/evm/spv_swap/EVMSpvWithdrawalData.d.ts +19 -0
  51. package/dist/evm/spv_swap/EVMSpvWithdrawalData.js +55 -0
  52. package/dist/evm/spv_swap/SpvVaultContractAbi.d.ts +91 -0
  53. package/dist/evm/spv_swap/SpvVaultContractAbi.js +849 -0
  54. package/dist/evm/spv_swap/SpvVaultContractTypechain.d.ts +450 -0
  55. package/dist/evm/spv_swap/SpvVaultContractTypechain.js +2 -0
  56. package/dist/evm/swaps/EVMSwapContract.d.ts +192 -0
  57. package/dist/evm/swaps/EVMSwapContract.js +373 -0
  58. package/dist/evm/swaps/EVMSwapData.d.ts +64 -0
  59. package/dist/evm/swaps/EVMSwapData.js +254 -0
  60. package/dist/evm/swaps/EVMSwapModule.d.ts +9 -0
  61. package/dist/evm/swaps/EVMSwapModule.js +11 -0
  62. package/dist/evm/swaps/EscrowManagerAbi.d.ts +120 -0
  63. package/dist/evm/swaps/EscrowManagerAbi.js +985 -0
  64. package/dist/evm/swaps/EscrowManagerTypechain.d.ts +475 -0
  65. package/dist/evm/swaps/EscrowManagerTypechain.js +2 -0
  66. package/dist/evm/swaps/handlers/IHandler.d.ts +13 -0
  67. package/dist/evm/swaps/handlers/IHandler.js +2 -0
  68. package/dist/evm/swaps/handlers/claim/ClaimHandlers.d.ts +10 -0
  69. package/dist/evm/swaps/handlers/claim/ClaimHandlers.js +13 -0
  70. package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.d.ts +20 -0
  71. package/dist/evm/swaps/handlers/claim/HashlockClaimHandler.js +39 -0
  72. package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.d.ts +24 -0
  73. package/dist/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.js +59 -0
  74. package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.d.ts +25 -0
  75. package/dist/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.js +51 -0
  76. package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.d.ts +21 -0
  77. package/dist/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.js +28 -0
  78. package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.d.ts +48 -0
  79. package/dist/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.js +63 -0
  80. package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.d.ts +17 -0
  81. package/dist/evm/swaps/handlers/refund/TimelockRefundHandler.js +28 -0
  82. package/dist/evm/swaps/modules/EVMLpVault.d.ts +69 -0
  83. package/dist/evm/swaps/modules/EVMLpVault.js +131 -0
  84. package/dist/evm/swaps/modules/EVMSwapClaim.d.ts +53 -0
  85. package/dist/evm/swaps/modules/EVMSwapClaim.js +101 -0
  86. package/dist/evm/swaps/modules/EVMSwapInit.d.ts +88 -0
  87. package/dist/evm/swaps/modules/EVMSwapInit.js +241 -0
  88. package/dist/evm/swaps/modules/EVMSwapRefund.d.ts +62 -0
  89. package/dist/evm/swaps/modules/EVMSwapRefund.js +132 -0
  90. package/dist/evm/typechain/common.d.ts +50 -0
  91. package/dist/evm/typechain/common.js +2 -0
  92. package/dist/evm/wallet/EVMSigner.d.ts +9 -0
  93. package/dist/evm/wallet/EVMSigner.js +16 -0
  94. package/dist/index.d.ts +37 -0
  95. package/dist/index.js +53 -0
  96. package/dist/utils/Utils.d.ts +15 -0
  97. package/dist/utils/Utils.js +71 -0
  98. package/package.json +37 -0
  99. package/src/chains/citrea/CitreaChainType.ts +28 -0
  100. package/src/chains/citrea/CitreaInitializer.ts +167 -0
  101. package/src/evm/btcrelay/BtcRelayAbi.ts +258 -0
  102. package/src/evm/btcrelay/BtcRelayTypechain.ts +371 -0
  103. package/src/evm/btcrelay/EVMBtcRelay.ts +517 -0
  104. package/src/evm/btcrelay/headers/EVMBtcHeader.ts +110 -0
  105. package/src/evm/btcrelay/headers/EVMBtcStoredHeader.ts +153 -0
  106. package/src/evm/chain/EVMChainInterface.ts +157 -0
  107. package/src/evm/chain/EVMModule.ts +21 -0
  108. package/src/evm/chain/modules/ERC20Abi.ts +222 -0
  109. package/src/evm/chain/modules/EVMAddresses.ts +24 -0
  110. package/src/evm/chain/modules/EVMBlocks.ts +75 -0
  111. package/src/evm/chain/modules/EVMEvents.ts +139 -0
  112. package/src/evm/chain/modules/EVMFees.ts +105 -0
  113. package/src/evm/chain/modules/EVMSignatures.ts +76 -0
  114. package/src/evm/chain/modules/EVMTokens.ts +115 -0
  115. package/src/evm/chain/modules/EVMTransactions.ts +246 -0
  116. package/src/evm/contract/EVMContractBase.ts +63 -0
  117. package/src/evm/contract/EVMContractModule.ts +16 -0
  118. package/src/evm/contract/modules/EVMContractEvents.ts +102 -0
  119. package/src/evm/events/EVMChainEvents.ts +81 -0
  120. package/src/evm/events/EVMChainEventsBrowser.ts +390 -0
  121. package/src/evm/spv_swap/EVMSpvVaultContract.ts +533 -0
  122. package/src/evm/spv_swap/EVMSpvVaultData.ts +201 -0
  123. package/src/evm/spv_swap/EVMSpvWithdrawalData.ts +70 -0
  124. package/src/evm/spv_swap/SpvVaultContractAbi.ts +846 -0
  125. package/src/evm/spv_swap/SpvVaultContractTypechain.ts +685 -0
  126. package/src/evm/swaps/EVMSwapContract.ts +590 -0
  127. package/src/evm/swaps/EVMSwapData.ts +367 -0
  128. package/src/evm/swaps/EVMSwapModule.ts +16 -0
  129. package/src/evm/swaps/EscrowManagerAbi.ts +982 -0
  130. package/src/evm/swaps/EscrowManagerTypechain.ts +723 -0
  131. package/src/evm/swaps/handlers/IHandler.ts +17 -0
  132. package/src/evm/swaps/handlers/claim/ClaimHandlers.ts +20 -0
  133. package/src/evm/swaps/handlers/claim/HashlockClaimHandler.ts +47 -0
  134. package/src/evm/swaps/handlers/claim/btc/BitcoinNoncedOutputClaimHandler.ts +82 -0
  135. package/src/evm/swaps/handlers/claim/btc/BitcoinOutputClaimHandler.ts +76 -0
  136. package/src/evm/swaps/handlers/claim/btc/BitcoinTxIdClaimHandler.ts +46 -0
  137. package/src/evm/swaps/handlers/claim/btc/IBitcoinClaimHandler.ts +115 -0
  138. package/src/evm/swaps/handlers/refund/TimelockRefundHandler.ts +38 -0
  139. package/src/evm/swaps/modules/EVMLpVault.ts +153 -0
  140. package/src/evm/swaps/modules/EVMSwapClaim.ts +141 -0
  141. package/src/evm/swaps/modules/EVMSwapInit.ts +292 -0
  142. package/src/evm/swaps/modules/EVMSwapRefund.ts +198 -0
  143. package/src/evm/typechain/common.ts +131 -0
  144. package/src/evm/wallet/EVMSigner.ts +23 -0
  145. package/src/index.ts +44 -0
  146. package/src/utils/Utils.ts +81 -0
@@ -0,0 +1,141 @@
1
+ import {ChainSwapType, RelaySynchronizer, SwapDataVerificationError} from "@atomiqlabs/base";
2
+ import {IClaimHandler} from "../handlers/claim/ClaimHandlers";
3
+ import {BitcoinOutputWitnessData} from "../handlers/claim/btc/BitcoinOutputClaimHandler";
4
+ import {BitcoinWitnessData} from "../handlers/claim/btc/IBitcoinClaimHandler";
5
+ import {Buffer} from "buffer";
6
+ import {EVMSwapModule} from "../EVMSwapModule";
7
+ import { EVMSwapData } from "../EVMSwapData";
8
+ import {TransactionRequest} from "ethers";
9
+ import {EVMFees} from "../../chain/modules/EVMFees";
10
+ import {EVMTx} from "../../chain/modules/EVMTransactions";
11
+ import {EVMBtcStoredHeader} from "../../btcrelay/headers/EVMBtcStoredHeader";
12
+
13
+ export class EVMSwapClaim extends EVMSwapModule {
14
+
15
+ private static readonly GasCosts = {
16
+ CLAIM: 120_000,
17
+ CLAIM_WITH_SUCCESS_ACTION: 150_000
18
+ };
19
+
20
+ /**
21
+ * Claim action which uses the provided witness for claiming the swap
22
+ *
23
+ * @param signer
24
+ * @param swapData
25
+ * @param witness
26
+ * @param feeRate
27
+ * @param claimHandlerGas
28
+ * @private
29
+ */
30
+ private async Claim(
31
+ signer: string,
32
+ swapData: EVMSwapData,
33
+ witness: Buffer,
34
+ feeRate: string,
35
+ claimHandlerGas?: number
36
+ ): Promise<TransactionRequest> {
37
+ //TODO: Claim with success action not supported yet!
38
+ const tx = await this.swapContract.claim.populateTransaction(swapData.toEscrowStruct(), witness);
39
+ tx.from = signer;
40
+ EVMFees.applyFeeRate(tx, EVMSwapClaim.GasCosts.CLAIM + (claimHandlerGas ?? 0), feeRate);
41
+ return tx;
42
+ }
43
+
44
+ /**
45
+ * Creates transactions claiming the swap using a secret (for HTLC swaps)
46
+ *
47
+ * @param signer
48
+ * @param swapData swap to claim
49
+ * @param secret hex encoded secret pre-image to the HTLC hash
50
+ * @param checkExpiry whether to check if the swap is already expired (trying to claim an expired swap with a secret
51
+ * is dangerous because we might end up revealing the secret to the counterparty without being able to claim the swap)
52
+ * @param feeRate fee rate to use for the transaction
53
+ */
54
+ async txsClaimWithSecret(
55
+ signer: string,
56
+ swapData: EVMSwapData,
57
+ secret: string,
58
+ checkExpiry?: boolean,
59
+ feeRate?: string
60
+ ): Promise<EVMTx[]> {
61
+ //We need to be sure that this transaction confirms in time, otherwise we reveal the secret to the counterparty
62
+ // and won't claim the funds
63
+ if(checkExpiry && await this.contract.isExpired(swapData.claimer.toString(), swapData)) {
64
+ throw new SwapDataVerificationError("Not enough time to reliably pay the invoice");
65
+ }
66
+
67
+ const claimHandler: IClaimHandler<Buffer, string> = this.contract.claimHandlersByAddress[swapData.claimHandler.toLowerCase()];
68
+ if(claimHandler==null) throw new SwapDataVerificationError("Unknown claim handler!");
69
+ if(claimHandler.getType()!==ChainSwapType.HTLC) throw new SwapDataVerificationError("Invalid claim handler!");
70
+
71
+ feeRate ??= await this.root.Fees.getFeeRate();
72
+
73
+ const {initialTxns, witness} = await claimHandler.getWitness(signer, swapData, secret, feeRate);
74
+ const tx = await this.Claim(signer, swapData, witness, feeRate, claimHandler.getGas(swapData));
75
+
76
+ this.logger.debug("txsClaimWithSecret(): creating claim transaction, swap: "+swapData.getClaimHash()+" witness: ", witness.toString("hex"));
77
+
78
+ return [...initialTxns, tx];
79
+ }
80
+
81
+ /**
82
+ * Creates transaction claiming the swap using a confirmed transaction data (for BTC on-chain swaps)
83
+ *
84
+ * @param signer
85
+ * @param swapData swap to claim
86
+ * @param tx bitcoin transaction that satisfies the swap condition
87
+ * @param requiredConfirmations
88
+ * @param vout vout of the bitcoin transaction that satisfies the swap condition
89
+ * @param commitedHeader commited header data from btc relay (fetched internally if null)
90
+ * @param synchronizer optional synchronizer to use in case we need to sync up the btc relay ourselves
91
+ * @param feeRate fee rate to be used for the transactions
92
+ */
93
+ async txsClaimWithTxData(
94
+ signer: string,
95
+ swapData: EVMSwapData,
96
+ tx: { blockhash: string, confirmations: number, txid: string, hex: string, height: number },
97
+ requiredConfirmations: number,
98
+ vout: number,
99
+ commitedHeader?: EVMBtcStoredHeader,
100
+ synchronizer?: RelaySynchronizer<EVMBtcStoredHeader, EVMTx, any>,
101
+ feeRate?: string
102
+ ): Promise<EVMTx[] | null> {
103
+ const claimHandler: IClaimHandler<any, BitcoinOutputWitnessData | BitcoinWitnessData> = this.contract.claimHandlersByAddress[swapData.claimHandler.toLowerCase()];
104
+ if(claimHandler==null) throw new SwapDataVerificationError("Unknown claim handler!");
105
+ if(
106
+ claimHandler.getType()!==ChainSwapType.CHAIN_NONCED &&
107
+ claimHandler.getType()!==ChainSwapType.CHAIN_TXID &&
108
+ claimHandler.getType()!==ChainSwapType.CHAIN
109
+ ) throw new SwapDataVerificationError("Invalid claim handler!");
110
+
111
+ feeRate ??= await this.root.Fees.getFeeRate();
112
+
113
+ const {initialTxns, witness} = await claimHandler.getWitness(signer, swapData, {
114
+ tx,
115
+ vout,
116
+ requiredConfirmations,
117
+ commitedHeader,
118
+ btcRelay: this.contract.btcRelay,
119
+ synchronizer,
120
+ }, feeRate);
121
+ const claimTx = await this.Claim(signer, swapData, witness, feeRate, claimHandler.getGas(swapData));
122
+
123
+ return [...initialTxns, claimTx];
124
+ }
125
+
126
+ /**
127
+ * Get the estimated starknet transaction fee of the claim transaction
128
+ */
129
+ public async getClaimFee(swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
130
+ feeRate ??= await this.root.Fees.getFeeRate();
131
+
132
+ //TODO: Claim with success action not supported yet!
133
+ let gasRequired = EVMSwapClaim.GasCosts.CLAIM;
134
+
135
+ const claimHandler: IClaimHandler<any, any> = this.contract.claimHandlersByAddress[swapData.claimHandler.toLowerCase()];
136
+ if(claimHandler!=null) gasRequired += claimHandler.getGas(swapData);
137
+
138
+ return EVMFees.getGasFee(gasRequired, feeRate);
139
+ }
140
+
141
+ }
@@ -0,0 +1,292 @@
1
+ import {SignatureVerificationError, SwapCommitStateType, SwapDataVerificationError} from "@atomiqlabs/base";
2
+ import {Buffer} from "buffer";
3
+ import { EVMSwapModule } from "../EVMSwapModule";
4
+ import {EVMSwapData} from "../EVMSwapData";
5
+ import {keccak256, TransactionRequest, ZeroHash} from "ethers";
6
+ import {EVMFees} from "../../chain/modules/EVMFees";
7
+ import {EVMSigner} from "../../wallet/EVMSigner";
8
+ import {EVMTx} from "../../chain/modules/EVMTransactions";
9
+ import {tryWithRetries} from "../../../utils/Utils";
10
+
11
+ export type EVMPreFetchVerification = {
12
+ safeBlockTime?: number
13
+ };
14
+
15
+ const Initialize = [
16
+ { name: "swapHash", type: "bytes32" },
17
+ { name: "offerer", type: "address" },
18
+ { name: "claimer", type: "address" },
19
+ { name: "amount", type: "uint256" },
20
+ { name: "token", type: "address" },
21
+ { name: "payIn", type: "bool" },
22
+ { name: "payOut", type: "bool" },
23
+ { name: "trackingReputation", type: "bool" },
24
+ { name: "claimHandler", type: "address" },
25
+ { name: "claimData", type: "bytes32" },
26
+ { name: "refundHandler", type: "address" },
27
+ { name: "refundData", type: "bytes32" },
28
+ { name: "securityDeposit", type: "uint256" },
29
+ { name: "claimerBounty", type: "uint256" },
30
+ { name: "depositToken", type: "address" },
31
+ { name: "claimActionHash", type: "bytes32" },
32
+ { name: "deadline", type: "uint256" },
33
+ { name: "extraDataHash", type: "bytes32" }
34
+ ];
35
+
36
+ export class EVMSwapInit extends EVMSwapModule {
37
+
38
+ private static readonly GasCosts = {
39
+ INIT: 100_000,
40
+ INIT_PAY_IN: 130_000,
41
+ };
42
+
43
+ /**
44
+ * bare Init action based on the data passed in swapData
45
+ *
46
+ * @param sender
47
+ * @param swapData
48
+ * @param timeout
49
+ * @param signature
50
+ * @param feeRate
51
+ * @private
52
+ */
53
+ private async Init(sender: string, swapData: EVMSwapData, timeout: bigint, signature: string, feeRate: string): Promise<TransactionRequest> {
54
+ let value = 0n;
55
+ if(swapData.isToken(this.root.getNativeCurrencyAddress())) value += swapData.getAmount();
56
+ if(swapData.isDepositToken(this.root.getNativeCurrencyAddress())) value += swapData.getTotalDeposit();
57
+ const tx = await this.swapContract.initialize.populateTransaction(swapData.toEscrowStruct(), signature, timeout, "0x"+(swapData.extraData ?? ""), {
58
+ value
59
+ });
60
+ tx.from = sender;
61
+ EVMFees.applyFeeRate(tx, swapData.isPayIn() ? EVMSwapInit.GasCosts.INIT_PAY_IN : EVMSwapInit.GasCosts.INIT, feeRate);
62
+ return tx;
63
+ }
64
+
65
+ /**
66
+ * Returns auth prefix to be used with a specific swap, payIn=true & payIn=false use different prefixes (these
67
+ * actually have no meaning for the smart contract in the EVM case)
68
+ *
69
+ * @param swapData
70
+ * @private
71
+ */
72
+ private getAuthPrefix(swapData: EVMSwapData): string {
73
+ return swapData.isPayIn() ? "claim_initialize" : "initialize";
74
+ }
75
+
76
+ public async preFetchForInitSignatureVerification(): Promise<EVMPreFetchVerification> {
77
+ return {
78
+ safeBlockTime: await this.root.Blocks.getBlockTime(this.root.config.safeBlockTag)
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Signs swap initialization authorization, using data from preFetchedBlockData if provided & still valid (subject
84
+ * to SIGNATURE_PREFETCH_DATA_VALIDITY)
85
+ *
86
+ * @param signer
87
+ * @param swapData
88
+ * @param authorizationTimeout
89
+ * @public
90
+ */
91
+ public async signSwapInitialization(
92
+ signer: EVMSigner,
93
+ swapData: EVMSwapData,
94
+ authorizationTimeout: number
95
+ ): Promise<{prefix: string, timeout: string, signature: string}> {
96
+ const authExpiry = Math.floor(Date.now()/1000)+authorizationTimeout;
97
+
98
+ const signature = await this.root.Signatures.signTypedMessage(this.contract.contractAddress, signer, Initialize, "Initialize", {
99
+ "swapHash": "0x"+swapData.getEscrowHash(),
100
+ "offerer": swapData.offerer,
101
+ "claimer": swapData.claimer,
102
+ "amount": swapData.amount,
103
+ "token": swapData.token,
104
+ "payIn": swapData.isPayIn(),
105
+ "payOut": swapData.isPayOut(),
106
+ "trackingReputation": swapData.reputation,
107
+ "claimHandler": swapData.claimHandler,
108
+ "claimData": "0x"+swapData.getClaimHash(),
109
+ "refundHandler": swapData.refundHandler,
110
+ "refundData": swapData.refundData.startsWith("0x") ? swapData.refundData : "0x"+swapData.refundData,
111
+ "securityDeposit": swapData.securityDeposit,
112
+ "claimerBounty": swapData.claimerBounty,
113
+ "depositToken": swapData.depositToken,
114
+ "claimActionHash": ZeroHash,
115
+ "deadline": authExpiry,
116
+ "extraDataHash": keccak256("0x"+(swapData.extraData ?? ""))
117
+ });
118
+
119
+ return {
120
+ prefix: this.getAuthPrefix(swapData),
121
+ timeout: authExpiry.toString(10),
122
+ signature
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Checks whether the provided signature data is valid, using preFetchedData if provided and still valid
128
+ *
129
+ * @param sender
130
+ * @param swapData
131
+ * @param timeout
132
+ * @param prefix
133
+ * @param signature
134
+ * @param preFetchData
135
+ * @public
136
+ */
137
+ public async isSignatureValid(
138
+ sender: string,
139
+ swapData: EVMSwapData,
140
+ timeout: string,
141
+ prefix: string,
142
+ signature: string,
143
+ preFetchData?: EVMPreFetchVerification
144
+ ): Promise<null> {
145
+ if(!swapData.isOfferer(sender) && !swapData.isClaimer(sender))
146
+ throw new SignatureVerificationError("TX sender not offerer nor claimer");
147
+
148
+ const signer = swapData.isOfferer(sender) ? swapData.claimer : swapData.offerer;
149
+
150
+ if(await this.contract.isExpired(sender, swapData)) {
151
+ throw new SignatureVerificationError("Swap will expire too soon!");
152
+ }
153
+
154
+ if(prefix!==this.getAuthPrefix(swapData)) throw new SignatureVerificationError("Invalid prefix");
155
+
156
+ const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
157
+ const timeoutBN = BigInt(timeout);
158
+ const isExpired = (timeoutBN - currentTimestamp) < BigInt(this.contract.authGracePeriod);
159
+ if (isExpired) throw new SignatureVerificationError("Authorization expired!");
160
+ if(await this.isSignatureExpired(timeout, preFetchData)) throw new SignatureVerificationError("Authorization expired!");
161
+
162
+ const valid = await this.root.Signatures.isValidSignature(this.contract.contractAddress, signature, signer, Initialize, "Initialize", {
163
+ "swapHash": "0x"+swapData.getEscrowHash(),
164
+ "offerer": swapData.offerer,
165
+ "claimer": swapData.claimer,
166
+ "amount": swapData.amount,
167
+ "token": swapData.token,
168
+ "payIn": swapData.isPayIn(),
169
+ "payOut": swapData.isPayOut(),
170
+ "trackingReputation": swapData.reputation,
171
+ "claimHandler": swapData.claimHandler,
172
+ "claimData": "0x"+swapData.getClaimHash(),
173
+ "refundHandler": swapData.refundHandler,
174
+ "refundData": swapData.refundData.startsWith("0x") ? swapData.refundData : "0x"+swapData.refundData,
175
+ "securityDeposit": swapData.securityDeposit,
176
+ "claimerBounty": swapData.claimerBounty,
177
+ "depositToken": swapData.depositToken,
178
+ "claimActionHash": ZeroHash,
179
+ "deadline": timeoutBN,
180
+ "extraDataHash": keccak256("0x"+(swapData.extraData ?? ""))
181
+ });
182
+
183
+ if(!valid) throw new SignatureVerificationError("Invalid signature!");
184
+
185
+ return null;
186
+ }
187
+
188
+ /**
189
+ * Gets expiry of the provided signature data, this is a minimum of slot expiry & swap signature expiry
190
+ *
191
+ * @param timeout
192
+ * @public
193
+ */
194
+ public async getSignatureExpiry(
195
+ timeout: string
196
+ ): Promise<number> {
197
+ const now = Date.now();
198
+ const timeoutExpiryTime = (parseInt(timeout)-this.contract.authGracePeriod)*1000;
199
+
200
+ if(timeoutExpiryTime<now) return 0;
201
+
202
+ return timeoutExpiryTime;
203
+ }
204
+
205
+ /**
206
+ * Checks whether signature is expired for good, compares the timestamp to the current "pending" block timestamp
207
+ *
208
+ * @param timeout
209
+ * @param preFetchData
210
+ * @public
211
+ */
212
+ public async isSignatureExpired(
213
+ timeout: string,
214
+ preFetchData?: EVMPreFetchVerification
215
+ ): Promise<boolean> {
216
+ if(preFetchData==null || preFetchData.safeBlockTime==null) {
217
+ preFetchData = await this.preFetchForInitSignatureVerification();
218
+ }
219
+ return preFetchData.safeBlockTime > parseInt(timeout);
220
+ }
221
+
222
+ /**
223
+ * Creates init transaction with a valid signature from an LP
224
+ *
225
+ * @param sender
226
+ * @param swapData swap to initialize
227
+ * @param timeout init signature timeout
228
+ * @param prefix init signature prefix
229
+ * @param signature init signature
230
+ * @param skipChecks whether to skip signature validity checks
231
+ * @param feeRate fee rate to use for the transaction
232
+ */
233
+ public async txsInit(
234
+ sender: string,
235
+ swapData: EVMSwapData,
236
+ timeout: string,
237
+ prefix: string,
238
+ signature: string,
239
+ skipChecks?: boolean,
240
+ feeRate?: string
241
+ ): Promise<EVMTx[]> {
242
+ if(
243
+ swapData.isClaimer(sender) && swapData.isPayIn() &&
244
+ swapData.isToken(this.root.getNativeCurrencyAddress())
245
+ ) throw new Error("Cannot initialize as claimer for payIn=true and native currency!");
246
+
247
+ if(!skipChecks) {
248
+ const [_, payStatus] = await Promise.all([
249
+ swapData.isOfferer(sender) && !swapData.reputation ? Promise.resolve() : tryWithRetries(
250
+ () => this.isSignatureValid(sender, swapData, timeout, prefix, signature),
251
+ this.retryPolicy, (e) => e instanceof SignatureVerificationError
252
+ ),
253
+ tryWithRetries(() => this.contract.getCommitStatus(sender, swapData), this.retryPolicy)
254
+ ]);
255
+ if(payStatus.type!==SwapCommitStateType.NOT_COMMITED) throw new SwapDataVerificationError("Invoice already being paid for or paid");
256
+ }
257
+
258
+ feeRate ??= await this.root.Fees.getFeeRate();
259
+
260
+ const txs: EVMTx[] = [];
261
+ const requiredApprovals: {[address: string]: bigint} = {};
262
+ if(swapData.payIn && swapData.isOfferer(sender)) {
263
+ if(!swapData.isToken(this.root.getNativeCurrencyAddress())) {
264
+ requiredApprovals[swapData.token.toLowerCase()] = swapData.amount;
265
+ }
266
+ }
267
+ if(swapData.getTotalDeposit() !== 0n) {
268
+ if(!swapData.isDepositToken(this.root.getNativeCurrencyAddress())) {
269
+ requiredApprovals[swapData.depositToken.toLowerCase()] ??= 0n;
270
+ requiredApprovals[swapData.depositToken.toLowerCase()] += swapData.getTotalDeposit();
271
+ }
272
+ }
273
+ for(let tokenAddress in requiredApprovals) {
274
+ txs.push(await this.root.Tokens.Approve(sender, tokenAddress, requiredApprovals[tokenAddress], this.contract.contractAddress, feeRate));
275
+ }
276
+ txs.push(await this.Init(sender, swapData, BigInt(timeout), signature ?? "0x", feeRate));
277
+
278
+ this.logger.debug("txsInitPayIn(): create swap init TX, swap: "+swapData.getClaimHash()+
279
+ " feerate: "+feeRate);
280
+
281
+ return txs;
282
+ }
283
+
284
+ /**
285
+ * Get the estimated solana fee of the init transaction, this includes the required deposit for creating swap PDA
286
+ * and also deposit for ATAs
287
+ */
288
+ async getInitFee(swapData?: EVMSwapData, feeRate?: string): Promise<bigint> {
289
+ feeRate ??= await this.root.Fees.getFeeRate();
290
+ return EVMFees.getGasFee(swapData.payIn ? EVMSwapInit.GasCosts.INIT_PAY_IN : EVMSwapInit.GasCosts.INIT, feeRate);
291
+ }
292
+ }
@@ -0,0 +1,198 @@
1
+ import {SignatureVerificationError, SwapDataVerificationError} from "@atomiqlabs/base";
2
+ import {tryWithRetries} from "../../../utils/Utils";
3
+ import {IHandler} from "../handlers/IHandler";
4
+ import {EVMSwapModule} from "../EVMSwapModule";
5
+ import {Buffer} from "buffer";
6
+ import {EVMSwapData} from "../EVMSwapData";
7
+ import {TransactionRequest} from "ethers";
8
+ import {EVMFees} from "../../chain/modules/EVMFees";
9
+ import {EVMSigner} from "../../wallet/EVMSigner";
10
+ import {EVMTx} from "../../chain/modules/EVMTransactions";
11
+
12
+ const Refund = [
13
+ { name: "swapHash", type: "bytes32" },
14
+ { name: "timeout", type: "uint256" }
15
+ ];
16
+
17
+ export class EVMSwapRefund extends EVMSwapModule {
18
+
19
+ private static readonly GasCosts = {
20
+ REFUND: 100_000,
21
+ REFUND_PAY_OUT: 130_000
22
+ };
23
+
24
+ /**
25
+ * Action for generic Refund instruction
26
+ *
27
+ * @param signer
28
+ * @param swapData
29
+ * @param witness
30
+ * @param feeRate
31
+ * @param handlerGas
32
+ * @private
33
+ */
34
+ private async Refund(
35
+ signer: string,
36
+ swapData: EVMSwapData,
37
+ witness: Buffer,
38
+ feeRate: string,
39
+ handlerGas?: number
40
+ ): Promise<TransactionRequest> {
41
+ const tx = await this.swapContract.refund.populateTransaction(swapData.toEscrowStruct(), witness);
42
+ tx.from = signer;
43
+ EVMFees.applyFeeRate(tx, (swapData.payIn ? EVMSwapRefund.GasCosts.REFUND_PAY_OUT : EVMSwapRefund.GasCosts.REFUND) + (handlerGas ?? 0), feeRate)
44
+ return tx;
45
+ }
46
+
47
+ /**
48
+ * Action for cooperative refunding with signature
49
+ *
50
+ * @param sender
51
+ * @param swapData
52
+ * @param timeout
53
+ * @param signature
54
+ * @param feeRate
55
+ * @private
56
+ */
57
+ private async RefundWithSignature(
58
+ sender: string,
59
+ swapData: EVMSwapData,
60
+ timeout: string,
61
+ signature: string,
62
+ feeRate: string
63
+ ): Promise<TransactionRequest> {
64
+ const tx = await this.swapContract.cooperativeRefund.populateTransaction(swapData.toEscrowStruct(), signature, BigInt(timeout));
65
+ tx.from = sender;
66
+ EVMFees.applyFeeRate(tx, swapData.payIn ? EVMSwapRefund.GasCosts.REFUND_PAY_OUT : EVMSwapRefund.GasCosts.REFUND, feeRate)
67
+ return tx;
68
+ }
69
+
70
+ public async signSwapRefund(
71
+ signer: EVMSigner,
72
+ swapData: EVMSwapData,
73
+ authorizationTimeout: number
74
+ ): Promise<{ prefix: string; timeout: string; signature: string }> {
75
+ const authPrefix = "refund";
76
+ const authTimeout = Math.floor(Date.now()/1000)+authorizationTimeout;
77
+
78
+ const signature = await this.root.Signatures.signTypedMessage(this.contract.contractAddress, signer, Refund, "Refund", {
79
+ "swapHash": "0x"+swapData.getEscrowHash(),
80
+ "timeout": BigInt(authTimeout)
81
+ });
82
+
83
+ return {
84
+ prefix: authPrefix,
85
+ timeout: authTimeout.toString(10),
86
+ signature: signature
87
+ };
88
+ }
89
+
90
+ public async isSignatureValid(
91
+ swapData: EVMSwapData,
92
+ timeout: string,
93
+ prefix: string,
94
+ signature: string
95
+ ): Promise<null> {
96
+ if(prefix!=="refund") throw new SignatureVerificationError("Invalid prefix");
97
+
98
+ const expiryTimestamp = BigInt(timeout);
99
+ const currentTimestamp = BigInt(Math.floor(Date.now() / 1000));
100
+
101
+ const isExpired = (expiryTimestamp - currentTimestamp) < BigInt(this.contract.authGracePeriod);
102
+ if(isExpired) throw new SignatureVerificationError("Authorization expired!");
103
+
104
+ const valid = await this.root.Signatures.isValidSignature(this.contract.contractAddress, signature, swapData.claimer, Refund, "Refund", {
105
+ "swapHash": "0x"+swapData.getEscrowHash(),
106
+ "timeout": BigInt(expiryTimestamp)
107
+ });
108
+
109
+ if(!valid) {
110
+ throw new SignatureVerificationError("Invalid signature!");
111
+ }
112
+
113
+ return null;
114
+ }
115
+
116
+ /**
117
+ * Creates transactions required for refunding timed out swap
118
+ *
119
+ * @param signer
120
+ * @param swapData swap data to refund
121
+ * @param check whether to check if swap is already expired and refundable
122
+ * @param feeRate fee rate to be used for the transactions
123
+ * @param witnessData
124
+ */
125
+ public async txsRefund<T>(
126
+ signer: string,
127
+ swapData: EVMSwapData,
128
+ check?: boolean,
129
+ feeRate?: string,
130
+ witnessData?: T
131
+ ): Promise<EVMTx[]> {
132
+ const refundHandler: IHandler<any, T> = this.contract.refundHandlersByAddress[swapData.refundHandler.toLowerCase()];
133
+ if(refundHandler==null) throw new Error("Invalid refund handler");
134
+
135
+ if(check && !await tryWithRetries(() => this.contract.isRequestRefundable(swapData.offerer.toString(), swapData), this.retryPolicy)) {
136
+ throw new SwapDataVerificationError("Not refundable yet!");
137
+ }
138
+
139
+ feeRate ??= await this.root.Fees.getFeeRate();
140
+
141
+ const {initialTxns, witness} = await refundHandler.getWitness(signer, swapData, witnessData, feeRate);
142
+
143
+ const tx = await this.Refund(signer, swapData, witness, feeRate, refundHandler.getGas(swapData));
144
+
145
+ this.logger.debug("txsRefund(): creating refund transaction, swap: "+swapData.getClaimHash());
146
+
147
+ return [...initialTxns, tx];
148
+ }
149
+
150
+ /**
151
+ * Creates transactions required for refunding the swap with authorization signature, also unwraps WSOL to SOL
152
+ *
153
+ * @param signer
154
+ * @param swapData swap data to refund
155
+ * @param timeout signature timeout
156
+ * @param prefix signature prefix of the counterparty
157
+ * @param signature signature of the counterparty
158
+ * @param check whether to check if swap is committed before attempting refund
159
+ * @param feeRate fee rate to be used for the transactions
160
+ */
161
+ public async txsRefundWithAuthorization(
162
+ signer: string,
163
+ swapData: EVMSwapData,
164
+ timeout: string,
165
+ prefix: string,
166
+ signature: string,
167
+ check?: boolean,
168
+ feeRate?: string
169
+ ): Promise<EVMTx[]> {
170
+ if(check && !await tryWithRetries(() => this.contract.isCommited(swapData), this.retryPolicy)) {
171
+ throw new SwapDataVerificationError("Not correctly committed");
172
+ }
173
+ await tryWithRetries(
174
+ () => this.isSignatureValid(swapData, timeout, prefix, signature),
175
+ this.retryPolicy,
176
+ (e) => e instanceof SignatureVerificationError
177
+ );
178
+
179
+ feeRate ??= await this.root.Fees.getFeeRate();
180
+
181
+ const tx = await this.RefundWithSignature(signer, swapData, timeout, signature, feeRate);
182
+
183
+ this.logger.debug("txsRefundWithAuthorization(): creating refund transaction, swap: "+swapData.getClaimHash()+
184
+ " auth expiry: "+timeout+" signature: "+signature);
185
+
186
+ return [tx];
187
+ }
188
+
189
+ /**
190
+ * Get the estimated solana transaction fee of the refund transaction, in the worst case scenario in case where the
191
+ * ATA needs to be initialized again (i.e. adding the ATA rent exempt lamports to the fee)
192
+ */
193
+ async getRefundFee(swapData: EVMSwapData, feeRate?: string): Promise<bigint> {
194
+ feeRate ??= await this.root.Fees.getFeeRate();
195
+ return EVMFees.getGasFee(swapData.payIn ? EVMSwapRefund.GasCosts.REFUND_PAY_OUT : EVMSwapRefund.GasCosts.REFUND, feeRate);
196
+ }
197
+
198
+ }