UNPKG

@atomiqlabs/chain-starknet

Version:
720 lines (660 loc) 30.3 kB
import { ChainEvent, ChainEvents, ChainSwapType, ClaimEvent, EventListener, InitializeEvent, RefundEvent, SpvVaultClaimEvent, SpvVaultCloseEvent, SpvVaultDepositEvent, SpvVaultFrontEvent, SpvVaultOpenEvent } from "@atomiqlabs/base"; import {StarknetSwapData} from "../swaps/StarknetSwapData"; import { bigNumberishToBuffer, bytes31SpanToBuffer, findLastIndex, getLogger, onceAsync, toBigInt, toHex } from "../../utils/Utils"; import {StarknetSwapContract} from "../swaps/StarknetSwapContract"; import { BigNumberish, BlockTag, hash, Provider, SubscriptionStarknetEventsEvent, TransactionFinalityStatus, WebSocketChannel } from "starknet"; import {StarknetAbiEvent} from "../contract/modules/StarknetContractEvents"; import {EscrowManagerAbiType} from "../swaps/EscrowManagerAbi"; import {ExtractAbiFunctionNames} from "abi-wan-kanabi/dist/kanabi"; import {IClaimHandler} from "../swaps/handlers/claim/ClaimHandlers"; import {StarknetSpvVaultContract} from "../spv_swap/StarknetSpvVaultContract"; import {StarknetChainInterface} from "../chain/StarknetChainInterface"; import {SpvVaultContractAbiType} from "../spv_swap/SpvVaultContractAbi"; import {sha256} from "@noble/hashes/sha2"; import {Buffer} from "buffer"; const PROCESSED_EVENTS_BACKLOG = 5000; const LOGS_SLIDING_WINDOW = 60; /** * Current state of the starknet event listener, contains the last processed * block number and transaction hash of the last processed event * * @category Events */ export type StarknetEventListenerState = { /** * Block number of the last processed event */ lastBlockNumber: number, /** * Transaction hash of the last processed event */ lastTxHash?: string }; /** * Starknet on-chain event handler for front-end systems without access to fs, uses WS or long-polling to subscribe, might lose * out on some events if the network is unreliable, front-end systems should take this into consideration and not * rely purely on events * * @category Events */ export class StarknetChainEventsBrowser implements ChainEvents<StarknetSwapData, StarknetEventListenerState[]> { private eventsProcessing: { [eventFingerprint: string]: Promise<void> } = {}; private processedEvents: Set<string> = new Set(); /** * @internal */ protected readonly Chain: StarknetChainInterface; /** * @internal */ protected readonly listeners: EventListener<StarknetSwapData>[] = []; /** * @internal */ protected readonly wsChannel?: WebSocketChannel; /** * @internal */ protected readonly provider: Provider; /** * @internal */ protected readonly starknetSwapContract: StarknetSwapContract; /** * @internal */ protected readonly starknetSpvVaultContract: StarknetSpvVaultContract; /** * @internal */ protected readonly logger = getLogger("StarknetChainEventsBrowser: "); /** * @internal */ protected escrowContractSubscription?: SubscriptionStarknetEventsEvent; /** * @internal */ protected spvVaultContractSubscription?: SubscriptionStarknetEventsEvent; /** * @internal */ protected stopped: boolean = true; /** * @internal */ protected pollIntervalSeconds: number; private timeout: any; constructor( chainInterface: StarknetChainInterface, starknetSwapContract: StarknetSwapContract, starknetSpvVaultContract: StarknetSpvVaultContract, pollIntervalSeconds: number = 5 ) { this.Chain = chainInterface; this.wsChannel = chainInterface.wsChannel; this.provider = chainInterface.provider; this.starknetSwapContract = starknetSwapContract; this.starknetSpvVaultContract = starknetSpvVaultContract; this.pollIntervalSeconds = pollIntervalSeconds; } /** * * @param event * @private */ private getEventFingerprint(event: {keys: string[], data: string[], txHash: string}): string { const eventData = Buffer.concat([ ...event.keys.map(value => bigNumberishToBuffer(value, 32)), ...event.data.map(value => bigNumberishToBuffer(value, 32)) ]); const fingerprint = Buffer.from(sha256(eventData)); return event.txHash+":"+fingerprint.toString("hex"); } /** * * @param event * @private */ private addProcessedEvent(event: {keys: string[], data: string[], txHash: string}) { this.processedEvents.add(this.getEventFingerprint(event)); if(this.processedEvents.size > PROCESSED_EVENTS_BACKLOG) this.processedEvents.delete(this.processedEvents.keys().next().value!); } /** * * @param eventOrFingerprint * @private */ private isEventProcessed(eventOrFingerprint: {keys: string[], data: string[], txHash: string} | string): boolean { const eventFingerprint: string = typeof(eventOrFingerprint)==="string" ? eventOrFingerprint : this.getEventFingerprint(eventOrFingerprint); return this.processedEvents.has(eventFingerprint); } /** * Returns async getter for fetching on-demand initialize event swap data * * @param event * @param claimHandler * @private * @returns {() => Promise<StarknetSwapData>} getter to be passed to InitializeEvent constructor */ private getSwapDataGetter( event: StarknetAbiEvent<EscrowManagerAbiType, "escrow_manager::events::Initialize">, claimHandler: IClaimHandler<any, any> ): () => Promise<StarknetSwapData | null> { return async () => { const trace = await this.Chain.Transactions.traceTransaction(event.txHash, event.blockHash); if(trace==null) return null; return this.starknetSwapContract.findInitSwapData(trace, event.params.escrow_hash, claimHandler); } } /** * * @param event * @private */ private parseInitializeEvent( event: StarknetAbiEvent<EscrowManagerAbiType, "escrow_manager::events::Initialize"> ): InitializeEvent<StarknetSwapData> | null { const escrowHashBuffer = bigNumberishToBuffer(event.params.escrow_hash, 32); const escrowHash = escrowHashBuffer.toString("hex"); const claimHandlerHex = toHex(event.params.claim_handler); const claimHandler = this.starknetSwapContract.claimHandlersByAddress[claimHandlerHex]; if(claimHandler==null) { this.logger.warn("parseInitializeEvent("+escrowHash+"): Unknown claim handler with claim: "+claimHandlerHex); return null; } const swapType: ChainSwapType = claimHandler.getType(); this.logger.debug("InitializeEvent claimHash: "+toHex(event.params.claim_data)+" escrowHash: "+escrowHash); return new InitializeEvent<StarknetSwapData>( escrowHash, swapType, onceAsync<StarknetSwapData | null>(this.getSwapDataGetter(event, claimHandler)) ); } /** * * @param event * @private */ private parseRefundEvent( event: StarknetAbiEvent<EscrowManagerAbiType, "escrow_manager::events::Refund"> ): RefundEvent<StarknetSwapData> { const escrowHashBuffer = bigNumberishToBuffer(event.params.escrow_hash, 32); const escrowHash = escrowHashBuffer.toString("hex"); this.logger.debug("RefundEvent claimHash: "+toHex(event.params.claim_data)+" escrowHash: "+escrowHash); return new RefundEvent<StarknetSwapData>(escrowHash); } /** * * @param event * @private */ private parseClaimEvent( event: StarknetAbiEvent<EscrowManagerAbiType, "escrow_manager::events::Claim"> ): ClaimEvent<StarknetSwapData> | null { const escrowHashBuffer = bigNumberishToBuffer(event.params.escrow_hash, 32); const escrowHash = escrowHashBuffer.toString("hex"); const claimHandlerHex = toHex(event.params.claim_handler); const claimHandler = this.starknetSwapContract.claimHandlersByAddress[claimHandlerHex]; if(claimHandler==null) { this.logger.warn("parseClaimEvent("+escrowHash+"): Unknown claim handler with claim: "+claimHandlerHex); return null; } const witnessResult = claimHandler.parseWitnessResult(event.params.witness_result); this.logger.debug("ClaimEvent claimHash: "+toHex(event.params.claim_data)+ " witnessResult: "+witnessResult+" escrowHash: "+escrowHash); return new ClaimEvent<StarknetSwapData>(escrowHash, witnessResult); } /** * * @param event * @private */ private parseSpvOpenEvent( event: StarknetAbiEvent<SpvVaultContractAbiType, "spv_swap_vault::events::Opened"> ): SpvVaultOpenEvent { const owner = toHex(event.params.owner); const vaultId = toBigInt(event.params.vault_id); const btcTxId = bigNumberishToBuffer(event.params.btc_tx_hash, 32).reverse().toString("hex"); const vout = Number(toBigInt(event.params.vout)); this.logger.debug("SpvOpenEvent owner: "+owner+" vaultId: "+vaultId+" utxo: "+btcTxId+":"+vout); return new SpvVaultOpenEvent(owner, vaultId, btcTxId, vout); } /** * * @param event * @private */ private parseSpvDepositEvent( event: StarknetAbiEvent<SpvVaultContractAbiType, "spv_swap_vault::events::Deposited"> ): SpvVaultDepositEvent { const owner = toHex(event.params.owner); const vaultId = toBigInt(event.params.vault_id); const amounts = [toBigInt(event.params.amounts["0"] as BigNumberish), toBigInt(event.params.amounts["1"] as BigNumberish)]; const depositCount = Number(toBigInt(event.params.deposit_count)); this.logger.debug("SpvDepositEvent owner: "+owner+" vaultId: "+vaultId+" depositCount: "+depositCount+" amounts: ", amounts); return new SpvVaultDepositEvent(owner, vaultId, amounts, depositCount); } /** * * @param event * @private */ private parseSpvFrontEvent( event: StarknetAbiEvent<SpvVaultContractAbiType, "spv_swap_vault::events::Fronted"> ): SpvVaultFrontEvent { const owner = toHex(event.params.owner); const vaultId = toBigInt(event.params.vault_id); const btcTxId = bigNumberishToBuffer(event.params.btc_tx_hash, 32).reverse().toString("hex"); const recipient = toHex(event.params.recipient); const executionHash = toHex(event.params.execution_hash); const amounts = [toBigInt(event.params.amounts["0"] as BigNumberish), toBigInt(event.params.amounts["1"] as BigNumberish)]; const frontingAddress = toHex(event.params.caller); this.logger.debug("SpvFrontEvent owner: "+owner+" vaultId: "+vaultId+" btcTxId: "+btcTxId+ " recipient: "+recipient+" frontedBy: "+frontingAddress+" amounts: ", amounts); return new SpvVaultFrontEvent(owner, vaultId, btcTxId, recipient, executionHash, amounts, frontingAddress); } /** * * @param event * @private */ private parseSpvClaimEvent( event: StarknetAbiEvent<SpvVaultContractAbiType, "spv_swap_vault::events::Claimed"> ): SpvVaultClaimEvent { const owner = toHex(event.params.owner); const vaultId = toBigInt(event.params.vault_id); const btcTxId = bigNumberishToBuffer(event.params.btc_tx_hash, 32).reverse().toString("hex"); const recipient = toHex(event.params.recipient); const executionHash = toHex(event.params.execution_hash); const amounts = [toBigInt(event.params.amounts["0"] as BigNumberish), toBigInt(event.params.amounts["1"] as BigNumberish)]; const caller = toHex(event.params.caller); const frontingAddress = toHex(event.params.fronting_address); const withdrawCount = Number(toBigInt(event.params.withdraw_count)); this.logger.debug("SpvClaimEvent owner: "+owner+" vaultId: "+vaultId+" btcTxId: "+btcTxId+" withdrawCount: "+withdrawCount+ " recipient: "+recipient+" frontedBy: "+frontingAddress+" claimedBy: "+caller+" amounts: ", amounts); return new SpvVaultClaimEvent(owner, vaultId, btcTxId, recipient, executionHash, amounts, caller, frontingAddress, withdrawCount); } /** * * @param event * @private */ private parseSpvCloseEvent( event: StarknetAbiEvent<SpvVaultContractAbiType, "spv_swap_vault::events::Closed"> ): SpvVaultCloseEvent { const owner = toHex(event.params.owner); const vaultId = toBigInt(event.params.vault_id); const btcTxId = bigNumberishToBuffer(event.params.btc_tx_hash, 32).reverse().toString("hex"); const error = bigNumberishToBuffer(event.params.error).toString(); return new SpvVaultCloseEvent(owner, vaultId, btcTxId, error); } /** * Processes event as received from the chain, parses it & calls event listeners * * @param events * @param currentBlockNumber * @param currentBlockTimestamp * @private */ private async processEvents( events : (StarknetAbiEvent< EscrowManagerAbiType, "escrow_manager::events::Initialize" | "escrow_manager::events::Refund" | "escrow_manager::events::Claim" > | StarknetAbiEvent< SpvVaultContractAbiType, "spv_swap_vault::events::Opened" | "spv_swap_vault::events::Deposited" | "spv_swap_vault::events::Fronted" | "spv_swap_vault::events::Claimed" | "spv_swap_vault::events::Closed" >)[], currentBlockNumber?: number, currentBlockTimestamp?: number ) { const blockTimestampsCache: {[blockNumber: string]: number} = {}; const getBlockTimestamp: (blockNumber?: number) => Promise<number> = async (blockNumber?: number)=> { //Use current timestamp for events without block height (probably pre-confirmed) if(blockNumber==null) return Math.floor(Date.now() / 1000); if(currentBlockTimestamp!=null && blockNumber===currentBlockNumber) return currentBlockTimestamp; const blockNumberString = blockNumber.toString(); blockTimestampsCache[blockNumberString] ??= await this.Chain.Blocks.getBlockTime(blockNumber); return blockTimestampsCache[blockNumberString]; } for(let event of events) { const eventIdentifier = this.getEventFingerprint(event); if(this.isEventProcessed(eventIdentifier)) { this.logger.debug("processEvents(): skipping already processed event: "+eventIdentifier); continue; } let parsedEvent: ChainEvent<StarknetSwapData> | null; switch(event.name) { case "escrow_manager::events::Claim": parsedEvent = this.parseClaimEvent(event as any); break; case "escrow_manager::events::Refund": parsedEvent = this.parseRefundEvent(event as any); break; case "escrow_manager::events::Initialize": parsedEvent = this.parseInitializeEvent(event as any); break; case "spv_swap_vault::events::Opened": parsedEvent = this.parseSpvOpenEvent(event as any); break; case "spv_swap_vault::events::Deposited": parsedEvent = this.parseSpvDepositEvent(event as any); break; case "spv_swap_vault::events::Fronted": parsedEvent = this.parseSpvFrontEvent(event as any); break; case "spv_swap_vault::events::Claimed": parsedEvent = this.parseSpvClaimEvent(event as any); break; case "spv_swap_vault::events::Closed": parsedEvent = this.parseSpvCloseEvent(event as any); break; } if(this.eventsProcessing[eventIdentifier]!=null) { this.logger.debug("processEvents(): awaiting event that is currently processing: "+eventIdentifier); await this.eventsProcessing[eventIdentifier]; continue; } const promise = (async() => { if(parsedEvent==null) return; //We are not trusting pre-confs for events, so this shall never happen if(event.blockNumber==null) throw new Error("Event block number cannot be null!"); const timestamp = await getBlockTimestamp(event.blockNumber); parsedEvent.meta = { blockTime: timestamp, txId: event.txHash, timestamp //Maybe deprecated } as any; const eventsArr = [parsedEvent]; for(let listener of this.listeners) { await listener(eventsArr); } this.addProcessedEvent(event); })(); this.eventsProcessing[eventIdentifier] = promise; try { await promise; delete this.eventsProcessing[eventIdentifier]; } catch (e) { delete this.eventsProcessing[eventIdentifier]; throw e; } } } /** * * @param currentBlock * @param lastTxHash * @param lastBlockNumber * @private */ private async checkEventsEcrowManager( currentBlock: {timestamp: number, block_number: number}, lastTxHash?: string, lastBlockNumber?: number ): Promise<StarknetEventListenerState> { const currentBlockNumber: number = currentBlock.block_number; lastBlockNumber ??= currentBlockNumber; if(currentBlockNumber < lastBlockNumber) { this.logger.warn(`checkEventsEscrowManager(): Sanity check triggered - not processing events, currentBlock: ${currentBlockNumber}, lastBlock: ${lastBlockNumber}`); return {lastTxHash, lastBlockNumber}; } // this.logger.debug("checkEvents(EscrowManager): Requesting logs: "+logStartHeight+"...pending"); let events = await this.starknetSwapContract._Events.getContractBlockEvents( ["escrow_manager::events::Initialize", "escrow_manager::events::Claim", "escrow_manager::events::Refund"], [], lastBlockNumber, null ); if(lastTxHash!=null) { const latestProcessedEventIndex = findLastIndex(events, val => val.txHash===lastTxHash); if(latestProcessedEventIndex!==-1) { events.splice(0, latestProcessedEventIndex+1); this.logger.debug("checkEvents(EscrowManager): Splicing processed events, resulting size: "+events.length); } } if(events.length>0) { await this.processEvents(events, currentBlock?.block_number, currentBlock?.timestamp); const lastProcessed = events[events.length-1]; lastTxHash = lastProcessed.txHash; const lastProcessedWithBlockHeightIndex = findLastIndex(events, val => val.blockNumber!=null); if(lastProcessedWithBlockHeightIndex!==-1) { const lastProcessedWithBlockHeight = events[lastProcessedWithBlockHeightIndex]; if(lastProcessedWithBlockHeight.blockNumber! > lastBlockNumber) lastBlockNumber = lastProcessedWithBlockHeight.blockNumber!; } } else if(currentBlockNumber - lastBlockNumber > LOGS_SLIDING_WINDOW) { lastTxHash = undefined; lastBlockNumber = currentBlockNumber - LOGS_SLIDING_WINDOW; } return {lastTxHash, lastBlockNumber}; } private async checkEventsSpvVaults(currentBlock: {timestamp: number, block_number: number}, lastTxHash?: string, lastBlockNumber?: number): Promise<StarknetEventListenerState> { const currentBlockNumber: number = currentBlock.block_number; lastBlockNumber ??= currentBlockNumber; if(currentBlockNumber < lastBlockNumber) { this.logger.warn(`checkEventsSpvVaults(): Sanity check triggered - not processing events, currentBlock: ${currentBlockNumber}, lastBlock: ${lastBlockNumber}`); return {lastTxHash, lastBlockNumber}; } // this.logger.debug("checkEvents(SpvVaults): Requesting logs: "+logStartHeight+"...pending"); let events = await this.starknetSpvVaultContract._Events.getContractBlockEvents( ["spv_swap_vault::events::Opened", "spv_swap_vault::events::Deposited", "spv_swap_vault::events::Closed", "spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed"], [], lastBlockNumber, null ); if(lastTxHash!=null) { const latestProcessedEventIndex = findLastIndex(events, val => val.txHash===lastTxHash); if(latestProcessedEventIndex!==-1) { events.splice(0, latestProcessedEventIndex+1); this.logger.debug("checkEvents(SpvVaults): Splicing processed events, resulting size: "+events.length); } } if(events.length>0) { await this.processEvents(events, currentBlock?.block_number, currentBlock?.timestamp); const lastProcessed = events[events.length-1]; lastTxHash = lastProcessed.txHash; const lastProcessedWithBlockHeightIndex = findLastIndex(events, val => val.blockNumber!=null); if(lastProcessedWithBlockHeightIndex!==-1) { const lastProcessedWithBlockHeight = events[lastProcessedWithBlockHeightIndex]; if(lastProcessedWithBlockHeight.blockNumber! > lastBlockNumber) lastBlockNumber = lastProcessedWithBlockHeight.blockNumber!; } } else if(currentBlockNumber - lastBlockNumber > LOGS_SLIDING_WINDOW) { lastTxHash = undefined; lastBlockNumber = currentBlockNumber - LOGS_SLIDING_WINDOW; } return {lastTxHash, lastBlockNumber}; } /** * @inheritDoc */ async poll(lastState?: StarknetEventListenerState[]): Promise<StarknetEventListenerState[]> { lastState ??= []; const currentBlock = await this.Chain.Blocks.getBlock(BlockTag.LATEST); const resultEscrow = await this.checkEventsEcrowManager(currentBlock as any, lastState?.[0]?.lastTxHash, lastState?.[0]?.lastBlockNumber); const resultSpvVault = await this.checkEventsSpvVaults(currentBlock as any, lastState?.[1]?.lastTxHash, lastState?.[1]?.lastBlockNumber); return [ resultEscrow, resultSpvVault ]; } /** * Sets up event handlers listening for swap events over websocket * * @internal */ protected async setupPoll( lastState?: StarknetEventListenerState[], saveLatestProcessedBlockNumber?: (newState: StarknetEventListenerState[]) => Promise<void> ) { let func: () => Promise<void>; func = async () => { await this.poll(lastState).then(newState => { lastState = newState; if(saveLatestProcessedBlockNumber!=null) return saveLatestProcessedBlockNumber(newState); }).catch(e => { this.logger.error("setupPoll(): Failed to fetch starknet log: ", e); }); if(this.stopped) return; this.timeout = setTimeout(func, this.pollIntervalSeconds*1000); }; await func(); } /** * @internal */ protected wsStarted: boolean = false; /** * * @private */ private async subscribeWsEscrowEvents() { let subscription: SubscriptionStarknetEventsEvent | undefined; do { try { subscription = await this.wsChannel!.subscribeEvents({ fromAddress: this.starknetSwapContract.contract.address, keys: this.starknetSwapContract._Events.toFilter( ["escrow_manager::events::Initialize", "escrow_manager::events::Claim", "escrow_manager::events::Refund"], [] ), finalityStatus: TransactionFinalityStatus.ACCEPTED_ON_L2 }); } catch (e) { this.logger.error("subscribeWsEscrowEvents(): Failed to subscribe to escrow events, retrying in 10 seconds..."); await new Promise(resolve => setTimeout(resolve, 10*1000)); } } while(subscription==null); subscription.on((event) => { const parsedEvents = this.starknetSwapContract._Events.toStarknetAbiEvents< "escrow_manager::events::Initialize" | "escrow_manager::events::Claim" | "escrow_manager::events::Refund" >([event]); this.processEvents(parsedEvents, event.block_number).catch(e => { console.error(`WS: EscrowContract: Failed to process event ${parsedEvents[0].txHash}:${parsedEvents[0].name}: `, e); }); }); this.escrowContractSubscription = subscription; this.logger.debug("subscribeWsEscrowEvents(): Successfully subscribed to escrow contract WS events"); } /** * * @private */ private async subscribeWsSpvVaultEvents() { let subscription: SubscriptionStarknetEventsEvent | undefined; do { try { subscription = await this.wsChannel!.subscribeEvents({ fromAddress: this.starknetSpvVaultContract.contract.address, keys: this.starknetSpvVaultContract._Events.toFilter( ["spv_swap_vault::events::Opened", "spv_swap_vault::events::Deposited", "spv_swap_vault::events::Closed", "spv_swap_vault::events::Fronted", "spv_swap_vault::events::Claimed"], [] ), finalityStatus: TransactionFinalityStatus.ACCEPTED_ON_L2 }); } catch (e) { this.logger.error("subscribeWsSpvVaultEvents(): Failed to subscribe to spv vault events, retrying in 10 seconds..."); await new Promise(resolve => setTimeout(resolve, 10*1000)); } } while(subscription==null); subscription.on((event) => { const parsedEvents = this.starknetSpvVaultContract._Events.toStarknetAbiEvents< "spv_swap_vault::events::Opened" | "spv_swap_vault::events::Deposited" | "spv_swap_vault::events::Closed" | "spv_swap_vault::events::Fronted" | "spv_swap_vault::events::Claimed" >([event]); this.processEvents(parsedEvents, event.block_number).catch(e => { console.error(`WS: SpvVaultContract: Failed to process event ${parsedEvents[0].txHash}:${parsedEvents[0].name}: `, e); }); }); this.spvVaultContractSubscription = subscription; this.logger.debug("subscribeWsSpvVaultEvents(): Successfully subscribed to spv vault contract WS events"); } /** * @internal */ protected async setupWebsocket() { if(this.wsChannel==null) throw new Error("Tried to setup websocket subscription on a provider without WS"); this.wsStarted = true; this.wsChannel.on("open", () => { this.logger.info("setupWebsocket(): Websocket connection opened!"); }); this.wsChannel.on("close", () => { this.logger.warn("setupWebsocket(): Websocket connection closed!"); }); this.wsChannel.on("error", (err) => { this.logger.error("setupWebsocket(): Websocket connection error: ", err); }); //We don't await these, since they might block indefinitely this.subscribeWsEscrowEvents(); this.subscribeWsSpvVaultEvents(); } /** * @inheritDoc */ async init(noAutomaticPoll?: boolean): Promise<void> { if(noAutomaticPoll) return; this.stopped = false; if(this.wsChannel!=null) { this.logger.debug("init(): WS channel detected, setting up websocket-based subscription!"); await this.setupWebsocket(); } else { this.logger.debug("init(): Setting up HTTP polling events subscription!"); await this.setupPoll(); } } /** * Stops all event subscriptions and timers */ async stop(): Promise<void> { this.stopped = true; if(this.timeout!=null) clearTimeout(this.timeout); if(this.wsStarted) { if(this.escrowContractSubscription!=null) await this.escrowContractSubscription.unsubscribe(); if(this.spvVaultContractSubscription!=null) await this.spvVaultContractSubscription.unsubscribe(); this.wsStarted = false; } } /** * @inheritDoc */ registerListener(cbk: EventListener<StarknetSwapData>): void { this.listeners.push(cbk); } /** * @inheritDoc */ unregisterListener(cbk: EventListener<StarknetSwapData>): boolean { const index = this.listeners.indexOf(cbk); if(index>=0) { this.listeners.splice(index, 1); return true; } return false; } }