UNPKG

@atomiqlabs/sdk

Version:

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

218 lines (191 loc) 8.81 kB
import {IEscrowSwap, IEscrowSwapInit, isIEscrowSwapInit} from "./IEscrowSwap"; import {ChainType, SignatureData, SignatureVerificationError, SwapData} from "@atomiqlabs/base"; import {IEscrowSwapDefinition, IEscrowSwapWrapper} from "./IEscrowSwapWrapper"; import {SwapTypeDefinition} from "../ISwapWrapper"; import {TokenAmount, toTokenAmount} from "../../types/TokenAmount"; import {SCToken} from "../../types/Token"; import {timeoutPromise} from "../../utils/TimeoutUtils"; export type IEscrowSelfInitSwapInit<T extends SwapData> = IEscrowSwapInit<T> & { feeRate: string, signatureData?: SignatureData, }; export function isIEscrowSelfInitSwapInit<T extends SwapData>(obj: any): obj is IEscrowSelfInitSwapInit<T> { return typeof obj === "object" && typeof(obj.feeRate) === "string" && (obj.signatureData == null || ( typeof(obj.signatureData) === "object" && typeof(obj.signatureData.prefix)==="string" && typeof(obj.signatureData.timeout)==="string" && typeof(obj.signatureData.signature)==="string" )) && isIEscrowSwapInit(obj); } export type IEscrowSelfInitSwapDefinition<T extends ChainType, W extends IEscrowSwapWrapper<T, any>, S extends IEscrowSelfInitSwap<T>> = SwapTypeDefinition<T, W, S>; /** * Base class for escrow-based swaps (i.e. swaps utilizing PrTLC and HTLC primitives) where the * user needs to initiate the escrow on the smart chain side * * @category Swaps/Abstract */ export abstract class IEscrowSelfInitSwap< T extends ChainType = ChainType, D extends IEscrowSelfInitSwapDefinition<T, IEscrowSwapWrapper<T, D>, IEscrowSelfInitSwap<T, D, S>> = IEscrowSwapDefinition<T, IEscrowSwapWrapper<T, any>, IEscrowSelfInitSwap<T, any, any>>, S extends number = number > extends IEscrowSwap<T, D, S> { /** * Fee rate to be used for the escrow initiation transaction * @internal */ protected readonly feeRate: string; /** * Signature data received from the intermediary (LP) allowing the user * to initiate the swap escrow * @internal */ protected signatureData?: SignatureData; protected constructor(wrapper: D["Wrapper"], obj: any); protected constructor(wrapper: D["Wrapper"], swapInit: IEscrowSelfInitSwapInit<T["Data"]>); protected constructor( wrapper: D["Wrapper"], swapInitOrObj: IEscrowSelfInitSwapInit<T["Data"]> | any, ) { super(wrapper, swapInitOrObj); if(isIEscrowSelfInitSwapInit(swapInitOrObj)) { this.feeRate = swapInitOrObj.feeRate; this.signatureData = swapInitOrObj.signatureData; } else { if(swapInitOrObj.signature!=null) this.signatureData ={ prefix: swapInitOrObj.prefix, timeout: swapInitOrObj.timeout, signature: swapInitOrObj.signature }; this.feeRate = swapInitOrObj.feeRate; } } ////////////////////////////// //// Watchdogs /** * Periodically checks for init signature's expiry * * @param intervalSeconds How often to check (in seconds), default to 5s * @param abortSignal * @internal */ protected async watchdogWaitTillSignatureExpiry(intervalSeconds?: number, abortSignal?: AbortSignal): Promise<void> { if(this._data==null || this.signatureData==null) throw new Error("Tried to await signature expiry but data or signature is null, invalid state?"); intervalSeconds ??= 5; let expired = false while(!expired) { await timeoutPromise(intervalSeconds*1000, abortSignal); try { expired = await this._contract.isInitAuthorizationExpired(this._data, this.signatureData); } catch (e) { this.logger.error("watchdogWaitTillSignatureExpiry(): Error when checking signature expiry: ", e); } } if(abortSignal!=null) abortSignal.throwIfAborted(); } ////////////////////////////// //// Amounts & fees /** * Get the estimated smart chain fee of the commit transaction * @internal */ protected getCommitFee(): Promise<bigint> { return this._contract.getCommitFee(this._getInitiator(), this.getSwapData(), this.feeRate); } /** * Returns the transaction fee paid on the smart chain side to initiate the escrow */ async getSmartChainNetworkFee(): Promise<TokenAmount<SCToken<T["ChainId"]>, true>> { const swapContract: T["Contract"] = this._contract; return toTokenAmount( await ( swapContract.getRawCommitFee!=null ? swapContract.getRawCommitFee(this._getInitiator(), this.getSwapData(), this.feeRate) : swapContract.getCommitFee(this._getInitiator(), this.getSwapData(), this.feeRate) ), this.wrapper._getNativeToken(), this.wrapper._prices ); } /** * Checks if the initiator/sender has enough balance on the smart chain side * to cover the transaction fee for processing the swap */ abstract hasEnoughForTxFees(): Promise<{ enoughBalance: boolean, balance: TokenAmount<SCToken<T["ChainId"]>, true>, required: TokenAmount<SCToken<T["ChainId"]>, true> }>; ////////////////////////////// //// Commit and claim /** * Returns transactions for initiating (committing) the escrow on the smart chain side. After sending the * transactions manually be sure to call the {@link waitTillCommited} function to wait till the initiation * transaction is observed, processed by the SDK and state of the swap properly updated. * * @param skipChecks Skip checks like making sure init signature is still valid and swap wasn't commited yet * (this is handled on swap creation, if you commit right after quoting, you can use `skipChecks=true`) */ abstract txsCommit(skipChecks?: boolean): Promise<T["TX"][]>; /** * Initiates (commits) the escrow on the smart chain side * * @param _signer Signer to sign the transactions with, must be the same as used in the initialization * @param abortSignal Abort signal * @param skipChecks Skip checks like making sure init signature is still valid and swap wasn't commited yet * (this is handled on swap creation, if you commit right after quoting, you can use `skipChecks=true`) * @param onBeforeTxSent Callback called before the transactions are broadcasted */ abstract commit(_signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, skipChecks?: boolean, onBeforeTxSent?: (txId: string) => void): Promise<string>; /** * Waits till a swap is initiated (committed) on-chain, should be called after sending the commit * transactions ({@link txsCommit}) manually to wait till the SDK processes the escrow * initialization and updates the swap state accordingly * * @param abortSignal AbortSignal */ abstract waitTillCommited(abortSignal?: AbortSignal): Promise<void> ////////////////////////////// //// Quote verification /** * Checks if the swap's quote is expired for good (i.e. the swap strictly cannot be committed on-chain anymore) */ async _verifyQuoteDefinitelyExpired(): Promise<boolean> { if(this._data==null || this.signatureData==null) throw new Error("data or signature data are null!"); return this._contract.isInitAuthorizationExpired( this._data!, this.signatureData! ); } /** * Checks if the swap's quote is still valid */ async _verifyQuoteValid(): Promise<boolean> { if(this._data==null || this.signatureData==null) throw new Error("data or signature data are null!"); try { await this._contract.isValidInitAuthorization( this._getInitiator(), this._data!, this.signatureData!, this.feeRate ); return true; } catch (e) { if(e instanceof SignatureVerificationError) { return false; } throw e; } } /** * @inheritDoc */ serialize(): any { return { ...super.serialize(), prefix: this.signatureData?.prefix, timeout: this.signatureData?.timeout, signature: this.signatureData?.signature, feeRate: this.feeRate==null ? null : this.feeRate.toString(), } }; }