UNPKG

@atomiqlabs/sdk

Version:

atomiq labs SDK for cross-chain swaps between smart chains and bitcoin

272 lines (240 loc) 8.96 kB
import {isISwapInit, ISwap, ISwapInit} from "../ISwap"; import { ChainType, SwapCommitState, SwapCommitStateType, SwapData, SwapExpiredState, SwapNotCommitedState, SwapPaidState } from "@atomiqlabs/base"; import {IEscrowSwapDefinition, IEscrowSwapWrapper} from "./IEscrowSwapWrapper"; import {Buffer} from "buffer"; import {timeoutPromise} from "../../utils/TimeoutUtils"; export type IEscrowSwapInit<T extends SwapData> = ISwapInit & { data?: T, }; export function isIEscrowSwapInit<T extends SwapData>(obj: any): obj is IEscrowSwapInit<T> { return typeof obj === 'object' && (obj.data == null || typeof obj.data === 'object') && isISwapInit(obj); } /** * Base class for escrow-based swaps (i.e. swaps utilizing PrTLC and HTLC primitives) * * @category Swaps/Abstract */ export abstract class IEscrowSwap< T extends ChainType = ChainType, D extends IEscrowSwapDefinition<T, IEscrowSwapWrapper<T, D>, IEscrowSwap<T, D, S>> = IEscrowSwapDefinition<T, IEscrowSwapWrapper<T, any>, IEscrowSwap<T, any, any>>, S extends number = number > extends ISwap<T, D, S> { /** * @internal */ _data?: T["Data"]; /** * Transaction IDs for the swap on the smart chain side * @internal */ _commitTxId?: string; /** * @internal */ _refundTxId?: string; /** * @internal */ _claimTxId?: string; /** * @internal */ protected _contract: T["Contract"]; protected constructor(wrapper: D["Wrapper"], obj: any); protected constructor(wrapper: D["Wrapper"], swapInit: IEscrowSwapInit<T["Data"]>); protected constructor( wrapper: D["Wrapper"], swapInitOrObj: IEscrowSwapInit<T["Data"]> | any, ) { super(wrapper, swapInitOrObj); if(isIEscrowSwapInit(swapInitOrObj)) { this._data = swapInitOrObj.data; } else { if(swapInitOrObj.data!=null) this._data = new (wrapper._swapDataDeserializer(this._contractVersion))(swapInitOrObj.data); this._commitTxId = swapInitOrObj.commitTxId; this._claimTxId = swapInitOrObj.claimTxId; this._refundTxId = swapInitOrObj.refundTxId; } this._contract = wrapper._contract(this._contractVersion); } /** * Returns the swap escrow data for this swap * * @internal */ protected abstract getSwapData(): T["Data"]; ////////////////////////////// //// Identifiers /** * Returns the identification hash of the swap, usually claim data hash, but can be overriden, e.g. for * lightning swaps the identifier hash is used instead of claim data hash * * @internal */ protected getIdentifierHash(): Buffer { const claimHashBuffer = Buffer.from(this.getClaimHash(), "hex"); if(this._randomNonce==null) return claimHashBuffer; return Buffer.concat([claimHashBuffer, Buffer.from(this._randomNonce, "hex")]); } /** * Returns the identification hash of the swap, usually claim data hash, but can be overriden, e.g. for * lightning swaps the identifier hash is used instead of claim data hash * * @internal */ protected getIdentifierHashString(): string { const identifierHash = this.getIdentifierHash(); return identifierHash.toString("hex"); } /** * @inheritDoc * @internal */ _getEscrowHash(): string | null { return this._data?.getEscrowHash() ?? null; } /** * Returns the escrow hash - i.e. hash of the escrow data */ getEscrowHash(): string | null { return this._getEscrowHash(); } /** * Returns the claim data hash specifying the claim path of the escrow - i.e. hash passed to the claim handler */ getClaimHash(): string { return this.getSwapData().getClaimHash(); } /** * @inheritDoc */ getId(): string { return this.getIdentifierHashString(); } /** * Returns the smart chain transaction ID of the tx that initiated the escrow */ getEscrowInitTxId(): string | undefined { return this._commitTxId; } /** * Returns the smart chain transaction ID of the tx that claimed (settled) the escrow */ getEscrowClaimTxId(): string | undefined { return this._claimTxId; } /** * Returns the smart chain transaction ID of the tx that refunded the escrow */ getEscrowRefundTxId(): string | undefined { return this._refundTxId; } ////////////////////////////// //// Watchdogs /** * Periodically checks the chain to see whether the swap is committed * * @param intervalSeconds How often to check (in seconds), default to 5s * @param abortSignal * @internal */ protected async watchdogWaitTillCommited(intervalSeconds?: number, abortSignal?: AbortSignal): Promise<SwapCommitState | null> { if(this._data==null) throw new Error("Tried to await commitment but data is null, invalid state?"); intervalSeconds ??= 5; let status: SwapCommitState = {type: SwapCommitStateType.NOT_COMMITED}; while(status?.type===SwapCommitStateType.NOT_COMMITED) { await timeoutPromise(intervalSeconds*1000, abortSignal); try { status = await this._contract.getCommitStatus(this._getInitiator(), this._data); if( status?.type===SwapCommitStateType.NOT_COMMITED && await this._verifyQuoteDefinitelyExpired() ) return null; } catch (e) { this.logger.error("watchdogWaitTillCommited(): Error when fetching commit status or signature expiry: ", e); } } if(abortSignal!=null) abortSignal.throwIfAborted(); return status?.type===SwapCommitStateType.EXPIRED ? null : status; } /** * Periodically checks the chain to see whether the swap was finished (claimed or refunded) * * @param intervalSeconds How often to check (in seconds), default to 5s * @param abortSignal * @internal */ protected async watchdogWaitTillResult(intervalSeconds?: number, abortSignal?: AbortSignal): Promise< SwapPaidState | SwapExpiredState | SwapNotCommitedState > { if(this._data==null) throw new Error("Tried to await result but data is null, invalid state?"); intervalSeconds ??= 5; let status: SwapCommitState = {type: SwapCommitStateType.COMMITED}; while(status?.type===SwapCommitStateType.COMMITED || status?.type===SwapCommitStateType.REFUNDABLE) { await timeoutPromise(intervalSeconds*1000, abortSignal); try { status = await this._contract.getCommitStatus(this._getInitiator(), this._data); } catch (e) { this.logger.error("watchdogWaitTillResult(): Error when fetching commit status: ", e); } } if(abortSignal!=null) abortSignal.throwIfAborted(); return status; } ////////////////////////////// //// Helpers for batched swap checks /** * Whether on-chain state should be fetched for this swap * @internal */ abstract _shouldFetchOnchainState(): boolean; /** * Whether expiration status of the swap quote should be checked for this swap * @internal */ abstract _shouldFetchExpiryStatus(): boolean; /** * @inheritDoc * * @param save Whether to save the new swap state or not * @param quoteDefinitelyExpired Optionally pass whether the quote is definitely expired from a batch pre-fetch, * fetched on-demand if not provided * @param commitStatus Optionally pass the quote on-chain state from a batch pre-fetch, fetched on-demand if * not provided * * @internal */ abstract _sync(save?: boolean, quoteDefinitelyExpired?: boolean, commitStatus?: SwapCommitState): Promise<boolean>; /** * Forcibly overrides current swap state from fetched on-chain swap state * * @param commitStatus Swap state fetched from the smart chain * * @internal */ abstract _forciblySetOnchainState(commitStatus: SwapCommitState): Promise<boolean>; /** * @inheritDoc */ serialize(): any { return { ...super.serialize(), data: this._data!=null ? this._data.serialize() : null, commitTxId: this._commitTxId, claimTxId: this._claimTxId, refundTxId: this._refundTxId } }; }