@atomiqlabs/sdk
Version:
atomiq labs SDK for cross-chain swaps between smart chains and bitcoin
301 lines (258 loc) • 11 kB
text/typescript
import {IFromBTCWrapper} from "./IFromBTCWrapper";
import {ChainType, SignatureVerificationError,} from "@atomiqlabs/base";
import {Fee} from "../../../types/fees/Fee";
import {IAddressSwap} from "../../IAddressSwap";
import {IEscrowSelfInitSwap, IEscrowSelfInitSwapDefinition, IEscrowSelfInitSwapInit} from "../IEscrowSelfInitSwap";
import {FeeType} from "../../../enums/FeeType";
import {ppmToPercentage} from "../../../types/fees/PercentagePPM";
import {TokenAmount, toTokenAmount} from "../../../types/TokenAmount";
import {BtcToken, SCToken} from "../../../types/Token";
import {IClaimableSwap} from "../../IClaimableSwap";
export type IFromBTCSelfInitDefinition<T extends ChainType, W extends IFromBTCWrapper<T, any>, S extends IFromBTCSelfInitSwap<T>> = IEscrowSelfInitSwapDefinition<T, W, S>;
/**
* Base class for legacy escrow-based Bitcoin (on-chain & lightning) -> Smart chain swaps,
* which require the user to manually initiate the escrow on the destination smart chain
*
* @category Swaps/Abstract
*/
export abstract class IFromBTCSelfInitSwap<
T extends ChainType = ChainType,
D extends IFromBTCSelfInitDefinition<T, IFromBTCWrapper<T, D>, IFromBTCSelfInitSwap<T, D, S>> = IFromBTCSelfInitDefinition<T, IFromBTCWrapper<T, any>, IFromBTCSelfInitSwap<T, any, any>>,
S extends number = number
> extends IEscrowSelfInitSwap<T, D, S> implements IAddressSwap, IClaimableSwap<T, D, S> {
/**
* @internal
*/
protected abstract readonly inputToken: BtcToken;
protected constructor(wrapper: D["Wrapper"], init: IEscrowSelfInitSwapInit<T["Data"]>);
protected constructor(wrapper: D["Wrapper"], obj: any);
protected constructor(
wrapper: D["Wrapper"],
initOrObj: IEscrowSelfInitSwapInit<T["Data"]> | any
) {
super(wrapper, initOrObj);
}
/**
* @inheritDoc
* @internal
*/
protected tryRecomputeSwapPrice() {
const input = this.getInput();
if(this.swapFeeBtc==null && input.rawAmount!=null) {
this.swapFeeBtc = this.swapFee * input.rawAmount / this.getOutAmountWithoutFee();
}
super.tryRecomputeSwapPrice();
}
//////////////////////////////
//// Getters & utils
/**
* @inheritDoc
*/
abstract getAddress(): string;
/**
* @inheritDoc
*/
abstract getHyperlink(): string;
/**
* @inheritDoc
*/
abstract isClaimable(): boolean;
/**
* Returns if the swap can be committed
* @internal
*/
protected abstract canCommit(skipQuoteExpiryChecks?: boolean): boolean;
/**
* @inheritDoc
* @internal
*/
_getInitiator(): string {
return this.getSwapData().getClaimer();
}
/**
* @inheritDoc
*/
getOutputTxId(): string | null {
return this._claimTxId ?? null;
}
/**
* @inheritDoc
*/
getOutputAddress(): string | null {
return this._getInitiator();
}
/**
* @inheritDoc
*/
requiresAction(): boolean {
return this.isClaimable();
}
//////////////////////////////
//// Amounts & fees
/**
* Returns the swap output amount in destination token based units without any fees, this
* value is therefore always higher than the actual received output.
*
* @internal
*/
protected getOutAmountWithoutFee(): bigint {
return this.getSwapData().getAmount() + this.swapFee;
}
/**
* Returns the swap fee charged by the intermediary (LP) on this swap
*
* @internal
*/
protected getSwapFee(): Fee<T["ChainId"], BtcToken, SCToken<T["ChainId"]>> {
if(this.pricingInfo==null) throw new Error("No pricing info known, cannot estimate fee!");
const feeWithoutBaseFee = this.swapFeeBtc - this.pricingInfo.satsBaseFee;
const inputWithoutFee = this.getInputWithoutFee();
const swapFeePPM = inputWithoutFee.rawAmount==null ? 0n : feeWithoutBaseFee * 1000000n / inputWithoutFee.rawAmount;
const amountInSrcToken = toTokenAmount(this.swapFeeBtc, this.inputToken, this.wrapper._prices, this.pricingInfo);
return {
amountInSrcToken,
amountInDstToken: toTokenAmount(this.swapFee, this.wrapper._tokens[this.getSwapData().getToken()], this.wrapper._prices, this.pricingInfo),
currentUsdValue: amountInSrcToken.currentUsdValue,
usdValue: amountInSrcToken.usdValue,
pastUsdValue: amountInSrcToken.pastUsdValue,
composition: {
base: toTokenAmount(this.pricingInfo.satsBaseFee, this.inputToken, this.wrapper._prices, this.pricingInfo),
percentage: ppmToPercentage(swapFeePPM)
}
};
}
/**
* @inheritDoc
*/
getFee(): Fee {
return this.getSwapFee();
}
/**
* @inheritDoc
*/
getFeeBreakdown(): [{type: FeeType.SWAP, fee: Fee<T["ChainId"], BtcToken, SCToken<T["ChainId"]>>}] {
return [{
type: FeeType.SWAP,
fee: this.getSwapFee()
}];
}
/**
* @inheritDoc
*/
getOutputToken(): SCToken<T["ChainId"]> {
return this.wrapper._tokens[this.getSwapData().getToken()];
}
/**
* @inheritDoc
*/
getOutput(): TokenAmount<SCToken<T["ChainId"]>, true> {
return toTokenAmount(this.getSwapData().getAmount(), this.wrapper._tokens[this.getSwapData().getToken()], this.wrapper._prices, this.pricingInfo);
}
/**
* @inheritDoc
*/
abstract getInput(): TokenAmount<BtcToken>;
/**
* @inheritDoc
*/
getInputWithoutFee(): TokenAmount<BtcToken> {
const input = this.getInput();
if(input.rawAmount==null) return toTokenAmount(null, this.inputToken, this.wrapper._prices, this.pricingInfo);
return toTokenAmount(input.rawAmount - this.swapFeeBtc, this.inputToken, this.wrapper._prices, this.pricingInfo);
}
/**
* @inheritDoc
*/
async hasEnoughForTxFees(): Promise<{
enoughBalance: boolean,
balance: TokenAmount<SCToken<T["ChainId"]>, true>,
required: TokenAmount<SCToken<T["ChainId"]>, true>
}> {
const [balance, commitFee] = await Promise.all([
this._contract.getBalance(this._getInitiator(), this.wrapper._chain.getNativeCurrencyAddress(), false),
this.getCommitFee()
]);
const totalFee = commitFee + this.getSwapData().getTotalDeposit();
return {
enoughBalance: balance >= totalFee,
balance: toTokenAmount(balance, this.wrapper._getNativeToken(), this.wrapper._prices, this.pricingInfo),
required: toTokenAmount(totalFee, this.wrapper._getNativeToken(), this.wrapper._prices, this.pricingInfo)
};
}
/**
* Returns the amount of native token of the destination chain locked up during initialization of the escrow
* to act as a security deposit that can be taken by the intermediary (LP) if the user doesn't go through
* with the swap
*/
getSecurityDeposit(): TokenAmount<SCToken<T["ChainId"]>, true> {
return toTokenAmount(this.getSwapData().getSecurityDeposit(), this.wrapper._getNativeToken(), this.wrapper._prices, this.pricingInfo);
}
/**
* Returns the total amount of native token of the destination chain locked up during initialization of the escrow.
* This covers the security deposit and the watchtower fee (if applicable), it is calculated a maximum of those
* two values.
*/
getTotalDeposit(): TokenAmount<SCToken<T["ChainId"]>, true> {
return toTokenAmount(this.getSwapData().getTotalDeposit(), this.wrapper._getNativeToken(), this.wrapper._prices, this.pricingInfo);
}
//////////////////////////////
//// Commit
/**
* Returns transactions for initiating (committing) the escrow on the destination smart chain side, pre-locking the
* tokens from the intermediary (LP) into an escrow.
*
* @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)
*
* @throws {Error} When in invalid state to commit the swap
*/
async txsCommit(skipChecks?: boolean): Promise<T["TX"][]> {
if(!this.canCommit(skipChecks)) throw new Error("Must be in CREATED state!");
if(this._data==null || this.signatureData==null) throw new Error("data or signature data is null, invalid state?");
if(!this.initiated) {
this.initiated = true;
await this._saveAndEmit();
}
return await this._contract.txsInit(
this._getInitiator(), this._data, this.signatureData, skipChecks, this.feeRate
).catch(e => Promise.reject(e instanceof SignatureVerificationError ? new Error("Request timed out") : e));
}
/**
* Creates the escrow on the destination smart chain side, pre-locking the tokens from the intermediary (LP)
* into an escrow.
*
* @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)
*
* @throws {Error} If invalid signer is provided that doesn't match the swap data
*/
abstract commit(signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal, skipChecks?: boolean): Promise<string>;
//////////////////////////////
//// Claim
/**
* Returns the transaction fee required for the claim transaction to settle the escrow on the destination
* smart chain
*/
async getClaimNetworkFee(): Promise<TokenAmount<SCToken<T["ChainId"]>, true>> {
const swapContract: T["Contract"] = this._contract;
return toTokenAmount(
await swapContract.getClaimFee(this._getInitiator(), this.getSwapData()),
this.wrapper._getNativeToken(),
this.wrapper._prices
);
}
/**
* @inheritDoc
*/
abstract txsClaim(signer?: string | T["Signer"] | T["NativeSigner"]): Promise<T["TX"][]>;
/**
* @inheritDoc
*/
abstract claim(signer: T["Signer"] | T["NativeSigner"], abortSignal?: AbortSignal): Promise<string>;
/**
* @inheritDoc
*/
abstract waitTillClaimed(maxWaitTimeSeconds?: number, abortSignal?: AbortSignal): Promise<boolean>;
}