@atomiqlabs/sdk
Version:
atomiq labs SDK for cross-chain swaps between smart chains and bitcoin
324 lines (312 loc) • 12.3 kB
text/typescript
import {ChainType} from "@atomiqlabs/base";
import {Transaction} from "@scure/btc-signer";
import {isTokenAmount, TokenAmount} from "./TokenAmount";
import {BitcoinTokens, BtcToken, isBtcToken} from "./Token";
const swapExecutionActionWaitNames = {
LP: "Awaiting LP payout",
SETTLEMENT: "Automatic settlement",
BITCOIN_CONFS: "Bitcoin confirmations"
} as const;
function isSwapExecutionActionPsbtTx(
obj: any,
type?: "FUNDED_PSBT" | "RAW_PSBT"
): obj is SwapExecutionActionSignPSBT["txs"][number] {
const resolvedType = type ?? obj?.type;
if(obj == null || typeof(obj) !== "object") return false;
if(resolvedType !== "FUNDED_PSBT" && resolvedType !== "RAW_PSBT") return false;
return obj.type === resolvedType &&
obj.psbt instanceof Transaction &&
typeof(obj.psbtHex) === "string" &&
typeof(obj.psbtBase64) === "string" &&
(
resolvedType === "FUNDED_PSBT"
? Array.isArray(obj.signInputs) && obj.signInputs.every((input: any) => typeof(input) === "number")
: typeof(obj.in1sequence) === "number"
);
}
/**
* Swap execution action requiring the user to send assets to a specific LIGHTNING invoice or BITCOIN
* on-chain address
*
* @category Swap Actions
*/
export type SwapExecutionActionSendToAddress<Lightning extends boolean = boolean> = {
type: "SendToAddress",
/**
* Human-readable name of the action
*/
name: Lightning extends true ? "Deposit on Lightning" : "Deposit on Bitcoin",
/**
* Human-readable description of the action
*/
description: string,
/**
* Chain on which the payment is expected, either `LIGHTNING` or `BITCOIN` for on-chain
*/
chain: Lightning extends true ? "LIGHTNING" : "BITCOIN",
/**
* An array of payments that should be made to different addresses, usually only a single address is returned
*/
txs: {
type: Lightning extends true ? "BOLT11_PAYMENT_REQUEST" : "BITCOIN_ADDRESS",
address: string,
hyperlink: string,
amount: TokenAmount<BtcToken<Lightning>, true>
}[],
/**
* Waits till the transaction is received, doesn't wait for the actual confirmation!
*
* @returns A transaction ID of the received transaction
*/
waitForTransactions: (maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal) => Promise<string>
}
/**
* Type guard for {@link SwapExecutionActionSendToAddress}
*
* @category Swap Actions
*/
export function isSwapExecutionActionSendToAddress<Lightning extends boolean = boolean>(
obj: any,
lightning?: Lightning
): obj is SwapExecutionActionSendToAddress<Lightning> {
const resolvedLightning = lightning ?? (obj?.chain === "LIGHTNING");
return obj != null &&
typeof(obj) === "object" &&
obj.type === "SendToAddress" &&
obj.name === (resolvedLightning ? "Deposit on Lightning" : "Deposit on Bitcoin") &&
typeof(obj.description) === "string" &&
obj.chain === (resolvedLightning ? "LIGHTNING" : "BITCOIN") &&
Array.isArray(obj.txs) &&
obj.txs.every((tx: any) =>
tx != null &&
typeof(tx) === "object" &&
tx.type === (resolvedLightning ? "BOLT11_PAYMENT_REQUEST" : "BITCOIN_ADDRESS") &&
typeof(tx.address) === "string" &&
typeof(tx.hyperlink) === "string" &&
isTokenAmount(
tx.amount,
resolvedLightning ? BitcoinTokens.BTCLN : BitcoinTokens.BTC,
true
)
) &&
typeof(obj.waitForTransactions) === "function";
}
/**
* Swap execution action requiring the user to sign the provided PSBT and then submit it back via the provided
* `submitPsbt()` function, has two variations:
* - `"FUNDED_PSBT"` - A ready to sign PSBT with the inputs populated from the provided bitcoin wallet address
* - `"RAW_PSBT"` - Raw PSBT without the inputs, the implementor needs to add the input UTXOs before signing
* the transaction (also make sure to set the `nSequence` field of the 2nd input, index 1, to the provided
* `in1sequence` value)
*
* @category Swap Actions
*/
export type SwapExecutionActionSignPSBT<
T extends "FUNDED_PSBT" | "RAW_PSBT" = "FUNDED_PSBT" | "RAW_PSBT"
> = {
type: "SignPSBT",
/**
* Human-readable name of the action
*/
name: "Deposit on Bitcoin",
/**
* Human-readable description of the action
*/
description: string,
/**
* Chain is always bitcoin
*/
chain: "BITCOIN",
/**
* An array of PSBTs that need to be signed, usually contains only a single PSBT
*/
txs: T extends "FUNDED_PSBT" ? {
type: "FUNDED_PSBT",
psbt: Transaction,
psbtHex: string,
psbtBase64: string,
signInputs: number[],
feeRate: number
}[] : {
type: "RAW_PSBT",
psbt: Transaction,
psbtHex: string,
psbtBase64: string,
in1sequence: number,
feeRate: number
}[],
/**
* Submit a signed PSBT, accepts hexadecimal, base64 and `@scure/btc-signer` {@link Transaction} object.
*
* @returns An array of transaction IDs of the submitted Bitcoin transactions
*/
submitPsbt: (signedPsbt: string | Transaction | (string | Transaction)[], idempotent?: boolean) => Promise<string[]>
}
/**
* Type guard for {@link SwapExecutionActionSignPSBT}
*
* @category Swap Actions
*/
export function isSwapExecutionActionSignPSBT<
T extends "FUNDED_PSBT" | "RAW_PSBT" = "FUNDED_PSBT" | "RAW_PSBT"
>(
obj: any,
psbtType?: T
): obj is SwapExecutionActionSignPSBT<T> {
const resolvedPsbtType = psbtType ?? obj?.txs?.[0]?.type;
return obj != null &&
typeof(obj) === "object" &&
obj.type === "SignPSBT" &&
obj.name === "Deposit on Bitcoin" &&
typeof(obj.description) === "string" &&
obj.chain === "BITCOIN" &&
Array.isArray(obj.txs) &&
obj.txs.every((tx: any) => isSwapExecutionActionPsbtTx(tx, resolvedPsbtType)) &&
typeof(obj.submitPsbt) === "function";
}
/**
* Swap execution action requiring the user to sign the provided smart chain transactions, these can then
* be either broadcasted manually, or sent via the provided `submitTransactions()` function
*
* @category Swap Actions
*/
export type SwapExecutionActionSignSmartChainTx<T extends ChainType = ChainType> = {
type: "SignSmartChainTransaction",
/**
* Human-readable name of the action
*/
name: "Initiate swap" | "Settle manually" | "Refund",
/**
* Human-readable description of the action
*/
description: string,
/**
* Chain identifier of the smart chain on which the corresponding transactions should be signed
*/
chain: T["ChainId"],
/**
* Smart chain transactions that should be signed and either broadcasted manually or submitted back
* to the provided `submitTransactions()` function
*/
txs: T["TX"][],
/**
* Submits the signed transactions and waits for their confirmation
*
* @remarks This might not do any validation on the submitted transactions, so returned txids are informational
* only and may not be persisted immediately. The swap may wait for an authoritative state transition before
* considering the submitted transactions accepted.
*
* Make sure to only submit valid signed transactions obtained from this action object, and pass an AbortSignal
* if you need a timeout, otherwise this call can wait indefinitely when invalid transactions are submitted.
*/
submitTransactions: (txs: (T["SignedTXType"] | string)[], abortSignal?: AbortSignal, idempotent?: boolean) => Promise<string[]>,
/**
* The address of the signer that has to sign the transactions
*/
requiredSigner: string
}
/**
* Type guard for {@link SwapExecutionActionSignSmartChainTx}
*
* @category Swap Actions
*/
export function isSwapExecutionActionSignSmartChainTx<T extends ChainType = ChainType>(
obj: any,
chainIdentifier?: T["ChainId"] | T["ChainId"][]
): obj is SwapExecutionActionSignSmartChainTx<T> {
const allowedChains = chainIdentifier == null ? null : Array.isArray(chainIdentifier) ? chainIdentifier : [chainIdentifier];
return obj != null &&
typeof(obj) === "object" &&
obj.type === "SignSmartChainTransaction" &&
(
obj.name === "Initiate swap" ||
obj.name === "Settle manually" ||
obj.name === "Refund"
) &&
typeof(obj.description) === "string" &&
typeof(obj.chain) === "string" &&
(allowedChains == null || allowedChains.includes(obj.chain)) &&
Array.isArray(obj.txs) &&
typeof(obj.submitTransactions) === "function" &&
typeof(obj.requiredSigner) === "string";
}
/**
* Swap action indicating that the user should wait for either LP to process the swap, automatic settlement to happen or
* until the Bitcoin transaction gets enough confirmations
*
* @category Swap Actions
*/
export type SwapExecutionActionWait<
T extends "LP" | "SETTLEMENT" | "BITCOIN_CONFS" = "LP" | "SETTLEMENT" | "BITCOIN_CONFS"
> = {
type: "Wait",
/**
* Human-readable name of the action
*/
name: T extends "LP"
? "Awaiting LP payout"
: T extends "SETTLEMENT"
? "Automatic settlement"
: "Bitcoin confirmations",
/**
* Human-readable description of the action
*/
description: string,
/**
* Allows you to await till this action resolves
*
* @param maxWaitTimeSeconds Maximum time in seconds to wait for
* @param pollIntervalSeconds How often to poll for the state change (default 5 seconds)
* @param abortSignal AbortSignal to abort the wait
* @param btcConfirmationsCallback Optional callback when awaiting bitcoin confirmations, gets called when
* number of bitcoin confirmations change
*/
wait: T extends "BITCOIN_CONFS"
? (
maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal,
btcConfirmationsCallback?: (txId?: string, confirmations?: number, targetConfirmations?: number, txEtaMs?: number) => void
) => Promise<void>
: (maxWaitTimeSeconds?: number, pollIntervalSeconds?: number, abortSignal?: AbortSignal) => Promise<void>,
/**
* Expected time in seconds for this action to take
*/
expectedTimeSeconds: number,
/**
* Recommended time interval in seconds after which you should re-check the current action
*/
pollTimeSeconds: number
}
/**
* Type guard for {@link SwapExecutionActionWait}
*
* @category Swap Actions
*/
export function isSwapExecutionActionWait<
T extends "LP" | "SETTLEMENT" | "BITCOIN_CONFS" = "LP" | "SETTLEMENT" | "BITCOIN_CONFS"
>(
obj: any,
waitType?: T
): obj is SwapExecutionActionWait<T> {
const resolvedWaitType = waitType ??
(Object.keys(swapExecutionActionWaitNames).find(key =>
swapExecutionActionWaitNames[key as keyof typeof swapExecutionActionWaitNames] === obj?.name
) as keyof typeof swapExecutionActionWaitNames | undefined);
return obj != null &&
typeof(obj) === "object" &&
obj.type === "Wait" &&
resolvedWaitType != null &&
obj.name === swapExecutionActionWaitNames[resolvedWaitType] &&
typeof(obj.description) === "string" &&
typeof(obj.wait) === "function" &&
typeof(obj.expectedTimeSeconds) === "number" &&
typeof(obj.pollTimeSeconds) === "number";
}
/**
* Swap execution action, a single step in the swapping process
*
* @category Swap Actions
*/
export type SwapExecutionAction = SwapExecutionActionSendToAddress |
SwapExecutionActionSignPSBT |
SwapExecutionActionSignSmartChainTx |
SwapExecutionActionWait;