UNPKG

@atomiqlabs/sdk-lib

Version:

Basic SDK functionality library for atomiq

241 lines (192 loc) 8.96 kB
import {IFromBTCWrapper} from "./IFromBTCWrapper"; import {Fee, ISwap, ISwapInit} from "../ISwap"; import { ChainType, SignatureVerificationError, } from "@atomiqlabs/base"; import {PriceInfoType} from "../../prices/abstract/ISwapPrice"; import {BtcToken, SCToken, TokenAmount, toTokenAmount} from "../Tokens"; export abstract class IFromBTCSwap< T extends ChainType = ChainType, S extends number = number > extends ISwap<T, S> { protected abstract readonly inputToken: BtcToken; protected constructor(wrapper: IFromBTCWrapper<T, IFromBTCSwap<T, S>>, init: ISwapInit<T["Data"]>); protected constructor(wrapper: IFromBTCWrapper<T, IFromBTCSwap<T, S>>, obj: any); protected constructor( wrapper: IFromBTCWrapper<T, IFromBTCSwap<T, S>>, initOrObj: ISwapInit<T["Data"]> | any ) { super(wrapper, initOrObj); } /** * In case swapFee in BTC is not supplied it recalculates it based on swap price * @protected */ protected tryCalculateSwapFee() { if(this.swapFeeBtc==null) { this.swapFeeBtc = this.swapFee * this.getInput().rawAmount / this.getOutAmountWithoutFee(); } if(this.pricingInfo.swapPriceUSatPerToken==null) { this.pricingInfo = this.wrapper.prices.recomputePriceInfoReceive( this.chainIdentifier, this.getInput().rawAmount, this.pricingInfo.satsBaseFee, this.pricingInfo.feePPM, this.getSwapData().getAmount(), this.getSwapData().getToken() ); } } protected getSwapData(): T["Data"] { return this.data; } ////////////////////////////// //// Pricing async refreshPriceData(): Promise<PriceInfoType> { if(this.pricingInfo==null) return null; const priceData = await this.wrapper.prices.isValidAmountReceive( this.chainIdentifier, this.getInput().rawAmount, this.pricingInfo.satsBaseFee, this.pricingInfo.feePPM, this.getSwapData().getAmount(), this.getSwapData().getToken() ); this.pricingInfo = priceData; return priceData; } getSwapPrice(): number { return Number(this.pricingInfo.swapPriceUSatPerToken) / 100000000000000; } getMarketPrice(): number { return Number(this.pricingInfo.realPriceUSatPerToken) / 100000000000000; } getRealSwapFeePercentagePPM(): bigint { const feeWithoutBaseFee = this.swapFeeBtc - this.pricingInfo.satsBaseFee; return feeWithoutBaseFee * 1000000n / this.getInputWithoutFee().rawAmount; } ////////////////////////////// //// Getters & utils abstract getInputTxId(): string | null; getOutputTxId(): string | null { return this.claimTxId; } getInputAddress(): string | null { return this.getAddress(); } getOutputAddress(): string | null { return this.getInitiator(); } /** * Returns the bitcoin address or lightning invoice to be paid for the swap */ abstract getAddress(): string; /** * Returns a string that can be displayed as QR code representation of the address or lightning invoice * (with bitcoin: or lightning: prefix) */ abstract getQrData(): string; abstract isClaimable(): boolean; isActionable(): boolean { return this.isClaimable(); } /** * Returns if the swap can be committed */ abstract canCommit(): boolean; ////////////////////////////// //// Amounts & fees protected getOutAmountWithoutFee(): bigint { return this.getSwapData().getAmount() + this.swapFee; } getOutputWithoutFee(): TokenAmount<T["ChainId"], SCToken<T["ChainId"]>> { return toTokenAmount(this.getSwapData().getAmount() + this.swapFee, this.wrapper.tokens[this.getSwapData().getToken()], this.wrapper.prices); } getOutput(): TokenAmount<T["ChainId"], SCToken<T["ChainId"]>> { return toTokenAmount(this.getSwapData().getAmount(), this.wrapper.tokens[this.getSwapData().getToken()], this.wrapper.prices); } getInputWithoutFee(): TokenAmount<T["ChainId"], BtcToken> { return toTokenAmount(this.getInput().rawAmount - this.swapFeeBtc, this.inputToken, this.wrapper.prices); } getSwapFee(): Fee { return { amountInSrcToken: toTokenAmount(this.swapFeeBtc, this.inputToken, this.wrapper.prices), amountInDstToken: toTokenAmount(this.swapFee, this.wrapper.tokens[this.getSwapData().getToken()], this.wrapper.prices), usdValue: (abortSignal?: AbortSignal, preFetchedUsdPrice?: number) => this.wrapper.prices.getBtcUsdValue(this.swapFeeBtc, abortSignal, preFetchedUsdPrice) }; } getSecurityDeposit(): TokenAmount<T["ChainId"], SCToken<T["ChainId"]>> { return toTokenAmount(this.getSwapData().getSecurityDeposit(), this.wrapper.getNativeToken(), this.wrapper.prices); } getTotalDeposit(): TokenAmount<T["ChainId"], SCToken<T["ChainId"]>> { return toTokenAmount(this.getSwapData().getTotalDeposit(), this.wrapper.getNativeToken(), this.wrapper.prices); } getInitiator(): string { return this.getSwapData().getClaimer(); } getClaimFee(): Promise<bigint> { return this.wrapper.contract.getClaimFee(this.getInitiator(), this.getSwapData()); } async hasEnoughForTxFees(): Promise<{enoughBalance: boolean, balance: TokenAmount, required: TokenAmount}> { const [balance, commitFee] = await Promise.all([ this.wrapper.contract.getBalance(this.getInitiator(), this.wrapper.contract.getNativeCurrencyAddress(), false), this.getCommitFee() ]); const totalFee = commitFee + this.getSwapData().getTotalDeposit(); return { enoughBalance: balance >= totalFee, balance: toTokenAmount(balance, this.wrapper.getNativeToken(), this.wrapper.prices), required: toTokenAmount(totalFee, this.wrapper.getNativeToken(), this.wrapper.prices) }; } ////////////////////////////// //// Commit /** * Commits the swap on-chain, locking the tokens from the intermediary in an HTLC or PTLC * * @param signer Signer to sign the transactions with, must be the same as used in the initialization * @param abortSignal Abort signal to stop waiting for the transaction confirmation and abort * @param skipChecks Skip checks like making sure init signature is still valid and swap wasn't commited yet * (this is handled when swap is created (quoted), if you commit right after quoting, you can use skipChecks=true) * @throws {Error} If invalid signer is provided that doesn't match the swap data */ abstract commit(signer: T["Signer"], abortSignal?: AbortSignal, skipChecks?: boolean): Promise<string>; /** * Returns the transactions required for committing the swap on-chain, locking the tokens from the intermediary * in an HTLC or PTLC * * @param skipChecks Skip checks like making sure init signature is still valid and swap wasn't commited yet * (this is handled when swap is created (quoted), if you commit right after quoting, you can use skipChecks=true) * @throws {Error} When in invalid state to commit the swap */ async txsCommit(skipChecks?: boolean): Promise<T["TX"][]> { if(!this.canCommit()) throw new Error("Must be in CREATED state!"); if(!this.initiated) { this.initiated = true; await this._saveAndEmit(); } return await this.wrapper.contract.txsInit( this.data, this.signatureData, skipChecks, this.feeRate ).catch(e => Promise.reject(e instanceof SignatureVerificationError ? new Error("Request timed out") : e)); } abstract waitTillCommited(abortSignal?: AbortSignal): Promise<void>; ////////////////////////////// //// Claim /** * Claims and finishes the swap * * @param signer Signer to sign the transactions with, can also be different to the initializer * @param abortSignal Abort signal to stop waiting for transaction confirmation */ abstract claim(signer: T["Signer"], abortSignal?: AbortSignal): Promise<string>; abstract txsClaim(signer?: T["Signer"]): Promise<T["TX"][]>; /** * Waits till the swap is successfully claimed * * @param abortSignal AbortSignal * @throws {Error} If swap is in invalid state (must be COMMIT) */ abstract waitTillClaimed(abortSignal?: AbortSignal): Promise<void>; }