@atomiqlabs/chain-starknet 8.0.13 → 8.1.7

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.
@@ -10,15 +10,15 @@ import {
10
10
  TransactionConfirmationOptions
11
11
  } from "@atomiqlabs/base";
12
12
  import {Buffer} from "buffer";
13
- import {EscrowManagerAbi} from "./EscrowManagerAbi";
13
+ import {EscrowManagerAbi, EscrowManagerAbiType} from "./EscrowManagerAbi";
14
14
  import {StarknetContractBase} from "../contract/StarknetContractBase";
15
- import {StarknetTx} from "../chain/modules/StarknetTransactions";
15
+ import {StarknetTraceCall, StarknetTx} from "../chain/modules/StarknetTransactions";
16
16
  import {StarknetSigner} from "../wallet/StarknetSigner";
17
- import {BigNumberish, constants, logger} from "starknet";
17
+ import {BigNumberish, constants, hash, logger} from "starknet";
18
18
  import {StarknetChainInterface} from "../chain/StarknetChainInterface";
19
19
  import {StarknetBtcRelay} from "../btcrelay/StarknetBtcRelay";
20
20
  import {StarknetSwapData} from "./StarknetSwapData";
21
- import {bigNumberishToBuffer, toHex} from "../../utils/Utils";
21
+ import {bigNumberishToBuffer, bytes31SpanToBuffer, toBigInt, toHex} from "../../utils/Utils";
22
22
  import {TimelockRefundHandler} from "./handlers/refund/TimelockRefundHandler";
23
23
  import {StarknetLpVault} from "./modules/StarknetLpVault";
24
24
  import {StarknetPreFetchVerification, StarknetSwapInit} from "./modules/StarknetSwapInit";
@@ -28,6 +28,8 @@ import {StarknetSwapClaim} from "./modules/StarknetSwapClaim";
28
28
  import {IHandler} from "./handlers/IHandler";
29
29
  import {StarknetBtcStoredHeader} from "../btcrelay/headers/StarknetBtcStoredHeader";
30
30
  import {sha256} from "@noble/hashes/sha2";
31
+ import {StarknetAbiEvent} from "../contract/modules/StarknetContractEvents";
32
+ import {ExtractAbiFunctionNames} from "abi-wan-kanabi/dist/kanabi";
31
33
 
32
34
  const ESCROW_STATE_COMMITTED = 1;
33
35
  const ESCROW_STATE_CLAIMED = 2;
@@ -38,6 +40,11 @@ const swapContractAddreses = {
38
40
  [constants.StarknetChainId.SN_MAIN]: "0x04f278e1f19e495c3b1dd35ef307c4f7510768ed95481958fbae588bd173f79a"
39
41
  };
40
42
 
