UNPKG

@atomiqlabs/sdk

Version:

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

809 lines 102 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Swapper = void 0; const base_1 = require("@atomiqlabs/base"); const ToBTCLNWrapper_1 = require("../swaps/escrow_swaps/tobtc/ln/ToBTCLNWrapper"); const ToBTCWrapper_1 = require("../swaps/escrow_swaps/tobtc/onchain/ToBTCWrapper"); const FromBTCLNWrapper_1 = require("../swaps/escrow_swaps/frombtc/ln/FromBTCLNWrapper"); const FromBTCWrapper_1 = require("../swaps/escrow_swaps/frombtc/onchain/FromBTCWrapper"); const IntermediaryDiscovery_1 = require("../intermediaries/IntermediaryDiscovery"); const bolt11_1 = require("@atomiqlabs/bolt11"); const IntermediaryError_1 = require("../errors/IntermediaryError"); const SwapType_1 = require("../enums/SwapType"); const LnForGasWrapper_1 = require("../swaps/trusted/ln/LnForGasWrapper"); const events_1 = require("events"); const Utils_1 = require("../utils/Utils"); const RequestError_1 = require("../errors/RequestError"); const SwapperWithChain_1 = require("./SwapperWithChain"); const OnchainForGasWrapper_1 = require("../swaps/trusted/onchain/OnchainForGasWrapper"); const utils_1 = require("@scure/btc-signer/utils"); const UnifiedSwapStorage_1 = require("../storage/UnifiedSwapStorage"); const UnifiedSwapEventListener_1 = require("../events/UnifiedSwapEventListener"); const SpvFromBTCWrapper_1 = require("../swaps/spv_swaps/SpvFromBTCWrapper"); const SpvFromBTCSwap_1 = require("../swaps/spv_swaps/SpvFromBTCSwap"); const SwapperUtils_1 = require("./SwapperUtils"); const FromBTCLNAutoWrapper_1 = require("../swaps/escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper"); const UserError_1 = require("../errors/UserError"); const AutomaticClockDriftCorrection_1 = require("../utils/AutomaticClockDriftCorrection"); const SwapUtils_1 = require("../utils/SwapUtils"); const IndexedDBUnifiedStorage_1 = require("../storage-browser/IndexedDBUnifiedStorage"); const TokenAmount_1 = require("../types/TokenAmount"); const Token_1 = require("../types/Token"); const Logger_1 = require("../utils/Logger"); const LNURLWithdraw_1 = require("../types/lnurl/LNURLWithdraw"); const LNURLPay_1 = require("../types/lnurl/LNURLPay"); const RetryUtils_1 = require("../utils/RetryUtils"); const IEscrowSwap_1 = require("../swaps/escrow_swaps/IEscrowSwap"); const LightningInvoiceCreateService_1 = require("../types/wallets/LightningInvoiceCreateService"); const IntermediaryAPI_1 = require("../intermediaries/apis/IntermediaryAPI"); const BitcoinWalletUtils_1 = require("../utils/BitcoinWalletUtils"); const SignedKeyBasedAuth_1 = require("../intermediaries/auth/SignedKeyBasedAuth"); /** * Core orchestrator for all atomiq swap operations * * @category Core */ class Swapper extends events_1.EventEmitter { /** * @internal */ constructor(bitcoinRpc, lightningApi, bitcoinSynchronizer, chainsData, pricing, tokens, messenger, options) { super(); this.logger = (0, Logger_1.getLogger)(this.constructor.name + ": "); this.initialized = false; /** * Helper information about various swap protocol and their features: * - `requiresInputWallet`: Whether a swap requires a connected wallet on the input chain able to sign * arbitrary transaction * - `requiresOutputWallet`: Whether a swap requires a connected wallet on the output chain able to sign * arbitrary transactions * - `supportsGasDrop`: Whether a swap supports the "gas drop" feature, allowing to user to receive a small * amount of native token as part of the swap when swapping to smart chains * * Uses a `Record` type here, use the {@link SwapProtocolInfo} import for a literal readonly type, with * pre-filled exact values in the type. */ this.SwapTypeInfo = SwapUtils_1.SwapProtocolInfo; const storagePrefix = options?.storagePrefix ?? "atomiq-"; options ??= {}; options.saveUninitializedSwaps ??= true; options.bitcoinNetwork = options.bitcoinNetwork == null ? base_1.BitcoinNetwork.TESTNET : options.bitcoinNetwork; const swapStorage = options.swapStorage ??= (name) => new IndexedDBUnifiedStorage_1.IndexedDBUnifiedStorage(name); this.options = options; this.bitcoinNetwork = options.bitcoinNetwork; this._btcNetwork = options.bitcoinNetwork === base_1.BitcoinNetwork.MAINNET ? utils_1.NETWORK : (options.bitcoinNetwork === base_1.BitcoinNetwork.TESTNET || options.bitcoinNetwork === base_1.BitcoinNetwork.TESTNET4) ? utils_1.TEST_NETWORK : { bech32: 'bcrt', pubKeyHash: 111, scriptHash: 196, wif: 239 }; this.Utils = new SwapperUtils_1.SwapperUtils(this); this.prices = pricing; this._bitcoinRpc = bitcoinRpc; this.messenger = messenger; this._tokens = {}; this._tokensByTicker = {}; for (let tokenData of tokens) { const chainId = tokenData.chainId; this._tokens[chainId] ??= {}; this._tokensByTicker[chainId] ??= {}; this._tokens[chainId][tokenData.address] = this._tokensByTicker[chainId][tokenData.ticker] = tokenData; } const lpApi = new IntermediaryAPI_1.IntermediaryAPI(this.options.signedKeyBasedAuth != null ? (0, SignedKeyBasedAuth_1.getSignedKeyBasedAuthHandler)(this.options.signedKeyBasedAuth.certificate, this.options.signedKeyBasedAuth.privateKey) : undefined); this.lpApi = lpApi; this.swapStateListener = (swap) => { this.emit("swapState", swap); }; this._chains = (0, Utils_1.objectMap)(chainsData, (chainData, key) => { let { chainInterface, chainEvents, chainId, btcRelay, swapContract, swapDataConstructor, spvVaultContract, spvVaultWithdrawalDataConstructor, spvVaultDataConstructor, defaultVersion, versions } = chainData; defaultVersion ??= "v1"; if (versions == null) { versions = { [defaultVersion]: { btcRelay, swapContract, swapDataConstructor, spvVaultContract, spvVaultDataConstructor, spvVaultWithdrawalDataConstructor } }; } const versionedContracts = (0, Utils_1.objectMap)(versions, (value, key) => { return { swapContract: value.swapContract, spvVaultContract: value.spvVaultContract, btcRelay: value.btcRelay, synchronizer: bitcoinSynchronizer(value.btcRelay) }; }); const storageHandler = swapStorage(storagePrefix + chainId); const unifiedSwapStorage = new UnifiedSwapStorage_1.UnifiedSwapStorage(storageHandler, this.options.noSwapCache); const unifiedChainEvents = new UnifiedSwapEventListener_1.UnifiedSwapEventListener(unifiedSwapStorage, chainEvents); const wrappers = {}; wrappers[SwapType_1.SwapType.TO_BTCLN] = new ToBTCLNWrapper_1.ToBTCLNWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, pricing, this._tokens[chainId], versions, lpApi, { getRequestTimeout: this.options.getRequestTimeout, postRequestTimeout: this.options.postRequestTimeout, saveUninitializedSwaps: this.options.saveUninitializedSwaps, }); wrappers[SwapType_1.SwapType.TO_BTC] = new ToBTCWrapper_1.ToBTCWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, pricing, this._tokens[chainId], versions, this._bitcoinRpc, lpApi, { getRequestTimeout: this.options.getRequestTimeout, postRequestTimeout: this.options.postRequestTimeout, saveUninitializedSwaps: this.options.saveUninitializedSwaps, bitcoinNetwork: this._btcNetwork }); wrappers[SwapType_1.SwapType.FROM_BTCLN] = new FromBTCLNWrapper_1.FromBTCLNWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, pricing, this._tokens[chainId], versions, lightningApi, lpApi, { getRequestTimeout: this.options.getRequestTimeout, postRequestTimeout: this.options.postRequestTimeout, saveUninitializedSwaps: this.options.saveUninitializedSwaps, unsafeSkipLnNodeCheck: this.bitcoinNetwork === base_1.BitcoinNetwork.TESTNET4 || this.bitcoinNetwork === base_1.BitcoinNetwork.REGTEST }); wrappers[SwapType_1.SwapType.FROM_BTC] = new FromBTCWrapper_1.FromBTCWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, pricing, this._tokens[chainId], versions, versionedContracts, this._bitcoinRpc, lpApi, { getRequestTimeout: this.options.getRequestTimeout, postRequestTimeout: this.options.postRequestTimeout, saveUninitializedSwaps: this.options.saveUninitializedSwaps, bitcoinNetwork: this._btcNetwork }); wrappers[SwapType_1.SwapType.TRUSTED_FROM_BTCLN] = new LnForGasWrapper_1.LnForGasWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, pricing, this._tokens[chainId], lpApi, { getRequestTimeout: this.options.getRequestTimeout, postRequestTimeout: this.options.postRequestTimeout, saveUninitializedSwaps: this.options.saveUninitializedSwaps, }); wrappers[SwapType_1.SwapType.TRUSTED_FROM_BTC] = new OnchainForGasWrapper_1.OnchainForGasWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, pricing, this._tokens[chainId], bitcoinRpc, lpApi, { getRequestTimeout: this.options.getRequestTimeout, postRequestTimeout: this.options.postRequestTimeout, saveUninitializedSwaps: this.options.saveUninitializedSwaps, bitcoinNetwork: this._btcNetwork }); // This is gated on the default version of the contracts if (spvVaultContract != null) { wrappers[SwapType_1.SwapType.SPV_VAULT_FROM_BTC] = new SpvFromBTCWrapper_1.SpvFromBTCWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, pricing, this._tokens[chainId], versions, versionedContracts, bitcoinRpc, lpApi, { getRequestTimeout: this.options.getRequestTimeout, postRequestTimeout: this.options.postRequestTimeout, saveUninitializedSwaps: this.options.saveUninitializedSwaps, bitcoinNetwork: this._btcNetwork }); } // This is gated on the default version of the contracts if (swapContract.supportsInitWithoutClaimer) { wrappers[SwapType_1.SwapType.FROM_BTCLN_AUTO] = new FromBTCLNAutoWrapper_1.FromBTCLNAutoWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, pricing, this._tokens[chainId], versions, lightningApi, this.messenger, lpApi, { getRequestTimeout: this.options.getRequestTimeout, postRequestTimeout: this.options.postRequestTimeout, saveUninitializedSwaps: this.options.saveUninitializedSwaps, unsafeSkipLnNodeCheck: this.bitcoinNetwork === base_1.BitcoinNetwork.TESTNET4 || this.bitcoinNetwork === base_1.BitcoinNetwork.REGTEST }); } Object.keys(wrappers).forEach(key => wrappers[key].events.on("swapState", this.swapStateListener)); const reviver = (val) => { const wrapper = wrappers[val.type]; if (wrapper == null) return null; return new wrapper._swapDeserializer(wrapper, val); }; return { chainEvents, chainInterface, wrappers, unifiedChainEvents, unifiedSwapStorage, defaultVersion, reviver, versionedContracts }; }); const contracts = (0, Utils_1.objectMap)(chainsData, (data) => data.versions ?? { [data.defaultVersion ?? "v1"]: { swapContract: data.swapContract, spvVaultContract: data.spvVaultContract } }); if (options.intermediaryUrl != null) { this.intermediaryDiscovery = new IntermediaryDiscovery_1.IntermediaryDiscovery(contracts, lpApi, options.registryUrl, Array.isArray(options.intermediaryUrl) ? options.intermediaryUrl : [options.intermediaryUrl], options.getRequestTimeout); } else { this.intermediaryDiscovery = new IntermediaryDiscovery_1.IntermediaryDiscovery(contracts, lpApi, options.registryUrl, undefined, options.getRequestTimeout); } this.intermediaryDiscovery.on("removed", (intermediaries) => { this.emit("lpsRemoved", intermediaries); }); this.intermediaryDiscovery.on("added", (intermediaries) => { this.emit("lpsAdded", intermediaries); }); } async _init() { this.logger.debug("init(): Initializing swapper"); const abortController = new AbortController(); const promises = []; let automaticClockDriftCorrectionPromise = undefined; if (this.options.automaticClockDriftCorrection) { promises.push(automaticClockDriftCorrectionPromise = (0, RetryUtils_1.tryWithRetries)(AutomaticClockDriftCorrection_1.correctClock, undefined, undefined, abortController.signal).catch((err) => { abortController.abort(err); })); } this.logger.debug("init(): Initializing intermediary discovery"); if (!this.options.dontFetchLPs) promises.push(this.intermediaryDiscovery.init(abortController.signal).catch(err => { if (abortController.signal.aborted) return; this.logger.error("init(): Failed to fetch intermediaries/LPs: ", err); })); if (this.options.defaultTrustedIntermediaryUrl != null) { promises.push(this.intermediaryDiscovery.getIntermediary(this.options.defaultTrustedIntermediaryUrl, abortController.signal) .then(val => { if (val == null) throw new Error("Cannot get trusted LP"); this.defaultTrustedIntermediary = val; }) .catch(err => { if (abortController.signal.aborted) return; this.logger.error("init(): Failed to contact trusted LP url: ", err); })); } if (automaticClockDriftCorrectionPromise != null) { //We should await the promises here before checking the swaps await automaticClockDriftCorrectionPromise; } const chainPromises = []; for (let chainIdentifier in this._chains) { chainPromises.push((async () => { const { chainInterface, versionedContracts, unifiedChainEvents, unifiedSwapStorage, wrappers, reviver } = this._chains[chainIdentifier]; try { const _chainInterface = chainInterface; if (_chainInterface.verifyNetwork != null) { await _chainInterface.verifyNetwork(this.bitcoinNetwork); } for (let contractVersion in versionedContracts) { await versionedContracts[contractVersion].swapContract.start(); this.logger.debug("init(): Intialized swap contract: " + chainIdentifier + ` version: ${contractVersion}`); } await unifiedSwapStorage.init(); if (unifiedSwapStorage.storage instanceof IndexedDBUnifiedStorage_1.IndexedDBUnifiedStorage) { //Try to migrate the data here const storagePrefix = chainIdentifier === "SOLANA" ? "SOLv4-" + this.bitcoinNetwork + "-Swaps-" : "atomiqsdk-" + this.bitcoinNetwork + chainIdentifier + "-Swaps-"; await unifiedSwapStorage.storage.tryMigrate([ [storagePrefix + "FromBTC", SwapType_1.SwapType.FROM_BTC], [storagePrefix + "FromBTCLN", SwapType_1.SwapType.FROM_BTCLN], [storagePrefix + "ToBTC", SwapType_1.SwapType.TO_BTC], [storagePrefix + "ToBTCLN", SwapType_1.SwapType.TO_BTCLN] ], (obj) => { const swap = reviver(obj); if (swap._randomNonce == null) { const oldIdentifierHash = swap.getId(); swap._randomNonce = (0, Utils_1.randomBytes)(16).toString("hex"); const newIdentifierHash = swap.getId(); this.logger.info("init(): Found older swap version without randomNonce, replacing, old hash: " + oldIdentifierHash + " new hash: " + newIdentifierHash); } return swap; }); } await unifiedChainEvents.start(this.options.noEvents); this.logger.debug("init(): Initialized events: " + chainIdentifier); } catch (e) { if (!this.options.gracefullyHandleChainErrors) throw e; this.logger.error(`init(): Failed to initialize ${chainIdentifier} (skipped): `, e); delete this._chains[chainIdentifier]; return; } for (let key in wrappers) { // this.logger.debug("init(): Initializing "+SwapType[key]+": "+chainIdentifier); await wrappers[key].init(this.options.noTimers, this.options.dontCheckPastSwaps); } })()); } await Promise.all(chainPromises); await Promise.all(promises); this.logger.debug("init(): Initializing messenger"); await this.messenger.init(); } /** * Initializes the swap storage and loads existing swaps, needs to be called before any other action */ async init() { if (this.initialized) return; if (this.initPromise != null) { await this.initPromise; return; } try { const promise = this._init(); this.initPromise = promise; await promise; delete this.initPromise; this.initialized = true; } catch (e) { delete this.initPromise; throw e; } } /** * Whether the SDK is initialized (after {@link init} is called) */ isInitialized() { return this.initialized; } /** * Stops listening for onchain events and closes this Swapper instance */ async stop() { if (this.initPromise) await this.initPromise; for (let chainIdentifier in this._chains) { const { wrappers, unifiedChainEvents } = this._chains[chainIdentifier]; for (let key in wrappers) { const wrapper = wrappers[key]; wrapper.events.removeListener("swapState", this.swapStateListener); await wrapper.stop(); } await unifiedChainEvents.stop(); await this.messenger.stop(); } this.initialized = false; } /** * Creates swap & handles intermediary, quote selection * * @param chainIdentifier * @param create Callback to create the * @param amountData Amount data as passed to the function * @param swapType Swap type of the execution * @param maxWaitTimeMS Maximum waiting time after the first intermediary returns the quote * @private * @throws {Error} when no intermediary was found * @throws {Error} if the chain with the provided identifier cannot be found */ async createSwap(chainIdentifier, create, amountData, swapType, maxWaitTimeMS = 2000) { if (!this.initialized) throw new Error("Swapper not initialized, init first with swapper.init()!"); if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); let candidates; const inBtc = swapType === SwapType_1.SwapType.TO_BTCLN || swapType === SwapType_1.SwapType.TO_BTC ? !amountData.exactIn : amountData.exactIn; if (!inBtc || amountData.amount == null) { //Get candidates not based on the amount candidates = this.intermediaryDiscovery.getSwapCandidates(chainIdentifier, swapType, amountData.token); } else { candidates = this.intermediaryDiscovery.getSwapCandidates(chainIdentifier, swapType, amountData.token, amountData.amount); } let swapLimitsChanged = false; if (candidates.length === 0) { this.logger.warn("createSwap(): No valid intermediary found to execute the swap with, reloading intermediary database..."); await this.intermediaryDiscovery.reloadIntermediaries(); swapLimitsChanged = true; if (!inBtc || amountData.amount == null) { //Get candidates not based on the amount candidates = this.intermediaryDiscovery.getSwapCandidates(chainIdentifier, swapType, amountData.token); } else { candidates = this.intermediaryDiscovery.getSwapCandidates(chainIdentifier, swapType, amountData.token, amountData.amount); if (candidates.length === 0) { const min = this.intermediaryDiscovery.getSwapMinimum(chainIdentifier, swapType, amountData.token); const max = this.intermediaryDiscovery.getSwapMaximum(chainIdentifier, swapType, amountData.token); if (min != null && max != null) { if (amountData.amount < BigInt(min)) throw new RequestError_1.OutOfBoundsError("Swap amount too low! Try swapping a higher amount.", 200, BigInt(min), BigInt(max)); if (amountData.amount > BigInt(max)) throw new RequestError_1.OutOfBoundsError("Swap amount too high! Try swapping a lower amount.", 200, BigInt(min), BigInt(max)); } } } if (candidates.length === 0) throw new Error("No intermediary found for the requested pair and amount! You can try swapping different pair or higher/lower amount."); } const abortController = new AbortController(); this.logger.debug("createSwap() Swap candidates: ", candidates.map(lp => lp.url).join()); const quotePromises = await create(candidates, abortController.signal, this._chains[chainIdentifier]); const promiseAll = new Promise((resolve, reject) => { let min; let max; let error; let numResolved = 0; let quotes = []; let timeout; quotePromises.forEach(data => { data.quote.then(quote => { if (numResolved === 0) { timeout = setTimeout(() => { abortController.abort(new Error("Timed out waiting for quote!")); resolve(quotes); }, maxWaitTimeMS); } numResolved++; quotes.push({ quote, intermediary: data.intermediary }); if (numResolved === quotePromises.length) { clearTimeout(timeout); resolve(quotes); return; } }).catch(e => { numResolved++; if (e instanceof IntermediaryError_1.IntermediaryError) { //Blacklist that node this.intermediaryDiscovery.removeIntermediary(data.intermediary); swapLimitsChanged = true; } else if (e instanceof RequestError_1.OutOfBoundsError) { if (min == null || max == null) { min = e.min; max = e.max; } else { min = (0, Utils_1.bigIntMin)(min, e.min); max = (0, Utils_1.bigIntMax)(max, e.max); } data.intermediary.swapBounds[swapType] ??= {}; data.intermediary.swapBounds[swapType][chainIdentifier] ??= {}; const tokenBoundsData = (data.intermediary.swapBounds[swapType][chainIdentifier][amountData.token] ??= { input: {}, output: {} }); if (amountData.exactIn) { tokenBoundsData.input = { min: e.min, max: e.max }; } else { tokenBoundsData.output = { min: e.min, max: e.max }; } swapLimitsChanged = true; } this.logger.warn("createSwap(): Intermediary " + data.intermediary.url + " error: ", e); error = e; if (numResolved === quotePromises.length) { if (timeout != null) clearTimeout(timeout); if (quotes.length > 0) { resolve(quotes); return; } if (min != null && max != null) { let msg = "Swap amount too high or too low! Try swapping a different amount."; if (amountData.amount != null) { if (min > amountData.amount) msg = "Swap amount too low! Try swapping a higher amount."; if (max < amountData.amount) msg = "Swap amount too high! Try swapping a lower amount."; } reject(new RequestError_1.OutOfBoundsError(msg, 400, min, max)); return; } reject(error); } }); }); }); try { const quotes = await promiseAll; //TODO: Intermediary's reputation is not taken into account! quotes.sort((a, b) => { if (amountData.exactIn) { //Compare outputs return (0, Utils_1.bigIntCompare)(b.quote.getOutput().rawAmount, a.quote.getOutput().rawAmount); } else { //Compare inputs return (0, Utils_1.bigIntCompare)(a.quote.getInput().rawAmount, b.quote.getInput().rawAmount); } }); this.logger.debug("createSwap(): Sorted quotes, best price to worst: ", quotes); if (swapLimitsChanged) this.emit("swapLimitsChanged"); const quote = quotes[0].quote; await quote._save(); return quote; } catch (e) { if (swapLimitsChanged) this.emit("swapLimitsChanged"); throw e; } } /** * Creates Smart chain -> Bitcoin ({@link SwapType.TO_BTC}) swap * * @param chainIdentifier Chain identifier string of the source smart chain * @param signer Signer's address on the source chain * @param tokenAddress Token address to pay with * @param address Recipient's bitcoin address * @param amount Amount to send in token based units (if `exactIn=true`) or receive in satoshis (if `exactIn=false`) * @param exactIn Whether to use exact in instead of exact out * @param additionalParams Additional parameters sent to the LP when creating the swap * @param options Additional options for the swap */ createToBTCSwap(chainIdentifier, signer, tokenAddress, address, amount, exactIn = false, additionalParams = this.options.defaultAdditionalParameters, options) { if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); if (address.startsWith("bitcoin:")) { address = address.substring(8).split("?")[0]; } if (!this.Utils.isValidBitcoinAddress(address)) throw new Error("Invalid bitcoin address"); if (!this._chains[chainIdentifier].chainInterface.isValidAddress(signer, true)) throw new Error("Invalid " + chainIdentifier + " address"); signer = this._chains[chainIdentifier].chainInterface.normalizeAddress(signer); const amountData = { amount, token: tokenAddress, exactIn }; return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => Promise.resolve(chain.wrappers[SwapType_1.SwapType.TO_BTC].create(signer, address, amountData, candidates, options, additionalParams, abortSignal)), amountData, SwapType_1.SwapType.TO_BTC); } /** * Creates Smart chain -> Bitcoin Lightning ({@link SwapType.TO_BTCLN}) swap * * @param chainIdentifier Chain identifier string of the source smart chain * @param signer Signer's address on the source chain * @param tokenAddress Token address to pay with * @param paymentRequest BOLT11 lightning network invoice to be paid (needs to have a fixed amount), and the swap * amount is taken from this fixed amount, hence only exact output swaps are supported * @param additionalParams Additional parameters sent to the LP when creating the swap * @param options Additional options for the swap */ async createToBTCLNSwap(chainIdentifier, signer, tokenAddress, paymentRequest, additionalParams = this.options.defaultAdditionalParameters, options) { if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); if (paymentRequest.startsWith("lightning:")) paymentRequest = paymentRequest.substring(10); if (!this.Utils.isValidLightningInvoice(paymentRequest)) throw new Error("Invalid lightning network invoice"); if (!this._chains[chainIdentifier].chainInterface.isValidAddress(signer, true)) throw new Error("Invalid " + chainIdentifier + " address"); signer = this._chains[chainIdentifier].chainInterface.normalizeAddress(signer); const parsedPR = (0, bolt11_1.decode)(paymentRequest); if (parsedPR.millisatoshis == null) throw new Error("Invalid lightning network invoice, no msat value field!"); const amountData = { amount: (BigInt(parsedPR.millisatoshis) + 999n) / 1000n, token: tokenAddress, exactIn: false }; return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => chain.wrappers[SwapType_1.SwapType.TO_BTCLN].create(signer, paymentRequest, amountData, candidates, options, additionalParams, abortSignal), amountData, SwapType_1.SwapType.TO_BTCLN); } /** * Creates Smart chain -> Bitcoin Lightning ({@link SwapType.TO_BTCLN}) swap via LNURL-pay link * * @param chainIdentifier Chain identifier string of the source smart chain * @param signer Signer's address on the source chain * @param tokenAddress Token address to pay with * @param lnurlPay LNURL-pay link to use for the payment * @param amount Amount to send in token based units (if `exactIn=true`) or receive in satoshis (if `exactIn=false`) * @param exactIn Whether to do an exact in swap instead of exact out * @param additionalParams Additional parameters sent to the LP when creating the swap * @param options Additional options for the swap */ async createToBTCLNSwapViaLNURL(chainIdentifier, signer, tokenAddress, lnurlPay, amount, exactIn = false, additionalParams = this.options.defaultAdditionalParameters, options) { if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); if (typeof (lnurlPay) === "string" && !this.Utils.isValidLNURL(lnurlPay)) throw new Error("Invalid LNURL-pay link"); if (!this._chains[chainIdentifier].chainInterface.isValidAddress(signer, true)) throw new Error("Invalid " + chainIdentifier + " address"); signer = this._chains[chainIdentifier].chainInterface.normalizeAddress(signer); const amountData = { amount, token: tokenAddress, exactIn }; return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => chain.wrappers[SwapType_1.SwapType.TO_BTCLN].createViaLNURL(signer, typeof (lnurlPay) === "string" ? (lnurlPay.startsWith("lightning:") ? lnurlPay.substring(10) : lnurlPay) : lnurlPay.params, amountData, candidates, options, additionalParams, abortSignal), amountData, SwapType_1.SwapType.TO_BTCLN); } /** * Creates Smart chain -> Bitcoin Lightning ({@link SwapType.TO_BTCLN}) swap via {@link LightningInvoiceCreateService} * * @param chainIdentifier Chain identifier string of the source smart chain * @param signer Signer's address on the source chain * @param tokenAddress Token address to pay with * @param service Invoice create service object which facilitates the creation of fixed amount LN invoices * @param amount Amount to send in token based units (if `exactIn=true`) or receive in satoshis (if `exactIn=false`) * @param exactIn Whether to do an exact in swap instead of exact out * @param additionalParams Additional parameters sent to the LP when creating the swap * @param options Additional options for the swap */ async createToBTCLNSwapViaInvoiceCreateService(chainIdentifier, signer, tokenAddress, service, amount, exactIn = false, additionalParams = this.options.defaultAdditionalParameters, options) { if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); if (!this._chains[chainIdentifier].chainInterface.isValidAddress(signer, true)) throw new Error("Invalid " + chainIdentifier + " address"); signer = this._chains[chainIdentifier].chainInterface.normalizeAddress(signer); const amountData = { amount, token: tokenAddress, exactIn }; return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => chain.wrappers[SwapType_1.SwapType.TO_BTCLN].createViaInvoiceCreateService(signer, Promise.resolve(service), amountData, candidates, options, additionalParams, abortSignal), amountData, SwapType_1.SwapType.TO_BTCLN); } /** * Creates Bitcoin -> Smart chain ({@link SwapType.SPV_VAULT_FROM_BTC}) swap * * @param chainIdentifier Chain identifier string of the destination smart chain * @param recipient Recipient address on the destination chain * @param tokenAddress Token address to receive * @param amount Amount to send in satoshis (if `exactOut=false`) or receive in token based units (if `exactOut=true`) * @param exactOut Whether to use a exact out instead of exact in * @param additionalParams Additional parameters sent to the LP when creating the swap * @param options Additional options for the swap */ async createFromBTCSwapNew(chainIdentifier, recipient, tokenAddress, amount, exactOut = false, additionalParams = this.options.defaultAdditionalParameters, options) { if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); if (this._chains[chainIdentifier].wrappers[SwapType_1.SwapType.SPV_VAULT_FROM_BTC] == null) throw new Error("Chain " + chainIdentifier + " doesn't support new BTC swap protocol (spv vault swaps)!"); if (!this._chains[chainIdentifier].chainInterface.isValidAddress(recipient, true)) throw new Error("Invalid " + chainIdentifier + " address"); recipient = this._chains[chainIdentifier].chainInterface.normalizeAddress(recipient); const amountData = { amount: amount ?? undefined, token: tokenAddress, exactIn: !exactOut }; return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => Promise.resolve(chain.wrappers[SwapType_1.SwapType.SPV_VAULT_FROM_BTC].create(recipient, amountData, candidates, options, additionalParams, abortSignal)), amountData, SwapType_1.SwapType.SPV_VAULT_FROM_BTC); } /** * Creates LEGACY Bitcoin -> Smart chain ({@link SwapType.FROM_BTC}) swap * * @param chainIdentifier Chain identifier string of the destination smart chain * @param recipient Recipient address on the destination chain * @param tokenAddress Token address to receive * @param amount Amount to send in satoshis (if `exactOut=false`) or receive in token based units (if `exactOut=true`) * @param exactOut Whether to use a exact out instead of exact in * @param additionalParams Additional parameters sent to the LP when creating the swap * @param options Additional options for the swap */ async createFromBTCSwap(chainIdentifier, recipient, tokenAddress, amount, exactOut = false, additionalParams = this.options.defaultAdditionalParameters, options) { if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); if (!this._chains[chainIdentifier].chainInterface.isValidAddress(recipient, true)) throw new Error("Invalid " + chainIdentifier + " address"); recipient = this._chains[chainIdentifier].chainInterface.normalizeAddress(recipient); const amountData = { amount, token: tokenAddress, exactIn: !exactOut }; return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => Promise.resolve(chain.wrappers[SwapType_1.SwapType.FROM_BTC].create(recipient, amountData, candidates, options, additionalParams, abortSignal)), amountData, SwapType_1.SwapType.FROM_BTC); } /** * Creates LEGACY Bitcoin Lightning -> Smart chain ({@link SwapType.FROM_BTCLN}) swap * * @param chainIdentifier Chain identifier string of the destination smart chain * @param recipient Recipient address on the destination chain * @param tokenAddress Token address to receive * @param amount Amount to send in satoshis (if `exactOut=false`) or receive in token based units (if `exactOut=true`) * @param exactOut Whether to use a exact out instead of exact in * @param additionalParams Additional parameters sent to the LP when creating the swap * @param options Additional options for the swap */ async createFromBTCLNSwap(chainIdentifier, recipient, tokenAddress, amount, exactOut = false, additionalParams = this.options.defaultAdditionalParameters, options) { if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); if (!this._chains[chainIdentifier].chainInterface.isValidAddress(recipient, true)) throw new Error("Invalid " + chainIdentifier + " address"); recipient = this._chains[chainIdentifier].chainInterface.normalizeAddress(recipient); const amountData = { amount, token: tokenAddress, exactIn: !exactOut }; return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => Promise.resolve(chain.wrappers[SwapType_1.SwapType.FROM_BTCLN].create(recipient, amountData, candidates, options, additionalParams, abortSignal)), amountData, SwapType_1.SwapType.FROM_BTCLN); } /** * Creates LEGACY Bitcoin Lightning -> Smart chain ({@link SwapType.FROM_BTCLN}) swap, withdrawing from * an LNURL-withdraw link * * @param chainIdentifier Chain identifier string of the destination smart chain * @param recipient Recipient address on the destination chain * @param tokenAddress Token address to receive * @param lnurl LNURL-withdraw link to pull the funds from * @param amount Amount to send in satoshis (if `exactOut=false`) or receive in token based units (if `exactOut=true`) * @param exactOut Whether to use a exact out instead of exact in * @param additionalParams Additional parameters sent to the LP when creating the swap * @param options Additional options for the swap */ async createFromBTCLNSwapViaLNURL(chainIdentifier, recipient, tokenAddress, lnurl, amount, exactOut = false, additionalParams = this.options.defaultAdditionalParameters, options) { if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); if (typeof (lnurl) === "string" && !this.Utils.isValidLNURL(lnurl)) throw new Error("Invalid LNURL-withdraw link"); if (!this._chains[chainIdentifier].chainInterface.isValidAddress(recipient, true)) throw new Error("Invalid " + chainIdentifier + " address"); recipient = this._chains[chainIdentifier].chainInterface.normalizeAddress(recipient); const amountData = { amount, token: tokenAddress, exactIn: !exactOut }; return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => chain.wrappers[SwapType_1.SwapType.FROM_BTCLN].createViaLNURL(recipient, typeof (lnurl) === "string" ? (lnurl.startsWith("lightning:") ? lnurl.substring(10) : lnurl) : lnurl.params, amountData, candidates, options, additionalParams, abortSignal), amountData, SwapType_1.SwapType.FROM_BTCLN); } /** * Creates Bitcoin Lightning -> Smart chain ({@link SwapType.FROM_BTCLN_AUTO}) swap * * @param chainIdentifier Chain identifier string of the destination smart chain * @param recipient Recipient address on the destination chain * @param tokenAddress Token address to receive * @param amount Amount to send in satoshis (if `exactOut=false`) or receive in token based units (if `exactOut=true`) * @param exactOut Whether to use a exact out instead of exact in * @param additionalParams Additional parameters sent to the LP when creating the swap * @param options Additional options for the swap */ async createFromBTCLNSwapNew(chainIdentifier, recipient, tokenAddress, amount, exactOut = false, additionalParams = this.options.defaultAdditionalParameters, options) { if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); if (this._chains[chainIdentifier].wrappers[SwapType_1.SwapType.FROM_BTCLN_AUTO] == null) throw new Error("Chain " + chainIdentifier + " doesn't support new lightning swap protocol (from btcln auto)!"); if (!this._chains[chainIdentifier].chainInterface.isValidAddress(recipient, true)) throw new Error("Invalid " + chainIdentifier + " address"); recipient = this._chains[chainIdentifier].chainInterface.normalizeAddress(recipient); const amountData = { amount, token: tokenAddress, exactIn: !exactOut }; return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => Promise.resolve(chain.wrappers[SwapType_1.SwapType.FROM_BTCLN_AUTO].create(recipient, amountData, candidates, options, additionalParams, abortSignal)), amountData, SwapType_1.SwapType.FROM_BTCLN_AUTO); } /** * Creates Bitcoin Lightning -> Smart chain ({@link SwapType.FROM_BTCLN_AUTO}) swap, withdrawing from * an LNURL-withdraw link * * @param chainIdentifier Chain identifier string of the destination smart chain * @param recipient Recipient address on the destination chain * @param tokenAddress Token address to receive * @param lnurl LNURL-withdraw link to pull the funds from * @param amount Amount to send in satoshis (if `exactOut=false`) or receive in token based units (if `exactOut=true`) * @param exactOut Whether to use a exact out instead of exact in * @param additionalParams Additional parameters sent to the LP when creating the swap * @param options Additional options for the swap */ async createFromBTCLNSwapNewViaLNURL(chainIdentifier, recipient, tokenAddress, lnurl, amount, exactOut = false, additionalParams = this.options.defaultAdditionalParameters, options) { if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); if (this._chains[chainIdentifier].wrappers[SwapType_1.SwapType.FROM_BTCLN_AUTO] == null) throw new Error("Chain " + chainIdentifier + " doesn't support new lightning swap protocol (from btcln auto)!"); if (typeof (lnurl) === "string" && !this.Utils.isValidLNURL(lnurl)) throw new Error("Invalid LNURL-withdraw link"); if (!this._chains[chainIdentifier].chainInterface.isValidAddress(recipient, true)) throw new Error("Invalid " + chainIdentifier + " address"); recipient = this._chains[chainIdentifier].chainInterface.normalizeAddress(recipient); const amountData = { amount, token: tokenAddress, exactIn: !exactOut }; return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => chain.wrappers[SwapType_1.SwapType.FROM_BTCLN_AUTO].createViaLNURL(recipient, typeof (lnurl) === "string" ? (lnurl.startsWith("lightning:") ? lnurl.substring(10) : lnurl) : lnurl.params, amountData, candidates, options, additionalParams, abortSignal), amountData, SwapType_1.SwapType.FROM_BTCLN_AUTO); } /** * Creates a trusted Bitcoin Lightning -> Smart chain ({@link SwapType.TRUSTED_FROM_BTCLN}) gas swap * * @param chainIdentifier Chain identifier string of the destination smart chain * @param recipient Recipient address on the destination chain * @param amount Amount of native token to receive, in base units * @param trustedIntermediaryOrUrl URL or Intermediary object of the trusted intermediary to use, otherwise uses default * @throws {Error} If no trusted intermediary specified */ async createTrustedLNForGasSwap(chainIdentifier, recipient, amount, trustedIntermediaryOrUrl) { if (this._chains[chainIdentifier] == null) throw new Error("Invalid chain identifier! Unknown chain: " + chainIdentifier); if (!this._chains[chainIdentifier].chainInterface.isValidAddress(recipient, true)) throw new Error("Invalid " + chainIdentifier + " address"); recipient = this._chains[chainIdentifier].chainInterface.normalizeAddress(recipient); const useUrl = trustedIntermediaryOrUrl ?? this.defaultTrustedIntermediary ?? this.options.defaultTrustedIntermediaryUrl; if (useUrl == null) throw new Error("No trusted intermediary specified!"); const swap = await this._chains[chainIdentifier].wrappers[SwapType_1.SwapType.TRUSTED_FROM_BTCLN].create(recipient, amount, useUrl); await swap._save(); return swap; } /** * Creates a trusted Bitcoin -> Smart chain ({@link SwapType.TRUSTED_FROM_BTC}) gas swap * * @param chainIdentifier Chain identifier string of the destination smart chain * @param recipient Recipient address on the destination chain * @param amount Amount of native token to receive, in base units * @param refundAddress Bitcoin refund address, in case the swap fails the funds are refunded here * @param trustedIntermediaryOrUrl URL or Intermediary object of the trusted intermediary to use, otherwise uses default * @throws {Error} If no trusted intermediary specified */ async createTrustedOnchainForGasSwap(chainIdentifier, recipient, amount, refundAddress, trustedIntermediaryOrUrl) { if (this._c