@atomiqlabs/chain-solana 11.0.0 → 12.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
- import {SolanaSwapData} from "./SolanaSwapData";
1
+ import {SolanaSwapData, InitInstruction} from "./SolanaSwapData";
2
2
  import {IdlAccounts} from "@coral-xyz/anchor";
3
3
  import {
4
+ ParsedTransactionWithMeta,
4
5
  PublicKey,
5
6
  } from "@solana/web3.js";
6
7
  import {sha256} from "@noble/hashes/sha2";
@@ -36,11 +37,14 @@ import {SolanaSigner} from "../wallet/SolanaSigner";
36
37
  import {fromClaimHash, toBN, toClaimHash, toEscrowHash} from "../../utils/Utils";
37
38
  import {SolanaTokens} from "../chain/modules/SolanaTokens";
38
39
  import * as BN from "bn.js";
40
+ import {ProgramEvent} from "../program/modules/SolanaProgramEvents";
39
41
 
40
42
  function toPublicKeyOrNull(str: string | null): PublicKey | null {
41
43
  return str==null ? null : new PublicKey(str);
42
44
  }
43
45
 
46
+ const MAX_PARALLEL_COMMIT_STATUS_CHECKS = 5;
47
+
44
48
  export class SolanaSwapProgram
45
49
  extends SolanaProgramBase<SwapProgram>
46
50
  implements SwapContract<
@@ -269,21 +273,19 @@ export class SolanaSwapProgram
269
273
  }
270
274
 
271
275
  //Check if paid or what
