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