UNPKG

@atomiqlabs/sdk

Version:

atomiq labs SDK for cross-chain swaps between smart chains and bitcoin

324 lines (312 loc) 12.3 kB
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;