272
- const status: SwapNotCommitedState | SwapExpiredState | SwapPaidState = await this.Events.findInEvents(escrowStateKey, async (event, info) => {
276
+ const status: SwapNotCommitedState | SwapExpiredState | SwapPaidState = await this.Events.findInEvents(escrowStateKey, async (event, tx) => {
273
277
  if(event.name==="ClaimEvent") {
274
278
  const paymentHash = Buffer.from(event.data.hash).toString("hex");
275
279
  if(paymentHash!==data.paymentHash) return null;
276
280
  if(!event.data.sequence.eq(data.sequence)) return null;
277
281
  return {
278
282
  type: SwapCommitStateType.PAID,
279
- getClaimTxId: () => Promise.resolve(info.signature),
283
+ getClaimTxId: () => Promise.resolve(tx.transaction.signatures[0]),
280
284
  getClaimResult: () => Promise.resolve(Buffer.from(event.data.secret).toString("hex")),
281
- getTxBlock: async () => {
282
- return {
283
- blockHeight: (await this.Chain.Blocks.getParsedBlock(info.slot)).blockHeight,
284
- blockTime: info.blockTime
285
- };
286
- }
285
+ getTxBlock: () => Promise.resolve({
286
+ blockHeight: tx.slot,
287
+ blockTime: tx.blockTime
288
+ })
287
289
  }
288
290
  }
289
291
  if(event.name==="RefundEvent") {
@@ -292,13 +294,11 @@ export class SolanaSwapProgram
292
294
  if(!event.data.sequence.eq(data.sequence)) return null;
293
295
  return {
294
296
  type: isExpired ? SwapCommitStateType.EXPIRED : SwapCommitStateType.NOT_COMMITED,
295
- getRefundTxId: () => Promise.resolve(info.signature),
296
- getTxBlock: async () => {
297
- return {
298
- blockHeight: (await this.Chain.Blocks.getParsedBlock(info.slot)).blockHeight,
299
- blockTime: info.blockTime
300
- };
301
- }
297
+ getRefundTxId: () => Promise.resolve(tx.transaction.signatures[0]),
298
+ getTxBlock: () => Promise.resolve({
299
+ blockHeight: tx.slot,
300
+ blockTime: tx.blockTime
301
+ })
302
302
  };
303
303
  }
304
304
  });
@@ -308,6 +308,26 @@ export class SolanaSwapProgram
308
308
  return {type: SwapCommitStateType.NOT_COMMITED};
309
309
  }
310
310
 
311
+ async getCommitStatuses(request: { signer: string; swapData: SolanaSwapData }[]): Promise<{
312
+ [p: string]: SwapCommitState
313
+ }> {
314
+ const result: {
315
+ [p: string]: SwapCommitState
316
+ } = {};
317
+ let promises: Promise<void>[] = [];
318
+ for(let {signer, swapData} of request) {
319
+ promises.push(this.getCommitStatus(signer, swapData).then(val => {
320
+ result[swapData.getEscrowHash()] = val;
321
+ }));
322
+ if(promises.length>=MAX_PARALLEL_COMMIT_STATUS_CHECKS) {
323
+ await Promise.all(promises);
324
+ promises = [];
325
+ }
326
+ }
327
+ await Promise.all(promises);
328
+ return result;
329
+ }
330
+
311
331
  /**
312
332
  * Checks the status of the specific payment hash
313
333
  *
@@ -358,6 +378,138 @@ export class SolanaSwapProgram
358
378
  return SolanaSwapData.fromEscrowState(account);
359
379
  }
360
380
 
381
+ async getHistoricalSwaps(signer: string, startBlockheight?: number): Promise<{
382
+ swaps: {
383
+ [escrowHash: string]: {
384
+ init?: {
385
+ data: SolanaSwapData,
386
+ getInitTxId: () => Promise<string>,
387
+ getTxBlock: () => Promise<{
388
+ blockTime: number,
389
+ blockHeight: number
390
+ }>
391
+ },
392
+ state: SwapCommitState
393
+ }
394
+ },
395
+ latestBlockheight: number
396
+ }> {
397
+ let latestBlockheight: number;
398
+
399
+ const events: {event: ProgramEvent<SwapProgram>, tx: ParsedTransactionWithMeta}[] = [];
400
+
401
+ await this.Events.findInEvents(new PublicKey(signer), async (event, tx) => {
402
+ if(latestBlockheight==null) latestBlockheight = tx.slot;
403
+ events.push({event, tx});
404
+ }, undefined, undefined, startBlockheight);
405
+
406
+ const swapsOpened: {[escrowHash: string]: {
407
+ data: SolanaSwapData,
408
+ getInitTxId: () => Promise<string>,
409
+ getTxBlock: () => Promise<{
410
+ blockTime: number,
411
+ blockHeight: number
412
+ }>
413
+ }} = {};
414
+ const resultingSwaps: {
415
+ [escrowHash: string]: {
416
+ init?: {
417
+ data: SolanaSwapData,
418
+ getInitTxId: () => Promise<string>,
419
+ getTxBlock: () => Promise<{
420
+ blockTime: number,
421
+ blockHeight: number
422
+ }>
423
+ },
424
+ state: SwapCommitState
425
+ }
426
+ } = {};
427
+
428
+ events.reverse();
429
+ for(let {event, tx} of events) {
430
+ const txSignature = tx.transaction.signatures[0];
431
+ const paymentHash: string = Buffer.from(event.data.hash).toString("hex");
432
+ const escrowHash = toEscrowHash(paymentHash, event.data.sequence);
433
+
434
+ if(event.name==="InitializeEvent") {
435
+ //Parse swap data from initialize event
436
+ const txoHash: string = Buffer.from(event.data.txoHash).toString("hex");
437
+ const instructions = this.Events.decodeInstructions(tx.transaction.message);
438
+ if(instructions == null) {
439
+ this.logger.warn(`getHistoricalSwaps(): Skipping tx ${txSignature} because cannot parse instructions!`);
440
+ continue;
441
+ }
442
+
443
+ const initIx = instructions.find(
444
+ ix => ix!=null && (ix.name === "offererInitializePayIn" || ix.name === "offererInitialize")
445
+ ) as InitInstruction;
446
+ if(initIx == null) {
447
+ this.logger.warn(`getHistoricalSwaps(): Skipping tx ${txSignature} because cannot init instruction not found!`);
448
+ continue;
449
+ }
450
+
451
+ swapsOpened[escrowHash] = {
452
+ data: SolanaSwapData.fromInstruction(initIx, txoHash),
453
+ getInitTxId: () => Promise.resolve(txSignature),
454
+ getTxBlock: () => Promise.resolve({
455
+ blockHeight: tx.slot,
456
+ blockTime: tx.blockTime
457
+ })
458
+ };
459
+ }
460
+
461
+ if(event.name==="ClaimEvent") {
462
+ const foundSwapData = swapsOpened[escrowHash];
463
+ delete swapsOpened[escrowHash];
464
+ resultingSwaps[escrowHash] = {
465
+ init: foundSwapData,
466
+ state: {
467
+ type: SwapCommitStateType.PAID,
468
+ getClaimTxId: () => Promise.resolve(txSignature),
469
+ getClaimResult: () => Promise.resolve(Buffer.from(event.data.secret).toString("hex")),
470
+ getTxBlock: () => Promise.resolve({
471
+ blockHeight: tx.slot,
472
+ blockTime: tx.blockTime
473
+ })
474
+ }
475
+ }
476
+ }
477
+
478
+ if(event.name==="RefundEvent") {
479
+ const foundSwapData = swapsOpened[escrowHash];
480
+ delete swapsOpened[escrowHash];
481
+ const isExpired = foundSwapData!=null && await this.isExpired(signer, foundSwapData.data);
482
+ resultingSwaps[escrowHash] = {
483
+ init: foundSwapData,
484
+ state: {
485
+ type: isExpired ? SwapCommitStateType.EXPIRED : SwapCommitStateType.NOT_COMMITED,
486
+ getRefundTxId: () => Promise.resolve(txSignature),
487
+ getTxBlock: () => Promise.resolve({
488
+ blockHeight: tx.slot,
489
+ blockTime: tx.blockTime
490
+ })
491
+ }
492
+ }
493
+ }
494
+ }
495
+
496
+ for(let escrowHash in swapsOpened) {
497
+ const foundSwapData = swapsOpened[escrowHash];
498
+ const isExpired = await this.isExpired(signer, foundSwapData.data);
499
+ resultingSwaps[escrowHash] = {
500
+ init: foundSwapData,
501
+ state: foundSwapData.data.isOfferer(signer) && isExpired
502
+ ? {type: SwapCommitStateType.REFUNDABLE}
503
+ : {type: SwapCommitStateType.COMMITED}
504
+ }
505
+ }
506
+
507
+ return {
508
+ swaps: resultingSwaps,
509
+ latestBlockheight
510
+ }
511
+ }
512
+
361
513
  ////////////////////////////////////////////
362
514
  //// Swap data initializer
363
515
  createSwapData(
@@ -662,28 +814,28 @@ export class SolanaSwapProgram
662
814
  /**
663
815
  * Get the estimated solana fee of the commit transaction
664
816
  */
665
- getCommitFee(swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
817
+ getCommitFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
666
818
  return this.Init.getInitFee(swapData, feeRate);
667
819
  }
668
820
 
669
821
  /**
670
822
  * Get the estimated solana fee of the commit transaction, without any deposits
671
823
  */
672
- getRawCommitFee(swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
824
+ getRawCommitFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
673
825
  return this.Init.getRawInitFee(swapData, feeRate);
674
826
  }
675
827
 
676
828
  /**
677
829
  * Get the estimated solana transaction fee of the refund transaction
678
830
  */
679
- getRefundFee(swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
831
+ getRefundFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
680
832
  return this.Refund.getRefundFee(swapData, feeRate);
681
833
  }
682
834
 
683
835
  /**
684
836
  * Get the estimated solana transaction fee of the refund transaction
685
837
  */
686
- getRawRefundFee(swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
838
+ getRawRefundFee(signer: string, swapData: SolanaSwapData, feeRate?: string): Promise<bigint> {
687
839
  return this.Refund.getRawRefundFee(swapData, feeRate);
688
840
  }
689
841
 
@@ -3,6 +3,7 @@ import {AbstractSigner} from "@atomiqlabs/base";
3
3
  import {PublicKey, Signer} from "@solana/web3.js";
4
4
 
5
5
  export class SolanaSigner implements AbstractSigner {
6
+ type = "AtomiqAbstractSigner" as const;
6
7
 
7
8
  wallet: Wallet;
8
9
  keypair?: Signer;