@atomiqlabs/chain-solana 13.5.13 → 13.5.14
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/README.md +73 -73
- package/dist/index.d.ts +81 -81
- package/dist/index.js +102 -102
- package/dist/node/index.d.ts +9 -9
- package/dist/node/index.js +13 -13
- package/dist/solana/SolanaChainType.d.ts +15 -15
- package/dist/solana/SolanaChainType.js +2 -2
- package/dist/solana/SolanaChains.d.ts +12 -12
- package/dist/solana/SolanaChains.js +45 -45
- package/dist/solana/SolanaInitializer.d.ts +94 -94
- package/dist/solana/SolanaInitializer.js +174 -174
- package/dist/solana/btcrelay/SolanaBtcRelay.d.ts +222 -222
- package/dist/solana/btcrelay/SolanaBtcRelay.js +455 -455
- package/dist/solana/btcrelay/headers/SolanaBtcHeader.d.ts +84 -84
- package/dist/solana/btcrelay/headers/SolanaBtcHeader.js +70 -70
- package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.d.ts +92 -92
- package/dist/solana/btcrelay/headers/SolanaBtcStoredHeader.js +109 -109
- package/dist/solana/btcrelay/program/programIdl.json +671 -671
- package/dist/solana/chain/SolanaAction.d.ts +26 -26
- package/dist/solana/chain/SolanaAction.js +87 -87
- package/dist/solana/chain/SolanaChainInterface.d.ts +224 -224
- package/dist/solana/chain/SolanaChainInterface.js +275 -275
- 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 +32 -32
- package/dist/solana/chain/modules/SolanaBlocks.js +78 -78
- package/dist/solana/chain/modules/SolanaEvents.d.ts +68 -68
- package/dist/solana/chain/modules/SolanaEvents.js +238 -238
- package/dist/solana/chain/modules/SolanaFees.d.ts +189 -189
- package/dist/solana/chain/modules/SolanaFees.js +434 -434
- package/dist/solana/chain/modules/SolanaSignatures.d.ts +24 -24
- package/dist/solana/chain/modules/SolanaSignatures.js +39 -39
- package/dist/solana/chain/modules/SolanaSlots.d.ts +33 -33
- package/dist/solana/chain/modules/SolanaSlots.js +72 -72
- package/dist/solana/chain/modules/SolanaTokens.d.ts +123 -123
- package/dist/solana/chain/modules/SolanaTokens.js +242 -242
- package/dist/solana/chain/modules/SolanaTransactions.d.ts +149 -149
- package/dist/solana/chain/modules/SolanaTransactions.js +445 -445
- package/dist/solana/connection/ConnectionWithRetries.d.ts +35 -35
- package/dist/solana/connection/ConnectionWithRetries.js +86 -71
- package/dist/solana/events/SolanaChainEvents.d.ts +45 -45
- package/dist/solana/events/SolanaChainEvents.js +108 -108
- package/dist/solana/events/SolanaChainEventsBrowser.d.ts +205 -205
- package/dist/solana/events/SolanaChainEventsBrowser.js +404 -404
- package/dist/solana/program/SolanaProgramBase.d.ts +73 -73
- package/dist/solana/program/SolanaProgramBase.js +54 -54
- 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 +53 -53
- package/dist/solana/program/modules/SolanaProgramEvents.js +117 -117
- package/dist/solana/swaps/SolanaSwapData.d.ts +333 -333
- package/dist/solana/swaps/SolanaSwapData.js +535 -535
- package/dist/solana/swaps/SolanaSwapModule.d.ts +11 -11
- package/dist/solana/swaps/SolanaSwapModule.js +12 -12
- package/dist/solana/swaps/SolanaSwapProgram.d.ts +376 -376
- package/dist/solana/swaps/SolanaSwapProgram.js +769 -769
- package/dist/solana/swaps/SwapTypeEnum.d.ts +11 -11
- package/dist/solana/swaps/SwapTypeEnum.js +43 -43
- package/dist/solana/swaps/modules/SolanaDataAccount.d.ts +95 -95
- package/dist/solana/swaps/modules/SolanaDataAccount.js +232 -232
- package/dist/solana/swaps/modules/SolanaLpVault.d.ts +69 -69
- package/dist/solana/swaps/modules/SolanaLpVault.js +171 -171
- package/dist/solana/swaps/modules/SwapClaim.d.ts +126 -126
- package/dist/solana/swaps/modules/SwapClaim.js +294 -294
- package/dist/solana/swaps/modules/SwapInit.d.ts +213 -213
- package/dist/solana/swaps/modules/SwapInit.js +658 -658
- package/dist/solana/swaps/modules/SwapRefund.d.ts +87 -87
- package/dist/solana/swaps/modules/SwapRefund.js +293 -293
- 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/swaps/v1/programIdl.json +945 -945
- package/dist/solana/swaps/v1/programTypes.d.ts +943 -943
- package/dist/solana/swaps/v1/programTypes.js +945 -945
- package/dist/solana/swaps/v2/programIdl.json +952 -952
- package/dist/solana/swaps/v2/programTypes.d.ts +950 -950
- package/dist/solana/swaps/v2/programTypes.js +952 -952
- package/dist/solana/wallet/SolanaKeypairWallet.d.ts +29 -29
- package/dist/solana/wallet/SolanaKeypairWallet.js +50 -50
- package/dist/solana/wallet/SolanaSigner.d.ts +30 -30
- package/dist/solana/wallet/SolanaSigner.js +30 -30
- package/dist/utils/Utils.d.ts +58 -58
- package/dist/utils/Utils.js +170 -170
- package/node/index.d.ts +1 -1
- package/node/index.js +3 -3
- package/package.json +46 -46
- package/src/index.ts +87 -87
- package/src/node/index.ts +9 -9
- package/src/solana/SolanaChainType.ts +32 -32
- package/src/solana/SolanaChains.ts +46 -46
- package/src/solana/SolanaInitializer.ts +278 -278
- package/src/solana/btcrelay/SolanaBtcRelay.ts +615 -615
- package/src/solana/btcrelay/headers/SolanaBtcHeader.ts +116 -116
- package/src/solana/btcrelay/headers/SolanaBtcStoredHeader.ts +148 -148
- package/src/solana/btcrelay/program/programIdl.json +670 -670
- package/src/solana/chain/SolanaAction.ts +109 -109
- package/src/solana/chain/SolanaChainInterface.ts +404 -404
- package/src/solana/chain/SolanaModule.ts +20 -20
- package/src/solana/chain/modules/SolanaAddresses.ts +20 -20
- package/src/solana/chain/modules/SolanaBlocks.ts +89 -89
- package/src/solana/chain/modules/SolanaEvents.ts +271 -271
- package/src/solana/chain/modules/SolanaFees.ts +522 -522
- package/src/solana/chain/modules/SolanaSignatures.ts +39 -39
- package/src/solana/chain/modules/SolanaSlots.ts +85 -85
- package/src/solana/chain/modules/SolanaTokens.ts +300 -300
- package/src/solana/chain/modules/SolanaTransactions.ts +503 -503
- package/src/solana/connection/ConnectionWithRetries.ts +113 -96
- package/src/solana/events/SolanaChainEvents.ts +127 -127
- package/src/solana/events/SolanaChainEventsBrowser.ts +495 -495
- package/src/solana/program/SolanaProgramBase.ts +119 -119
- package/src/solana/program/SolanaProgramModule.ts +15 -15
- package/src/solana/program/modules/SolanaProgramEvents.ts +157 -157
- package/src/solana/swaps/SolanaSwapData.ts +735 -735
- package/src/solana/swaps/SolanaSwapModule.ts +19 -19
- package/src/solana/swaps/SolanaSwapProgram.ts +1074 -1074
- package/src/solana/swaps/SwapTypeEnum.ts +30 -30
- package/src/solana/swaps/modules/SolanaDataAccount.ts +302 -302
- package/src/solana/swaps/modules/SolanaLpVault.ts +208 -208
- package/src/solana/swaps/modules/SwapClaim.ts +387 -387
- package/src/solana/swaps/modules/SwapInit.ts +785 -785
- package/src/solana/swaps/modules/SwapRefund.ts +353 -353
- package/src/solana/swaps/v1/programIdl.json +944 -944
- package/src/solana/swaps/v1/programTypes.ts +1885 -1885
- package/src/solana/swaps/v2/programIdl.json +951 -951
- package/src/solana/swaps/v2/programTypes.ts +1899 -1899
- package/src/solana/wallet/SolanaKeypairWallet.ts +56 -56
- package/src/solana/wallet/SolanaSigner.ts +43 -43
- package/src/utils/Utils.ts +194 -194
|
@@ -1,1074 +1,1074 @@
|
|
|
1
|
-
import {SolanaSwapData, InitInstruction} from "./SolanaSwapData";
|
|
2
|
-
import {IdlAccounts, Program} from "@coral-xyz/anchor";
|
|
3
|
-
import {
|
|
4
|
-
ParsedTransactionWithMeta,
|
|
5
|
-
PublicKey,
|
|
6
|
-
} from "@solana/web3.js";
|
|
7
|
-
import {sha256} from "@noble/hashes/sha2";
|
|
8
|
-
import {SolanaBtcRelay} from "../btcrelay/SolanaBtcRelay";
|
|
9
|
-
import * as programIdlV1 from "./v1/programIdl.json";
|
|
10
|
-
import * as programIdlV2 from "./v2/programIdl.json";
|
|
11
|
-
import {
|
|
12
|
-
IStorageManager,
|
|
13
|
-
SwapContract,
|
|
14
|
-
ChainSwapType,
|
|
15
|
-
IntermediaryReputationType,
|
|
16
|
-
TransactionConfirmationOptions,
|
|
17
|
-
SignatureData,
|
|
18
|
-
RelaySynchronizer,
|
|
19
|
-
BigIntBufferUtils,
|
|
20
|
-
SwapCommitState,
|
|
21
|
-
SwapCommitStateType, SwapNotCommitedState, SwapExpiredState, SwapPaidState, BitcoinNetwork
|
|
22
|
-
} from "@atomiqlabs/base";
|
|
23
|
-
import {SolanaBtcStoredHeader} from "../btcrelay/headers/SolanaBtcStoredHeader";
|
|
24
|
-
import {
|
|
25
|
-
getAssociatedTokenAddressSync,
|
|
26
|
-
} from "@solana/spl-token";
|
|
27
|
-
import {SwapProgram} from "./v1/programTypes";
|
|
28
|
-
import {SolanaChainInterface} from "../chain/SolanaChainInterface";
|
|
29
|
-
import {SolanaProgramBase} from "../program/SolanaProgramBase";
|
|
30
|
-
import {SolanaTx} from "../chain/modules/SolanaTransactions";
|
|
31
|
-
import {SwapInit, SolanaPreFetchData, SolanaPreFetchVerification} from "./modules/SwapInit";
|
|
32
|
-
import {SolanaDataAccount, StoredDataAccount} from "./modules/SolanaDataAccount";
|
|
33
|
-
import {SwapRefund} from "./modules/SwapRefund";
|
|
34
|
-
import {SwapClaim} from "./modules/SwapClaim";
|
|
35
|
-
import {SolanaLpVault} from "./modules/SolanaLpVault";
|
|
36
|
-
import {Buffer} from "buffer";
|
|
37
|
-
import {SolanaSigner} from "../wallet/SolanaSigner";
|
|
38
|
-
import {fromClaimHash, onceAsync, toBN, toClaimHash, toEscrowHash} from "../../utils/Utils";
|
|
39
|
-
import {SolanaTokens} from "../chain/modules/SolanaTokens";
|
|
40
|
-
import * as BN from "bn.js";
|
|
41
|
-
import {ProgramEvent} from "../program/modules/SolanaProgramEvents";
|
|
42
|
-
import {SwapProgramV2} from "./v2/programTypes";
|
|
43
|
-
import {SolanaChains} from "../SolanaChains";
|
|
44
|
-
|
|
45
|
-
export function isSwapProgramV1(obj: any): obj is Program<SwapProgram> {
|
|
46
|
-
return obj.idl.version==="0.1.0";
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function isSwapProgramV2(obj: any): obj is Program<SwapProgramV2> {
|
|
50
|
-
return obj.idl.version==="0.2.0";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function toPublicKeyOrNull(str: string | null | undefined): PublicKey | undefined {
|
|
54
|
-
return str==null ? undefined : new PublicKey(str);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const MAX_PARALLEL_COMMIT_STATUS_CHECKS = 5;
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Solana swap (escrow manager) program representation handling PrTLC (on-chain) and HTLC (lightning) based swaps.
|
|
61
|
-
*
|
|
62
|
-
* @category Swaps
|
|
63
|
-
*/
|
|
64
|
-
export class SolanaSwapProgram<Version extends "v1" | "v2" = "v1" | "v2">
|
|
65
|
-
extends SolanaProgramBase<SwapProgram | SwapProgramV2>
|
|
66
|
-
implements SwapContract<
|
|
67
|
-
SolanaSwapData,
|
|
68
|
-
SolanaTx,
|
|
69
|
-
SolanaPreFetchData,
|
|
70
|
-
SolanaPreFetchVerification,
|
|
71
|
-
SolanaSigner,
|
|
72
|
-
"SOLANA"
|
|
73
|
-
> {
|
|
74
|
-
|
|
75
|
-
////////////////////////
|
|
76
|
-
//// Constants
|
|
77
|
-
/**
|
|
78
|
-
* Rent-exempt amount (lamports) for escrow state accounts.
|
|
79
|
-
*/
|
|
80
|
-
public readonly ESCROW_STATE_RENT_EXEMPT: number;
|
|
81
|
-
|
|
82
|
-
public readonly version: Version;
|
|
83
|
-
|
|
84
|
-
public readonly supportsInitWithoutClaimer: Version extends "v1" ? false : true;
|
|
85
|
-
|
|
86
|
-
////////////////////////
|
|
87
|
-
//// PDA accessors
|
|
88
|
-
/**
|
|
89
|
-
* PDA of the swap vault authority.
|
|
90
|
-
* @internal
|
|
91
|
-
*/
|
|
92
|
-
static readonly _SwapVaultAuthority = SolanaProgramBase._pda("authority"); // Only necessary for V1 program
|
|
93
|
-
readonly _SwapVaultAuthority = SolanaSwapProgram._SwapVaultAuthority(this.program.programId); // Only necessary for V1 program
|
|
94
|
-
/**
|
|
95
|
-
* PDA helper for global token vault accounts.
|
|
96
|
-
* @internal
|
|
97
|
-
*/
|
|
98
|
-
static readonly _SwapVault = SolanaProgramBase._pda("vault", (tokenAddress: PublicKey) => [tokenAddress.toBuffer()]);
|
|
99
|
-
readonly _SwapVault = SolanaSwapProgram._SwapVault.bind(this, this.program.programId);
|
|
100
|
-
/**
|
|
101
|
-
* PDA helper for per-user token vault accounts.
|
|
102
|
-
* @internal
|
|
103
|
-
*/
|
|
104
|
-
static readonly _SwapUserVault = SolanaProgramBase._pda("uservault",
|
|
105
|
-
(publicKey: PublicKey, tokenAddress: PublicKey) => [publicKey.toBuffer(), tokenAddress.toBuffer()]
|
|
106
|
-
);
|
|
107
|
-
readonly _SwapUserVault = SolanaSwapProgram._SwapUserVault.bind(this, this.program.programId);
|
|
108
|
-
/**
|
|
109
|
-
* PDA helper for escrow state accounts.
|
|
110
|
-
* @internal
|
|
111
|
-
*/
|
|
112
|
-
static readonly _SwapEscrowState = SolanaProgramBase._pda("state", (hash: Buffer) => [hash]);
|
|
113
|
-
readonly _SwapEscrowState = SolanaSwapProgram._SwapEscrowState.bind(this, this.program.programId);
|
|
114
|
-
|
|
115
|
-
////////////////////////
|
|
116
|
-
//// Timeouts
|
|
117
|
-
/**
|
|
118
|
-
* @inheritDoc
|
|
119
|
-
*/
|
|
120
|
-
readonly chainId: "SOLANA" = "SOLANA";
|
|
121
|
-
/**
|
|
122
|
-
* @inheritDoc
|
|
123
|
-
*/
|
|
124
|
-
readonly claimWithSecretTimeout: number = 45;
|
|
125
|
-
/**
|
|
126
|
-
* @inheritDoc
|
|
127
|
-
*/
|
|
128
|
-
readonly claimWithTxDataTimeout: number = 120;
|
|
129
|
-
/**
|
|
130
|
-
* @inheritDoc
|
|
131
|
-
*/
|
|
132
|
-
readonly refundTimeout: number = 45;
|
|
133
|
-
/**
|
|
134
|
-
* Grace period (seconds) applied to claimer-side expiry checks.
|
|
135
|
-
*/
|
|
136
|
-
private readonly claimGracePeriod: number = 10*60;
|
|
137
|
-
/**
|
|
138
|
-
* Grace period (seconds) applied to offerer-side expiry checks.
|
|
139
|
-
*/
|
|
140
|
-
private readonly refundGracePeriod: number = 10*60;
|
|
141
|
-
/**
|
|
142
|
-
* Authorization grace period in seconds.
|
|
143
|
-
* @internal
|
|
144
|
-
*/
|
|
145
|
-
readonly _authGracePeriod: number = 30;
|
|
146
|
-
|
|
147
|
-
////////////////////////
|
|
148
|
-
//// Services
|
|
149
|
-
/**
|
|
150
|
-
* Swap initialization service.
|
|
151
|
-
*/
|
|
152
|
-
private readonly Init: SwapInit;
|
|
153
|
-
/**
|
|
154
|
-
* Swap refund service.
|
|
155
|
-
*/
|
|
156
|
-
private readonly Refund: SwapRefund;
|
|
157
|
-
/**
|
|
158
|
-
* Swap claim service.
|
|
159
|
-
*/
|
|
160
|
-
private readonly Claim: SwapClaim;
|
|
161
|
-
/**
|
|
162
|
-
* LP vault interaction service.
|
|
163
|
-
*/
|
|
164
|
-
private readonly LpVault: SolanaLpVault;
|
|
165
|
-
/**
|
|
166
|
-
* Temporary data-account lifecycle service.
|
|
167
|
-
* @internal
|
|
168
|
-
*/
|
|
169
|
-
readonly _DataAccount: SolanaDataAccount;
|
|
170
|
-
|
|
171
|
-
constructor(
|
|
172
|
-
chainInterface: SolanaChainInterface,
|
|
173
|
-
btcRelay: SolanaBtcRelay<any>,
|
|
174
|
-
storage: IStorageManager<StoredDataAccount>,
|
|
175
|
-
programAddress?: string,
|
|
176
|
-
bitcoinNetwork?: BitcoinNetwork,
|
|
177
|
-
version?: Version
|
|
178
|
-
) {
|
|
179
|
-
version ??= "v1" as any;
|
|
180
|
-
if(bitcoinNetwork!=null && programAddress==null) {
|
|
181
|
-
programAddress = SolanaChains[bitcoinNetwork]?.addresses[version ?? "v1"]?.swapContract;
|
|
182
|
-
}
|
|
183
|
-
super(chainInterface, version==="v1" ? programIdlV1 : programIdlV2, programAddress);
|
|
184
|
-
|
|
185
|
-
this.version = version!;
|
|
186
|
-
this.ESCROW_STATE_RENT_EXEMPT = this.version==="v1" ? 2658720 : 2665680;
|
|
187
|
-
this.supportsInitWithoutClaimer = (this.version!=="v1") as any;
|
|
188
|
-
|
|
189
|
-
this.Init = new SwapInit(chainInterface, this);
|
|
190
|
-
this.Refund = new SwapRefund(chainInterface, this);
|
|
191
|
-
this.Claim = new SwapClaim(chainInterface, this, btcRelay);
|
|
192
|
-
this._DataAccount = new SolanaDataAccount(chainInterface, this, storage);
|
|
193
|
-
this.LpVault = new SolanaLpVault(chainInterface, this);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* @inheritDoc
|
|
198
|
-
*/
|
|
199
|
-
async start(): Promise<void> {
|
|
200
|
-
await this._DataAccount.init();
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* @inheritDoc
|
|
205
|
-
*/
|
|
206
|
-
getClaimableDeposits(signer: string): Promise<{count: number, totalValue: bigint}> {
|
|
207
|
-
return this._DataAccount.getDataAccountsInfo(new PublicKey(signer));
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* @inheritDoc
|
|
212
|
-
*/
|
|
213
|
-
claimDeposits(signer: SolanaSigner): Promise<{txIds: string[], count: number, totalValue: bigint}> {
|
|
214
|
-
return this._DataAccount.sweepDataAccounts(signer);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
////////////////////////////////////////////
|
|
218
|
-
//// Signatures
|
|
219
|
-
/**
|
|
220
|
-
* @inheritDoc
|
|
221
|
-
*/
|
|
222
|
-
preFetchForInitSignatureVerification(data: SolanaPreFetchData): Promise<SolanaPreFetchVerification> {
|
|
223
|
-
return this.Init.preFetchForInitSignatureVerification(data);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* @inheritDoc
|
|
228
|
-
*/
|
|
229
|
-
preFetchBlockDataForSignatures(): Promise<SolanaPreFetchData> {
|
|
230
|
-
return this.Init.preFetchBlockDataForSignatures();
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* @inheritDoc
|
|
235
|
-
*/
|
|
236
|
-
getInitSignature(signer: SolanaSigner, swapData: SolanaSwapData, authorizationTimeout: number, preFetchedBlockData?: SolanaPreFetchData, feeRate?: string): Promise<SignatureData> {
|
|
237
|
-
return this.Init.signSwapInitialization(signer, swapData, authorizationTimeout, preFetchedBlockData, feeRate);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* @inheritDoc
|
|
242
|
-
*/
|
|
243
|
-
isValidInitAuthorization(signer: string, swapData: SolanaSwapData, sig: SignatureData, feeRate?: string, preFetchedData?: SolanaPreFetchVerification): Promise<Buffer> {
|
|
244
|
-
return this.Init.isSignatureValid(new PublicKey(signer), swapData, sig.timeout, sig.prefix, sig.signature, feeRate, preFetchedData);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* @inheritDoc
|
|
249
|
-
*/
|
|
250
|
-
getInitAuthorizationExpiry(swapData: SolanaSwapData, sig: SignatureData, preFetchedData?: SolanaPreFetchVerification): Promise<number> {
|
|
251
|
-
return this.Init.getSignatureExpiry(sig.timeout, sig.signature, preFetchedData);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* @inheritDoc
|
|
256
|
-
*/
|
|
257
|
-
isInitAuthorizationExpired(swapData: SolanaSwapData, sig: SignatureData): Promise<boolean> {
|
|
258
|
-
return this.Init.isSignatureExpired(sig.signature, sig.timeout);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* @inheritDoc
|
|
263
|
-
*/
|
|
264
|
-
getRefundSignature(signer: SolanaSigner, swapData: SolanaSwapData, authorizationTimeout: number): Promise<SignatureData> {
|
|
265
|
-
return this.Refund.signSwapRefund(signer, swapData, authorizationTimeout);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* @inheritDoc
|
|
270
|
-
*/
|
|
271
|
-
isValidRefundAuthorization(swapData: SolanaSwapData, sig: SignatureData): Promise<Buffer> {
|
|
272
|
-
return this.Refund.isSignatureValid(swapData, sig.timeout, sig.prefix, sig.signature);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* @inheritDoc
|
|
277
|
-
*/
|
|
278
|
-
getDataSignature(signer: SolanaSigner, data: Buffer): Promise<string> {
|
|
279
|
-
return this._Chain.Signatures.getDataSignature(signer, data);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* @inheritDoc
|
|
284
|
-
*/
|
|
285
|
-
isValidDataSignature(data: Buffer, signature: string, publicKey: string): Promise<boolean> {
|
|
286
|
-
return this._Chain.Signatures.isValidDataSignature(data, signature, publicKey);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
////////////////////////////////////////////
|
|
290
|
-
//// Swap data utils
|
|
291
|
-
/**
|
|
292
|
-
* @inheritDoc
|
|
293
|
-
*/
|
|
294
|
-
async isClaimable(signer: string, data: SolanaSwapData): Promise<boolean> {
|
|
295
|
-
if(!data.isClaimer(signer)) return false;
|
|
296
|
-
if(await this.isExpired(signer, data)) return false;
|
|
297
|
-
return await this.isCommited(data);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* @inheritDoc
|
|
302
|
-
*/
|
|
303
|
-
async isCommited(swapData: SolanaSwapData): Promise<boolean> {
|
|
304
|
-
const paymentHash = Buffer.from(swapData.paymentHash, "hex");
|
|
305
|
-
|
|
306
|
-
const account = await this.program.account.escrowState.fetchNullable(this._SwapEscrowState(paymentHash));
|
|
307
|
-
if(account==null) return false;
|
|
308
|
-
|
|
309
|
-
return swapData.correctPDA(account);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* @inheritDoc
|
|
314
|
-
*/
|
|
315
|
-
isExpired(signer: string, data: SolanaSwapData, refundSide?: boolean): Promise<boolean> {
|
|
316
|
-
let currentTimestamp: BN = new BN(Math.floor(Date.now()/1000));
|
|
317
|
-
if(data.isClaimer(signer) && !refundSide) {
|
|
318
|
-
currentTimestamp = currentTimestamp.add(new BN(this.claimGracePeriod));
|
|
319
|
-
} else {
|
|
320
|
-
currentTimestamp = currentTimestamp.sub(new BN(this.refundGracePeriod));
|
|
321
|
-
}
|
|
322
|
-
return Promise.resolve(data.expiry.lt(currentTimestamp));
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* @inheritDoc
|
|
327
|
-
*/
|
|
328
|
-
async isRequestRefundable(signer: string, data: SolanaSwapData): Promise<boolean> {
|
|
329
|
-
//V1 Swap can only be refunded by the offerer
|
|
330
|
-
if(isSwapProgramV1(this.program) && !data.isOfferer(signer)) return false;
|
|
331
|
-
if(!(await this.isExpired(signer, data, true))) return false;
|
|
332
|
-
return await this.isCommited(data);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* @inheritDoc
|
|
337
|
-
*/
|
|
338
|
-
getHashForOnchain(outputScript: Buffer, amount: bigint, confirmations: number, nonce?: bigint): Buffer {
|
|
339
|
-
nonce ??= 0n;
|
|
340
|
-
const paymentHash = Buffer.from(sha256(Buffer.concat([
|
|
341
|
-
BigIntBufferUtils.toBuffer(nonce, "le", 8),
|
|
342
|
-
BigIntBufferUtils.toBuffer(amount, "le", 8),
|
|
343
|
-
outputScript
|
|
344
|
-
]))).toString("hex");
|
|
345
|
-
return Buffer.from(toClaimHash(paymentHash, nonce, confirmations), "hex");
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* @inheritDoc
|
|
350
|
-
*/
|
|
351
|
-
getHashForHtlc(swapHash: Buffer): Buffer {
|
|
352
|
-
return Buffer.from(toClaimHash(
|
|
353
|
-
swapHash.toString("hex"),
|
|
354
|
-
0n,
|
|
355
|
-
0
|
|
356
|
-
), "hex");
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* @inheritDoc
|
|
361
|
-
*/
|
|
362
|
-
getHashForTxId(txId: string, confirmations: number): Buffer {
|
|
363
|
-
return Buffer.from(toClaimHash(
|
|
364
|
-
Buffer.from(txId, "hex").reverse().toString("hex"),
|
|
365
|
-
0n,
|
|
366
|
-
confirmations
|
|
367
|
-
), "hex");
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
////////////////////////////////////////////
|
|
371
|
-
//// Swap data getters
|
|
372
|
-
/**
|
|
373
|
-
* @inheritDoc
|
|
374
|
-
*/
|
|
375
|
-
async getCommitStatus(signer: string, data: SolanaSwapData): Promise<SwapCommitState> {
|
|
376
|
-
const escrowStateKey = this._SwapEscrowState(Buffer.from(data.paymentHash, "hex"));
|
|
377
|
-
const [escrowState, isExpired] = await Promise.all([
|
|
378
|
-
this.program.account.escrowState.fetchNullable(escrowStateKey) as Promise<IdlAccounts<SwapProgram | SwapProgramV2>["escrowState"]>,
|
|
379
|
-
this.isExpired(signer,data)
|
|
380
|
-
]);
|
|
381
|
-
|
|
382
|
-
const getInitTxId = onceAsync(async () => {
|
|
383
|
-
const txId = await this._Events.findInEvents(escrowStateKey, async (event, tx) => {
|
|
384
|
-
if (event.name === "InitializeEvent") {
|
|
385
|
-
const paymentHash = Buffer.from(event.data.hash).toString("hex");
|
|
386
|
-
if(paymentHash!==data.paymentHash) return null;
|
|
387
|
-
if(!event.data.sequence.eq(data.sequence)) return null;
|
|
388
|
-
return tx.transaction.signatures[0];
|
|
389
|
-
}
|
|
390
|
-
});
|
|
391
|
-
if(txId==null) throw new Error("Initialize event not found!");
|
|
392
|
-
return txId;
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
if(escrowState!=null) {
|
|
396
|
-
if(data.correctPDA(escrowState)) {
|
|
397
|
-
if(data.isOfferer(signer) && isExpired) return {type: SwapCommitStateType.REFUNDABLE, getInitTxId};
|
|
398
|
-
return {type: SwapCommitStateType.COMMITED, getInitTxId};
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if(data.isOfferer(signer) && isExpired) return {type: SwapCommitStateType.EXPIRED};
|
|
402
|
-
return {type: SwapCommitStateType.NOT_COMMITED};
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
//Check if paid or what
|
|
406
|
-
const status: SwapNotCommitedState | SwapExpiredState | SwapPaidState | null = await this._Events.findInEvents(escrowStateKey, async (event, tx) => {
|
|
407
|
-
if(event.name==="ClaimEvent") {
|
|
408
|
-
const paymentHash = Buffer.from(event.data.hash).toString("hex");
|
|
409
|
-
if(paymentHash!==data.paymentHash) return null;
|
|
410
|
-
if(!event.data.sequence.eq(data.sequence)) return null;
|
|
411
|
-
return {
|
|
412
|
-
type: SwapCommitStateType.PAID,
|
|
413
|
-
getInitTxId,
|
|
414
|
-
getClaimTxId: () => Promise.resolve(tx.transaction.signatures[0]),
|
|
415
|
-
getClaimResult: () => Promise.resolve(Buffer.from(event.data.secret).toString("hex")),
|
|
416
|
-
getTxBlock: () => Promise.resolve({
|
|
417
|
-
blockHeight: tx.slot,
|
|
418
|
-
blockTime: tx.blockTime!
|
|
419
|
-
})
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
if(event.name==="RefundEvent") {
|
|
423
|
-
const paymentHash = Buffer.from(event.data.hash).toString("hex");
|
|
424
|
-
if(paymentHash!==data.paymentHash) return null;
|
|
425
|
-
if(!event.data.sequence.eq(data.sequence)) return null;
|
|
426
|
-
return {
|
|
427
|
-
type: isExpired ? SwapCommitStateType.EXPIRED : SwapCommitStateType.NOT_COMMITED,
|
|
428
|
-
getInitTxId,
|
|
429
|
-
getRefundTxId: () => Promise.resolve(tx.transaction.signatures[0]),
|
|
430
|
-
getTxBlock: () => Promise.resolve({
|
|
431
|
-
blockHeight: tx.slot,
|
|
432
|
-
blockTime: tx.blockTime!
|
|
433
|
-
})
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
});
|
|
437
|
-
if(status!=null) return status;
|
|
438
|
-
|
|
439
|
-
if(isExpired) return {type: SwapCommitStateType.EXPIRED};
|
|
440
|
-
return {type: SwapCommitStateType.NOT_COMMITED};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* @inheritDoc
|
|
445
|
-
*/
|
|
446
|
-
async getCommitStatuses(request: { signer: string; swapData: SolanaSwapData }[]): Promise<{
|
|
447
|
-
[p: string]: SwapCommitState
|
|
448
|
-
}> {
|
|
449
|
-
const result: {
|
|
450
|
-
[p: string]: SwapCommitState
|
|
451
|
-
} = {};
|
|
452
|
-
let promises: Promise<void>[] = [];
|
|
453
|
-
for(let {signer, swapData} of request) {
|
|
454
|
-
promises.push(this.getCommitStatus(signer, swapData).then(val => {
|
|
455
|
-
result[swapData.getEscrowHash()] = val;
|
|
456
|
-
}));
|
|
457
|
-
if(promises.length>=MAX_PARALLEL_COMMIT_STATUS_CHECKS) {
|
|
458
|
-
await Promise.all(promises);
|
|
459
|
-
promises = [];
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
await Promise.all(promises);
|
|
463
|
-
return result;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* @inheritDoc
|
|
468
|
-
*/
|
|
469
|
-
async getClaimHashStatus(claimHash: string): Promise<SwapCommitStateType> {
|
|
470
|
-
const {paymentHash} = fromClaimHash(claimHash);
|
|
471
|
-
const escrowStateKey = this._SwapEscrowState(Buffer.from(paymentHash, "hex"));
|
|
472
|
-
const abortController = new AbortController();
|
|
473
|
-
|
|
474
|
-
//Start fetching events before checking escrow PDA, this call is used when quoting, so saving 100ms here helps a lot!
|
|
475
|
-
const eventsPromise = this._Events.findInEvents(escrowStateKey, async (event) => {
|
|
476
|
-
if(event.name==="ClaimEvent") return SwapCommitStateType.PAID;
|
|
477
|
-
if(event.name==="RefundEvent") return SwapCommitStateType.NOT_COMMITED;
|
|
478
|
-
}, abortController.signal).catch(e => {
|
|
479
|
-
abortController.abort(e)
|
|
480
|
-
return null;
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
const escrowState = await this.program.account.escrowState.fetchNullable(escrowStateKey);
|
|
484
|
-
abortController.signal.throwIfAborted();
|
|
485
|
-
if(escrowState!=null) {
|
|
486
|
-
abortController.abort();
|
|
487
|
-
return SwapCommitStateType.COMMITED;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
//Check if paid or what
|
|
491
|
-
const eventsStatus = await eventsPromise;
|
|
492
|
-
abortController.signal.throwIfAborted();
|
|
493
|
-
if(eventsStatus!=null) return eventsStatus;
|
|
494
|
-
|
|
495
|
-
return SwapCommitStateType.NOT_COMMITED;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* @inheritDoc
|
|
500
|
-
*/
|
|
501
|
-
async getCommitedData(claimHashHex: string): Promise<SolanaSwapData | null> {
|
|
502
|
-
const {paymentHash} = fromClaimHash(claimHashHex);
|
|
503
|
-
const paymentHashBuffer = Buffer.from(paymentHash, "hex");
|
|
504
|
-
|
|
505
|
-
const account: IdlAccounts<SwapProgram | SwapProgramV2>["escrowState"] | null =
|
|
506
|
-
await this.program.account.escrowState.fetchNullable(
|
|
507
|
-
this._SwapEscrowState(paymentHashBuffer)
|
|
508
|
-
);
|
|
509
|
-
if(account==null) return null;
|
|
510
|
-
|
|
511
|
-
return SolanaSwapData.fromEscrowState(this.program.programId, this.version, account);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
* @inheritDoc
|
|
516
|
-
*/
|
|
517
|
-
async getHistoricalSwaps(signer: string, startBlockheight?: number): Promise<{
|
|
518
|
-
swaps: {
|
|
519
|
-
[escrowHash: string]: {
|
|
520
|
-
init?: {
|
|
521
|
-
data: SolanaSwapData,
|
|
522
|
-
getInitTxId: () => Promise<string>,
|
|
523
|
-
getTxBlock: () => Promise<{
|
|
524
|
-
blockTime: number,
|
|
525
|
-
blockHeight: number
|
|
526
|
-
}>
|
|
527
|
-
},
|
|
528
|
-
state: SwapCommitState
|
|
529
|
-
}
|
|
530
|
-
},
|
|
531
|
-
latestBlockheight?: number
|
|
532
|
-
}> {
|
|
533
|
-
let latestBlockheight: number | undefined;
|
|
534
|
-
|
|
535
|
-
const events: {event: ProgramEvent<SwapProgram>, tx: ParsedTransactionWithMeta}[] = [];
|
|
536
|
-
|
|
537
|
-
await this._Events.findInEvents(new PublicKey(signer), async (event, tx) => {
|
|
538
|
-
if(latestBlockheight==null) latestBlockheight = tx.slot;
|
|
539
|
-
events.push({event, tx});
|
|
540
|
-
}, undefined, undefined, startBlockheight);
|
|
541
|
-
|
|
542
|
-
this.logger.debug(`getHistoricalSwaps(): Found ${events.length} atomiq related events!`);
|
|
543
|
-
|
|
544
|
-
const swapsOpened: {[escrowHash: string]: {
|
|
545
|
-
data: SolanaSwapData,
|
|
546
|
-
getInitTxId: () => Promise<string>,
|
|
547
|
-
getTxBlock: () => Promise<{
|
|
548
|
-
blockTime: number,
|
|
549
|
-
blockHeight: number
|
|
550
|
-
}>
|
|
551
|
-
}} = {};
|
|
552
|
-
const resultingSwaps: {
|
|
553
|
-
[escrowHash: string]: {
|
|
554
|
-
init?: {
|
|
555
|
-
data: SolanaSwapData,
|
|
556
|
-
getInitTxId: () => Promise<string>,
|
|
557
|
-
getTxBlock: () => Promise<{
|
|
558
|
-
blockTime: number,
|
|
559
|
-
blockHeight: number
|
|
560
|
-
}>
|
|
561
|
-
},
|
|
562
|
-
state: SwapCommitState
|
|
563
|
-
}
|
|
564
|
-
} = {};
|
|
565
|
-
|
|
566
|
-
events.reverse();
|
|
567
|
-
for(let {event, tx} of events) {
|
|
568
|
-
const txSignature = tx.transaction.signatures[0];
|
|
569
|
-
const paymentHash: string = Buffer.from(event.data.hash).toString("hex");
|
|
570
|
-
const escrowHash = toEscrowHash(paymentHash, event.data.sequence);
|
|
571
|
-
|
|
572
|
-
if(event.name==="InitializeEvent") {
|
|
573
|
-
//Parse swap data from initialize event
|
|
574
|
-
const txoHash: string = Buffer.from(event.data.txoHash).toString("hex");
|
|
575
|
-
const instructions = this._Events.decodeInstructions(tx.transaction.message);
|
|
576
|
-
if(instructions == null) {
|
|
577
|
-
this.logger.warn(`getHistoricalSwaps(): Skipping tx ${txSignature} because cannot parse instructions!`);
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const initIx = instructions.find(
|
|
582
|
-
ix => ix!=null && (ix.name === "offererInitializePayIn" || ix.name === "offererInitialize")
|
|
583
|
-
) as InitInstruction;
|
|
584
|
-
if(initIx == null) {
|
|
585
|
-
this.logger.warn(`getHistoricalSwaps(): Skipping tx ${txSignature} because init instruction not found!`);
|
|
586
|
-
continue;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
swapsOpened[escrowHash] = {
|
|
590
|
-
data: SolanaSwapData.fromInstruction(this.program.programId, this.version, initIx, txoHash),
|
|
591
|
-
getInitTxId: () => Promise.resolve(txSignature),
|
|
592
|
-
getTxBlock: () => Promise.resolve({
|
|
593
|
-
blockHeight: tx.slot,
|
|
594
|
-
blockTime: tx.blockTime!
|
|
595
|
-
})
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
if(event.name==="ClaimEvent") {
|
|
600
|
-
const foundSwapData = swapsOpened[escrowHash];
|
|
601
|
-
delete swapsOpened[escrowHash];
|
|
602
|
-
resultingSwaps[escrowHash] = {
|
|
603
|
-
init: foundSwapData,
|
|
604
|
-
state: {
|
|
605
|
-
type: SwapCommitStateType.PAID,
|
|
606
|
-
getInitTxId: foundSwapData?.getInitTxId,
|
|
607
|
-
getClaimTxId: () => Promise.resolve(txSignature),
|
|
608
|
-
getClaimResult: () => Promise.resolve(Buffer.from(event.data.secret).toString("hex")),
|
|
609
|
-
getTxBlock: () => Promise.resolve({
|
|
610
|
-
blockHeight: tx.slot,
|
|
611
|
-
blockTime: tx.blockTime!
|
|
612
|
-
})
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
if(event.name==="RefundEvent") {
|
|
618
|
-
const foundSwapData = swapsOpened[escrowHash];
|
|
619
|
-
delete swapsOpened[escrowHash];
|
|
620
|
-
const isExpired = foundSwapData!=null && await this.isExpired(signer, foundSwapData.data);
|
|
621
|
-
resultingSwaps[escrowHash] = {
|
|
622
|
-
init: foundSwapData,
|
|
623
|
-
state: {
|
|
624
|
-
type: isExpired ? SwapCommitStateType.EXPIRED : SwapCommitStateType.NOT_COMMITED,
|
|
625
|
-
getInitTxId: foundSwapData?.getInitTxId,
|
|
626
|
-
getRefundTxId: () => Promise.resolve(txSignature),
|
|
627
|
-
getTxBlock: () => Promise.resolve({
|
|
628
|
-
blockHeight: tx.slot,
|
|
629
|
-
blockTime: tx.blockTime!
|
|
630
|
-
})
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
this.logger.debug(`getHistoricalSwaps(): Found ${Object.keys(resultingSwaps).length} settled swaps!`);
|
|
637
|
-
this.logger.debug(`getHistoricalSwaps(): Found ${Object.keys(swapsOpened).length} unsettled swaps!`);
|
|
638
|
-
|
|
639
|
-
for(let escrowHash in swapsOpened) {
|
|
640
|
-
const foundSwapData = swapsOpened[escrowHash];
|
|
641
|
-
const isExpired = await this.isExpired(signer, foundSwapData.data);
|
|
642
|
-
resultingSwaps[escrowHash] = {
|
|
643
|
-
init: foundSwapData,
|
|
644
|
-
state: foundSwapData.data.isOfferer(signer) && isExpired
|
|
645
|
-
? {type: SwapCommitStateType.REFUNDABLE, getInitTxId: foundSwapData.getInitTxId}
|
|
646
|
-
: {type: SwapCommitStateType.COMMITED, getInitTxId: foundSwapData.getInitTxId}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
return {
|
|
651
|
-
swaps: resultingSwaps,
|
|
652
|
-
latestBlockheight: latestBlockheight ?? startBlockheight
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
////////////////////////////////////////////
|
|
657
|
-
//// Swap data initializer
|
|
658
|
-
/**
|
|
659
|
-
* @inheritDoc
|
|
660
|
-
*/
|
|
661
|
-
createSwapData(
|
|
662
|
-
type: ChainSwapType,
|
|
663
|
-
offerer: string,
|
|
664
|
-
claimer: string,
|
|
665
|
-
token: string,
|
|
666
|
-
amount: bigint,
|
|
667
|
-
claimHash: string,
|
|
668
|
-
sequence: bigint,
|
|
669
|
-
expiry: bigint,
|
|
670
|
-
payIn: boolean,
|
|
671
|
-
payOut: boolean,
|
|
672
|
-
securityDeposit: bigint,
|
|
673
|
-
claimerBounty: bigint,
|
|
674
|
-
depositToken?: string
|
|
675
|
-
): Promise<SolanaSwapData> {
|
|
676
|
-
if(depositToken!=null) {
|
|
677
|
-
if(!new PublicKey(depositToken).equals(SolanaTokens.WSOL_ADDRESS)) throw new Error("Only SOL supported as deposit token!");
|
|
678
|
-
}
|
|
679
|
-
const tokenAddr: PublicKey = new PublicKey(token);
|
|
680
|
-
const offererKey = new PublicKey(offerer);
|
|
681
|
-
const claimerKey = new PublicKey(claimer);
|
|
682
|
-
const {paymentHash, nonce, confirmations} = fromClaimHash(claimHash);
|
|
683
|
-
const swapData = new SolanaSwapData({
|
|
684
|
-
programId: this.program.programId,
|
|
685
|
-
version: this.version,
|
|
686
|
-
offerer: offererKey,
|
|
687
|
-
claimer: claimerKey,
|
|
688
|
-
token: tokenAddr,
|
|
689
|
-
amount: toBN(amount),
|
|
690
|
-
paymentHash,
|
|
691
|
-
sequence: toBN(sequence),
|
|
692
|
-
expiry: toBN(expiry),
|
|
693
|
-
nonce,
|
|
694
|
-
confirmations,
|
|
695
|
-
payOut,
|
|
696
|
-
kind: SolanaSwapData.typeToKind(type),
|
|
697
|
-
payIn,
|
|
698
|
-
offererAta: payIn ? getAssociatedTokenAddressSync(tokenAddr, offererKey) : PublicKey.default,
|
|
699
|
-
claimerAta: payOut ? getAssociatedTokenAddressSync(tokenAddr, claimerKey) : PublicKey.default,
|
|
700
|
-
securityDeposit: toBN(securityDeposit),
|
|
701
|
-
claimerBounty: toBN(claimerBounty)
|
|
702
|
-
});
|
|
703
|
-
return Promise.resolve(swapData);
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
////////////////////////////////////////////
|
|
707
|
-
//// Utils
|
|
708
|
-
/**
|
|
709
|
-
* @inheritDoc
|
|
710
|
-
*/
|
|
711
|
-
async getBalance(signer: string, tokenAddress: string, inContract: boolean): Promise<bigint> {
|
|
712
|
-
if(!inContract) {
|
|
713
|
-
return await this._Chain.getBalance(signer, tokenAddress);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
const token = new PublicKey(tokenAddress);
|
|
717
|
-
const publicKey = new PublicKey(signer);
|
|
718
|
-
|
|
719
|
-
return await this.getIntermediaryBalance(publicKey, token);
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
/**
|
|
723
|
-
* @inheritDoc
|
|
724
|
-
*/
|
|
725
|
-
getIntermediaryData(address: string, token: string): Promise<{
|
|
726
|
-
balance: bigint,
|
|
727
|
-
reputation: IntermediaryReputationType
|
|
728
|
-
} | null> {
|
|
729
|
-
return this.LpVault.getIntermediaryData(new PublicKey(address), new PublicKey(token));
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/**
|
|
733
|
-
* @inheritDoc
|
|
734
|
-
*/
|
|
735
|
-
getIntermediaryReputation(address: string, token: string): Promise<IntermediaryReputationType | null> {
|
|
736
|
-
return this.LpVault.getIntermediaryReputation(new PublicKey(address), new PublicKey(token));
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
* Returns intermediary vault balance for a specific token.
|
|
741
|
-
*
|
|
742
|
-
* @param address Intermediary address
|
|
743
|
-
* @param token Token mint
|
|
744
|
-
*/
|
|
745
|
-
getIntermediaryBalance(address: PublicKey, token: PublicKey): Promise<bigint> {
|
|
746
|
-
return this.LpVault.getIntermediaryBalance(address, token);
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
////////////////////////////////////////////
|
|
750
|
-
//// Transaction initializers
|
|
751
|
-
/**
|
|
752
|
-
* @inheritDoc
|
|
753
|
-
*/
|
|
754
|
-
async txsClaimWithSecret(
|
|
755
|
-
signer: string | SolanaSigner,
|
|
756
|
-
swapData: SolanaSwapData,
|
|
757
|
-
secret: string,
|
|
758
|
-
checkExpiry?: boolean,
|
|
759
|
-
initAta?: boolean,
|
|
760
|
-
feeRate?: string,
|
|
761
|
-
skipAtaCheck?: boolean
|
|
762
|
-
): Promise<SolanaTx[]> {
|
|
763
|
-
return this.Claim.txsClaimWithSecret(typeof(signer)==="string" ? new PublicKey(signer) : signer.getPublicKey(), swapData, secret, checkExpiry, initAta, feeRate, skipAtaCheck)
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
/**
|
|
767
|
-
* @inheritDoc
|
|
768
|
-
*/
|
|
769
|
-
async txsClaimWithTxData(
|
|
770
|
-
signer: string | SolanaSigner,
|
|
771
|
-
swapData: SolanaSwapData,
|
|
772
|
-
tx: { blockhash: string, confirmations: number, txid: string, hex: string, height: number },
|
|
773
|
-
requiredConfirmations: number,
|
|
774
|
-
vout: number,
|
|
775
|
-
commitedHeader?: SolanaBtcStoredHeader,
|
|
776
|
-
synchronizer?: RelaySynchronizer<any, SolanaTx, any>,
|
|
777
|
-
initAta?: boolean,
|
|
778
|
-
feeRate?: string,
|
|
779
|
-
): Promise<SolanaTx[]> {
|
|
780
|
-
if(swapData.confirmations!==requiredConfirmations) throw new Error("Invalid requiredConfirmations provided!");
|
|
781
|
-
const {txs} = await this.Claim.txsClaimWithTxData(
|
|
782
|
-
typeof(signer)==="string" ? new PublicKey(signer) : signer,
|
|
783
|
-
swapData, tx, vout, commitedHeader, synchronizer, initAta, feeRate
|
|
784
|
-
);
|
|
785
|
-
return txs;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
/**
|
|
789
|
-
* @inheritDoc
|
|
790
|
-
*/
|
|
791
|
-
txsRefund(signer: string, swapData: SolanaSwapData, check?: boolean, initAta?: boolean, feeRate?: string): Promise<SolanaTx[]> {
|
|
792
|
-
return this.Refund.txsRefund(new PublicKey(signer), swapData, check, initAta, feeRate);
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
/**
|
|
796
|
-
* @inheritDoc
|
|
797
|
-
*/
|
|
798
|
-
txsRefundWithAuthorization(signer: string, swapData: SolanaSwapData, sig: SignatureData, check?: boolean, initAta?: boolean, feeRate?: string): Promise<SolanaTx[]> {
|
|
799
|
-
return this.Refund.txsRefundWithAuthorization(new PublicKey(signer), swapData, sig.timeout, sig.prefix, sig.signature,check,initAta, feeRate);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
/**
|
|
803
|
-
* @inheritDoc
|
|
804
|
-
*/
|
|
805
|
-
txsInit(sender: string, swapData: SolanaSwapData, sig: SignatureData, skipChecks?: boolean, feeRate?: string): Promise<SolanaTx[]> {
|
|
806
|
-
if(swapData.isPayIn()) {
|
|
807
|
-
return this.Init.txsInitPayIn(new PublicKey(sender), swapData, sig.timeout, sig.prefix, sig.signature, skipChecks, feeRate);
|
|
808
|
-
} else {
|
|
809
|
-
return this.Init.txsInit(new PublicKey(sender), swapData, sig.timeout, sig.prefix, sig.signature, skipChecks, feeRate);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
/**
|
|
814
|
-
* @inheritDoc
|
|
815
|
-
*/
|
|
816
|
-
txsWithdraw(signer: string, token: string, amount: bigint, feeRate?: string): Promise<SolanaTx[]> {
|
|
817
|
-
return this.LpVault.txsWithdraw(new PublicKey(signer), new PublicKey(token), amount, feeRate);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* @inheritDoc
|
|
822
|
-
*/
|
|
823
|
-
txsDeposit(signer: string, token: string, amount: bigint, feeRate?: string): Promise<SolanaTx[]> {
|
|
824
|
-
return this.LpVault.txsDeposit(new PublicKey(signer), new PublicKey(token), amount, feeRate);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
////////////////////////////////////////////
|
|
828
|
-
//// Executors
|
|
829
|
-
/**
|
|
830
|
-
* @inheritDoc
|
|
831
|
-
*/
|
|
832
|
-
async claimWithSecret(
|
|
833
|
-
signer: SolanaSigner,
|
|
834
|
-
swapData: SolanaSwapData,
|
|
835
|
-
secret: string,
|
|
836
|
-
checkExpiry?: boolean,
|
|
837
|
-
initAta?: boolean,
|
|
838
|
-
txOptions?: TransactionConfirmationOptions
|
|
839
|
-
): Promise<string> {
|
|
840
|
-
const result = await this.Claim.txsClaimWithSecret(signer.getPublicKey(), swapData, secret, checkExpiry, initAta, txOptions?.feeRate);
|
|
841
|
-
const [signature] = await this._Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
842
|
-
return signature;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
/**
|
|
846
|
-
* @inheritDoc
|
|
847
|
-
*/
|
|
848
|
-
async claimWithTxData(
|
|
849
|
-
signer: SolanaSigner,
|
|
850
|
-
swapData: SolanaSwapData,
|
|
851
|
-
tx: { blockhash: string, confirmations: number, txid: string, hex: string, height: number },
|
|
852
|
-
requiredConfirmations: number,
|
|
853
|
-
vout: number,
|
|
854
|
-
commitedHeader?: SolanaBtcStoredHeader,
|
|
855
|
-
synchronizer?: RelaySynchronizer<any, SolanaTx, any>,
|
|
856
|
-
initAta?: boolean,
|
|
857
|
-
txOptions?: TransactionConfirmationOptions
|
|
858
|
-
): Promise<string> {
|
|
859
|
-
if(requiredConfirmations!==swapData.confirmations) throw new Error("Invalid requiredConfirmations provided!");
|
|
860
|
-
|
|
861
|
-
const {txs, claimTxIndex, storageAcc} = await this.Claim.txsClaimWithTxData(
|
|
862
|
-
signer, swapData, tx, vout,
|
|
863
|
-
commitedHeader, synchronizer, initAta, txOptions?.feeRate
|
|
864
|
-
);
|
|
865
|
-
|
|
866
|
-
const signatures = await this._Chain.sendAndConfirm(signer, txs, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
867
|
-
await this._DataAccount.removeDataAccount(storageAcc);
|
|
868
|
-
|
|
869
|
-
return signatures[claimTxIndex] ?? signatures[0];
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
/**
|
|
873
|
-
* @inheritDoc
|
|
874
|
-
*/
|
|
875
|
-
async refund(
|
|
876
|
-
signer: SolanaSigner,
|
|
877
|
-
swapData: SolanaSwapData,
|
|
878
|
-
check?: boolean,
|
|
879
|
-
initAta?: boolean,
|
|
880
|
-
txOptions?: TransactionConfirmationOptions
|
|
881
|
-
): Promise<string> {
|
|
882
|
-
let result = await this.txsRefund(signer.getAddress(), swapData, check, initAta, txOptions?.feeRate);
|
|
883
|
-
|
|
884
|
-
const [signature] = await this._Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
885
|
-
|
|
886
|
-
return signature;
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
/**
|
|
890
|
-
* @inheritDoc
|
|
891
|
-
*/
|
|
892
|
-
async refundWithAuthorization(
|
|
893
|
-
signer: SolanaSigner,
|
|
894
|
-
swapData: SolanaSwapData,
|
|
895
|
-
signature: SignatureData,
|
|
896
|
-
check?: boolean,
|
|
897
|
-
initAta?: boolean,
|
|
898
|
-
txOptions?: TransactionConfirmationOptions
|
|
899
|
-
): Promise<string> {
|
|
900
|
-
let result = await this.txsRefundWithAuthorization(signer.getAddress(), swapData, signature, check, initAta, txOptions?.feeRate);
|
|
901
|
-
|
|
902
|
-
const [txSignature] = await this._Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
903
|
-
|
|
904
|
-
return txSignature;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* @inheritDoc
|
|
909
|
-
*/
|
|
910
|
-
async init(
|
|
911
|
-
signer: SolanaSigner,
|
|
912
|
-
swapData: SolanaSwapData,
|
|
913
|
-
signature: SignatureData,
|
|
914
|
-
skipChecks?: boolean,
|
|
915
|
-
txOptions?: TransactionConfirmationOptions
|
|
916
|
-
): Promise<string> {
|
|
917
|
-
const result = await this.txsInit(signer.getAddress(), swapData, signature, skipChecks, txOptions?.feeRate);
|
|
918
|
-
|
|
919
|
-
const signatures = await this._Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
920
|
-
|
|
921
|
-
return signatures[signatures.length-1];
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
/**
|
|
925
|
-
* @inheritDoc
|
|
926
|
-
*/
|
|
927
|
-
async initAndClaimWithSecret(
|
|
928
|
-
signer: SolanaSigner,
|
|
929
|
-
swapData: SolanaSwapData,
|
|
930
|
-
signature: SignatureData,
|
|
931
|
-
secret: string,
|
|
932
|
-
skipChecks?: boolean,
|
|
933
|
-
txOptions?: TransactionConfirmationOptions
|
|
934
|
-
): Promise<string[]> {
|
|
935
|
-
if(!signer.getPublicKey().equals(swapData.claimer)) throw new Error("Invalid signer provided!");
|
|
936
|
-
|
|
937
|
-
const txsCommit = await this.txsInit(signer.getAddress(), swapData, signature, skipChecks, txOptions?.feeRate);
|
|
938
|
-
let txsClaim: SolanaTx[];
|
|
939
|
-
if(isSwapProgramV1(this.program)) {
|
|
940
|
-
// In V1 the initialize instruction has to contain the ATA initialization, hence we can skip checking it in claim
|
|
941
|
-
txsClaim = await this.Claim.txsClaimWithSecret(signer.getPublicKey(), swapData, secret, true, false, txOptions?.feeRate, true);
|
|
942
|
-
} else {
|
|
943
|
-
// In V2 the initialize instruction doesn't necessarily contain the ATA initialization, hence the claim instruction needs to
|
|
944
|
-
// check and initialize the ATA if needed
|
|
945
|
-
txsClaim = await this.Claim.txsClaimWithSecret(signer.getPublicKey(), swapData, secret, true, true, txOptions?.feeRate, false);
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
const signatures = await this._Chain.sendAndConfirm(signer, txsCommit.concat(txsClaim), txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
949
|
-
return [signatures[txsCommit.length-1], signatures[signatures.length-1]]
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
/**
|
|
953
|
-
* @inheritDoc
|
|
954
|
-
*/
|
|
955
|
-
async withdraw(
|
|
956
|
-
signer: SolanaSigner,
|
|
957
|
-
token: string,
|
|
958
|
-
amount: bigint,
|
|
959
|
-
txOptions?: TransactionConfirmationOptions
|
|
960
|
-
): Promise<string> {
|
|
961
|
-
const txs = await this.LpVault.txsWithdraw(signer.getPublicKey(), new PublicKey(token), amount, txOptions?.feeRate);
|
|
962
|
-
const [txId] = await this._Chain.sendAndConfirm(signer, txs, txOptions?.waitForConfirmation, txOptions?.abortSignal, false);
|
|
963
|
-
return txId;
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
/**
|
|
967
|
-
* @inheritDoc
|
|
968
|
-
*/
|
|
969
|
-
async deposit(
|
|
970
|
-
signer: SolanaSigner,
|
|
971
|
-
token: string,
|
|
972
|
-
amount: bigint,
|
|
973
|
-
txOptions?: TransactionConfirmationOptions
|
|
974
|
-
): Promise<string> {
|
|
975
|
-
const txs = await this.LpVault.txsDeposit(signer.getPublicKey(), new PublicKey(token), amount, txOptions?.feeRate);
|
|
976
|
-
const [txId] = await this._Chain.sendAndConfirm(signer, txs, txOptions?.waitForConfirmation, txOptions?.abortSignal, false);
|
|
977
|
-
return txId;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
////////////////////////////////////////////
|
|
981
|
-
//// Fees
|
|
982
|
-
/**
|
|
983
|
-
* @inheritDoc
|
|
984
|
-
*/
|
|
985
|
-
getInitPayInFeeRate(offerer?: string, claimer?: string, token?: string, claimHash?: string): Promise<string> {
|
|
986
|
-
const paymentHash = claimHash==null ? undefined : fromClaimHash(claimHash).paymentHash;
|
|
987
|
-
return this.Init.getInitPayInFeeRate(
|
|
988
|
-
toPublicKeyOrNull(offerer),
|
|
989
|
-
toPublicKeyOrNull(claimer),
|
|
990
|
-
toPublicKeyOrNull(token),
|
|
991
|
-
paymentHash
|
|
992
|
-
);
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
/**
|
|
996
|
-
* @inheritDoc
|
|
997
|
-
*/
|
|
998
|
-
getInitFeeRate(offerer?: string, claimer?: string, token?: string, claimHash?: string): Promise<string> {
|
|
999
|
-
const paymentHash = claimHash==null ? undefined : fromClaimHash(claimHash).paymentHash;
|
|
1000
|
-
return this.Init.getInitFeeRate(
|
|
1001
|
-
toPublicKeyOrNull(offerer),
|
|
1002
|
-
toPublicKeyOrNull(claimer),
|
|
1003
|
-
toPublicKeyOrNull(token),
|
|
1004
|
-
paymentHash
|
|
1005
|
-
);
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
/**
|
|
1009
|
-
* @inheritDoc
|
|
1010
|
-
*/
|
|
1011
|
-
getRefundFeeRate(swapData: SolanaSwapData): Promise<string> {
|
|
1012
|
-
return this.Refund.getRefundFeeRate(swapData);
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
/**
|
|
1016
|
-
* @inheritDoc
|
|
1017
|
-
*/
|
|
1018
|
-
getClaimFeeRate(signer: string, swapData: SolanaSwapData): Promise<string> {
|
|
1019
|
-
return this.Claim.getClaimFeeRate(new PublicKey(signer), swapData);
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
|
-
/**
|
|
1023
|
-
* @inheritDoc
|
|
1024
|
-
*/
|
|
1025
|
-
getClaimFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1026
|
-
return this.Claim.getClaimFee(new PublicKey(signer), swapData, feeRate);
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
/**
|
|
1030
|
-
* @inheritDoc
|
|
1031
|
-
*/
|
|
1032
|
-
getRawClaimFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1033
|
-
return this.Claim.getRawClaimFee(new PublicKey(signer), swapData, feeRate);
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
/**
|
|
1037
|
-
* @inheritDoc
|
|
1038
|
-
*/
|
|
1039
|
-
getCommitFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1040
|
-
return this.Init.getInitFee(swapData, feeRate);
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
/**
|
|
1044
|
-
* @inheritDoc
|
|
1045
|
-
*/
|
|
1046
|
-
getRawCommitFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1047
|
-
return this.Init.getRawInitFee(swapData, feeRate);
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
/**
|
|
1051
|
-
* @inheritDoc
|
|
1052
|
-
*/
|
|
1053
|
-
getRefundFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1054
|
-
return this.Refund.getRefundFee(swapData, feeRate);
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
/**
|
|
1058
|
-
* @inheritDoc
|
|
1059
|
-
*/
|
|
1060
|
-
getRawRefundFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1061
|
-
return this.Refund.getRawRefundFee(swapData, feeRate);
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
/**
|
|
1065
|
-
* @inheritDoc
|
|
1066
|
-
*/
|
|
1067
|
-
getExtraData(outputScript: Buffer, amount: bigint, confirmations: number, nonce?: bigint): Buffer {
|
|
1068
|
-
return Buffer.from(sha256(Buffer.concat([
|
|
1069
|
-
BigIntBufferUtils.toBuffer(amount, "le", 8),
|
|
1070
|
-
outputScript
|
|
1071
|
-
])));
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
}
|
|
1
|
+
import {SolanaSwapData, InitInstruction} from "./SolanaSwapData";
|
|
2
|
+
import {IdlAccounts, Program} from "@coral-xyz/anchor";
|
|
3
|
+
import {
|
|
4
|
+
ParsedTransactionWithMeta,
|
|
5
|
+
PublicKey,
|
|
6
|
+
} from "@solana/web3.js";
|
|
7
|
+
import {sha256} from "@noble/hashes/sha2";
|
|
8
|
+
import {SolanaBtcRelay} from "../btcrelay/SolanaBtcRelay";
|
|
9
|
+
import * as programIdlV1 from "./v1/programIdl.json";
|
|
10
|
+
import * as programIdlV2 from "./v2/programIdl.json";
|
|
11
|
+
import {
|
|
12
|
+
IStorageManager,
|
|
13
|
+
SwapContract,
|
|
14
|
+
ChainSwapType,
|
|
15
|
+
IntermediaryReputationType,
|
|
16
|
+
TransactionConfirmationOptions,
|
|
17
|
+
SignatureData,
|
|
18
|
+
RelaySynchronizer,
|
|
19
|
+
BigIntBufferUtils,
|
|
20
|
+
SwapCommitState,
|
|
21
|
+
SwapCommitStateType, SwapNotCommitedState, SwapExpiredState, SwapPaidState, BitcoinNetwork
|
|
22
|
+
} from "@atomiqlabs/base";
|
|
23
|
+
import {SolanaBtcStoredHeader} from "../btcrelay/headers/SolanaBtcStoredHeader";
|
|
24
|
+
import {
|
|
25
|
+
getAssociatedTokenAddressSync,
|
|
26
|
+
} from "@solana/spl-token";
|
|
27
|
+
import {SwapProgram} from "./v1/programTypes";
|
|
28
|
+
import {SolanaChainInterface} from "../chain/SolanaChainInterface";
|
|
29
|
+
import {SolanaProgramBase} from "../program/SolanaProgramBase";
|
|
30
|
+
import {SolanaTx} from "../chain/modules/SolanaTransactions";
|
|
31
|
+
import {SwapInit, SolanaPreFetchData, SolanaPreFetchVerification} from "./modules/SwapInit";
|
|
32
|
+
import {SolanaDataAccount, StoredDataAccount} from "./modules/SolanaDataAccount";
|
|
33
|
+
import {SwapRefund} from "./modules/SwapRefund";
|
|
34
|
+
import {SwapClaim} from "./modules/SwapClaim";
|
|
35
|
+
import {SolanaLpVault} from "./modules/SolanaLpVault";
|
|
36
|
+
import {Buffer} from "buffer";
|
|
37
|
+
import {SolanaSigner} from "../wallet/SolanaSigner";
|
|
38
|
+
import {fromClaimHash, onceAsync, toBN, toClaimHash, toEscrowHash} from "../../utils/Utils";
|
|
39
|
+
import {SolanaTokens} from "../chain/modules/SolanaTokens";
|
|
40
|
+
import * as BN from "bn.js";
|
|
41
|
+
import {ProgramEvent} from "../program/modules/SolanaProgramEvents";
|
|
42
|
+
import {SwapProgramV2} from "./v2/programTypes";
|
|
43
|
+
import {SolanaChains} from "../SolanaChains";
|
|
44
|
+
|
|
45
|
+
export function isSwapProgramV1(obj: any): obj is Program<SwapProgram> {
|
|
46
|
+
return obj.idl.version==="0.1.0";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isSwapProgramV2(obj: any): obj is Program<SwapProgramV2> {
|
|
50
|
+
return obj.idl.version==="0.2.0";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toPublicKeyOrNull(str: string | null | undefined): PublicKey | undefined {
|
|
54
|
+
return str==null ? undefined : new PublicKey(str);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const MAX_PARALLEL_COMMIT_STATUS_CHECKS = 5;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Solana swap (escrow manager) program representation handling PrTLC (on-chain) and HTLC (lightning) based swaps.
|
|
61
|
+
*
|
|
62
|
+
* @category Swaps
|
|
63
|
+
*/
|
|
64
|
+
export class SolanaSwapProgram<Version extends "v1" | "v2" = "v1" | "v2">
|
|
65
|
+
extends SolanaProgramBase<SwapProgram | SwapProgramV2>
|
|
66
|
+
implements SwapContract<
|
|
67
|
+
SolanaSwapData,
|
|
68
|
+
SolanaTx,
|
|
69
|
+
SolanaPreFetchData,
|
|
70
|
+
SolanaPreFetchVerification,
|
|
71
|
+
SolanaSigner,
|
|
72
|
+
"SOLANA"
|
|
73
|
+
> {
|
|
74
|
+
|
|
75
|
+
////////////////////////
|
|
76
|
+
//// Constants
|
|
77
|
+
/**
|
|
78
|
+
* Rent-exempt amount (lamports) for escrow state accounts.
|
|
79
|
+
*/
|
|
80
|
+
public readonly ESCROW_STATE_RENT_EXEMPT: number;
|
|
81
|
+
|
|
82
|
+
public readonly version: Version;
|
|
83
|
+
|
|
84
|
+
public readonly supportsInitWithoutClaimer: Version extends "v1" ? false : true;
|
|
85
|
+
|
|
86
|
+
////////////////////////
|
|
87
|
+
//// PDA accessors
|
|
88
|
+
/**
|
|
89
|
+
* PDA of the swap vault authority.
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
static readonly _SwapVaultAuthority = SolanaProgramBase._pda("authority"); // Only necessary for V1 program
|
|
93
|
+
readonly _SwapVaultAuthority = SolanaSwapProgram._SwapVaultAuthority(this.program.programId); // Only necessary for V1 program
|
|
94
|
+
/**
|
|
95
|
+
* PDA helper for global token vault accounts.
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
static readonly _SwapVault = SolanaProgramBase._pda("vault", (tokenAddress: PublicKey) => [tokenAddress.toBuffer()]);
|
|
99
|
+
readonly _SwapVault = SolanaSwapProgram._SwapVault.bind(this, this.program.programId);
|
|
100
|
+
/**
|
|
101
|
+
* PDA helper for per-user token vault accounts.
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
static readonly _SwapUserVault = SolanaProgramBase._pda("uservault",
|
|
105
|
+
(publicKey: PublicKey, tokenAddress: PublicKey) => [publicKey.toBuffer(), tokenAddress.toBuffer()]
|
|
106
|
+
);
|
|
107
|
+
readonly _SwapUserVault = SolanaSwapProgram._SwapUserVault.bind(this, this.program.programId);
|
|
108
|
+
/**
|
|
109
|
+
* PDA helper for escrow state accounts.
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
static readonly _SwapEscrowState = SolanaProgramBase._pda("state", (hash: Buffer) => [hash]);
|
|
113
|
+
readonly _SwapEscrowState = SolanaSwapProgram._SwapEscrowState.bind(this, this.program.programId);
|
|
114
|
+
|
|
115
|
+
////////////////////////
|
|
116
|
+
//// Timeouts
|
|
117
|
+
/**
|
|
118
|
+
* @inheritDoc
|
|
119
|
+
*/
|
|
120
|
+
readonly chainId: "SOLANA" = "SOLANA";
|
|
121
|
+
/**
|
|
122
|
+
* @inheritDoc
|
|
123
|
+
*/
|
|
124
|
+
readonly claimWithSecretTimeout: number = 45;
|
|
125
|
+
/**
|
|
126
|
+
* @inheritDoc
|
|
127
|
+
*/
|
|
128
|
+
readonly claimWithTxDataTimeout: number = 120;
|
|
129
|
+
/**
|
|
130
|
+
* @inheritDoc
|
|
131
|
+
*/
|
|
132
|
+
readonly refundTimeout: number = 45;
|
|
133
|
+
/**
|
|
134
|
+
* Grace period (seconds) applied to claimer-side expiry checks.
|
|
135
|
+
*/
|
|
136
|
+
private readonly claimGracePeriod: number = 10*60;
|
|
137
|
+
/**
|
|
138
|
+
* Grace period (seconds) applied to offerer-side expiry checks.
|
|
139
|
+
*/
|
|
140
|
+
private readonly refundGracePeriod: number = 10*60;
|
|
141
|
+
/**
|
|
142
|
+
* Authorization grace period in seconds.
|
|
143
|
+
* @internal
|
|
144
|
+
*/
|
|
145
|
+
readonly _authGracePeriod: number = 30;
|
|
146
|
+
|
|
147
|
+
////////////////////////
|
|
148
|
+
//// Services
|
|
149
|
+
/**
|
|
150
|
+
* Swap initialization service.
|
|
151
|
+
*/
|
|
152
|
+
private readonly Init: SwapInit;
|
|
153
|
+
/**
|
|
154
|
+
* Swap refund service.
|
|
155
|
+
*/
|
|
156
|
+
private readonly Refund: SwapRefund;
|
|
157
|
+
/**
|
|
158
|
+
* Swap claim service.
|
|
159
|
+
*/
|
|
160
|
+
private readonly Claim: SwapClaim;
|
|
161
|
+
/**
|
|
162
|
+
* LP vault interaction service.
|
|
163
|
+
*/
|
|
164
|
+
private readonly LpVault: SolanaLpVault;
|
|
165
|
+
/**
|
|
166
|
+
* Temporary data-account lifecycle service.
|
|
167
|
+
* @internal
|
|
168
|
+
*/
|
|
169
|
+
readonly _DataAccount: SolanaDataAccount;
|
|
170
|
+
|
|
171
|
+
constructor(
|
|
172
|
+
chainInterface: SolanaChainInterface,
|
|
173
|
+
btcRelay: SolanaBtcRelay<any>,
|
|
174
|
+
storage: IStorageManager<StoredDataAccount>,
|
|
175
|
+
programAddress?: string,
|
|
176
|
+
bitcoinNetwork?: BitcoinNetwork,
|
|
177
|
+
version?: Version
|
|
178
|
+
) {
|
|
179
|
+
version ??= "v1" as any;
|
|
180
|
+
if(bitcoinNetwork!=null && programAddress==null) {
|
|
181
|
+
programAddress = SolanaChains[bitcoinNetwork]?.addresses[version ?? "v1"]?.swapContract;
|
|
182
|
+
}
|
|
183
|
+
super(chainInterface, version==="v1" ? programIdlV1 : programIdlV2, programAddress);
|
|
184
|
+
|
|
185
|
+
this.version = version!;
|
|
186
|
+
this.ESCROW_STATE_RENT_EXEMPT = this.version==="v1" ? 2658720 : 2665680;
|
|
187
|
+
this.supportsInitWithoutClaimer = (this.version!=="v1") as any;
|
|
188
|
+
|
|
189
|
+
this.Init = new SwapInit(chainInterface, this);
|
|
190
|
+
this.Refund = new SwapRefund(chainInterface, this);
|
|
191
|
+
this.Claim = new SwapClaim(chainInterface, this, btcRelay);
|
|
192
|
+
this._DataAccount = new SolanaDataAccount(chainInterface, this, storage);
|
|
193
|
+
this.LpVault = new SolanaLpVault(chainInterface, this);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @inheritDoc
|
|
198
|
+
*/
|
|
199
|
+
async start(): Promise<void> {
|
|
200
|
+
await this._DataAccount.init();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @inheritDoc
|
|
205
|
+
*/
|
|
206
|
+
getClaimableDeposits(signer: string): Promise<{count: number, totalValue: bigint}> {
|
|
207
|
+
return this._DataAccount.getDataAccountsInfo(new PublicKey(signer));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @inheritDoc
|
|
212
|
+
*/
|
|
213
|
+
claimDeposits(signer: SolanaSigner): Promise<{txIds: string[], count: number, totalValue: bigint}> {
|
|
214
|
+
return this._DataAccount.sweepDataAccounts(signer);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
////////////////////////////////////////////
|
|
218
|
+
//// Signatures
|
|
219
|
+
/**
|
|
220
|
+
* @inheritDoc
|
|
221
|
+
*/
|
|
222
|
+
preFetchForInitSignatureVerification(data: SolanaPreFetchData): Promise<SolanaPreFetchVerification> {
|
|
223
|
+
return this.Init.preFetchForInitSignatureVerification(data);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @inheritDoc
|
|
228
|
+
*/
|
|
229
|
+
preFetchBlockDataForSignatures(): Promise<SolanaPreFetchData> {
|
|
230
|
+
return this.Init.preFetchBlockDataForSignatures();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @inheritDoc
|
|
235
|
+
*/
|
|
236
|
+
getInitSignature(signer: SolanaSigner, swapData: SolanaSwapData, authorizationTimeout: number, preFetchedBlockData?: SolanaPreFetchData, feeRate?: string): Promise<SignatureData> {
|
|
237
|
+
return this.Init.signSwapInitialization(signer, swapData, authorizationTimeout, preFetchedBlockData, feeRate);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @inheritDoc
|
|
242
|
+
*/
|
|
243
|
+
isValidInitAuthorization(signer: string, swapData: SolanaSwapData, sig: SignatureData, feeRate?: string, preFetchedData?: SolanaPreFetchVerification): Promise<Buffer> {
|
|
244
|
+
return this.Init.isSignatureValid(new PublicKey(signer), swapData, sig.timeout, sig.prefix, sig.signature, feeRate, preFetchedData);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* @inheritDoc
|
|
249
|
+
*/
|
|
250
|
+
getInitAuthorizationExpiry(swapData: SolanaSwapData, sig: SignatureData, preFetchedData?: SolanaPreFetchVerification): Promise<number> {
|
|
251
|
+
return this.Init.getSignatureExpiry(sig.timeout, sig.signature, preFetchedData);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @inheritDoc
|
|
256
|
+
*/
|
|
257
|
+
isInitAuthorizationExpired(swapData: SolanaSwapData, sig: SignatureData): Promise<boolean> {
|
|
258
|
+
return this.Init.isSignatureExpired(sig.signature, sig.timeout);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @inheritDoc
|
|
263
|
+
*/
|
|
264
|
+
getRefundSignature(signer: SolanaSigner, swapData: SolanaSwapData, authorizationTimeout: number): Promise<SignatureData> {
|
|
265
|
+
return this.Refund.signSwapRefund(signer, swapData, authorizationTimeout);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* @inheritDoc
|
|
270
|
+
*/
|
|
271
|
+
isValidRefundAuthorization(swapData: SolanaSwapData, sig: SignatureData): Promise<Buffer> {
|
|
272
|
+
return this.Refund.isSignatureValid(swapData, sig.timeout, sig.prefix, sig.signature);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @inheritDoc
|
|
277
|
+
*/
|
|
278
|
+
getDataSignature(signer: SolanaSigner, data: Buffer): Promise<string> {
|
|
279
|
+
return this._Chain.Signatures.getDataSignature(signer, data);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* @inheritDoc
|
|
284
|
+
*/
|
|
285
|
+
isValidDataSignature(data: Buffer, signature: string, publicKey: string): Promise<boolean> {
|
|
286
|
+
return this._Chain.Signatures.isValidDataSignature(data, signature, publicKey);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
////////////////////////////////////////////
|
|
290
|
+
//// Swap data utils
|
|
291
|
+
/**
|
|
292
|
+
* @inheritDoc
|
|
293
|
+
*/
|
|
294
|
+
async isClaimable(signer: string, data: SolanaSwapData): Promise<boolean> {
|
|
295
|
+
if(!data.isClaimer(signer)) return false;
|
|
296
|
+
if(await this.isExpired(signer, data)) return false;
|
|
297
|
+
return await this.isCommited(data);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @inheritDoc
|
|
302
|
+
*/
|
|
303
|
+
async isCommited(swapData: SolanaSwapData): Promise<boolean> {
|
|
304
|
+
const paymentHash = Buffer.from(swapData.paymentHash, "hex");
|
|
305
|
+
|
|
306
|
+
const account = await this.program.account.escrowState.fetchNullable(this._SwapEscrowState(paymentHash));
|
|
307
|
+
if(account==null) return false;
|
|
308
|
+
|
|
309
|
+
return swapData.correctPDA(account);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* @inheritDoc
|
|
314
|
+
*/
|
|
315
|
+
isExpired(signer: string, data: SolanaSwapData, refundSide?: boolean): Promise<boolean> {
|
|
316
|
+
let currentTimestamp: BN = new BN(Math.floor(Date.now()/1000));
|
|
317
|
+
if(data.isClaimer(signer) && !refundSide) {
|
|
318
|
+
currentTimestamp = currentTimestamp.add(new BN(this.claimGracePeriod));
|
|
319
|
+
} else {
|
|
320
|
+
currentTimestamp = currentTimestamp.sub(new BN(this.refundGracePeriod));
|
|
321
|
+
}
|
|
322
|
+
return Promise.resolve(data.expiry.lt(currentTimestamp));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @inheritDoc
|
|
327
|
+
*/
|
|
328
|
+
async isRequestRefundable(signer: string, data: SolanaSwapData): Promise<boolean> {
|
|
329
|
+
//V1 Swap can only be refunded by the offerer
|
|
330
|
+
if(isSwapProgramV1(this.program) && !data.isOfferer(signer)) return false;
|
|
331
|
+
if(!(await this.isExpired(signer, data, true))) return false;
|
|
332
|
+
return await this.isCommited(data);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* @inheritDoc
|
|
337
|
+
*/
|
|
338
|
+
getHashForOnchain(outputScript: Buffer, amount: bigint, confirmations: number, nonce?: bigint): Buffer {
|
|
339
|
+
nonce ??= 0n;
|
|
340
|
+
const paymentHash = Buffer.from(sha256(Buffer.concat([
|
|
341
|
+
BigIntBufferUtils.toBuffer(nonce, "le", 8),
|
|
342
|
+
BigIntBufferUtils.toBuffer(amount, "le", 8),
|
|
343
|
+
outputScript
|
|
344
|
+
]))).toString("hex");
|
|
345
|
+
return Buffer.from(toClaimHash(paymentHash, nonce, confirmations), "hex");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* @inheritDoc
|
|
350
|
+
*/
|
|
351
|
+
getHashForHtlc(swapHash: Buffer): Buffer {
|
|
352
|
+
return Buffer.from(toClaimHash(
|
|
353
|
+
swapHash.toString("hex"),
|
|
354
|
+
0n,
|
|
355
|
+
0
|
|
356
|
+
), "hex");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @inheritDoc
|
|
361
|
+
*/
|
|
362
|
+
getHashForTxId(txId: string, confirmations: number): Buffer {
|
|
363
|
+
return Buffer.from(toClaimHash(
|
|
364
|
+
Buffer.from(txId, "hex").reverse().toString("hex"),
|
|
365
|
+
0n,
|
|
366
|
+
confirmations
|
|
367
|
+
), "hex");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
////////////////////////////////////////////
|
|
371
|
+
//// Swap data getters
|
|
372
|
+
/**
|
|
373
|
+
* @inheritDoc
|
|
374
|
+
*/
|
|
375
|
+
async getCommitStatus(signer: string, data: SolanaSwapData): Promise<SwapCommitState> {
|
|
376
|
+
const escrowStateKey = this._SwapEscrowState(Buffer.from(data.paymentHash, "hex"));
|
|
377
|
+
const [escrowState, isExpired] = await Promise.all([
|
|
378
|
+
this.program.account.escrowState.fetchNullable(escrowStateKey) as Promise<IdlAccounts<SwapProgram | SwapProgramV2>["escrowState"]>,
|
|
379
|
+
this.isExpired(signer,data)
|
|
380
|
+
]);
|
|
381
|
+
|
|
382
|
+
const getInitTxId = onceAsync(async () => {
|
|
383
|
+
const txId = await this._Events.findInEvents(escrowStateKey, async (event, tx) => {
|
|
384
|
+
if (event.name === "InitializeEvent") {
|
|
385
|
+
const paymentHash = Buffer.from(event.data.hash).toString("hex");
|
|
386
|
+
if(paymentHash!==data.paymentHash) return null;
|
|
387
|
+
if(!event.data.sequence.eq(data.sequence)) return null;
|
|
388
|
+
return tx.transaction.signatures[0];
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
if(txId==null) throw new Error("Initialize event not found!");
|
|
392
|
+
return txId;
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
if(escrowState!=null) {
|
|
396
|
+
if(data.correctPDA(escrowState)) {
|
|
397
|
+
if(data.isOfferer(signer) && isExpired) return {type: SwapCommitStateType.REFUNDABLE, getInitTxId};
|
|
398
|
+
return {type: SwapCommitStateType.COMMITED, getInitTxId};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if(data.isOfferer(signer) && isExpired) return {type: SwapCommitStateType.EXPIRED};
|
|
402
|
+
return {type: SwapCommitStateType.NOT_COMMITED};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
//Check if paid or what
|
|
406
|
+
const status: SwapNotCommitedState | SwapExpiredState | SwapPaidState | null = await this._Events.findInEvents(escrowStateKey, async (event, tx) => {
|
|
407
|
+
if(event.name==="ClaimEvent") {
|
|
408
|
+
const paymentHash = Buffer.from(event.data.hash).toString("hex");
|
|
409
|
+
if(paymentHash!==data.paymentHash) return null;
|
|
410
|
+
if(!event.data.sequence.eq(data.sequence)) return null;
|
|
411
|
+
return {
|
|
412
|
+
type: SwapCommitStateType.PAID,
|
|
413
|
+
getInitTxId,
|
|
414
|
+
getClaimTxId: () => Promise.resolve(tx.transaction.signatures[0]),
|
|
415
|
+
getClaimResult: () => Promise.resolve(Buffer.from(event.data.secret).toString("hex")),
|
|
416
|
+
getTxBlock: () => Promise.resolve({
|
|
417
|
+
blockHeight: tx.slot,
|
|
418
|
+
blockTime: tx.blockTime!
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if(event.name==="RefundEvent") {
|
|
423
|
+
const paymentHash = Buffer.from(event.data.hash).toString("hex");
|
|
424
|
+
if(paymentHash!==data.paymentHash) return null;
|
|
425
|
+
if(!event.data.sequence.eq(data.sequence)) return null;
|
|
426
|
+
return {
|
|
427
|
+
type: isExpired ? SwapCommitStateType.EXPIRED : SwapCommitStateType.NOT_COMMITED,
|
|
428
|
+
getInitTxId,
|
|
429
|
+
getRefundTxId: () => Promise.resolve(tx.transaction.signatures[0]),
|
|
430
|
+
getTxBlock: () => Promise.resolve({
|
|
431
|
+
blockHeight: tx.slot,
|
|
432
|
+
blockTime: tx.blockTime!
|
|
433
|
+
})
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
if(status!=null) return status;
|
|
438
|
+
|
|
439
|
+
if(isExpired) return {type: SwapCommitStateType.EXPIRED};
|
|
440
|
+
return {type: SwapCommitStateType.NOT_COMMITED};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* @inheritDoc
|
|
445
|
+
*/
|
|
446
|
+
async getCommitStatuses(request: { signer: string; swapData: SolanaSwapData }[]): Promise<{
|
|
447
|
+
[p: string]: SwapCommitState
|
|
448
|
+
}> {
|
|
449
|
+
const result: {
|
|
450
|
+
[p: string]: SwapCommitState
|
|
451
|
+
} = {};
|
|
452
|
+
let promises: Promise<void>[] = [];
|
|
453
|
+
for(let {signer, swapData} of request) {
|
|
454
|
+
promises.push(this.getCommitStatus(signer, swapData).then(val => {
|
|
455
|
+
result[swapData.getEscrowHash()] = val;
|
|
456
|
+
}));
|
|
457
|
+
if(promises.length>=MAX_PARALLEL_COMMIT_STATUS_CHECKS) {
|
|
458
|
+
await Promise.all(promises);
|
|
459
|
+
promises = [];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
await Promise.all(promises);
|
|
463
|
+
return result;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* @inheritDoc
|
|
468
|
+
*/
|
|
469
|
+
async getClaimHashStatus(claimHash: string): Promise<SwapCommitStateType> {
|
|
470
|
+
const {paymentHash} = fromClaimHash(claimHash);
|
|
471
|
+
const escrowStateKey = this._SwapEscrowState(Buffer.from(paymentHash, "hex"));
|
|
472
|
+
const abortController = new AbortController();
|
|
473
|
+
|
|
474
|
+
//Start fetching events before checking escrow PDA, this call is used when quoting, so saving 100ms here helps a lot!
|
|
475
|
+
const eventsPromise = this._Events.findInEvents(escrowStateKey, async (event) => {
|
|
476
|
+
if(event.name==="ClaimEvent") return SwapCommitStateType.PAID;
|
|
477
|
+
if(event.name==="RefundEvent") return SwapCommitStateType.NOT_COMMITED;
|
|
478
|
+
}, abortController.signal).catch(e => {
|
|
479
|
+
abortController.abort(e)
|
|
480
|
+
return null;
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const escrowState = await this.program.account.escrowState.fetchNullable(escrowStateKey);
|
|
484
|
+
abortController.signal.throwIfAborted();
|
|
485
|
+
if(escrowState!=null) {
|
|
486
|
+
abortController.abort();
|
|
487
|
+
return SwapCommitStateType.COMMITED;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
//Check if paid or what
|
|
491
|
+
const eventsStatus = await eventsPromise;
|
|
492
|
+
abortController.signal.throwIfAborted();
|
|
493
|
+
if(eventsStatus!=null) return eventsStatus;
|
|
494
|
+
|
|
495
|
+
return SwapCommitStateType.NOT_COMMITED;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* @inheritDoc
|
|
500
|
+
*/
|
|
501
|
+
async getCommitedData(claimHashHex: string): Promise<SolanaSwapData | null> {
|
|
502
|
+
const {paymentHash} = fromClaimHash(claimHashHex);
|
|
503
|
+
const paymentHashBuffer = Buffer.from(paymentHash, "hex");
|
|
504
|
+
|
|
505
|
+
const account: IdlAccounts<SwapProgram | SwapProgramV2>["escrowState"] | null =
|
|
506
|
+
await this.program.account.escrowState.fetchNullable(
|
|
507
|
+
this._SwapEscrowState(paymentHashBuffer)
|
|
508
|
+
);
|
|
509
|
+
if(account==null) return null;
|
|
510
|
+
|
|
511
|
+
return SolanaSwapData.fromEscrowState(this.program.programId, this.version, account);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* @inheritDoc
|
|
516
|
+
*/
|
|
517
|
+
async getHistoricalSwaps(signer: string, startBlockheight?: number): Promise<{
|
|
518
|
+
swaps: {
|
|
519
|
+
[escrowHash: string]: {
|
|
520
|
+
init?: {
|
|
521
|
+
data: SolanaSwapData,
|
|
522
|
+
getInitTxId: () => Promise<string>,
|
|
523
|
+
getTxBlock: () => Promise<{
|
|
524
|
+
blockTime: number,
|
|
525
|
+
blockHeight: number
|
|
526
|
+
}>
|
|
527
|
+
},
|
|
528
|
+
state: SwapCommitState
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
latestBlockheight?: number
|
|
532
|
+
}> {
|
|
533
|
+
let latestBlockheight: number | undefined;
|
|
534
|
+
|
|
535
|
+
const events: {event: ProgramEvent<SwapProgram>, tx: ParsedTransactionWithMeta}[] = [];
|
|
536
|
+
|
|
537
|
+
await this._Events.findInEvents(new PublicKey(signer), async (event, tx) => {
|
|
538
|
+
if(latestBlockheight==null) latestBlockheight = tx.slot;
|
|
539
|
+
events.push({event, tx});
|
|
540
|
+
}, undefined, undefined, startBlockheight);
|
|
541
|
+
|
|
542
|
+
this.logger.debug(`getHistoricalSwaps(): Found ${events.length} atomiq related events!`);
|
|
543
|
+
|
|
544
|
+
const swapsOpened: {[escrowHash: string]: {
|
|
545
|
+
data: SolanaSwapData,
|
|
546
|
+
getInitTxId: () => Promise<string>,
|
|
547
|
+
getTxBlock: () => Promise<{
|
|
548
|
+
blockTime: number,
|
|
549
|
+
blockHeight: number
|
|
550
|
+
}>
|
|
551
|
+
}} = {};
|
|
552
|
+
const resultingSwaps: {
|
|
553
|
+
[escrowHash: string]: {
|
|
554
|
+
init?: {
|
|
555
|
+
data: SolanaSwapData,
|
|
556
|
+
getInitTxId: () => Promise<string>,
|
|
557
|
+
getTxBlock: () => Promise<{
|
|
558
|
+
blockTime: number,
|
|
559
|
+
blockHeight: number
|
|
560
|
+
}>
|
|
561
|
+
},
|
|
562
|
+
state: SwapCommitState
|
|
563
|
+
}
|
|
564
|
+
} = {};
|
|
565
|
+
|
|
566
|
+
events.reverse();
|
|
567
|
+
for(let {event, tx} of events) {
|
|
568
|
+
const txSignature = tx.transaction.signatures[0];
|
|
569
|
+
const paymentHash: string = Buffer.from(event.data.hash).toString("hex");
|
|
570
|
+
const escrowHash = toEscrowHash(paymentHash, event.data.sequence);
|
|
571
|
+
|
|
572
|
+
if(event.name==="InitializeEvent") {
|
|
573
|
+
//Parse swap data from initialize event
|
|
574
|
+
const txoHash: string = Buffer.from(event.data.txoHash).toString("hex");
|
|
575
|
+
const instructions = this._Events.decodeInstructions(tx.transaction.message);
|
|
576
|
+
if(instructions == null) {
|
|
577
|
+
this.logger.warn(`getHistoricalSwaps(): Skipping tx ${txSignature} because cannot parse instructions!`);
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const initIx = instructions.find(
|
|
582
|
+
ix => ix!=null && (ix.name === "offererInitializePayIn" || ix.name === "offererInitialize")
|
|
583
|
+
) as InitInstruction;
|
|
584
|
+
if(initIx == null) {
|
|
585
|
+
this.logger.warn(`getHistoricalSwaps(): Skipping tx ${txSignature} because init instruction not found!`);
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
swapsOpened[escrowHash] = {
|
|
590
|
+
data: SolanaSwapData.fromInstruction(this.program.programId, this.version, initIx, txoHash),
|
|
591
|
+
getInitTxId: () => Promise.resolve(txSignature),
|
|
592
|
+
getTxBlock: () => Promise.resolve({
|
|
593
|
+
blockHeight: tx.slot,
|
|
594
|
+
blockTime: tx.blockTime!
|
|
595
|
+
})
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if(event.name==="ClaimEvent") {
|
|
600
|
+
const foundSwapData = swapsOpened[escrowHash];
|
|
601
|
+
delete swapsOpened[escrowHash];
|
|
602
|
+
resultingSwaps[escrowHash] = {
|
|
603
|
+
init: foundSwapData,
|
|
604
|
+
state: {
|
|
605
|
+
type: SwapCommitStateType.PAID,
|
|
606
|
+
getInitTxId: foundSwapData?.getInitTxId,
|
|
607
|
+
getClaimTxId: () => Promise.resolve(txSignature),
|
|
608
|
+
getClaimResult: () => Promise.resolve(Buffer.from(event.data.secret).toString("hex")),
|
|
609
|
+
getTxBlock: () => Promise.resolve({
|
|
610
|
+
blockHeight: tx.slot,
|
|
611
|
+
blockTime: tx.blockTime!
|
|
612
|
+
})
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if(event.name==="RefundEvent") {
|
|
618
|
+
const foundSwapData = swapsOpened[escrowHash];
|
|
619
|
+
delete swapsOpened[escrowHash];
|
|
620
|
+
const isExpired = foundSwapData!=null && await this.isExpired(signer, foundSwapData.data);
|
|
621
|
+
resultingSwaps[escrowHash] = {
|
|
622
|
+
init: foundSwapData,
|
|
623
|
+
state: {
|
|
624
|
+
type: isExpired ? SwapCommitStateType.EXPIRED : SwapCommitStateType.NOT_COMMITED,
|
|
625
|
+
getInitTxId: foundSwapData?.getInitTxId,
|
|
626
|
+
getRefundTxId: () => Promise.resolve(txSignature),
|
|
627
|
+
getTxBlock: () => Promise.resolve({
|
|
628
|
+
blockHeight: tx.slot,
|
|
629
|
+
blockTime: tx.blockTime!
|
|
630
|
+
})
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
this.logger.debug(`getHistoricalSwaps(): Found ${Object.keys(resultingSwaps).length} settled swaps!`);
|
|
637
|
+
this.logger.debug(`getHistoricalSwaps(): Found ${Object.keys(swapsOpened).length} unsettled swaps!`);
|
|
638
|
+
|
|
639
|
+
for(let escrowHash in swapsOpened) {
|
|
640
|
+
const foundSwapData = swapsOpened[escrowHash];
|
|
641
|
+
const isExpired = await this.isExpired(signer, foundSwapData.data);
|
|
642
|
+
resultingSwaps[escrowHash] = {
|
|
643
|
+
init: foundSwapData,
|
|
644
|
+
state: foundSwapData.data.isOfferer(signer) && isExpired
|
|
645
|
+
? {type: SwapCommitStateType.REFUNDABLE, getInitTxId: foundSwapData.getInitTxId}
|
|
646
|
+
: {type: SwapCommitStateType.COMMITED, getInitTxId: foundSwapData.getInitTxId}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
swaps: resultingSwaps,
|
|
652
|
+
latestBlockheight: latestBlockheight ?? startBlockheight
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
////////////////////////////////////////////
|
|
657
|
+
//// Swap data initializer
|
|
658
|
+
/**
|
|
659
|
+
* @inheritDoc
|
|
660
|
+
*/
|
|
661
|
+
createSwapData(
|
|
662
|
+
type: ChainSwapType,
|
|
663
|
+
offerer: string,
|
|
664
|
+
claimer: string,
|
|
665
|
+
token: string,
|
|
666
|
+
amount: bigint,
|
|
667
|
+
claimHash: string,
|
|
668
|
+
sequence: bigint,
|
|
669
|
+
expiry: bigint,
|
|
670
|
+
payIn: boolean,
|
|
671
|
+
payOut: boolean,
|
|
672
|
+
securityDeposit: bigint,
|
|
673
|
+
claimerBounty: bigint,
|
|
674
|
+
depositToken?: string
|
|
675
|
+
): Promise<SolanaSwapData> {
|
|
676
|
+
if(depositToken!=null) {
|
|
677
|
+
if(!new PublicKey(depositToken).equals(SolanaTokens.WSOL_ADDRESS)) throw new Error("Only SOL supported as deposit token!");
|
|
678
|
+
}
|
|
679
|
+
const tokenAddr: PublicKey = new PublicKey(token);
|
|
680
|
+
const offererKey = new PublicKey(offerer);
|
|
681
|
+
const claimerKey = new PublicKey(claimer);
|
|
682
|
+
const {paymentHash, nonce, confirmations} = fromClaimHash(claimHash);
|
|
683
|
+
const swapData = new SolanaSwapData({
|
|
684
|
+
programId: this.program.programId,
|
|
685
|
+
version: this.version,
|
|
686
|
+
offerer: offererKey,
|
|
687
|
+
claimer: claimerKey,
|
|
688
|
+
token: tokenAddr,
|
|
689
|
+
amount: toBN(amount),
|
|
690
|
+
paymentHash,
|
|
691
|
+
sequence: toBN(sequence),
|
|
692
|
+
expiry: toBN(expiry),
|
|
693
|
+
nonce,
|
|
694
|
+
confirmations,
|
|
695
|
+
payOut,
|
|
696
|
+
kind: SolanaSwapData.typeToKind(type),
|
|
697
|
+
payIn,
|
|
698
|
+
offererAta: payIn ? getAssociatedTokenAddressSync(tokenAddr, offererKey) : PublicKey.default,
|
|
699
|
+
claimerAta: payOut ? getAssociatedTokenAddressSync(tokenAddr, claimerKey) : PublicKey.default,
|
|
700
|
+
securityDeposit: toBN(securityDeposit),
|
|
701
|
+
claimerBounty: toBN(claimerBounty)
|
|
702
|
+
});
|
|
703
|
+
return Promise.resolve(swapData);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
////////////////////////////////////////////
|
|
707
|
+
//// Utils
|
|
708
|
+
/**
|
|
709
|
+
* @inheritDoc
|
|
710
|
+
*/
|
|
711
|
+
async getBalance(signer: string, tokenAddress: string, inContract: boolean): Promise<bigint> {
|
|
712
|
+
if(!inContract) {
|
|
713
|
+
return await this._Chain.getBalance(signer, tokenAddress);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const token = new PublicKey(tokenAddress);
|
|
717
|
+
const publicKey = new PublicKey(signer);
|
|
718
|
+
|
|
719
|
+
return await this.getIntermediaryBalance(publicKey, token);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* @inheritDoc
|
|
724
|
+
*/
|
|
725
|
+
getIntermediaryData(address: string, token: string): Promise<{
|
|
726
|
+
balance: bigint,
|
|
727
|
+
reputation: IntermediaryReputationType
|
|
728
|
+
} | null> {
|
|
729
|
+
return this.LpVault.getIntermediaryData(new PublicKey(address), new PublicKey(token));
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* @inheritDoc
|
|
734
|
+
*/
|
|
735
|
+
getIntermediaryReputation(address: string, token: string): Promise<IntermediaryReputationType | null> {
|
|
736
|
+
return this.LpVault.getIntermediaryReputation(new PublicKey(address), new PublicKey(token));
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Returns intermediary vault balance for a specific token.
|
|
741
|
+
*
|
|
742
|
+
* @param address Intermediary address
|
|
743
|
+
* @param token Token mint
|
|
744
|
+
*/
|
|
745
|
+
getIntermediaryBalance(address: PublicKey, token: PublicKey): Promise<bigint> {
|
|
746
|
+
return this.LpVault.getIntermediaryBalance(address, token);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
////////////////////////////////////////////
|
|
750
|
+
//// Transaction initializers
|
|
751
|
+
/**
|
|
752
|
+
* @inheritDoc
|
|
753
|
+
*/
|
|
754
|
+
async txsClaimWithSecret(
|
|
755
|
+
signer: string | SolanaSigner,
|
|
756
|
+
swapData: SolanaSwapData,
|
|
757
|
+
secret: string,
|
|
758
|
+
checkExpiry?: boolean,
|
|
759
|
+
initAta?: boolean,
|
|
760
|
+
feeRate?: string,
|
|
761
|
+
skipAtaCheck?: boolean
|
|
762
|
+
): Promise<SolanaTx[]> {
|
|
763
|
+
return this.Claim.txsClaimWithSecret(typeof(signer)==="string" ? new PublicKey(signer) : signer.getPublicKey(), swapData, secret, checkExpiry, initAta, feeRate, skipAtaCheck)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* @inheritDoc
|
|
768
|
+
*/
|
|
769
|
+
async txsClaimWithTxData(
|
|
770
|
+
signer: string | SolanaSigner,
|
|
771
|
+
swapData: SolanaSwapData,
|
|
772
|
+
tx: { blockhash: string, confirmations: number, txid: string, hex: string, height: number },
|
|
773
|
+
requiredConfirmations: number,
|
|
774
|
+
vout: number,
|
|
775
|
+
commitedHeader?: SolanaBtcStoredHeader,
|
|
776
|
+
synchronizer?: RelaySynchronizer<any, SolanaTx, any>,
|
|
777
|
+
initAta?: boolean,
|
|
778
|
+
feeRate?: string,
|
|
779
|
+
): Promise<SolanaTx[]> {
|
|
780
|
+
if(swapData.confirmations!==requiredConfirmations) throw new Error("Invalid requiredConfirmations provided!");
|
|
781
|
+
const {txs} = await this.Claim.txsClaimWithTxData(
|
|
782
|
+
typeof(signer)==="string" ? new PublicKey(signer) : signer,
|
|
783
|
+
swapData, tx, vout, commitedHeader, synchronizer, initAta, feeRate
|
|
784
|
+
);
|
|
785
|
+
return txs;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* @inheritDoc
|
|
790
|
+
*/
|
|
791
|
+
txsRefund(signer: string, swapData: SolanaSwapData, check?: boolean, initAta?: boolean, feeRate?: string): Promise<SolanaTx[]> {
|
|
792
|
+
return this.Refund.txsRefund(new PublicKey(signer), swapData, check, initAta, feeRate);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* @inheritDoc
|
|
797
|
+
*/
|
|
798
|
+
txsRefundWithAuthorization(signer: string, swapData: SolanaSwapData, sig: SignatureData, check?: boolean, initAta?: boolean, feeRate?: string): Promise<SolanaTx[]> {
|
|
799
|
+
return this.Refund.txsRefundWithAuthorization(new PublicKey(signer), swapData, sig.timeout, sig.prefix, sig.signature,check,initAta, feeRate);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* @inheritDoc
|
|
804
|
+
*/
|
|
805
|
+
txsInit(sender: string, swapData: SolanaSwapData, sig: SignatureData, skipChecks?: boolean, feeRate?: string): Promise<SolanaTx[]> {
|
|
806
|
+
if(swapData.isPayIn()) {
|
|
807
|
+
return this.Init.txsInitPayIn(new PublicKey(sender), swapData, sig.timeout, sig.prefix, sig.signature, skipChecks, feeRate);
|
|
808
|
+
} else {
|
|
809
|
+
return this.Init.txsInit(new PublicKey(sender), swapData, sig.timeout, sig.prefix, sig.signature, skipChecks, feeRate);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* @inheritDoc
|
|
815
|
+
*/
|
|
816
|
+
txsWithdraw(signer: string, token: string, amount: bigint, feeRate?: string): Promise<SolanaTx[]> {
|
|
817
|
+
return this.LpVault.txsWithdraw(new PublicKey(signer), new PublicKey(token), amount, feeRate);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* @inheritDoc
|
|
822
|
+
*/
|
|
823
|
+
txsDeposit(signer: string, token: string, amount: bigint, feeRate?: string): Promise<SolanaTx[]> {
|
|
824
|
+
return this.LpVault.txsDeposit(new PublicKey(signer), new PublicKey(token), amount, feeRate);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
////////////////////////////////////////////
|
|
828
|
+
//// Executors
|
|
829
|
+
/**
|
|
830
|
+
* @inheritDoc
|
|
831
|
+
*/
|
|
832
|
+
async claimWithSecret(
|
|
833
|
+
signer: SolanaSigner,
|
|
834
|
+
swapData: SolanaSwapData,
|
|
835
|
+
secret: string,
|
|
836
|
+
checkExpiry?: boolean,
|
|
837
|
+
initAta?: boolean,
|
|
838
|
+
txOptions?: TransactionConfirmationOptions
|
|
839
|
+
): Promise<string> {
|
|
840
|
+
const result = await this.Claim.txsClaimWithSecret(signer.getPublicKey(), swapData, secret, checkExpiry, initAta, txOptions?.feeRate);
|
|
841
|
+
const [signature] = await this._Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
842
|
+
return signature;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* @inheritDoc
|
|
847
|
+
*/
|
|
848
|
+
async claimWithTxData(
|
|
849
|
+
signer: SolanaSigner,
|
|
850
|
+
swapData: SolanaSwapData,
|
|
851
|
+
tx: { blockhash: string, confirmations: number, txid: string, hex: string, height: number },
|
|
852
|
+
requiredConfirmations: number,
|
|
853
|
+
vout: number,
|
|
854
|
+
commitedHeader?: SolanaBtcStoredHeader,
|
|
855
|
+
synchronizer?: RelaySynchronizer<any, SolanaTx, any>,
|
|
856
|
+
initAta?: boolean,
|
|
857
|
+
txOptions?: TransactionConfirmationOptions
|
|
858
|
+
): Promise<string> {
|
|
859
|
+
if(requiredConfirmations!==swapData.confirmations) throw new Error("Invalid requiredConfirmations provided!");
|
|
860
|
+
|
|
861
|
+
const {txs, claimTxIndex, storageAcc} = await this.Claim.txsClaimWithTxData(
|
|
862
|
+
signer, swapData, tx, vout,
|
|
863
|
+
commitedHeader, synchronizer, initAta, txOptions?.feeRate
|
|
864
|
+
);
|
|
865
|
+
|
|
866
|
+
const signatures = await this._Chain.sendAndConfirm(signer, txs, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
867
|
+
await this._DataAccount.removeDataAccount(storageAcc);
|
|
868
|
+
|
|
869
|
+
return signatures[claimTxIndex] ?? signatures[0];
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* @inheritDoc
|
|
874
|
+
*/
|
|
875
|
+
async refund(
|
|
876
|
+
signer: SolanaSigner,
|
|
877
|
+
swapData: SolanaSwapData,
|
|
878
|
+
check?: boolean,
|
|
879
|
+
initAta?: boolean,
|
|
880
|
+
txOptions?: TransactionConfirmationOptions
|
|
881
|
+
): Promise<string> {
|
|
882
|
+
let result = await this.txsRefund(signer.getAddress(), swapData, check, initAta, txOptions?.feeRate);
|
|
883
|
+
|
|
884
|
+
const [signature] = await this._Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
885
|
+
|
|
886
|
+
return signature;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* @inheritDoc
|
|
891
|
+
*/
|
|
892
|
+
async refundWithAuthorization(
|
|
893
|
+
signer: SolanaSigner,
|
|
894
|
+
swapData: SolanaSwapData,
|
|
895
|
+
signature: SignatureData,
|
|
896
|
+
check?: boolean,
|
|
897
|
+
initAta?: boolean,
|
|
898
|
+
txOptions?: TransactionConfirmationOptions
|
|
899
|
+
): Promise<string> {
|
|
900
|
+
let result = await this.txsRefundWithAuthorization(signer.getAddress(), swapData, signature, check, initAta, txOptions?.feeRate);
|
|
901
|
+
|
|
902
|
+
const [txSignature] = await this._Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
903
|
+
|
|
904
|
+
return txSignature;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* @inheritDoc
|
|
909
|
+
*/
|
|
910
|
+
async init(
|
|
911
|
+
signer: SolanaSigner,
|
|
912
|
+
swapData: SolanaSwapData,
|
|
913
|
+
signature: SignatureData,
|
|
914
|
+
skipChecks?: boolean,
|
|
915
|
+
txOptions?: TransactionConfirmationOptions
|
|
916
|
+
): Promise<string> {
|
|
917
|
+
const result = await this.txsInit(signer.getAddress(), swapData, signature, skipChecks, txOptions?.feeRate);
|
|
918
|
+
|
|
919
|
+
const signatures = await this._Chain.sendAndConfirm(signer, result, txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
920
|
+
|
|
921
|
+
return signatures[signatures.length-1];
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/**
|
|
925
|
+
* @inheritDoc
|
|
926
|
+
*/
|
|
927
|
+
async initAndClaimWithSecret(
|
|
928
|
+
signer: SolanaSigner,
|
|
929
|
+
swapData: SolanaSwapData,
|
|
930
|
+
signature: SignatureData,
|
|
931
|
+
secret: string,
|
|
932
|
+
skipChecks?: boolean,
|
|
933
|
+
txOptions?: TransactionConfirmationOptions
|
|
934
|
+
): Promise<string[]> {
|
|
935
|
+
if(!signer.getPublicKey().equals(swapData.claimer)) throw new Error("Invalid signer provided!");
|
|
936
|
+
|
|
937
|
+
const txsCommit = await this.txsInit(signer.getAddress(), swapData, signature, skipChecks, txOptions?.feeRate);
|
|
938
|
+
let txsClaim: SolanaTx[];
|
|
939
|
+
if(isSwapProgramV1(this.program)) {
|
|
940
|
+
// In V1 the initialize instruction has to contain the ATA initialization, hence we can skip checking it in claim
|
|
941
|
+
txsClaim = await this.Claim.txsClaimWithSecret(signer.getPublicKey(), swapData, secret, true, false, txOptions?.feeRate, true);
|
|
942
|
+
} else {
|
|
943
|
+
// In V2 the initialize instruction doesn't necessarily contain the ATA initialization, hence the claim instruction needs to
|
|
944
|
+
// check and initialize the ATA if needed
|
|
945
|
+
txsClaim = await this.Claim.txsClaimWithSecret(signer.getPublicKey(), swapData, secret, true, true, txOptions?.feeRate, false);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const signatures = await this._Chain.sendAndConfirm(signer, txsCommit.concat(txsClaim), txOptions?.waitForConfirmation, txOptions?.abortSignal);
|
|
949
|
+
return [signatures[txsCommit.length-1], signatures[signatures.length-1]]
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* @inheritDoc
|
|
954
|
+
*/
|
|
955
|
+
async withdraw(
|
|
956
|
+
signer: SolanaSigner,
|
|
957
|
+
token: string,
|
|
958
|
+
amount: bigint,
|
|
959
|
+
txOptions?: TransactionConfirmationOptions
|
|
960
|
+
): Promise<string> {
|
|
961
|
+
const txs = await this.LpVault.txsWithdraw(signer.getPublicKey(), new PublicKey(token), amount, txOptions?.feeRate);
|
|
962
|
+
const [txId] = await this._Chain.sendAndConfirm(signer, txs, txOptions?.waitForConfirmation, txOptions?.abortSignal, false);
|
|
963
|
+
return txId;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* @inheritDoc
|
|
968
|
+
*/
|
|
969
|
+
async deposit(
|
|
970
|
+
signer: SolanaSigner,
|
|
971
|
+
token: string,
|
|
972
|
+
amount: bigint,
|
|
973
|
+
txOptions?: TransactionConfirmationOptions
|
|
974
|
+
): Promise<string> {
|
|
975
|
+
const txs = await this.LpVault.txsDeposit(signer.getPublicKey(), new PublicKey(token), amount, txOptions?.feeRate);
|
|
976
|
+
const [txId] = await this._Chain.sendAndConfirm(signer, txs, txOptions?.waitForConfirmation, txOptions?.abortSignal, false);
|
|
977
|
+
return txId;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
////////////////////////////////////////////
|
|
981
|
+
//// Fees
|
|
982
|
+
/**
|
|
983
|
+
* @inheritDoc
|
|
984
|
+
*/
|
|
985
|
+
getInitPayInFeeRate(offerer?: string, claimer?: string, token?: string, claimHash?: string): Promise<string> {
|
|
986
|
+
const paymentHash = claimHash==null ? undefined : fromClaimHash(claimHash).paymentHash;
|
|
987
|
+
return this.Init.getInitPayInFeeRate(
|
|
988
|
+
toPublicKeyOrNull(offerer),
|
|
989
|
+
toPublicKeyOrNull(claimer),
|
|
990
|
+
toPublicKeyOrNull(token),
|
|
991
|
+
paymentHash
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* @inheritDoc
|
|
997
|
+
*/
|
|
998
|
+
getInitFeeRate(offerer?: string, claimer?: string, token?: string, claimHash?: string): Promise<string> {
|
|
999
|
+
const paymentHash = claimHash==null ? undefined : fromClaimHash(claimHash).paymentHash;
|
|
1000
|
+
return this.Init.getInitFeeRate(
|
|
1001
|
+
toPublicKeyOrNull(offerer),
|
|
1002
|
+
toPublicKeyOrNull(claimer),
|
|
1003
|
+
toPublicKeyOrNull(token),
|
|
1004
|
+
paymentHash
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* @inheritDoc
|
|
1010
|
+
*/
|
|
1011
|
+
getRefundFeeRate(swapData: SolanaSwapData): Promise<string> {
|
|
1012
|
+
return this.Refund.getRefundFeeRate(swapData);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* @inheritDoc
|
|
1017
|
+
*/
|
|
1018
|
+
getClaimFeeRate(signer: string, swapData: SolanaSwapData): Promise<string> {
|
|
1019
|
+
return this.Claim.getClaimFeeRate(new PublicKey(signer), swapData);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* @inheritDoc
|
|
1024
|
+
*/
|
|
1025
|
+
getClaimFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1026
|
+
return this.Claim.getClaimFee(new PublicKey(signer), swapData, feeRate);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* @inheritDoc
|
|
1031
|
+
*/
|
|
1032
|
+
getRawClaimFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1033
|
+
return this.Claim.getRawClaimFee(new PublicKey(signer), swapData, feeRate);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* @inheritDoc
|
|
1038
|
+
*/
|
|
1039
|
+
getCommitFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1040
|
+
return this.Init.getInitFee(swapData, feeRate);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* @inheritDoc
|
|
1045
|
+
*/
|
|
1046
|
+
getRawCommitFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1047
|
+
return this.Init.getRawInitFee(swapData, feeRate);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* @inheritDoc
|
|
1052
|
+
*/
|
|
1053
|
+
getRefundFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1054
|
+
return this.Refund.getRefundFee(swapData, feeRate);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* @inheritDoc
|
|
1059
|
+
*/
|
|
1060
|
+
getRawRefundFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
|
|
1061
|
+
return this.Refund.getRawRefundFee(swapData, feeRate);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
/**
|
|
1065
|
+
* @inheritDoc
|
|
1066
|
+
*/
|
|
1067
|
+
getExtraData(outputScript: Buffer, amount: bigint, confirmations: number, nonce?: bigint): Buffer {
|
|
1068
|
+
return Buffer.from(sha256(Buffer.concat([
|
|
1069
|
+
BigIntBufferUtils.toBuffer(amount, "le", 8),
|
|
1070
|
+
outputScript
|
|
1071
|
+
])));
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
}
|