43
+ const swapContractDeploymentHeights = {
44
+ [constants.StarknetChainId.SN_SEPOLIA]: 1118142,
45
+ [constants.StarknetChainId.SN_MAIN]: 1617247
46
+ };
47
+
41
48
  const defaultClaimAddresses = {
42
49
  [constants.StarknetChainId.SN_SEPOLIA]: {
43
50
  [ChainSwapType.HTLC]: "0x04a57ea54d4637c352aad1bbee046868926a11702216a0aaf7eeec1568be2d7b",
@@ -127,6 +134,9 @@ export class StarknetSwapContract
127
134
 
128
135
  readonly btcRelay: StarknetBtcRelay<any>;
129
136
 
137
+ protected readonly initFunctionName: ExtractAbiFunctionNames<EscrowManagerAbiType> = "initialize";
138
+ protected readonly initEntryPointSelector = BigInt(hash.starknetKeccak(this.initFunctionName));
139
+
130
140
  /**
131
141
  * Constructs the swap contract (escrow manager)
132
142
  *
@@ -134,6 +144,7 @@ export class StarknetSwapContract
134
144
  * @param btcRelay Btc relay light client contract
135
145
  * @param contractAddress Optional underlying contract address (default is used otherwise)
136
146
  * @param _handlerAddresses Optional handler addresses (defaults are used otherwise)
147
+ * @param contractDeploymentHeight The height at which this contract was deployed (default is used otherwise)
137
148
  */
138
149
  constructor(
139
150
  chainInterface: StarknetChainInterface,
@@ -146,9 +157,16 @@ export class StarknetSwapContract
146
157
  claim?: {
147
158
  [type in ChainSwapType]?: string
148
159
  }
149
- }
160
+ },
161
+ contractDeploymentHeight?: number
150
162
  ) {
151
- super(chainInterface, contractAddress, EscrowManagerAbi);
163
+ super(
164
+ chainInterface, contractAddress, EscrowManagerAbi,
165
+ contractDeploymentHeight ??
166
+ (swapContractAddreses[chainInterface.starknetChainId]===contractAddress
167
+ ? swapContractDeploymentHeights[chainInterface.starknetChainId]
168
+ : undefined)
169
+ );
152
170
  this.Init = new StarknetSwapInit(chainInterface, this);
153
171
  this.Refund = new StarknetSwapRefund(chainInterface, this);
154
172
  this.Claim = new StarknetSwapClaim(chainInterface, this);
@@ -446,6 +464,186 @@ export class StarknetSwapContract
446
464
  return result;
447
465
  }
448
466
 
