@atomiqlabs/sdk
Version:
atomiq labs SDK for cross-chain swaps between smart chains and bitcoin
218 lines (191 loc) • 8.81 kB
text/typescript
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(),
}
};
}