@atomiqlabs/sdk-lib
Version:
Basic SDK functionality library for atomiq
858 lines (857 loc) • 82.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Swapper = void 0;
const base_1 = require("@atomiqlabs/base");
const ToBTCLNWrapper_1 = require("../escrow_swaps/tobtc/ln/ToBTCLNWrapper");
const ToBTCWrapper_1 = require("../escrow_swaps/tobtc/onchain/ToBTCWrapper");
const FromBTCLNWrapper_1 = require("../escrow_swaps/frombtc/ln/FromBTCLNWrapper");
const FromBTCWrapper_1 = require("../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 MempoolBtcRelaySynchronizer_1 = require("../../btc/mempool/synchronizer/MempoolBtcRelaySynchronizer");
const LnForGasWrapper_1 = require("../trusted/ln/LnForGasWrapper");
const events_1 = require("events");
const LNURL_1 = require("../../utils/LNURL");
const Utils_1 = require("../../utils/Utils");
const RequestError_1 = require("../../errors/RequestError");
const SwapperWithChain_1 = require("./SwapperWithChain");
const Tokens_1 = require("../../Tokens");
const OnchainForGasWrapper_1 = require("../trusted/onchain/OnchainForGasWrapper");
const utils_1 = require("@scure/btc-signer/utils");
const IndexedDBUnifiedStorage_1 = require("../../browser-storage/IndexedDBUnifiedStorage");
const UnifiedSwapStorage_1 = require("../../storage/UnifiedSwapStorage");
const UnifiedSwapEventListener_1 = require("../../events/UnifiedSwapEventListener");
const SpvFromBTCWrapper_1 = require("../spv_swaps/SpvFromBTCWrapper");
const SwapperUtils_1 = require("./utils/SwapperUtils");
const FromBTCLNAutoWrapper_1 = require("../escrow_swaps/frombtc/ln_auto/FromBTCLNAutoWrapper");
const UserError_1 = require("../../errors/UserError");
const AutomaticClockDriftCorrection_1 = require("../../utils/AutomaticClockDriftCorrection");
class Swapper extends events_1.EventEmitter {
constructor(bitcoinRpc, chainsData, pricing, tokens, messenger, options) {
super();
this.logger = (0, Utils_1.getLogger)(this.constructor.name + ": ");
this.initialized = false;
this.SwapTypeInfo = {
[SwapType_1.SwapType.TO_BTC]: {
requiresInputWallet: true,
requiresOutputWallet: false,
supportsGasDrop: false
},
[SwapType_1.SwapType.TO_BTCLN]: {
requiresInputWallet: true,
requiresOutputWallet: false,
supportsGasDrop: false
},
[SwapType_1.SwapType.FROM_BTC]: {
requiresInputWallet: false,
requiresOutputWallet: true,
supportsGasDrop: false
},
[SwapType_1.SwapType.FROM_BTCLN]: {
requiresInputWallet: false,
requiresOutputWallet: true,
supportsGasDrop: false
},
[SwapType_1.SwapType.SPV_VAULT_FROM_BTC]: {
requiresInputWallet: true,
requiresOutputWallet: false,
supportsGasDrop: true
},
[SwapType_1.SwapType.FROM_BTCLN_AUTO]: {
requiresInputWallet: false,
requiresOutputWallet: false,
supportsGasDrop: true
},
[SwapType_1.SwapType.TRUSTED_FROM_BTC]: {
requiresInputWallet: false,
requiresOutputWallet: false,
supportsGasDrop: false
},
[SwapType_1.SwapType.TRUSTED_FROM_BTCLN]: {
requiresInputWallet: false,
requiresOutputWallet: false,
supportsGasDrop: false
}
};
const storagePrefix = options?.storagePrefix ?? "atomiq-";
options.bitcoinNetwork = options.bitcoinNetwork == null ? base_1.BitcoinNetwork.TESTNET : options.bitcoinNetwork;
options.swapStorage ??= (name) => new IndexedDBUnifiedStorage_1.IndexedDBUnifiedStorage(name);
this._bitcoinNetwork = options.bitcoinNetwork;
this.bitcoinNetwork = 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.mempoolApi = bitcoinRpc.api;
this.messenger = messenger;
this.options = options;
this.tokens = {};
this.tokensByTicker = {};
for (let tokenData of tokens) {
for (let chainId in tokenData.chains) {
const chainData = tokenData.chains[chainId];
this.tokens[chainId] ??= {};
this.tokensByTicker[chainId] ??= {};
this.tokens[chainId][chainData.address] = this.tokensByTicker[chainId][tokenData.ticker] = {
chain: "SC",
chainId,
ticker: tokenData.ticker,
name: tokenData.name,
decimals: chainData.decimals,
displayDecimals: chainData.displayDecimals,
address: chainData.address
};
}
}
this.swapStateListener = (swap) => {
this.emit("swapState", swap);
};
this.chains = (0, Utils_1.objectMap)(chainsData, (chainData, key) => {
const { swapContract, chainEvents, btcRelay, chainInterface, spvVaultContract, spvVaultWithdrawalDataConstructor } = chainData;
const synchronizer = new MempoolBtcRelaySynchronizer_1.MempoolBtcRelaySynchronizer(btcRelay, bitcoinRpc);
const storageHandler = options.swapStorage(storagePrefix + chainData.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, swapContract, pricing, tokens, chainData.swapDataConstructor, {
getRequestTimeout: options.getRequestTimeout,
postRequestTimeout: options.postRequestTimeout,
});
wrappers[SwapType_1.SwapType.TO_BTC] = new ToBTCWrapper_1.ToBTCWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, swapContract, pricing, tokens, chainData.swapDataConstructor, this.bitcoinRpc, {
getRequestTimeout: options.getRequestTimeout,
postRequestTimeout: options.postRequestTimeout,
bitcoinNetwork: this.bitcoinNetwork
});
wrappers[SwapType_1.SwapType.FROM_BTCLN] = new FromBTCLNWrapper_1.FromBTCLNWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, swapContract, pricing, tokens, chainData.swapDataConstructor, bitcoinRpc, {
getRequestTimeout: options.getRequestTimeout,
postRequestTimeout: options.postRequestTimeout,
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, swapContract, pricing, tokens, chainData.swapDataConstructor, btcRelay, synchronizer, this.bitcoinRpc, {
getRequestTimeout: options.getRequestTimeout,
postRequestTimeout: options.postRequestTimeout,
bitcoinNetwork: this.bitcoinNetwork
});
wrappers[SwapType_1.SwapType.TRUSTED_FROM_BTCLN] = new LnForGasWrapper_1.LnForGasWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, pricing, tokens, {
getRequestTimeout: options.getRequestTimeout,
postRequestTimeout: options.postRequestTimeout
});
wrappers[SwapType_1.SwapType.TRUSTED_FROM_BTC] = new OnchainForGasWrapper_1.OnchainForGasWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, pricing, tokens, bitcoinRpc, {
getRequestTimeout: options.getRequestTimeout,
postRequestTimeout: options.postRequestTimeout,
bitcoinNetwork: this.bitcoinNetwork
});
if (spvVaultContract != null) {
wrappers[SwapType_1.SwapType.SPV_VAULT_FROM_BTC] = new SpvFromBTCWrapper_1.SpvFromBTCWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, spvVaultContract, pricing, tokens, spvVaultWithdrawalDataConstructor, btcRelay, synchronizer, bitcoinRpc, {
getRequestTimeout: options.getRequestTimeout,
postRequestTimeout: options.postRequestTimeout,
bitcoinNetwork: this.bitcoinNetwork
});
}
if (swapContract.supportsInitWithoutClaimer) {
wrappers[SwapType_1.SwapType.FROM_BTCLN_AUTO] = new FromBTCLNAutoWrapper_1.FromBTCLNAutoWrapper(key, unifiedSwapStorage, unifiedChainEvents, chainInterface, swapContract, pricing, tokens, chainData.swapDataConstructor, bitcoinRpc, this.messenger, {
getRequestTimeout: options.getRequestTimeout,
postRequestTimeout: options.postRequestTimeout,
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,
spvVaultContract,
swapContract,
chainInterface,
btcRelay,
synchronizer,
wrappers,
unifiedChainEvents,
unifiedSwapStorage,
reviver
};
});
const contracts = (0, Utils_1.objectMap)(chainsData, (data) => data.swapContract);
if (options.intermediaryUrl != null) {
this.intermediaryDiscovery = new IntermediaryDiscovery_1.IntermediaryDiscovery(contracts, options.registryUrl, Array.isArray(options.intermediaryUrl) ? options.intermediaryUrl : [options.intermediaryUrl], options.getRequestTimeout);
}
else {
this.intermediaryDiscovery = new IntermediaryDiscovery_1.IntermediaryDiscovery(contracts, options.registryUrl, null, 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, sdk-lib version 16.1.3");
const abortController = new AbortController();
const promises = [];
let automaticClockDriftCorrectionPromise;
if (this.options.automaticClockDriftCorrection) {
promises.push(automaticClockDriftCorrectionPromise = (0, Utils_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 => {
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 { swapContract, unifiedChainEvents, unifiedSwapStorage, wrappers, reviver } = this.chains[chainIdentifier];
await swapContract.start();
this.logger.debug("init(): Intialized swap contract: " + chainIdentifier);
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;
});
}
if (!this.options.noEvents)
await unifiedChainEvents.start();
this.logger.debug("init(): Intialized events: " + chainIdentifier);
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;
try {
const promise = this._init();
this.initPromise = promise;
await promise;
delete this.initPromise;
this.initialized = true;
}
catch (e) {
delete this.initPromise;
throw e;
}
}
/**
* 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) {
wrappers[key].events.removeListener("swapState", this.swapStateListener);
await wrappers[key].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) {
//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, reloading intermediary database...");
await this.intermediaryDiscovery.reloadIntermediaries();
swapLimitsChanged = true;
if (!inBtc) {
//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("Amount too low!", 200, BigInt(min), BigInt(max));
if (amountData.amount > BigInt(max))
throw new RequestError_1.OutOfBoundsError("Amount too high!", 200, BigInt(min), BigInt(max));
}
}
}
if (candidates.length === 0)
throw new Error("No intermediary found!");
}
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: null, output: null });
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) {
reject(new RequestError_1.OutOfBoundsError("Out of bounds", 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;
if (this.options.saveUninitializedSwaps) {
quote._setInitiated();
await quote._save();
}
return quote;
}
catch (e) {
if (swapLimitsChanged)
this.emit("swapLimitsChanged");
throw e;
}
}
/**
* Creates To BTC swap
*
* @param chainIdentifier
* @param signer
* @param tokenAddress Token address to pay with
* @param address Recipient's bitcoin address
* @param amount Amount to send in satoshis (bitcoin's smallest denomination)
* @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
*/
createToBTCSwap(chainIdentifier, signer, tokenAddress, address, amount, exactIn, 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);
options ??= {};
options.confirmationTarget ??= 3;
options.confirmations ??= 2;
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 To BTCLN swap
*
* @param chainIdentifier
* @param signer
* @param tokenAddress Token address to pay with
* @param paymentRequest BOLT11 lightning network invoice to be paid (needs to have a fixed amount)
* @param additionalParams Additional parameters sent to the LP when creating the swap
* @param options
*/
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);
options ??= {};
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);
const amountData = {
amount: (BigInt(parsedPR.millisatoshis) + 999n) / 1000n,
token: tokenAddress,
exactIn: false
};
options.expirySeconds ??= 5 * 24 * 3600;
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 To BTCLN swap via LNURL-pay
*
* @param chainIdentifier
* @param signer
* @param tokenAddress Token address to pay with
* @param lnurlPay LNURL-pay link to use for the payment
* @param amount Amount to be paid in sats
* @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
*/
async createToBTCLNSwapViaLNURL(chainIdentifier, signer, tokenAddress, lnurlPay, amount, exactIn, 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);
options ??= {};
const amountData = {
amount,
token: tokenAddress,
exactIn
};
options.expirySeconds ??= 5 * 24 * 3600;
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 To BTCLN swap via InvoiceCreationService
*
* @param chainIdentifier
* @param signer
* @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 be paid in sats
* @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
*/
async createToBTCLNSwapViaInvoiceCreateService(chainIdentifier, signer, tokenAddress, service, amount, exactIn, 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);
options ??= {};
const amountData = {
amount,
token: tokenAddress,
exactIn
};
options.expirySeconds ??= 5 * 24 * 3600;
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 From BTC swap
*
* @param chainIdentifier
* @param signer
* @param tokenAddress Token address to receive
* @param amount Amount to receive, in satoshis (bitcoin's smallest denomination)
* @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
*/
async createFromBTCSwapNew(chainIdentifier, signer, tokenAddress, amount, exactOut, 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: !exactOut
};
return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => Promise.resolve(chain.wrappers[SwapType_1.SwapType.SPV_VAULT_FROM_BTC].create(signer, amountData, candidates, options, additionalParams, abortSignal)), amountData, SwapType_1.SwapType.SPV_VAULT_FROM_BTC);
}
/**
* Creates From BTC swap
*
* @param chainIdentifier
* @param signer
* @param tokenAddress Token address to receive
* @param amount Amount to receive, in satoshis (bitcoin's smallest denomination)
* @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
*/
async createFromBTCSwap(chainIdentifier, signer, tokenAddress, amount, exactOut, 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: !exactOut
};
return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => Promise.resolve(chain.wrappers[SwapType_1.SwapType.FROM_BTC].create(signer, amountData, candidates, options, additionalParams, abortSignal)), amountData, SwapType_1.SwapType.FROM_BTC);
}
/**
* Creates From BTCLN swap
*
* @param chainIdentifier
* @param signer
* @param tokenAddress Token address to receive
* @param amount Amount to receive, in satoshis (bitcoin's smallest denomination)
* @param exactOut Whether to use exact out instead of exact in
* @param additionalParams Additional parameters sent to the LP when creating the swap
* @param options
*/
async createFromBTCLNSwap(chainIdentifier, signer, tokenAddress, amount, exactOut, 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: !exactOut
};
return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => Promise.resolve(chain.wrappers[SwapType_1.SwapType.FROM_BTCLN].create(signer, amountData, candidates, options, additionalParams, abortSignal)), amountData, SwapType_1.SwapType.FROM_BTCLN);
}
/**
* Creates From BTCLN swap, withdrawing from LNURL-withdraw
*
* @param chainIdentifier
* @param signer
* @param tokenAddress Token address to receive
* @param lnurl LNURL-withdraw to pull the funds from
* @param amount Amount to receive, in satoshis (bitcoin's smallest denomination)
* @param exactOut Whether to use exact out instead of exact in
* @param additionalParams Additional parameters sent to the LP when creating the swap
*/
async createFromBTCLNSwapViaLNURL(chainIdentifier, signer, tokenAddress, lnurl, amount, exactOut, additionalParams = this.options.defaultAdditionalParameters) {
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(signer, true))
throw new Error("Invalid " + chainIdentifier + " address");
signer = this.chains[chainIdentifier].chainInterface.normalizeAddress(signer);
const amountData = {
amount,
token: tokenAddress,
exactIn: !exactOut
};
return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => chain.wrappers[SwapType_1.SwapType.FROM_BTCLN].createViaLNURL(signer, typeof (lnurl) === "string" ? (lnurl.startsWith("lightning:") ? lnurl.substring(10) : lnurl) : lnurl.params, amountData, candidates, additionalParams, abortSignal), amountData, SwapType_1.SwapType.FROM_BTCLN);
}
/**
* Creates From BTCLN swap using new protocol
*
* @param chainIdentifier
* @param signer
* @param tokenAddress Token address to receive
* @param amount Amount to receive, in satoshis (bitcoin's smallest denomination)
* @param exactOut Whether to use exact out instead of exact in
* @param additionalParams Additional parameters sent to the LP when creating the swap
* @param options
*/
async createFromBTCLNSwapNew(chainIdentifier, signer, tokenAddress, amount, exactOut, 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: !exactOut
};
return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => Promise.resolve(chain.wrappers[SwapType_1.SwapType.FROM_BTCLN_AUTO].create(signer, amountData, candidates, options, additionalParams, abortSignal)), amountData, SwapType_1.SwapType.FROM_BTCLN_AUTO);
}
/**
* Creates From BTCLN swap using new protocol, withdrawing from LNURL-withdraw
*
* @param chainIdentifier
* @param signer
* @param tokenAddress Token address to receive
* @param lnurl LNURL-withdraw to pull the funds from
* @param amount Amount to receive, in satoshis (bitcoin's smallest denomination)
* @param exactOut Whether to use exact out instead of exact in
* @param additionalParams Additional parameters sent to the LP when creating the swap
* @param options
*/
async createFromBTCLNSwapNewViaLNURL(chainIdentifier, signer, tokenAddress, lnurl, amount, exactOut, 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(signer, true))
throw new Error("Invalid " + chainIdentifier + " address");
signer = this.chains[chainIdentifier].chainInterface.normalizeAddress(signer);
const amountData = {
amount,
token: tokenAddress,
exactIn: !exactOut
};
return this.createSwap(chainIdentifier, (candidates, abortSignal, chain) => chain.wrappers[SwapType_1.SwapType.FROM_BTCLN_AUTO].createViaLNURL(signer, 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 trusted LN for Gas swap
*
* @param chainId
* @param signer
* @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
*/
createTrustedLNForGasSwap(chainId, signer, amount, trustedIntermediaryOrUrl) {
if (this.chains[chainId] == null)
throw new Error("Invalid chain identifier! Unknown chain: " + chainId);
if (!this.chains[chainId].chainInterface.isValidAddress(signer, true))
throw new Error("Invalid " + chainId + " address");
signer = this.chains[chainId].chainInterface.normalizeAddress(signer);
const useUrl = trustedIntermediaryOrUrl ?? this.defaultTrustedIntermediary ?? this.options.defaultTrustedIntermediaryUrl;
if (useUrl == null)
throw new Error("No trusted intermediary specified!");
return this.chains[chainId].wrappers[SwapType_1.SwapType.TRUSTED_FROM_BTCLN].create(signer, amount, useUrl);
}
/**
* Creates trusted BTC on-chain for Gas swap
*
* @param chainId
* @param signer
* @param amount Amount of native token to receive, in base units
* @param refundAddress Bitcoin refund address, in case the swap fails
* @param trustedIntermediaryOrUrl URL or Intermediary object of the trusted intermediary to use, otherwise uses default
* @throws {Error} If no trusted intermediary specified
*/
createTrustedOnchainForGasSwap(chainId, signer, amount, refundAddress, trustedIntermediaryOrUrl) {
if (this.chains[chainId] == null)
throw new Error("Invalid chain identifier! Unknown chain: " + chainId);
if (!this.chains[chainId].chainInterface.isValidAddress(signer, true))
throw new Error("Invalid " + chainId + " address");
signer = this.chains[chainId].chainInterface.normalizeAddress(signer);
const useUrl = trustedIntermediaryOrUrl ?? this.defaultTrustedIntermediary ?? this.options.defaultTrustedIntermediaryUrl;
if (useUrl == null)
throw new Error("No trusted intermediary specified!");
return this.chains[chainId].wrappers[SwapType_1.SwapType.TRUSTED_FROM_BTC].create(signer, amount, useUrl, refundAddress);
}
/**
* Creates a swap from srcToken to dstToken, of a specific token amount, either specifying input amount (exactIn=true)
* or output amount (exactIn=false), NOTE: For regular -> BTC-LN (lightning) swaps the passed amount is ignored and
* invoice's pre-set amount is used instead.
* @deprecated Use swap() instead
*
* @param signer Smartchain (Solana, Starknet, etc.) address of the user
* @param srcToken Source token of the swap, user pays this token
* @param dstToken Destination token of the swap, user receives this token
* @param amount Amount of the swap
* @param exactIn Whether the amount specified is an input amount (exactIn=true) or an output amount (exactIn=false)
* @param addressLnurlLightningInvoice Bitcoin on-chain address, lightning invoice, LNURL-pay to pay or
* LNURL-withdrawal to withdraw money from
*/
create(signer, srcToken, dstToken, amount, exactIn, addressLnurlLightningInvoice) {
if (srcToken.chain === "BTC") {
return this.swap(srcToken, dstToken, amount, exactIn, addressLnurlLightningInvoice, signer);
}
else {
return this.swap(srcToken, dstToken, amount, exactIn, signer, addressLnurlLightningInvoice);
}
}
/**
* Creates a swap from srcToken to dstToken, of a specific token amount, either specifying input amount (exactIn=true)
* or output amount (exactIn=false), NOTE: For regular SmartChain -> BTC-LN (lightning) swaps the passed amount is ignored and
* invoice's pre-set amount is used instead, use LNURL-pay for dynamic amounts
*
* @param _srcToken Source token of the swap, user pays this token
* @param _dstToken Destination token of the swap, user receives this token
* @param _amount Amount of the swap either in base units as {bigint} or in human readable format (with decimals) as {string}
* @param exactIn Whether the amount specified is an input amount (exactIn=true) or an output amount (exactIn=false)
* @param src Source wallet/lnurl-withdraw of the swap
* @param dst Destination smart chain address, bitcoin on-chain address, lightning invoice, LNURL-pay
* @param options Options for the swap
*/
swap(_srcToken, _dstToken, _amount, exactIn, src, dst, options) {
const srcToken = typeof (_srcToken) === "string" ? this.getToken(_srcToken) : _srcToken;
const dstToken = typeof (_dstToken) === "string" ? this.getToken(_dstToken) : _dstToken;
const amount = _amount == null ? null : (typeof (_amount) === "bigint" ? _amount : (0, Tokens_1.fromDecimal)(_amount, exactIn ? srcToken.decimals : dstToken.decimals));
if (srcToken.chain === "BTC") {
if (dstToken.chain === "SC") {
if (typeof (dst) !== "string")
throw new Error("Destination for BTC/BTC-LN -> smart chain swaps must be a smart chain address!");
if (srcToken.lightning) {
//FROM_BTCLN
if (src != null) {
if (typeof (src) !== "string" && !(0, LNURL_1.isLNURLWithdraw)(src))
throw new Error("LNURL must be a string or LNURLWithdraw object!");
return this.supportsSwapType(dstToken.chainId, SwapType_1.SwapType.FROM_BTCLN_AUTO) ?
this.createFromBTCLNSwapNewViaLNURL(dstToken.chainId, dst, dstToken.address, src, amount, !exactIn, undefined, options) :
this.createFromBTCLNSwapViaLNURL(dstToken.chainId, dst, dstToken.address, src, amount, !exactIn);
}
else {
return this.supportsSwapType(dstToken.chainId, SwapType_1.SwapType.FROM_BTCLN_AUTO) ?
this.createFromBTCLNSwapNew(dstToken.chainId, dst, dstToken.address, amount, !exactIn, undefined, options) :
this.createFromBTCLNSwap(dstToken.chainId, dst, dstToken.address, amount, !exactIn, undefined, options);
}
}
else {
//FROM_BTC
if (this.supportsSwapType(dstToken.chainId, SwapType_1.SwapType.SPV_VAULT_FROM_BTC)) {
return this.createFromBTCSwapNew(dstToken.chainId, dst, dstToken.address, amount, !exactIn, undefined, options);
}
else {
return this.createFromBTCSwap(dstToken.chainId, dst, dstToken.address, amount, !exactIn, undefined, options);
}
}
}
}
else {
if (dstToken.chain === "BTC") {
if (typeof (src) !== "string")
throw new Error("Source address for BTC/BTC-LN -> smart chain swaps must be a smart chain address!");
if (dstToken.lightning) {
//TO_BTCLN
if (typeof (dst) !== "string" && !(0, LNURL_1.isLNURLPay)(dst))
throw new Error("Destination LNURL link/lightning invoice must be a string or LNURLPay object!");
if ((0, LNURL_1.isLNURLPay)(dst) || this.Utils.isValidLNURL(dst)) {
return this.createToBTCLNSwapViaLNURL(srcToken.chainId, src, srcToken.address, dst, amount, !!exactIn, undefined, options);
}
else if ((0, ToBTCLNWrapper_1.isInvoiceCreateService)(dst)) {
return this.createToBTCLNSwapViaInvoiceCreateService(srcToken.chainId, src, srcToken.address, dst, amount, !!exactIn, undefined, options);
}
else if (this.Utils.isLightningInvoice(dst)) {
if (!this.Utils.isValidLightningInvoice(dst))