467
+ /**
468
+ * @inheritDoc
469
+ */
470
+ async getHistoricalSwaps(signer: string, startBlockheight?: number): Promise<{
471
+ swaps: {
472
+ [escrowHash: string]: {
473
+ init?: {
474
+ data: StarknetSwapData;
475
+ getInitTxId: () => Promise<string>;
476
+ getTxBlock: () => Promise<{ blockTime: number; blockHeight: number }>
477
+ };
478
+ state: SwapCommitState
479
+ }
480
+ };
481
+ latestBlockheight?: number
482
+ }> {
483
+ const {height: latestBlockheight} = await this.Chain.getFinalizedBlock();
484
+
485
+ const swapsOpened: {
486
+ [escrowHash: string]: {
487
+ data: Promise<StarknetSwapData | null>,
488
+ getInitTxId: () => Promise<string>,
489
+ getTxBlock: () => Promise<{
490
+ blockTime: number,
491
+ blockHeight: number
492
+ }>
493
+ }
494
+ } = {};
495
+ const resultingSwaps: {
496
+ [escrowHash: string]: {
497
+ init?: {
498
+ data: StarknetSwapData;
499
+ getInitTxId: () => Promise<string>;
500
+ getTxBlock: () => Promise<{ blockTime: number; blockHeight: number }>
501
+ };
502
+ state: SwapCommitState
503
+ }
504
+ } = {};
505
+
506
+ const promises: Promise<void>[] = [];
507
+
508
+ const processor = async (_event: StarknetAbiEvent<
509
+ EscrowManagerAbiType,
510
+ "escrow_manager::events::Initialize" | "escrow_manager::events::Claim" | "escrow_manager::events::Refund"
511
+ >) => {
512
+ const escrowHash = toHex(_event.params.escrow_hash).substring(2);
513
+ if(_event.name==="escrow_manager::events::Initialize") {
514
+ const event = _event as StarknetAbiEvent<EscrowManagerAbiType, "escrow_manager::events::Initialize">;
515
+ const claimHandlerHex = toHex(event.params.claim_handler);
516
+ const claimHandler = this.claimHandlersByAddress[claimHandlerHex];
517
+ if(claimHandler==null) {
518
+ logger.warn(`getHistoricalSwaps(Initialize): Unknown claim handler in tx ${event.txHash} with claim handler: `+claimHandlerHex);
519
+ return null;
520
+ }
521
+
522
+ swapsOpened[escrowHash] = {
523
+ data: (async () => {
524
+ const txTrace = await this.Chain.Transactions.traceTransaction(event.txHash, event.blockHash);
525
+ if(txTrace==null) {
526
+ logger.warn(`getHistoricalSwaps(Initialize): Cannot get transaction trace for tx ${event.txHash}`);
527
+ return null;
528
+ }
529
+ const data = this.findInitSwapData(txTrace, event.params.escrow_hash, claimHandler);
530
+ if(data==null) {
531
+ logger.warn(`getHistoricalSwaps(Initialize): Cannot parse swap data from tx ${event.txHash} with escrow hash: `+escrowHash);
532
+ return null;
533
+ }
534
+ return data;
535
+ })(),
536
+ getInitTxId: () => Promise.resolve(event.txHash),
537
+ getTxBlock: async () => {
538
+ return {
539
+ blockHeight: event.blockNumber!,
540
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber!)
541
+ }
542
+ }
543
+ }
544
+ }
545
+ if(_event.name==="escrow_manager::events::Claim") {
546
+ const event = _event as StarknetAbiEvent<EscrowManagerAbiType, "escrow_manager::events::Claim">;
547
+ const claimHandlerHex = toHex(event.params.claim_handler);
548
+ const claimHandler = this.claimHandlersByAddress[claimHandlerHex];
549
+ if(claimHandler==null) {
550
+ logger.warn(`getHistoricalSwaps(Claim): Unknown claim handler in tx ${event.txHash} with claim handler: `+claimHandlerHex);
551
+ return null;
552
+ }
553
+
554
+ const foundSwapData = swapsOpened[escrowHash];
555
+ delete swapsOpened[escrowHash];
556
+ promises.push((async() => {
557
+ const data = await foundSwapData?.data;
558
+ resultingSwaps[escrowHash] = {
559
+ init: data==null ? undefined : {
560
+ data,
561
+ getInitTxId: foundSwapData.getInitTxId,
562
+ getTxBlock: foundSwapData.getTxBlock
563
+ },
564
+ state: {
565
+ type: SwapCommitStateType.PAID,
566
+ getClaimTxId: () => Promise.resolve(event.txHash),
567
+ getClaimResult: () => Promise.resolve(claimHandler.parseWitnessResult(event.params.witness_result)),
568
+ getTxBlock: async () => {
569
+ return {
570
+ blockHeight: event.blockNumber!,
571
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber!)
572
+ }
573
+ }
574
+ }
575
+ }
576
+ })());
577
+ }
578
+ if(_event.name==="escrow_manager::events::Refund") {
579
+ const event = _event as StarknetAbiEvent<EscrowManagerAbiType, "escrow_manager::events::Refund">;
580
+ const foundSwapData = swapsOpened[escrowHash];
581
+ delete swapsOpened[escrowHash];
582
+ promises.push((async() => {
583
+ const data = await foundSwapData?.data;
584
+ const isExpired = data!=null && await this.isExpired(signer, data);
585
+ resultingSwaps[escrowHash] = {
586
+ init: data==null ? undefined : {
587
+ data,
588
+ getInitTxId: foundSwapData.getInitTxId,
589
+ getTxBlock: foundSwapData.getTxBlock
590
+ },
591
+ state: {
592
+ type: isExpired ? SwapCommitStateType.EXPIRED : SwapCommitStateType.NOT_COMMITED,
593
+ getRefundTxId: () => Promise.resolve(event.txHash),
594
+ getTxBlock: async () => {
595
+ return {
596
+ blockHeight: event.blockNumber!,
597
+ blockTime: await this.Chain.Blocks.getBlockTime(event.blockNumber!)
598
+ }
599
+ }
600
+ }
601
+ }
602
+ })());
603
+ }
604
+ };
605
+
606
+ //We have to fetch separately the different directions
607
+ await this.Events.findInContractEventsForward(
608
+ ["escrow_manager::events::Initialize", "escrow_manager::events::Claim", "escrow_manager::events::Refund"],
609
+ [signer, null],
610
+ processor,
611
+ startBlockheight
612
+ );
613
+ await this.Events.findInContractEventsForward(
614
+ ["escrow_manager::events::Initialize", "escrow_manager::events::Claim", "escrow_manager::events::Refund"],
615
+ [null, signer],
616
+ processor,
617
+ startBlockheight
618
+ )
619
+
620
+ for(let escrowHash in swapsOpened) {
621
+ const foundSwapData = swapsOpened[escrowHash];
622
+ const data = await foundSwapData.data;
623
+ if(data==null) continue;
624
+ resultingSwaps[escrowHash] = {
625
+ init: {
626
+ data,
627
+ getInitTxId: foundSwapData.getInitTxId,
628
+ getTxBlock: foundSwapData.getTxBlock
629
+ },
630
+ state: data.isOfferer(signer) && await this.isExpired(signer, data)
631
+ ? {type: SwapCommitStateType.REFUNDABLE}
632
+ : {type: SwapCommitStateType.COMMITED}
633
+ }
634
+ }
635
+
636
+ await Promise.all(promises);
637
+
638
+ logger.debug(`getHistoricalSwaps(): Found ${Object.keys(resultingSwaps).length} settled swaps!`);
639
+ logger.debug(`getHistoricalSwaps(): Found ${Object.keys(swapsOpened).length} unsettled swaps!`);
640
+
641
+ return {
642
+ swaps: resultingSwaps,
643
+ latestBlockheight: latestBlockheight ?? startBlockheight
644
+ };
645
+ }
646
+
449
647
  ////////////////////////////////////////////
