@atomiqlabs/chain-solana 10.0.0-dev.3 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/dist/index.d.ts +29 -29
- package/dist/index.js +45 -45
- package/dist/solana/SolanaChainType.d.ts +10 -10
- package/dist/solana/SolanaChainType.js +2 -2
- package/dist/solana/SolanaChains.d.ts +20 -20
- package/dist/solana/SolanaChains.js +25 -25
- package/dist/solana/SolanaInitializer.d.ts +18 -18
- package/dist/solana/SolanaInitializer.js +63 -63
- package/dist/solana/btcrelay/SolanaBtcRelay.d.ts +228 -228
- package/dist/solana/btcrelay/SolanaBtcRelay.js +441 -441
- package/dist/solana/btcrelay/headers/SolanaBtcHeader.d.ts +29 -29
- package/dist/solana/btcrelay/headers/SolanaBtcHeader.js +34 -34
- package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.d.ts +46 -46
- package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.js +78 -78
- package/dist/solana/btcrelay/program/programIdl.json +671 -671
- package/dist/solana/chain/SolanaAction.d.ts +26 -26
- package/dist/solana/chain/SolanaAction.js +86 -86
- package/dist/solana/chain/SolanaChainInterface.d.ts +58 -58
- package/dist/solana/chain/SolanaChainInterface.js +112 -112
- package/dist/solana/chain/SolanaModule.d.ts +14 -14
- package/dist/solana/chain/SolanaModule.js +13 -13
- package/dist/solana/chain/modules/SolanaAddresses.d.ts +8 -8
- package/dist/solana/chain/modules/SolanaAddresses.js +22 -22
- package/dist/solana/chain/modules/SolanaBlocks.d.ts +28 -28
- package/dist/solana/chain/modules/SolanaBlocks.js +72 -72
- package/dist/solana/chain/modules/SolanaEvents.d.ts +25 -25
- package/dist/solana/chain/modules/SolanaEvents.js +58 -58
- package/dist/solana/chain/modules/SolanaFees.d.ts +121 -121
- package/dist/solana/chain/modules/SolanaFees.js +379 -379
- package/dist/solana/chain/modules/SolanaSignatures.d.ts +23 -23
- package/dist/solana/chain/modules/SolanaSignatures.js +39 -39
- package/dist/solana/chain/modules/SolanaSlots.d.ts +31 -31
- package/dist/solana/chain/modules/SolanaSlots.js +68 -68
- package/dist/solana/chain/modules/SolanaTokens.d.ts +136 -136
- package/dist/solana/chain/modules/SolanaTokens.js +248 -248
- package/dist/solana/chain/modules/SolanaTransactions.d.ts +124 -124
- package/dist/solana/chain/modules/SolanaTransactions.js +332 -332
- package/dist/solana/events/SolanaChainEvents.d.ts +88 -88
- package/dist/solana/events/SolanaChainEvents.js +256 -256
- package/dist/solana/events/SolanaChainEventsBrowser.d.ts +85 -85
- package/dist/solana/events/SolanaChainEventsBrowser.js +194 -194
- package/dist/solana/program/SolanaProgramBase.d.ts +40 -40
- package/dist/solana/program/SolanaProgramBase.js +43 -43
- package/dist/solana/program/SolanaProgramModule.d.ts +8 -8
- package/dist/solana/program/SolanaProgramModule.js +11 -11
- package/dist/solana/program/modules/SolanaProgramEvents.d.ts +59 -59
- package/dist/solana/program/modules/SolanaProgramEvents.js +103 -103
- package/dist/solana/swaps/SolanaSwapData.d.ts +59 -59
- package/dist/solana/swaps/SolanaSwapData.js +267 -267
- package/dist/solana/swaps/SolanaSwapModule.d.ts +10 -10
- package/dist/solana/swaps/SolanaSwapModule.js +11 -11
- package/dist/solana/swaps/SolanaSwapProgram.d.ts +202 -202
- package/dist/solana/swaps/SolanaSwapProgram.js +470 -463
- package/dist/solana/swaps/SwapTypeEnum.d.ts +11 -11
- package/dist/solana/swaps/SwapTypeEnum.js +42 -42
- package/dist/solana/swaps/modules/SolanaDataAccount.d.ts +94 -94
- package/dist/solana/swaps/modules/SolanaDataAccount.js +231 -231
- package/dist/solana/swaps/modules/SolanaLpVault.d.ts +71 -71
- package/dist/solana/swaps/modules/SolanaLpVault.js +173 -173
- package/dist/solana/swaps/modules/SwapClaim.d.ts +129 -129
- package/dist/solana/swaps/modules/SwapClaim.js +291 -291
- package/dist/solana/swaps/modules/SwapInit.d.ts +217 -217
- package/dist/solana/swaps/modules/SwapInit.js +519 -519
- package/dist/solana/swaps/modules/SwapRefund.d.ts +82 -82
- package/dist/solana/swaps/modules/SwapRefund.js +252 -252
- package/dist/solana/swaps/programIdl.json +945 -945
- package/dist/solana/swaps/programTypes.d.ts +943 -943
- package/dist/solana/swaps/programTypes.js +945 -945
- package/dist/solana/wallet/SolanaKeypairWallet.d.ts +9 -9
- package/dist/solana/wallet/SolanaKeypairWallet.js +33 -33
- package/dist/solana/wallet/SolanaSigner.d.ts +10 -10
- package/dist/solana/wallet/SolanaSigner.js +16 -16
- package/dist/utils/Utils.d.ts +53 -53
- package/dist/utils/Utils.js +170 -170
- package/package.json +41 -41
- package/src/index.ts +36 -36
- package/src/solana/SolanaChainType.ts +25 -25
- package/src/solana/SolanaChains.ts +23 -23
- package/src/solana/SolanaInitializer.ts +102 -102
- package/src/solana/btcrelay/SolanaBtcRelay.ts +589 -588
- package/src/solana/btcrelay/headers/SolanaBtcHeader.ts +57 -57
- package/src/solana/btcrelay/headers/SolanaBtcStoredHeader.ts +102 -102
- package/src/solana/btcrelay/program/programIdl.json +670 -670
- package/src/solana/chain/SolanaAction.ts +108 -108
- package/src/solana/chain/SolanaChainInterface.ts +174 -174
- package/src/solana/chain/SolanaModule.ts +20 -20
- package/src/solana/chain/modules/SolanaAddresses.ts +20 -20
- package/src/solana/chain/modules/SolanaBlocks.ts +78 -78
- package/src/solana/chain/modules/SolanaEvents.ts +56 -56
- package/src/solana/chain/modules/SolanaFees.ts +450 -450
- package/src/solana/chain/modules/SolanaSignatures.ts +39 -39
- package/src/solana/chain/modules/SolanaSlots.ts +82 -82
- package/src/solana/chain/modules/SolanaTokens.ts +307 -307
- package/src/solana/chain/modules/SolanaTransactions.ts +370 -370
- package/src/solana/events/SolanaChainEvents.ts +299 -299
- package/src/solana/events/SolanaChainEventsBrowser.ts +256 -256
- package/src/solana/program/SolanaProgramBase.ts +79 -79
- package/src/solana/program/SolanaProgramModule.ts +15 -15
- package/src/solana/program/modules/SolanaProgramEvents.ts +140 -140
- package/src/solana/swaps/SolanaSwapData.ts +379 -379
- package/src/solana/swaps/SolanaSwapModule.ts +16 -16
- package/src/solana/swaps/SolanaSwapProgram.ts +697 -692
- package/src/solana/swaps/SwapTypeEnum.ts +29 -29
- package/src/solana/swaps/modules/SolanaDataAccount.ts +307 -307
- package/src/solana/swaps/modules/SolanaLpVault.ts +215 -215
- package/src/solana/swaps/modules/SwapClaim.ts +389 -389
- package/src/solana/swaps/modules/SwapInit.ts +663 -663
- package/src/solana/swaps/modules/SwapRefund.ts +312 -312
- package/src/solana/swaps/programIdl.json +944 -944
- package/src/solana/swaps/programTypes.ts +1885 -1885
- package/src/solana/wallet/SolanaKeypairWallet.ts +36 -36
- package/src/solana/wallet/SolanaSigner.ts +23 -23
- package/src/utils/Utils.ts +180 -180
|
@@ -1,390 +1,390 @@
|
|
|
1
|
-
import {SolanaSwapModule} from "../SolanaSwapModule";
|
|
2
|
-
import {SolanaSwapData} from "../SolanaSwapData";
|
|
3
|
-
import {SolanaAction} from "../../chain/SolanaAction";
|
|
4
|
-
import {TOKEN_PROGRAM_ID} from "@solana/spl-token";
|
|
5
|
-
import {ChainSwapType, RelaySynchronizer, SwapDataVerificationError} from "@atomiqlabs/base";
|
|
6
|
-
import {PublicKey, SYSVAR_INSTRUCTIONS_PUBKEY} from "@solana/web3.js";
|
|
7
|
-
import {SolanaTx} from "../../chain/modules/SolanaTransactions";
|
|
8
|
-
import {SolanaBtcStoredHeader} from "../../btcrelay/headers/SolanaBtcStoredHeader";
|
|
9
|
-
import {tryWithRetries} from "../../../utils/Utils";
|
|
10
|
-
import {SolanaBtcRelay} from "../../btcrelay/SolanaBtcRelay";
|
|
11
|
-
import {SolanaSwapProgram} from "../SolanaSwapProgram";
|
|
12
|
-
import {SolanaSigner} from "../../wallet/SolanaSigner";
|
|
13
|
-
import {SolanaTokens} from "../../chain/modules/SolanaTokens";
|
|
14
|
-
import * as BN from "bn.js";
|
|
15
|
-
import {SolanaChainInterface} from "../../chain/SolanaChainInterface";
|
|
16
|
-
|
|
17
|
-
export class SwapClaim extends SolanaSwapModule {
|
|
18
|
-
|
|
19
|
-
private static readonly CUCosts = {
|
|
20
|
-
CLAIM: 25000,
|
|
21
|
-
CLAIM_PAY_OUT: 50000,
|
|
22
|
-
CLAIM_ONCHAIN: 600000,
|
|
23
|
-
CLAIM_ONCHAIN_PAY_OUT: 600000
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
readonly btcRelay: SolanaBtcRelay<any>;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Claim action which uses the provided hex encoded secret for claiming the swap
|
|
30
|
-
*
|
|
31
|
-
* @param signer
|
|
32
|
-
* @param swapData
|
|
33
|
-
* @param secret
|
|
34
|
-
* @constructor
|
|
35
|
-
* @private
|
|
36
|
-
*/
|
|
37
|
-
private Claim(signer: PublicKey, swapData: SolanaSwapData, secret: string): Promise<SolanaAction>;
|
|
38
|
-
/**
|
|
39
|
-
* Claim action which uses data in the provided data ccount with dataKey to claim the swap
|
|
40
|
-
*
|
|
41
|
-
* @param signer
|
|
42
|
-
* @param swapData
|
|
43
|
-
* @param dataKey
|
|
44
|
-
* @constructor
|
|
45
|
-
* @private
|
|
46
|
-
*/
|
|
47
|
-
private Claim(signer: PublicKey, swapData: SolanaSwapData, dataKey: PublicKey): Promise<SolanaAction>;
|
|
48
|
-
private async Claim(
|
|
49
|
-
signer: PublicKey,
|
|
50
|
-
swapData: SolanaSwapData,
|
|
51
|
-
secretOrDataKey: string | PublicKey
|
|
52
|
-
): Promise<SolanaAction> {
|
|
53
|
-
const isDataKey = typeof(secretOrDataKey)!=="string";
|
|
54
|
-
|
|
55
|
-
const accounts = {
|
|
56
|
-
signer,
|
|
57
|
-
initializer: swapData.isPayIn() ? swapData.offerer : swapData.claimer,
|
|
58
|
-
escrowState: this.program.SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")),
|
|
59
|
-
ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
60
|
-
data: isDataKey ? secretOrDataKey : null,
|
|
61
|
-
};
|
|
62
|
-
let secretBuffer = isDataKey ?
|
|
63
|
-
Buffer.alloc(0) :
|
|
64
|
-
Buffer.from(secretOrDataKey, "hex");
|
|
65
|
-
|
|
66
|
-
if(swapData.isPayOut()) {
|
|
67
|
-
return new SolanaAction(signer, this.root,
|
|
68
|
-
await this.swapProgram.methods
|
|
69
|
-
.claimerClaimPayOut(secretBuffer)
|
|
70
|
-
.accounts({
|
|
71
|
-
...accounts,
|
|
72
|
-
claimerAta: swapData.claimerAta,
|
|
73
|
-
vault: this.program.SwapVault(swapData.token),
|
|
74
|
-
vaultAuthority: this.program.SwapVaultAuthority,
|
|
75
|
-
tokenProgram: TOKEN_PROGRAM_ID
|
|
76
|
-
})
|
|
77
|
-
.instruction(),
|
|
78
|
-
this.getComputeBudget(swapData)
|
|
79
|
-
);
|
|
80
|
-
} else {
|
|
81
|
-
return new SolanaAction(signer, this.root,
|
|
82
|
-
await this.swapProgram.methods
|
|
83
|
-
.claimerClaim(secretBuffer)
|
|
84
|
-
.accounts({
|
|
85
|
-
...accounts,
|
|
86
|
-
claimerUserData: this.program.SwapUserVault(swapData.claimer, swapData.token)
|
|
87
|
-
})
|
|
88
|
-
.instruction(),
|
|
89
|
-
this.getComputeBudget(swapData)
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Verify and claim action required for BTC on-chain swaps verified through btc relay, adds the btc relay verify
|
|
96
|
-
* instruction to the 0th index in the transaction, also intentionally sets compute budget to null such that no
|
|
97
|
-
* compute budget instruction is added, since that takes up too much space and txs are limited to 1232 bytes
|
|
98
|
-
*
|
|
99
|
-
* @param signer
|
|
100
|
-
* @param swapData
|
|
101
|
-
* @param storeDataKey
|
|
102
|
-
* @param merkleProof
|
|
103
|
-
* @param commitedHeader
|
|
104
|
-
* @constructor
|
|
105
|
-
* @private
|
|
106
|
-
*/
|
|
107
|
-
private async VerifyAndClaim(
|
|
108
|
-
signer: PublicKey,
|
|
109
|
-
swapData: SolanaSwapData,
|
|
110
|
-
storeDataKey: PublicKey,
|
|
111
|
-
merkleProof: {reversedTxId: Buffer, pos: number, merkle: Buffer[]},
|
|
112
|
-
commitedHeader: SolanaBtcStoredHeader
|
|
113
|
-
): Promise<SolanaAction> {
|
|
114
|
-
const action = await this.btcRelay.Verify(
|
|
115
|
-
signer,
|
|
116
|
-
merkleProof.reversedTxId,
|
|
117
|
-
swapData.confirmations,
|
|
118
|
-
merkleProof.pos,
|
|
119
|
-
merkleProof.merkle,
|
|
120
|
-
commitedHeader
|
|
121
|
-
);
|
|
122
|
-
action.addAction(await this.Claim(signer, swapData, storeDataKey));
|
|
123
|
-
action.computeBudget = null;
|
|
124
|
-
return action;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
constructor(chainInterface: SolanaChainInterface, program: SolanaSwapProgram, btcRelay: SolanaBtcRelay<any>) {
|
|
128
|
-
super(chainInterface, program);
|
|
129
|
-
this.btcRelay = btcRelay;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Gets the compute budget required for claiming the swap
|
|
134
|
-
*
|
|
135
|
-
* @param swapData
|
|
136
|
-
* @private
|
|
137
|
-
*/
|
|
138
|
-
private getComputeBudget(swapData: SolanaSwapData) {
|
|
139
|
-
if(swapData.isPayOut()) {
|
|
140
|
-
return SwapClaim.CUCosts[swapData.getType()===ChainSwapType.HTLC ? "CLAIM_PAY_OUT" : "CLAIM_ONCHAIN_PAY_OUT"]
|
|
141
|
-
} else {
|
|
142
|
-
return SwapClaim.CUCosts[swapData.getType()===ChainSwapType.HTLC ? "CLAIM" : "CLAIM_ONCHAIN"];
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Gets committed header, identified by blockhash & blockheight, determines required BTC relay blockheight based on
|
|
148
|
-
* requiredConfirmations
|
|
149
|
-
* If synchronizer is passed & blockhash is not found, it produces transactions to sync up the btc relay to the
|
|
150
|
-
* current chain tip & adds them to the txs array
|
|
151
|
-
*
|
|
152
|
-
* @param signer
|
|
153
|
-
* @param txBlockheight transaction blockheight
|
|
154
|
-
* @param requiredConfirmations required confirmation for the swap to be claimable with that TX
|
|
155
|
-
* @param blockhash blockhash of the block which includes the transaction
|
|
156
|
-
* @param txs solana transaction array, in case we need to synchronize the btc relay ourselves the synchronization
|
|
157
|
-
* txns are added here
|
|
158
|
-
* @param synchronizer optional synchronizer to use to synchronize the btc relay in case it is not yet synchronized
|
|
159
|
-
* to the required blockheight
|
|
160
|
-
* @private
|
|
161
|
-
*/
|
|
162
|
-
private async getCommitedHeaderAndSynchronize(
|
|
163
|
-
signer: PublicKey,
|
|
164
|
-
txBlockheight: number,
|
|
165
|
-
requiredConfirmations: number,
|
|
166
|
-
blockhash: string,
|
|
167
|
-
txs: SolanaTx[],
|
|
168
|
-
synchronizer?: RelaySynchronizer<SolanaBtcStoredHeader, SolanaTx, any>,
|
|
169
|
-
): Promise<SolanaBtcStoredHeader> {
|
|
170
|
-
const requiredBlockheight = txBlockheight+requiredConfirmations-1;
|
|
171
|
-
|
|
172
|
-
const result = await tryWithRetries(
|
|
173
|
-
() => this.btcRelay.retrieveLogAndBlockheight({
|
|
174
|
-
blockhash: blockhash
|
|
175
|
-
}, requiredBlockheight),
|
|
176
|
-
this.retryPolicy
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
if(result!=null) return result.header;
|
|
180
|
-
|
|
181
|
-
//Need to synchronize
|
|
182
|
-
if(synchronizer==null) return null;
|
|
183
|
-
|
|
184
|
-
//TODO: We don't have to synchronize to tip, only to our required blockheight
|
|
185
|
-
const resp = await synchronizer.syncToLatestTxs(signer.toString());
|
|
186
|
-
this.logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay not synchronized to required blockheight, "+
|
|
187
|
-
"synchronizing ourselves in "+resp.txs.length+" txs");
|
|
188
|
-
this.logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay computed header map: ",resp.computedHeaderMap);
|
|
189
|
-
resp.txs.forEach(tx => txs.push(tx));
|
|
190
|
-
|
|
191
|
-
//Retrieve computed header
|
|
192
|
-
return resp.computedHeaderMap[txBlockheight];
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Adds the transactions required for initialization and writing of transaction data to the data account
|
|
197
|
-
*
|
|
198
|
-
* @param signer
|
|
199
|
-
* @param tx transaction to be written
|
|
200
|
-
* @param vout vout of the transaction to use to satisfy swap conditions
|
|
201
|
-
* @param feeRate fee rate for the transactions
|
|
202
|
-
* @param txs solana transaction array, init & write transactions are added here
|
|
203
|
-
* @private
|
|
204
|
-
* @returns {Promise<PublicKey>} publicKey/address of the data account
|
|
205
|
-
*/
|
|
206
|
-
private addTxsWriteTransactionData(
|
|
207
|
-
signer: PublicKey | SolanaSigner,
|
|
208
|
-
tx: {hex: string, txid: string},
|
|
209
|
-
vout: number,
|
|
210
|
-
feeRate: string,
|
|
211
|
-
txs: SolanaTx[]
|
|
212
|
-
): Promise<PublicKey> {
|
|
213
|
-
const reversedTxId = Buffer.from(tx.txid, "hex").reverse();
|
|
214
|
-
const writeData: Buffer = Buffer.concat([
|
|
215
|
-
Buffer.from(new BN(vout).toArray("le", 4)),
|
|
216
|
-
Buffer.from(tx.hex, "hex")
|
|
217
|
-
]);
|
|
218
|
-
this.logger.debug("addTxsWriteTransactionData(): writing transaction data: ", writeData.toString("hex"));
|
|
219
|
-
|
|
220
|
-
return this.program.DataAccount.addTxsWriteData(signer, reversedTxId, writeData, txs, feeRate);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Checks whether we should unwrap the WSOL to SOL when claiming the swap
|
|
225
|
-
*
|
|
226
|
-
* @param signer
|
|
227
|
-
* @param swapData
|
|
228
|
-
* @private
|
|
229
|
-
*/
|
|
230
|
-
private shouldUnwrap(signer: PublicKey, swapData: SolanaSwapData): boolean {
|
|
231
|
-
return swapData.isPayOut() &&
|
|
232
|
-
swapData.token.equals(SolanaTokens.WSOL_ADDRESS) &&
|
|
233
|
-
swapData.claimer.equals(signer);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Creates transactions claiming the swap using a secret (for HTLC swaps)
|
|
238
|
-
*
|
|
239
|
-
* @param signer
|
|
240
|
-
* @param swapData swap to claim
|
|
241
|
-
* @param secret hex encoded secret pre-image to the HTLC hash
|
|
242
|
-
* @param checkExpiry whether to check if the swap is already expired (trying to claim an expired swap with a secret
|
|
243
|
-
* is dangerous because we might end up revealing the secret to the counterparty without being able to claim the swap)
|
|
244
|
-
* @param initAta whether to init the claimer's ATA if it doesn't exist
|
|
245
|
-
* @param feeRate fee rate to use for the transaction
|
|
246
|
-
* @param skipAtaCheck whether to check if ATA exists
|
|
247
|
-
*/
|
|
248
|
-
async txsClaimWithSecret(
|
|
249
|
-
signer: PublicKey,
|
|
250
|
-
swapData: SolanaSwapData,
|
|
251
|
-
secret: string,
|
|
252
|
-
checkExpiry?: boolean,
|
|
253
|
-
initAta?: boolean,
|
|
254
|
-
feeRate?: string,
|
|
255
|
-
skipAtaCheck?: boolean
|
|
256
|
-
): Promise<SolanaTx[]> {
|
|
257
|
-
//We need to be sure that this transaction confirms in time, otherwise we reveal the secret to the counterparty
|
|
258
|
-
// and won't claim the funds
|
|
259
|
-
if(checkExpiry && await this.program.isExpired(swapData.claimer.toString(), swapData)) {
|
|
260
|
-
throw new SwapDataVerificationError("Not enough time to reliably pay the invoice");
|
|
261
|
-
}
|
|
262
|
-
const shouldInitAta = !skipAtaCheck && swapData.isPayOut() && !await this.root.Tokens.ataExists(swapData.claimerAta);
|
|
263
|
-
if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
|
|
264
|
-
|
|
265
|
-
if(feeRate==null) feeRate = await this.getClaimFeeRate(signer, swapData);
|
|
266
|
-
|
|
267
|
-
const action = new SolanaAction(signer, this.root);
|
|
268
|
-
|
|
269
|
-
const shouldUnwrap = this.shouldUnwrap(signer, swapData);
|
|
270
|
-
if(shouldInitAta) {
|
|
271
|
-
const initAction = this.root.Tokens.InitAta(signer, swapData.claimer, swapData.token, swapData.claimerAta);
|
|
272
|
-
if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address");
|
|
273
|
-
action.add(initAction);
|
|
274
|
-
}
|
|
275
|
-
action.add(await this.Claim(signer, swapData, secret));
|
|
276
|
-
if(shouldUnwrap) action.add(this.root.Tokens.Unwrap(signer));
|
|
277
|
-
|
|
278
|
-
this.logger.debug("txsClaimWithSecret(): creating claim transaction, swap: "+swapData.getClaimHash()+
|
|
279
|
-
" initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap);
|
|
280
|
-
|
|
281
|
-
return [await action.tx(feeRate)];
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Creates transaction claiming the swap using a confirmed transaction data (for BTC on-chain swaps)
|
|
286
|
-
*
|
|
287
|
-
* @param signer
|
|
288
|
-
* @param swapData swap to claim
|
|
289
|
-
* @param blockheight blockheight of the bitcoin transaction
|
|
290
|
-
* @param tx bitcoin transaction that satisfies the swap condition
|
|
291
|
-
* @param vout vout of the bitcoin transaction that satisfies the swap condition
|
|
292
|
-
* @param commitedHeader commited header data from btc relay (fetched internally if null)
|
|
293
|
-
* @param synchronizer optional synchronizer to use in case we need to sync up the btc relay ourselves
|
|
294
|
-
* @param initAta whether to initialize claimer's ATA
|
|
295
|
-
* @param storageAccHolder an object holder filled in with the created data account where tx data is written
|
|
296
|
-
* @param feeRate fee rate to be used for the transactions
|
|
297
|
-
*/
|
|
298
|
-
async txsClaimWithTxData(
|
|
299
|
-
signer: PublicKey | SolanaSigner,
|
|
300
|
-
swapData: SolanaSwapData,
|
|
301
|
-
tx: { blockhash: string, confirmations: number, txid: string, hex: string, height: number },
|
|
302
|
-
vout: number,
|
|
303
|
-
commitedHeader?: SolanaBtcStoredHeader,
|
|
304
|
-
synchronizer?: RelaySynchronizer<any, SolanaTx, any>,
|
|
305
|
-
initAta?: boolean,
|
|
306
|
-
storageAccHolder?: {storageAcc: PublicKey},
|
|
307
|
-
feeRate?: string
|
|
308
|
-
): Promise<SolanaTx[] | null> {
|
|
309
|
-
const shouldInitAta = swapData.isPayOut() && !await this.root.Tokens.ataExists(swapData.claimerAta);
|
|
310
|
-
if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
|
|
311
|
-
|
|
312
|
-
const signerKey = signer instanceof SolanaSigner ? signer.getPublicKey() : signer;
|
|
313
|
-
|
|
314
|
-
if(feeRate==null) feeRate = await this.getClaimFeeRate(signerKey, swapData);
|
|
315
|
-
|
|
316
|
-
const merkleProof = await this.btcRelay.bitcoinRpc.getMerkleProof(tx.txid, tx.blockhash);
|
|
317
|
-
this.logger.debug("txsClaimWithTxData(): merkle proof computed: ", merkleProof);
|
|
318
|
-
|
|
319
|
-
const txs: SolanaTx[] = [];
|
|
320
|
-
if(commitedHeader==null) commitedHeader = await this.getCommitedHeaderAndSynchronize(
|
|
321
|
-
signerKey, tx.height, swapData.confirmations,
|
|
322
|
-
tx.blockhash, txs, synchronizer
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
const storeDataKey = await this.addTxsWriteTransactionData(signer, tx, vout, feeRate, txs);
|
|
326
|
-
if(storageAccHolder!=null) storageAccHolder.storageAcc = storeDataKey;
|
|
327
|
-
|
|
328
|
-
const shouldUnwrap = this.shouldUnwrap(signerKey, swapData);
|
|
329
|
-
if(shouldInitAta) {
|
|
330
|
-
const initAction = this.root.Tokens.InitAta(signerKey, swapData.claimer, swapData.token, swapData.claimerAta);
|
|
331
|
-
if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address");
|
|
332
|
-
await initAction.addToTxs(txs, feeRate);
|
|
333
|
-
}
|
|
334
|
-
const claimAction = await this.VerifyAndClaim(signerKey, swapData, storeDataKey, merkleProof, commitedHeader);
|
|
335
|
-
await claimAction.addToTxs(txs, feeRate);
|
|
336
|
-
if(shouldUnwrap) await this.root.Tokens.Unwrap(signerKey).addToTxs(txs, feeRate);
|
|
337
|
-
|
|
338
|
-
this.logger.debug("txsClaimWithTxData(): creating claim transaction, swap: "+swapData.getClaimHash()+
|
|
339
|
-
" initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap+" num txns: "+txs.length);
|
|
340
|
-
|
|
341
|
-
return txs;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
public getClaimFeeRate(signer: PublicKey, swapData: SolanaSwapData): Promise<string> {
|
|
345
|
-
const accounts: PublicKey[] = [signer];
|
|
346
|
-
if(swapData.payOut) {
|
|
347
|
-
if(swapData.token!=null) accounts.push(this.program.SwapVault(swapData.token));
|
|
348
|
-
if(swapData.payIn) {
|
|
349
|
-
if(swapData.offerer!=null) accounts.push(swapData.offerer);
|
|
350
|
-
} else {
|
|
351
|
-
if(swapData.claimer!=null) accounts.push(swapData.claimer);
|
|
352
|
-
}
|
|
353
|
-
if(swapData.claimerAta!=null && !swapData.claimerAta.equals(PublicKey.default)) accounts.push(swapData.claimerAta);
|
|
354
|
-
} else {
|
|
355
|
-
if(swapData.claimer!=null && swapData.token!=null) accounts.push(this.program.SwapUserVault(swapData.claimer, swapData.token));
|
|
356
|
-
|
|
357
|
-
if(swapData.payIn) {
|
|
358
|
-
if(swapData.offerer!=null) accounts.push(swapData.offerer);
|
|
359
|
-
} else {
|
|
360
|
-
if(swapData.claimer!=null) accounts.push(swapData.claimer);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if(swapData.paymentHash!=null) accounts.push(this.program.SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")));
|
|
365
|
-
|
|
366
|
-
return this.root.Fees.getFeeRate(accounts);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Get the estimated solana transaction fee of the claim transaction in the worst case scenario in case where the
|
|
371
|
-
* ATA needs to be initialized again (i.e. adding the ATA rent exempt lamports to the fee)
|
|
372
|
-
*/
|
|
373
|
-
public async getClaimFee(signer: PublicKey, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
374
|
-
return BigInt(swapData==null || swapData.payOut ? SolanaTokens.SPL_ATA_RENT_EXEMPT : 0) +
|
|
375
|
-
await this.getRawClaimFee(signer, swapData, feeRate);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Get the estimated solana transaction fee of the claim transaction, without
|
|
380
|
-
*/
|
|
381
|
-
public async getRawClaimFee(signer: PublicKey, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
382
|
-
if(swapData==null) return 5000n;
|
|
383
|
-
|
|
384
|
-
feeRate = feeRate || await this.getClaimFeeRate(signer, swapData);
|
|
385
|
-
|
|
386
|
-
//Include rent exempt in claim fee, to take into consideration worst case cost when user destroys ATA
|
|
387
|
-
return 5000n + this.root.Fees.getPriorityFee(this.getComputeBudget(swapData), feeRate);
|
|
388
|
-
}
|
|
389
|
-
|
|
1
|
+
import {SolanaSwapModule} from "../SolanaSwapModule";
|
|
2
|
+
import {SolanaSwapData} from "../SolanaSwapData";
|
|
3
|
+
import {SolanaAction} from "../../chain/SolanaAction";
|
|
4
|
+
import {TOKEN_PROGRAM_ID} from "@solana/spl-token";
|
|
5
|
+
import {ChainSwapType, RelaySynchronizer, SwapDataVerificationError} from "@atomiqlabs/base";
|
|
6
|
+
import {PublicKey, SYSVAR_INSTRUCTIONS_PUBKEY} from "@solana/web3.js";
|
|
7
|
+
import {SolanaTx} from "../../chain/modules/SolanaTransactions";
|
|
8
|
+
import {SolanaBtcStoredHeader} from "../../btcrelay/headers/SolanaBtcStoredHeader";
|
|
9
|
+
import {tryWithRetries} from "../../../utils/Utils";
|
|
10
|
+
import {SolanaBtcRelay} from "../../btcrelay/SolanaBtcRelay";
|
|
11
|
+
import {SolanaSwapProgram} from "../SolanaSwapProgram";
|
|
12
|
+
import {SolanaSigner} from "../../wallet/SolanaSigner";
|
|
13
|
+
import {SolanaTokens} from "../../chain/modules/SolanaTokens";
|
|
14
|
+
import * as BN from "bn.js";
|
|
15
|
+
import {SolanaChainInterface} from "../../chain/SolanaChainInterface";
|
|
16
|
+
|
|
17
|
+
export class SwapClaim extends SolanaSwapModule {
|
|
18
|
+
|
|
19
|
+
private static readonly CUCosts = {
|
|
20
|
+
CLAIM: 25000,
|
|
21
|
+
CLAIM_PAY_OUT: 50000,
|
|
22
|
+
CLAIM_ONCHAIN: 600000,
|
|
23
|
+
CLAIM_ONCHAIN_PAY_OUT: 600000
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
readonly btcRelay: SolanaBtcRelay<any>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Claim action which uses the provided hex encoded secret for claiming the swap
|
|
30
|
+
*
|
|
31
|
+
* @param signer
|
|
32
|
+
* @param swapData
|
|
33
|
+
* @param secret
|
|
34
|
+
* @constructor
|
|
35
|
+
* @private
|
|
36
|
+
*/
|
|
37
|
+
private Claim(signer: PublicKey, swapData: SolanaSwapData, secret: string): Promise<SolanaAction>;
|
|
38
|
+
/**
|
|
39
|
+
* Claim action which uses data in the provided data ccount with dataKey to claim the swap
|
|
40
|
+
*
|
|
41
|
+
* @param signer
|
|
42
|
+
* @param swapData
|
|
43
|
+
* @param dataKey
|
|
44
|
+
* @constructor
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
private Claim(signer: PublicKey, swapData: SolanaSwapData, dataKey: PublicKey): Promise<SolanaAction>;
|
|
48
|
+
private async Claim(
|
|
49
|
+
signer: PublicKey,
|
|
50
|
+
swapData: SolanaSwapData,
|
|
51
|
+
secretOrDataKey: string | PublicKey
|
|
52
|
+
): Promise<SolanaAction> {
|
|
53
|
+
const isDataKey = typeof(secretOrDataKey)!=="string";
|
|
54
|
+
|
|
55
|
+
const accounts = {
|
|
56
|
+
signer,
|
|
57
|
+
initializer: swapData.isPayIn() ? swapData.offerer : swapData.claimer,
|
|
58
|
+
escrowState: this.program.SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")),
|
|
59
|
+
ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
|
|
60
|
+
data: isDataKey ? secretOrDataKey : null,
|
|
61
|
+
};
|
|
62
|
+
let secretBuffer = isDataKey ?
|
|
63
|
+
Buffer.alloc(0) :
|
|
64
|
+
Buffer.from(secretOrDataKey, "hex");
|
|
65
|
+
|
|
66
|
+
if(swapData.isPayOut()) {
|
|
67
|
+
return new SolanaAction(signer, this.root,
|
|
68
|
+
await this.swapProgram.methods
|
|
69
|
+
.claimerClaimPayOut(secretBuffer)
|
|
70
|
+
.accounts({
|
|
71
|
+
...accounts,
|
|
72
|
+
claimerAta: swapData.claimerAta,
|
|
73
|
+
vault: this.program.SwapVault(swapData.token),
|
|
74
|
+
vaultAuthority: this.program.SwapVaultAuthority,
|
|
75
|
+
tokenProgram: TOKEN_PROGRAM_ID
|
|
76
|
+
})
|
|
77
|
+
.instruction(),
|
|
78
|
+
this.getComputeBudget(swapData)
|
|
79
|
+
);
|
|
80
|
+
} else {
|
|
81
|
+
return new SolanaAction(signer, this.root,
|
|
82
|
+
await this.swapProgram.methods
|
|
83
|
+
.claimerClaim(secretBuffer)
|
|
84
|
+
.accounts({
|
|
85
|
+
...accounts,
|
|
86
|
+
claimerUserData: this.program.SwapUserVault(swapData.claimer, swapData.token)
|
|
87
|
+
})
|
|
88
|
+
.instruction(),
|
|
89
|
+
this.getComputeBudget(swapData)
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Verify and claim action required for BTC on-chain swaps verified through btc relay, adds the btc relay verify
|
|
96
|
+
* instruction to the 0th index in the transaction, also intentionally sets compute budget to null such that no
|
|
97
|
+
* compute budget instruction is added, since that takes up too much space and txs are limited to 1232 bytes
|
|
98
|
+
*
|
|
99
|
+
* @param signer
|
|
100
|
+
* @param swapData
|
|
101
|
+
* @param storeDataKey
|
|
102
|
+
* @param merkleProof
|
|
103
|
+
* @param commitedHeader
|
|
104
|
+
* @constructor
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
private async VerifyAndClaim(
|
|
108
|
+
signer: PublicKey,
|
|
109
|
+
swapData: SolanaSwapData,
|
|
110
|
+
storeDataKey: PublicKey,
|
|
111
|
+
merkleProof: {reversedTxId: Buffer, pos: number, merkle: Buffer[]},
|
|
112
|
+
commitedHeader: SolanaBtcStoredHeader
|
|
113
|
+
): Promise<SolanaAction> {
|
|
114
|
+
const action = await this.btcRelay.Verify(
|
|
115
|
+
signer,
|
|
116
|
+
merkleProof.reversedTxId,
|
|
117
|
+
swapData.confirmations,
|
|
118
|
+
merkleProof.pos,
|
|
119
|
+
merkleProof.merkle,
|
|
120
|
+
commitedHeader
|
|
121
|
+
);
|
|
122
|
+
action.addAction(await this.Claim(signer, swapData, storeDataKey));
|
|
123
|
+
action.computeBudget = null;
|
|
124
|
+
return action;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
constructor(chainInterface: SolanaChainInterface, program: SolanaSwapProgram, btcRelay: SolanaBtcRelay<any>) {
|
|
128
|
+
super(chainInterface, program);
|
|
129
|
+
this.btcRelay = btcRelay;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Gets the compute budget required for claiming the swap
|
|
134
|
+
*
|
|
135
|
+
* @param swapData
|
|
136
|
+
* @private
|
|
137
|
+
*/
|
|
138
|
+
private getComputeBudget(swapData: SolanaSwapData) {
|
|
139
|
+
if(swapData.isPayOut()) {
|
|
140
|
+
return SwapClaim.CUCosts[swapData.getType()===ChainSwapType.HTLC ? "CLAIM_PAY_OUT" : "CLAIM_ONCHAIN_PAY_OUT"]
|
|
141
|
+
} else {
|
|
142
|
+
return SwapClaim.CUCosts[swapData.getType()===ChainSwapType.HTLC ? "CLAIM" : "CLAIM_ONCHAIN"];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Gets committed header, identified by blockhash & blockheight, determines required BTC relay blockheight based on
|
|
148
|
+
* requiredConfirmations
|
|
149
|
+
* If synchronizer is passed & blockhash is not found, it produces transactions to sync up the btc relay to the
|
|
150
|
+
* current chain tip & adds them to the txs array
|
|
151
|
+
*
|
|
152
|
+
* @param signer
|
|
153
|
+
* @param txBlockheight transaction blockheight
|
|
154
|
+
* @param requiredConfirmations required confirmation for the swap to be claimable with that TX
|
|
155
|
+
* @param blockhash blockhash of the block which includes the transaction
|
|
156
|
+
* @param txs solana transaction array, in case we need to synchronize the btc relay ourselves the synchronization
|
|
157
|
+
* txns are added here
|
|
158
|
+
* @param synchronizer optional synchronizer to use to synchronize the btc relay in case it is not yet synchronized
|
|
159
|
+
* to the required blockheight
|
|
160
|
+
* @private
|
|
161
|
+
*/
|
|
162
|
+
private async getCommitedHeaderAndSynchronize(
|
|
163
|
+
signer: PublicKey,
|
|
164
|
+
txBlockheight: number,
|
|
165
|
+
requiredConfirmations: number,
|
|
166
|
+
blockhash: string,
|
|
167
|
+
txs: SolanaTx[],
|
|
168
|
+
synchronizer?: RelaySynchronizer<SolanaBtcStoredHeader, SolanaTx, any>,
|
|
169
|
+
): Promise<SolanaBtcStoredHeader> {
|
|
170
|
+
const requiredBlockheight = txBlockheight+requiredConfirmations-1;
|
|
171
|
+
|
|
172
|
+
const result = await tryWithRetries(
|
|
173
|
+
() => this.btcRelay.retrieveLogAndBlockheight({
|
|
174
|
+
blockhash: blockhash
|
|
175
|
+
}, requiredBlockheight),
|
|
176
|
+
this.retryPolicy
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if(result!=null) return result.header;
|
|
180
|
+
|
|
181
|
+
//Need to synchronize
|
|
182
|
+
if(synchronizer==null) return null;
|
|
183
|
+
|
|
184
|
+
//TODO: We don't have to synchronize to tip, only to our required blockheight
|
|
185
|
+
const resp = await synchronizer.syncToLatestTxs(signer.toString());
|
|
186
|
+
this.logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay not synchronized to required blockheight, "+
|
|
187
|
+
"synchronizing ourselves in "+resp.txs.length+" txs");
|
|
188
|
+
this.logger.debug("getCommitedHeaderAndSynchronize(): BTC Relay computed header map: ",resp.computedHeaderMap);
|
|
189
|
+
resp.txs.forEach(tx => txs.push(tx));
|
|
190
|
+
|
|
191
|
+
//Retrieve computed header
|
|
192
|
+
return resp.computedHeaderMap[txBlockheight];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Adds the transactions required for initialization and writing of transaction data to the data account
|
|
197
|
+
*
|
|
198
|
+
* @param signer
|
|
199
|
+
* @param tx transaction to be written
|
|
200
|
+
* @param vout vout of the transaction to use to satisfy swap conditions
|
|
201
|
+
* @param feeRate fee rate for the transactions
|
|
202
|
+
* @param txs solana transaction array, init & write transactions are added here
|
|
203
|
+
* @private
|
|
204
|
+
* @returns {Promise<PublicKey>} publicKey/address of the data account
|
|
205
|
+
*/
|
|
206
|
+
private addTxsWriteTransactionData(
|
|
207
|
+
signer: PublicKey | SolanaSigner,
|
|
208
|
+
tx: {hex: string, txid: string},
|
|
209
|
+
vout: number,
|
|
210
|
+
feeRate: string,
|
|
211
|
+
txs: SolanaTx[]
|
|
212
|
+
): Promise<PublicKey> {
|
|
213
|
+
const reversedTxId = Buffer.from(tx.txid, "hex").reverse();
|
|
214
|
+
const writeData: Buffer = Buffer.concat([
|
|
215
|
+
Buffer.from(new BN(vout).toArray("le", 4)),
|
|
216
|
+
Buffer.from(tx.hex, "hex")
|
|
217
|
+
]);
|
|
218
|
+
this.logger.debug("addTxsWriteTransactionData(): writing transaction data: ", writeData.toString("hex"));
|
|
219
|
+
|
|
220
|
+
return this.program.DataAccount.addTxsWriteData(signer, reversedTxId, writeData, txs, feeRate);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Checks whether we should unwrap the WSOL to SOL when claiming the swap
|
|
225
|
+
*
|
|
226
|
+
* @param signer
|
|
227
|
+
* @param swapData
|
|
228
|
+
* @private
|
|
229
|
+
*/
|
|
230
|
+
private shouldUnwrap(signer: PublicKey, swapData: SolanaSwapData): boolean {
|
|
231
|
+
return swapData.isPayOut() &&
|
|
232
|
+
swapData.token.equals(SolanaTokens.WSOL_ADDRESS) &&
|
|
233
|
+
swapData.claimer.equals(signer);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Creates transactions claiming the swap using a secret (for HTLC swaps)
|
|
238
|
+
*
|
|
239
|
+
* @param signer
|
|
240
|
+
* @param swapData swap to claim
|
|
241
|
+
* @param secret hex encoded secret pre-image to the HTLC hash
|
|
242
|
+
* @param checkExpiry whether to check if the swap is already expired (trying to claim an expired swap with a secret
|
|
243
|
+
* is dangerous because we might end up revealing the secret to the counterparty without being able to claim the swap)
|
|
244
|
+
* @param initAta whether to init the claimer's ATA if it doesn't exist
|
|
245
|
+
* @param feeRate fee rate to use for the transaction
|
|
246
|
+
* @param skipAtaCheck whether to check if ATA exists
|
|
247
|
+
*/
|
|
248
|
+
async txsClaimWithSecret(
|
|
249
|
+
signer: PublicKey,
|
|
250
|
+
swapData: SolanaSwapData,
|
|
251
|
+
secret: string,
|
|
252
|
+
checkExpiry?: boolean,
|
|
253
|
+
initAta?: boolean,
|
|
254
|
+
feeRate?: string,
|
|
255
|
+
skipAtaCheck?: boolean
|
|
256
|
+
): Promise<SolanaTx[]> {
|
|
257
|
+
//We need to be sure that this transaction confirms in time, otherwise we reveal the secret to the counterparty
|
|
258
|
+
// and won't claim the funds
|
|
259
|
+
if(checkExpiry && await this.program.isExpired(swapData.claimer.toString(), swapData)) {
|
|
260
|
+
throw new SwapDataVerificationError("Not enough time to reliably pay the invoice");
|
|
261
|
+
}
|
|
262
|
+
const shouldInitAta = !skipAtaCheck && swapData.isPayOut() && !await this.root.Tokens.ataExists(swapData.claimerAta);
|
|
263
|
+
if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
|
|
264
|
+
|
|
265
|
+
if(feeRate==null) feeRate = await this.getClaimFeeRate(signer, swapData);
|
|
266
|
+
|
|
267
|
+
const action = new SolanaAction(signer, this.root);
|
|
268
|
+
|
|
269
|
+
const shouldUnwrap = this.shouldUnwrap(signer, swapData);
|
|
270
|
+
if(shouldInitAta) {
|
|
271
|
+
const initAction = this.root.Tokens.InitAta(signer, swapData.claimer, swapData.token, swapData.claimerAta);
|
|
272
|
+
if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address");
|
|
273
|
+
action.add(initAction);
|
|
274
|
+
}
|
|
275
|
+
action.add(await this.Claim(signer, swapData, secret));
|
|
276
|
+
if(shouldUnwrap) action.add(this.root.Tokens.Unwrap(signer));
|
|
277
|
+
|
|
278
|
+
this.logger.debug("txsClaimWithSecret(): creating claim transaction, swap: "+swapData.getClaimHash()+
|
|
279
|
+
" initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap);
|
|
280
|
+
|
|
281
|
+
return [await action.tx(feeRate)];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Creates transaction claiming the swap using a confirmed transaction data (for BTC on-chain swaps)
|
|
286
|
+
*
|
|
287
|
+
* @param signer
|
|
288
|
+
* @param swapData swap to claim
|
|
289
|
+
* @param blockheight blockheight of the bitcoin transaction
|
|
290
|
+
* @param tx bitcoin transaction that satisfies the swap condition
|
|
291
|
+
* @param vout vout of the bitcoin transaction that satisfies the swap condition
|
|
292
|
+
* @param commitedHeader commited header data from btc relay (fetched internally if null)
|
|
293
|
+
* @param synchronizer optional synchronizer to use in case we need to sync up the btc relay ourselves
|
|
294
|
+
* @param initAta whether to initialize claimer's ATA
|
|
295
|
+
* @param storageAccHolder an object holder filled in with the created data account where tx data is written
|
|
296
|
+
* @param feeRate fee rate to be used for the transactions
|
|
297
|
+
*/
|
|
298
|
+
async txsClaimWithTxData(
|
|
299
|
+
signer: PublicKey | SolanaSigner,
|
|
300
|
+
swapData: SolanaSwapData,
|
|
301
|
+
tx: { blockhash: string, confirmations: number, txid: string, hex: string, height: number },
|
|
302
|
+
vout: number,
|
|
303
|
+
commitedHeader?: SolanaBtcStoredHeader,
|
|
304
|
+
synchronizer?: RelaySynchronizer<any, SolanaTx, any>,
|
|
305
|
+
initAta?: boolean,
|
|
306
|
+
storageAccHolder?: {storageAcc: PublicKey},
|
|
307
|
+
feeRate?: string
|
|
308
|
+
): Promise<SolanaTx[] | null> {
|
|
309
|
+
const shouldInitAta = swapData.isPayOut() && !await this.root.Tokens.ataExists(swapData.claimerAta);
|
|
310
|
+
if(shouldInitAta && !initAta) throw new SwapDataVerificationError("ATA not initialized");
|
|
311
|
+
|
|
312
|
+
const signerKey = signer instanceof SolanaSigner ? signer.getPublicKey() : signer;
|
|
313
|
+
|
|
314
|
+
if(feeRate==null) feeRate = await this.getClaimFeeRate(signerKey, swapData);
|
|
315
|
+
|
|
316
|
+
const merkleProof = await this.btcRelay.bitcoinRpc.getMerkleProof(tx.txid, tx.blockhash);
|
|
317
|
+
this.logger.debug("txsClaimWithTxData(): merkle proof computed: ", merkleProof);
|
|
318
|
+
|
|
319
|
+
const txs: SolanaTx[] = [];
|
|
320
|
+
if(commitedHeader==null) commitedHeader = await this.getCommitedHeaderAndSynchronize(
|
|
321
|
+
signerKey, tx.height, swapData.confirmations,
|
|
322
|
+
tx.blockhash, txs, synchronizer
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const storeDataKey = await this.addTxsWriteTransactionData(signer, tx, vout, feeRate, txs);
|
|
326
|
+
if(storageAccHolder!=null) storageAccHolder.storageAcc = storeDataKey;
|
|
327
|
+
|
|
328
|
+
const shouldUnwrap = this.shouldUnwrap(signerKey, swapData);
|
|
329
|
+
if(shouldInitAta) {
|
|
330
|
+
const initAction = this.root.Tokens.InitAta(signerKey, swapData.claimer, swapData.token, swapData.claimerAta);
|
|
331
|
+
if(initAction==null) throw new SwapDataVerificationError("Invalid claimer token account address");
|
|
332
|
+
await initAction.addToTxs(txs, feeRate);
|
|
333
|
+
}
|
|
334
|
+
const claimAction = await this.VerifyAndClaim(signerKey, swapData, storeDataKey, merkleProof, commitedHeader);
|
|
335
|
+
await claimAction.addToTxs(txs, feeRate);
|
|
336
|
+
if(shouldUnwrap) await this.root.Tokens.Unwrap(signerKey).addToTxs(txs, feeRate);
|
|
337
|
+
|
|
338
|
+
this.logger.debug("txsClaimWithTxData(): creating claim transaction, swap: "+swapData.getClaimHash()+
|
|
339
|
+
" initializingAta: "+shouldInitAta+" unwrapping: "+shouldUnwrap+" num txns: "+txs.length);
|
|
340
|
+
|
|
341
|
+
return txs;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public getClaimFeeRate(signer: PublicKey, swapData: SolanaSwapData): Promise<string> {
|
|
345
|
+
const accounts: PublicKey[] = [signer];
|
|
346
|
+
if(swapData.payOut) {
|
|
347
|
+
if(swapData.token!=null) accounts.push(this.program.SwapVault(swapData.token));
|
|
348
|
+
if(swapData.payIn) {
|
|
349
|
+
if(swapData.offerer!=null) accounts.push(swapData.offerer);
|
|
350
|
+
} else {
|
|
351
|
+
if(swapData.claimer!=null) accounts.push(swapData.claimer);
|
|
352
|
+
}
|
|
353
|
+
if(swapData.claimerAta!=null && !swapData.claimerAta.equals(PublicKey.default)) accounts.push(swapData.claimerAta);
|
|
354
|
+
} else {
|
|
355
|
+
if(swapData.claimer!=null && swapData.token!=null) accounts.push(this.program.SwapUserVault(swapData.claimer, swapData.token));
|
|
356
|
+
|
|
357
|
+
if(swapData.payIn) {
|
|
358
|
+
if(swapData.offerer!=null) accounts.push(swapData.offerer);
|
|
359
|
+
} else {
|
|
360
|
+
if(swapData.claimer!=null) accounts.push(swapData.claimer);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if(swapData.paymentHash!=null) accounts.push(this.program.SwapEscrowState(Buffer.from(swapData.paymentHash, "hex")));
|
|
365
|
+
|
|
366
|
+
return this.root.Fees.getFeeRate(accounts);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Get the estimated solana transaction fee of the claim transaction in the worst case scenario in case where the
|
|
371
|
+
* ATA needs to be initialized again (i.e. adding the ATA rent exempt lamports to the fee)
|
|
372
|
+
*/
|
|
373
|
+
public async getClaimFee(signer: PublicKey, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
374
|
+
return BigInt(swapData==null || swapData.payOut ? SolanaTokens.SPL_ATA_RENT_EXEMPT : 0) +
|
|
375
|
+
await this.getRawClaimFee(signer, swapData, feeRate);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Get the estimated solana transaction fee of the claim transaction, without
|
|
380
|
+
*/
|
|
381
|
+
public async getRawClaimFee(signer: PublicKey, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
382
|
+
if(swapData==null) return 5000n;
|
|
383
|
+
|
|
384
|
+
feeRate = feeRate || await this.getClaimFeeRate(signer, swapData);
|
|
385
|
+
|
|
386
|
+
//Include rent exempt in claim fee, to take into consideration worst case cost when user destroys ATA
|
|
387
|
+
return 5000n + this.root.Fees.getPriorityFee(this.getComputeBudget(swapData), feeRate);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
390
|
}
|