@atomiqlabs/sdk
Version:
atomiq labs SDK for cross-chain swaps between smart chains and bitcoin
559 lines (481 loc) • 31 kB
text/typescript
import {MultiChain, Swapper} from "../swapper/Swapper";
import {ApiEndpoint, createApiEndpoint, toApiAmount, toApiLNURL, toApiToken} from "./ApiTypes";
import {ISwap} from "../swaps/ISwap";
import {serializeAction} from "./SerializedAction";
import {FeeType} from "../enums/FeeType";
import {SwapSide} from "../enums/SwapSide";
import {SwapType} from "../enums/SwapType";
import {MinimalBitcoinWalletInterface} from "../types/wallets/MinimalBitcoinWalletInterface";
import {FromBTCLNSwap, FromBTCLNSwapState} from "../swaps/escrow_swaps/frombtc/ln/FromBTCLNSwap";
import {FromBTCLNAutoSwap, FromBTCLNAutoSwapState} from "../swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoSwap";
import {
CreateSwapInput,
CreateSwapOutput,
GetSpendableBalanceInput,
GetSpendableBalanceOutput,
GetSupportedTokensInput,
GetSupportedTokensOutput,
GetSwapCounterTokensInput,
GetSwapCounterTokensOutput,
GetSwapLimitsInput,
GetSwapLimitsOutput,
GetSwapStatusInput,
GetSwapStatusOutput,
ListPendingSwapsInput,
ListPendingSwapsOutput,
ListSwapOutput,
ListSwapsInput,
ListSwapsOutput,
ParseAddressInput,
ParseAddressOutput,
SettleWithLnurlInput,
SettleWithLnurlOutput,
SubmitTransactionInput,
SubmitTransactionOutput,
SwapOutputBase
} from "./ApiEndpoints";
import {SwapExecutionStep} from "../types/SwapExecutionStep";
import {SwapStateInfo} from "../types/SwapStateInfo";
import {IEscrowSwap} from "../swaps/escrow_swaps/IEscrowSwap";
import {ToBTCLNSwap} from "../swaps/escrow_swaps/tobtc/ln/ToBTCLNSwap";
import {isSwapType} from "../utils/SwapUtils";
function requiresSecretRevealForApi(swap: ISwap, state: number): boolean | undefined {
if(swap instanceof FromBTCLNSwap) {
if(swap.hasSecretPreimage()) return false;
return state===FromBTCLNSwapState.PR_PAID || state===FromBTCLNSwapState.CLAIM_COMMITED;
}
if(swap instanceof FromBTCLNAutoSwap) {
if(swap.hasSecretPreimage()) return false;
return state===FromBTCLNAutoSwapState.CLAIM_COMMITED;
}
}
function createSwapOutputBase(
swap: ISwap,
steps: SwapExecutionStep[],
stateInfo: SwapStateInfo<number>
): SwapOutputBase {
const input = swap.getInput();
const output = swap.getOutput();
const feeBreakdown = swap.getFeeBreakdown();
// Build fees from breakdown
const swapFeeEntry = feeBreakdown.find(f => f.type === FeeType.SWAP);
const networkFeeEntry = feeBreakdown.find(f => f.type === FeeType.NETWORK_OUTPUT);
const flags: any = {};
if(isSwapType(swap, SwapType.TO_BTCLN)) {
flags.lightningRecipientIsNonCustodialWallet = swap.isPayingToNonCustodialWallet();
flags.lightningPaymentWillLikelyFail = swap.willLikelyFail();
flags.lightningPaymentWithLongHTLCExpiration = swap.hasLongExpiration();
}
return {
swapId: swap.getId(),
swapType: SwapType[swap.getType()],
state: {
number: stateInfo.state,
name: stateInfo.name,
description: stateInfo.description
},
quote: {
inputAmount: toApiAmount(input),
outputAmount: toApiAmount(output),
fees: {
swap: swapFeeEntry
? toApiAmount(swapFeeEntry.fee.amountInSrcToken)
: { amount: "0", rawAmount: "0", decimals: 0, symbol: "", chain: "" },
...(networkFeeEntry ? {
networkOutput: toApiAmount(networkFeeEntry.fee.amountInSrcToken)
} : {})
},
expiry: swap.getQuoteExpiry(),
outputAddress: swap.getOutputAddress()!
},
flags,
createdAt: swap.createdAt,
steps,
...(swap instanceof ToBTCLNSwap && swap.isLNURL() ? {
lnurl: {
pay: swap.getLNURL()!,
successAction: swap.getSuccessAction() ?? undefined
}
} : (swap instanceof FromBTCLNSwap || swap instanceof FromBTCLNAutoSwap) && swap.isLNURL() ? {
lnurl: {
withdraw: swap.getLNURL()!,
}
} : {})
};
}
function createListSwapOutput(
swap: ISwap,
steps: SwapExecutionStep[],
stateInfo: SwapStateInfo<number>
): ListSwapOutput {
return {
...createSwapOutputBase(swap, steps, stateInfo),
isFinished: swap.isFinished(),
isSuccess: swap.isSuccessful(),
isFailed: swap.isFailed(),
isExpired: swap.isQuoteExpired()
};
}
function parseSwapSide(side: "INPUT" | "OUTPUT"): SwapSide {
return side === "INPUT" ? SwapSide.INPUT : SwapSide.OUTPUT;
}
export type SwapperApiConfig = {
syncOnGetStatus?: boolean,
idempotentTxSubmission?: boolean
};
export class SwapperApi<T extends MultiChain> {
readonly endpoints: {
createSwap: ApiEndpoint<CreateSwapInput, CreateSwapOutput, "POST">;
listSwaps: ApiEndpoint<ListSwapsInput, ListSwapsOutput, "GET">;
listPendingSwaps: ApiEndpoint<ListPendingSwapsInput, ListPendingSwapsOutput, "GET">;
getSupportedTokens: ApiEndpoint<GetSupportedTokensInput, GetSupportedTokensOutput, "GET">;
getSwapCounterTokens: ApiEndpoint<GetSwapCounterTokensInput, GetSwapCounterTokensOutput, "GET">;
getSwapLimits: ApiEndpoint<GetSwapLimitsInput, GetSwapLimitsOutput, "GET">;
parseAddress: ApiEndpoint<ParseAddressInput, ParseAddressOutput, "GET">;
getSpendableBalance: ApiEndpoint<GetSpendableBalanceInput, GetSpendableBalanceOutput, "GET">;
getSwapStatus: ApiEndpoint<GetSwapStatusInput, GetSwapStatusOutput, "GET">;
submitTransaction: ApiEndpoint<SubmitTransactionInput, SubmitTransactionOutput, "POST">;
settleWithLnurl: ApiEndpoint<SettleWithLnurlInput, SettleWithLnurlOutput, "POST">;
};
constructor(private swapper: Swapper<T>, private readonly config?: SwapperApiConfig) {
this.config ??= {};
this.config.syncOnGetStatus ??= true;
this.endpoints = {
createSwap: createApiEndpoint<CreateSwapInput, CreateSwapOutput, "POST">("POST", "Create a new cross-chain atomic swap. Returns a swap object with swapId, swapType, state, createdAt, quote (with inputAmount and outputAmount as ApiAmount objects each having amount/rawAmount/decimals/symbol/chain, a fees breakdown {swap, networkOutput?} of ApiAmount values, expiry, and outputAddress), a steps array, and optional lnurl. After creation, poll getSwapStatus periodically to get the next required action.", this.createSwap.bind(this), {
srcToken: { type: "string", required: true, description: "Source token ticker (e.g. 'BITCOIN-BTC', 'LIGHTNING-BTC', 'STARKNET-STRK', 'SOLANA-SOL')" },
dstToken: { type: "string", required: true, description: "Destination token ticker" },
amount: { type: "bigint", required: true, description: "Amount in base units as an integer" },
amountType: { type: "string", required: true, description: "EXACT_IN or EXACT_OUT", allowedValues: ["EXACT_IN", "EXACT_OUT"] },
srcAddress: { type: "string", required: false, description: "Source address (only required for Smart chain -> BTC/Lightning swaps)" },
dstAddress: { type: "string", required: true, description: "Destination address" },
gasAmount: { type: "bigint", required: false, description: "Gas token amount to receive on destination chain, in base units" },
paymentHash: { type: "string", required: false, description: "Custom payment hash for Lightning swaps" },
lightningInvoiceDescription: { type: "string", required: false, description: "Description for Lightning invoice" },
lightningInvoiceDescriptionHash: { type: "string", required: false, description: "Description hash for Lightning invoice (hex)" },
lightningPaymentHTLCTimeout: { type: "number", required: false, description: "Custom expiry time in seconds" },
lightningPaymentMaxHTLCTimeout: { type: "number", required: false, description: "Custom maximum expiry time in seconds" }
}),
listSwaps: createApiEndpoint<ListSwapsInput, ListSwapsOutput, "GET">("GET", "List all swaps for a given signer address. Returns an array of swap objects, each with swapId, swapType, state, quote, steps, and terminal state flags (isFinished, isSuccess, isFailed, isExpired). Optionally filter by smart chain.", this.listSwaps.bind(this), {
signer: { type: "string", required: true, description: "Smart chain signer address to filter swaps for" },
chainId: { type: "string", required: false, description: "Optional smart chain identifier to filter swaps" }
}),
listPendingSwaps: createApiEndpoint<ListPendingSwapsInput, ListPendingSwapsOutput, "GET">("GET", "List swaps that require user action for a given signer address. Returns an array of swap objects with the same structure as listSwaps.", this.listPendingSwaps.bind(this), {
signer: { type: "string", required: true, description: "Smart chain signer address to filter pending swaps for" },
chainId: { type: "string", required: false, description: "Optional smart chain identifier to filter pending swaps" }
}),
getSupportedTokens: createApiEndpoint<GetSupportedTokensInput, GetSupportedTokensOutput, "GET">("GET", "List all tokens available as swap input or output. Returns an array of ApiToken objects, each with id (e.g. BITCOIN-BTC, LIGHTNING-BTC, STARKNET-STRK), chainId, ticker, name, decimals, and address.", this.getSupportedTokens.bind(this), {
side: {
type: "string",
required: true,
description: "Whether to list valid source tokens (INPUT) or destination tokens (OUTPUT)",
allowedValues: ["INPUT", "OUTPUT"]
}
}),
getSwapCounterTokens: createApiEndpoint<GetSwapCounterTokensInput, GetSwapCounterTokensOutput, "GET">("GET", "Get tokens that can be swapped against a given token. Returns an array of ApiToken objects (id, chainId, ticker, name, decimals, address). Use to discover valid trading pairs.", this.getSwapCounterTokens.bind(this), {
token: {
type: "string",
required: true,
description: "Token identifier accepted by the API, e.g. BITCOIN-BTC, LIGHTNING-BTC, STARKNET-STRK, or a token address"
},
side: {
type: "string",
required: true,
description: "Treat the provided token as a source token (INPUT) or destination token (OUTPUT)",
allowedValues: ["INPUT", "OUTPUT"]
}
}),
getSwapLimits: createApiEndpoint<GetSwapLimitsInput, GetSwapLimitsOutput, "GET">("GET", "Get minimum and maximum swap amounts for a source/destination token pair. Returns {input: {min, max?}, output: {min, max?}} where each value is an ApiAmount object with amount (decimal string), rawAmount (base units string), decimals, symbol, and chain.", this.getSwapLimits.bind(this), {
srcToken: { type: "string", required: true, description: "Source token identifier accepted by the API, e.g. BITCOIN-BTC, LIGHTNING-BTC, STARKNET-STRK" },
dstToken: { type: "string", required: true, description: "Destination token identifier accepted by the API, e.g. BITCOIN-BTC, LIGHTNING-BTC, STARKNET-STRK" }
}),
parseAddress: createApiEndpoint<ParseAddressInput, ParseAddressOutput, "GET">("GET", "Parse and validate an address, Lightning invoice, LNURL, or Bitcoin URI. Returns {address, type} and optionally lnurl (ApiLNURL with pay/withdraw details), min/max/amount (as ApiAmount objects).", this.parseAddress.bind(this), {
address: { type: "string", required: true, description: "Address, invoice, LNURL, or URI string to parse" }
}),
getSpendableBalance: createApiEndpoint<GetSpendableBalanceInput, GetSpendableBalanceOutput, "GET">("GET", "Get the spendable balance for a wallet address and token, accounting for chain fees. Returns {balance: ApiAmount, feeRate?} where ApiAmount has amount (decimal string), rawAmount (base units string), decimals, symbol, and chain.", this.getSpendableBalance.bind(this), {
wallet: { type: "string", required: true, description: "Wallet address to query" },
token: { type: "string", required: true, description: "Token identifier accepted by the API, e.g. BITCOIN-BTC, STARKNET-STRK, or a token address" },
targetChain: { type: "string", required: false, description: "Destination smart chain for Bitcoin SPV-vault fee estimation" },
gasDrop: { type: "boolean", required: false, description: "Whether to include gas-drop footprint when estimating Bitcoin SPV-vault spendable balance" },
feeRate: { type: "string", required: false, description: "Manual fee rate override" },
minBitcoinFeeRate: { type: "number", required: false, description: "Minimum Bitcoin fee rate to enforce" },
feeMultiplier: { type: "number", required: false, description: "Multiplier applied to smart-chain native token commit fee estimate" }
}),
getSwapStatus: createApiEndpoint<GetSwapStatusInput, GetSwapStatusOutput, "GET">("GET", "Get the current status and next required action for a swap. Returns swap state, terminal flags (isFinished, isSuccess, isFailed, isExpired), and currentAction (an action object, or null when no action is currently required). For Lightning-to-smart-chain swaps it may also return requiresSecretReveal: true — when set, pass the secret parameter in subsequent getSwapStatus calls so the HTLC can be claimed. Handle each action type: SignPSBT — ask user to sign the Bitcoin PSBT with their wallet, then submit via submitTransaction. SignSmartChainTransaction — ask user to sign with their Solana/Starknet/EVM wallet, then submit via submitTransaction. SendToAddress — show the address and amount to the user, they pay externally, keep polling. Wait — poll again after pollTimeSeconds. Poll repeatedly until isFinished is true.", this.getSwapStatus.bind(this), {
swapId: { type: "string", required: true, description: "The swap identifier" },
secret: { type: "string", required: false, description: "Revealed swap secret pre-image (in hexadecimal format) for lightning network swaps" },
bitcoinAddress: { type: "string", required: false, description: "Bitcoin wallet address to obtain funded PSBT" },
bitcoinPublicKey: { type: "string", required: false, description: "Bitcoin wallet public key (in hexadecimal format) to obtain funded PSBT" },
bitcoinFeeRate: { type: "number", required: false, description: "Fee rate to use when creating a funded PSBT" },
signer: { type: "string", required: false, description: "Alternative different smart chain signer to use for refunds and manual settlement" }
}),
submitTransaction: createApiEndpoint<SubmitTransactionInput, SubmitTransactionOutput, "POST">("POST", "Submit signed transaction(s) for a swap. Call this after the user has signed the transaction returned by getSwapStatus. Returns {txHashes: string[]} with the submitted transaction hashes. After submission, continue polling getSwapStatus.", this.submitTransaction.bind(this), {
swapId: { type: "string", required: true, description: "The swap identifier" },
signedTxs: {
type: "array",
required: true,
description: "Array of signed transaction data",
items: {type: "string", required: true, description: "Single string-serialized & signed transaction"}
}
}),
settleWithLnurl: createApiEndpoint<SettleWithLnurlInput, SettleWithLnurlOutput, "POST">("POST", "Settle a Lightning Network swap using an LNURL-withdraw link. Returns {paymentHash: string} on success.", this.settleWithLnurl.bind(this), {
swapId: { type: "string", required: true, description: "The swap identifier" },
lnurlWithdraw: { type: "string", required: false, description: "LNURL-withdraw link to use to settle the Lightning network swap, if the swap was already created with the LNURL-withdraw link, this is optional" }
})
};
}
private txSerializer(chainId: string, tx: any): Promise<string> {
const chain = (this.swapper._chains as any)[chainId];
if (chain == null) throw new Error("Unknown chain: " + chainId);
return chain.chainInterface.serializeTx(tx);
}
async init(): Promise<void> {
await this.swapper.init();
}
/**
* Should be ran periodically, this synchronizes the swap's state with the on-chain data and also purges
* expired swaps from the persistent storage
*/
async sync(): Promise<void> {
await this.swapper._syncSwaps();
}
/**
* Optionally good to run this periodically, such that any LPs that are dropped off because they are unresponsive
* can be found again.
*/
async reloadLps(): Promise<void> {
await this.swapper.intermediaryDiscovery.reloadIntermediaries();
}
private async createSwap(input: CreateSwapInput): Promise<CreateSwapOutput> {
const exactIn = input.amountType === "EXACT_IN";
// Build options from input
const options: any = {};
if (input.gasAmount != null) options.gasAmount = input.gasAmount;
if (input.paymentHash != null) options.paymentHash = Buffer.from(input.paymentHash, "hex");
if (input.lightningInvoiceDescription != null) options.description = input.lightningInvoiceDescription;
if (input.lightningInvoiceDescriptionHash != null) options.descriptionHash = Buffer.from(input.lightningInvoiceDescriptionHash, "hex");
if (input.lightningPaymentHTLCTimeout != null) options.expirySeconds = input.lightningPaymentHTLCTimeout;
if (input.lightningPaymentMaxHTLCTimeout != null) options.maxExpirySeconds = input.lightningPaymentMaxHTLCTimeout;
// swapper.swap() handles routing based on token types
const swap = await this.swapper.swap(
input.srcToken,
input.dstToken,
input.amount,
exactIn,
input.srcAddress,
input.dstAddress,
Object.keys(options).length > 0 ? options : undefined
);
const {steps, stateInfo} = await swap.getExecutionStatus({skipBuildingAction: true});
return createSwapOutputBase(swap, steps, stateInfo);
}
private validateSwapListInput(input: ListSwapsInput): void {
if (input.chainId != null && !this.swapper.getSmartChains().includes(input.chainId as any)) {
throw new Error("Unknown chainId: " + input.chainId);
}
if (!this.swapper.Utils.isValidSmartChainAddress(input.signer, input.chainId as any)) {
throw new Error(
input.chainId != null
? `Invalid ${input.chainId} signer address: ` + input.signer
: `Invalid smart chain signer address: ` + input.signer
);
}
}
private async createListedSwapOutputs(swaps: ISwap[]): Promise<ListSwapsOutput> {
return Promise.all(
swaps
.filter(swap => swap.getType() !== SwapType.TRUSTED_FROM_BTC)
.map(async swap => {
const {steps, stateInfo} = await swap.getExecutionStatus({skipBuildingAction: true});
return createListSwapOutput(swap, steps, stateInfo);
})
);
}
private async listSwaps(input: ListSwapsInput): Promise<ListSwapsOutput> {
this.validateSwapListInput(input);
const swaps = await this.swapper.getAllSwaps(input.chainId as any, input.signer);
return this.createListedSwapOutputs(swaps);
}
private async listPendingSwaps(input: ListPendingSwapsInput): Promise<ListPendingSwapsOutput> {
this.validateSwapListInput(input);
const swaps = await this.swapper.getPendingSwaps(input.chainId as any, input.signer);
return this.createListedSwapOutputs(swaps);
}
private async getSupportedTokens(input: GetSupportedTokensInput): Promise<GetSupportedTokensOutput> {
return this.swapper.getSupportedTokens(parseSwapSide(input.side)).map(toApiToken);
}
private async getSwapCounterTokens(input: GetSwapCounterTokensInput): Promise<GetSwapCounterTokensOutput> {
const token = this.swapper.getToken(input.token);
return this.swapper.getSwapCounterTokens(token, parseSwapSide(input.side)).map(toApiToken);
}
private async getSwapLimits(input: GetSwapLimitsInput): Promise<GetSwapLimitsOutput> {
const srcToken = this.swapper.getToken(input.srcToken);
const dstToken = this.swapper.getToken(input.dstToken);
let limits = this.swapper.getSwapLimits(srcToken, dstToken);
if(dstToken.chainId!=="LIGHTNING") {
if(limits.input.min.rawAmount===1n || limits.output.min.rawAmount===1n) {
// Execute a dummy swap to get the proper limits
try {
await this.swapper.swap(
srcToken, dstToken,
1n, limits.input.min.rawAmount===1n,
srcToken.chainId==="LIGHTNING" ? undefined : this.swapper.Utils.randomAddress(srcToken.chainId),
this.swapper.Utils.randomAddress(dstToken.chainId)
);
} catch (e) {}
limits = this.swapper.getSwapLimits(srcToken, dstToken);
}
}
return {
input: {
min: toApiAmount(limits.input.min),
...(limits.input.max != null ? {max: toApiAmount(limits.input.max)} : {})
},
output: {
min: toApiAmount(limits.output.min),
...(limits.output.max != null ? {max: toApiAmount(limits.output.max)} : {})
}
};
}
private async parseAddress(input: ParseAddressInput): Promise<ParseAddressOutput> {
const result = await this.swapper.Utils.parseAddress(input.address);
if(result == null) throw new Error("Invalid address");
return {
address: result.address,
type: result.type,
...(result.lnurl != null ? {lnurl: toApiLNURL(result.lnurl, this.swapper)} : {}),
...(result.min != null ? {min: toApiAmount(result.min)} : {}),
...(result.max != null ? {max: toApiAmount(result.max)} : {}),
...(result.amount != null ? {amount: toApiAmount(result.amount)} : {})
};
}
private async getSpendableBalance(input: GetSpendableBalanceInput): Promise<GetSpendableBalanceOutput> {
const token = this.swapper.getToken(input.token);
if(token.chainId === "LIGHTNING")
throw new Error("Lightning wallet spendable balance is not supported by this endpoint.");
if(input.feeRate != null && input.feeMultiplier != null)
throw new Error("`feeMultiplier` cannot be specified alongside the `feeRate` parameter.");
if(token.chainId === "BITCOIN") {
if(input.targetChain != null && !this.swapper.getSmartChains().includes(input.targetChain as any)) {
throw new Error("Unknown targetChain: " + input.targetChain);
}
if (!this.swapper.Utils.isValidBitcoinAddress(input.wallet))
throw new Error(`Invalid BITCOIN wallet address: ` + input.wallet);
let btcFeeRate: number;
if(input.feeRate != null) {
btcFeeRate = parseFloat(input.feeRate);
if(isNaN(btcFeeRate) || btcFeeRate <= 0) throw new Error("Bitcoin `feeRate` must be a valid positive number!");
} else btcFeeRate = await this.swapper._bitcoinRpc.getFeeRate()
if(input.feeMultiplier != null) btcFeeRate *= input.feeMultiplier;
const {balance, feeRate} = await this.swapper.Utils.getBitcoinSpendableBalance(input.wallet, input.targetChain as any, {
gasDrop: input.gasDrop,
feeRate: btcFeeRate,
minFeeRate: input.minBitcoinFeeRate
});
return {
balance: toApiAmount(balance),
feeRate
};
}
if(input.gasDrop === true) throw new Error("`gasDrop` is only supported for Bitcoin balances.");
if(input.minBitcoinFeeRate != null) throw new Error("`minBitcoinFeeRate` is only supported for Bitcoin balances.");
if (!this.swapper.Utils.isValidSmartChainAddress(input.wallet, token.chainId))
throw new Error(`Invalid ${token.chainId} wallet address: ` + input.wallet);
const balance = await this.swapper.Utils.getSpendableBalance(input.wallet, token as any, {
feeMultiplier: input.feeMultiplier,
feeRate: input.feeRate
});
return {
balance: toApiAmount(balance)
};
}
private async getSwapStatus(input: GetSwapStatusInput): Promise<GetSwapStatusOutput> {
const swap = await this.swapper.getSwapById(input.swapId);
if (swap == null) {
throw new Error("Swap not found: " + input.swapId);
}
if (input.signer != null && !this.swapper.Utils.isValidSmartChainAddress(input.signer, swap.chainIdentifier)) {
throw new Error(`Invalid ${swap.chainIdentifier} signer address: ` + input.signer);
}
if (input.secret != null) {
try {
Buffer.from(input.secret, "hex");
} catch (e) {
throw new Error(`Invalid secret passed, has to be a hexadecimal string!`);
}
}
let bitcoinWallet: MinimalBitcoinWalletInterface | undefined;
if (input.bitcoinAddress != null && input.bitcoinPublicKey != null) {
bitcoinWallet = {
publicKey: input.bitcoinPublicKey,
address: input.bitcoinAddress
};
} else if(input.bitcoinAddress != null || input.bitcoinPublicKey != null) {
throw new Error("When specifying bitcoin wallet you have to pass both `bitcoinAddress` and `bitcoinPublicKey` params!");
}
if (input.bitcoinFeeRate != null) {
if(isNaN(input.bitcoinFeeRate)) throw new Error("Bitcoin fee rate passed cannot be NaN!");
if(input.bitcoinFeeRate <= 0) throw new Error("Bitcoin fee rate passed cannot be negative or 0!");
}
if(this.config?.syncOnGetStatus) await swap._sync(true);
const {steps, stateInfo, currentAction} = await swap.getExecutionStatus({
secret: input.secret,
bitcoinWallet,
bitcoinFeeRate: input.bitcoinFeeRate,
manualSettlementSmartChainSigner: input.signer,
refundSmartChainSigner: input.signer
});
return {
...createListSwapOutput(swap, steps, stateInfo),
currentAction: currentAction ? await serializeAction(currentAction, this.txSerializer.bind(this)) : null,
requiresSecretReveal: requiresSecretRevealForApi(swap, stateInfo.state),
escrow: swap instanceof IEscrowSwap && swap._data!=null ? {
data: swap._data.getEscrowStruct(),
initTxId: swap._commitTxId
} : undefined
};
}
private async submitTransaction(input: SubmitTransactionInput, abortSignal?: AbortSignal): Promise<SubmitTransactionOutput> {
const swap = await this.swapper.getSwapById(input.swapId);
if (swap == null) {
throw new Error("Swap not found: " + input.swapId);
}
return {
txHashes: await swap._submitExecutionTransactions(input.signedTxs, abortSignal, undefined, this.config?.idempotentTxSubmission)
}
}
private async settleWithLnurl(input: SettleWithLnurlInput, abortSignal?: AbortSignal): Promise<SettleWithLnurlOutput> {
const swap = await this.swapper.getSwapById(input.swapId);
if (swap == null) throw new Error("Swap not found: " + input.swapId);
if (swap instanceof FromBTCLNAutoSwap) {
if(swap._state!==FromBTCLNAutoSwapState.PR_CREATED)
throw new Error("Invalid swap state, must be in PR_CREATED state!");
} else if (swap instanceof FromBTCLNSwap) {
if(swap._state!==FromBTCLNSwapState.PR_CREATED)
throw new Error("Invalid swap state, must be in PR_CREATED state!");
} else {
throw new Error("Endpoint only supports swaps from Lightning");
}
if (!swap.isLNURL()) {
if (input.lnurlWithdraw==null)
throw new Error("The swap is not configured to use LNURL, please pass the `lnurlWithdraw` parameter!");
if (!this.swapper.Utils.isValidLNURL(input.lnurlWithdraw))
throw new Error("Invalid LNURL-withdraw link provided: " + input.lnurlWithdraw);
await swap.settleWithLNURLWithdraw(input.lnurlWithdraw);
} else {
if (input.lnurlWithdraw!=null)
throw new Error("The swap is already configured with an LNURL link, don't pass the `lnurlWithdraw` parameter!");
}
let success: boolean;
if (swap instanceof FromBTCLNAutoSwap) {
// For non-legacy swap, we don't need to wait till the swap advances all the way to committed state
success = await swap._waitForLpPaymentReceived(2, abortSignal);
} else {
// For legacy swap waitForPayment waits just for the swap to transition into PR_PAID
success = await swap.waitForPayment(undefined, 2, abortSignal);
}
if(!success) throw new Error("Failed to settle the swap with the LNURL-withdraw link!");
return {
paymentHash: swap.getInputTxId()!
};
}
}