450
648
  //// Swap data initializer
451
649
  /**
@@ -489,6 +687,44 @@ export class StarknetSwapContract
489
687
  }));
490
688
  }
491
689
 
690
+ /**
691
+ *
692
+ * @param call
693
+ * @param escrowHash
694
+ * @param claimHandler
695
+ * @private
696
+ */
697
+ findInitSwapData(call: StarknetTraceCall, escrowHash: BigNumberish, claimHandler: IClaimHandler<any, any>): StarknetSwapData | null {
698
+ if(
699
+ BigInt(call.contract_address)===BigInt(this.contract.address) &&
700
+ BigInt(call.entry_point_selector)===this.initEntryPointSelector
701
+ ) {
702
+ //Found, check correct escrow hash
703
+ const escrow = StarknetSwapData.fromSerializedFeltArray(call.calldata, claimHandler);
704
+ if(call.calldata.length < 1) throw new Error("Calldata invalid length");
705
+ const signatureLen = Number(toBigInt(call.calldata.shift()!));
706
+ if(call.calldata.length < signatureLen + 2) throw new Error("Calldata invalid length");
707
+ const _signature = call.calldata.splice(0, signatureLen);
708
+ const _timeout = toBigInt(call.calldata.shift()!);
709
+ const extraDataLen = Number(toBigInt(call.calldata.shift()!));
710
+ if(call.calldata.length < extraDataLen) throw new Error("Calldata invalid length");
711
+ const extraData = call.calldata.splice(0, extraDataLen);
712
+ if(call.calldata.length!==0) throw new Error("Calldata not read fully!");
713
+
714
+ if("0x"+escrow.getEscrowHash()===toHex(escrowHash)) {
715
+ if(extraData.length!==0) {
716
+ escrow.setExtraData(bytes31SpanToBuffer(extraData, 42).toString("hex"));
717
+ }
718
+ return escrow;
719
+ }
720
+ }
721
+ for(let _call of call.calls) {
722
+ const found = this.findInitSwapData(_call, escrowHash, claimHandler);
723
+ if(found!=null) return found;
724
+ }
725
+ return null;
726
+ }
727
+
492
728
  ////////////////////////////////////////////
493
729
  //// Utils
494
730
  /**