UNPKG

@atomiqlabs/sdk-lib

Version:

Basic SDK functionality library for atomiq

363 lines (362 loc) 15.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ISwap = exports.isISwapInit = void 0; const SwapType_1 = require("./SwapType"); const events_1 = require("events"); const buffer_1 = require("buffer"); const base_1 = require("@atomiqlabs/base"); const ISwapPrice_1 = require("../prices/abstract/ISwapPrice"); const Utils_1 = require("../utils/Utils"); const Tokens_1 = require("./Tokens"); const SwapDirection_1 = require("./SwapDirection"); function isISwapInit(obj) { return typeof obj === 'object' && obj != null && (0, ISwapPrice_1.isPriceInfoType)(obj.pricingInfo) && typeof obj.url === 'string' && typeof obj.expiry === 'number' && typeof (obj.swapFee) === "bigint" && (obj.swapFeeBtc == null || typeof (obj.swapFeeBtc) === "bigint") && obj.feeRate != null && (obj.signatureData == null || (typeof (obj.signatureData) === 'object' && typeof (obj.signatureData.prefix) === "string" && typeof (obj.signatureData.timeout) === "string" && typeof (obj.signatureData.signature) === "string")) && (obj.data == null || typeof obj.data === 'object') && (typeof obj.exactIn === 'boolean'); } exports.isISwapInit = isISwapInit; class ISwap { constructor(wrapper, swapInitOrObj) { this.currentVersion = 1; this.initiated = false; /** * Event emitter emitting "swapState" event when swap's state changes */ this.events = new events_1.EventEmitter(); this.chainIdentifier = wrapper.chainIdentifier; this.wrapper = wrapper; if (isISwapInit(swapInitOrObj)) { Object.assign(this, swapInitOrObj); this.version = this.currentVersion; this.createdAt = Date.now(); this.randomNonce = (0, Utils_1.randomBytes)(16).toString("hex"); } else { this.expiry = swapInitOrObj.expiry; this.url = swapInitOrObj.url; this.state = swapInitOrObj.state; this.pricingInfo = { isValid: swapInitOrObj._isValid, differencePPM: swapInitOrObj._differencePPM == null ? null : BigInt(swapInitOrObj._differencePPM), satsBaseFee: swapInitOrObj._satsBaseFee == null ? null : BigInt(swapInitOrObj._satsBaseFee), feePPM: swapInitOrObj._feePPM == null ? null : BigInt(swapInitOrObj._feePPM), realPriceUSatPerToken: swapInitOrObj._realPriceUSatPerToken == null ? null : BigInt(swapInitOrObj._realPriceUSatPerToken), swapPriceUSatPerToken: swapInitOrObj._swapPriceUSatPerToken == null ? null : BigInt(swapInitOrObj._swapPriceUSatPerToken), }; this.data = swapInitOrObj.data != null ? new wrapper.swapDataDeserializer(swapInitOrObj.data) : null; this.swapFee = swapInitOrObj.swapFee == null ? null : BigInt(swapInitOrObj.swapFee); this.swapFeeBtc = swapInitOrObj.swapFeeBtc == null ? null : BigInt(swapInitOrObj.swapFeeBtc); this.signatureData = swapInitOrObj.signature == null ? null : { prefix: swapInitOrObj.prefix, timeout: swapInitOrObj.timeout, signature: swapInitOrObj.signature }; this.feeRate = swapInitOrObj.feeRate; this.commitTxId = swapInitOrObj.commitTxId; this.claimTxId = swapInitOrObj.claimTxId; this.refundTxId = swapInitOrObj.refundTxId; this.version = swapInitOrObj.version; this.initiated = swapInitOrObj.initiated; this.exactIn = swapInitOrObj.exactIn; this.createdAt = swapInitOrObj.createdAt ?? swapInitOrObj.expiry; this.randomNonce = swapInitOrObj.randomNonce; } if (this.version !== this.currentVersion) { this.upgradeVersion(); } if (this.initiated == null) this.initiated = true; } /** * Periodically checks for init signature's expiry * * @param abortSignal * @param interval How often to check (in seconds), default to 5s * @protected */ async watchdogWaitTillSignatureExpiry(abortSignal, interval = 5) { let expired = false; while (!expired) { await (0, Utils_1.timeoutPromise)(interval * 1000, abortSignal); try { expired = await this.wrapper.contract.isInitAuthorizationExpired(this.data, this.signatureData); } catch (e) { this.logger.warn("watchdogWaitTillSignatureExpiry(): Error when checking signature expiry: ", e); } } if (abortSignal != null) abortSignal.throwIfAborted(); } /** * Periodically checks the chain to see whether the swap is committed * * @param abortSignal * @param interval How often to check (in seconds), default to 5s * @protected */ async watchdogWaitTillCommited(abortSignal, interval = 5) { let status = base_1.SwapCommitStatus.NOT_COMMITED; while (status === base_1.SwapCommitStatus.NOT_COMMITED) { await (0, Utils_1.timeoutPromise)(interval * 1000, abortSignal); try { status = await this.wrapper.contract.getCommitStatus(this.getInitiator(), this.data); if (status === base_1.SwapCommitStatus.NOT_COMMITED && await this.wrapper.contract.isInitAuthorizationExpired(this.data, this.signatureData)) return false; } catch (e) { this.logger.warn("watchdogWaitTillCommited(): Error when fetching commit status or signature expiry: ", e); } } if (abortSignal != null) abortSignal.throwIfAborted(); return true; } /** * Periodically checks the chain to see whether the swap was finished (claimed or refunded) * * @param abortSignal * @param interval How often to check (in seconds), default to 5s * @protected */ async watchdogWaitTillResult(abortSignal, interval = 5) { let status = base_1.SwapCommitStatus.COMMITED; while (status === base_1.SwapCommitStatus.COMMITED || status === base_1.SwapCommitStatus.REFUNDABLE) { await (0, Utils_1.timeoutPromise)(interval * 1000, abortSignal); try { status = await this.wrapper.contract.getCommitStatus(this.getInitiator(), this.data); } catch (e) { this.logger.warn("watchdogWaitTillResult(): Error when fetching commit status: ", e); } } if (abortSignal != null) abortSignal.throwIfAborted(); return status; } /** * Waits till the swap reaches a specific state * * @param targetState The state to wait for * @param type Whether to wait for the state exactly or also to a state with a higher number * @param abortSignal * @protected */ waitTillState(targetState, type = "eq", abortSignal) { return new Promise((resolve, reject) => { let listener; listener = (swap) => { if (type === "eq" ? swap.state === targetState : type === "gte" ? swap.state >= targetState : swap.state != targetState) { resolve(); this.events.removeListener("swapState", listener); } }; this.events.on("swapState", listener); if (abortSignal != null) abortSignal.addEventListener("abort", () => { this.events.removeListener("swapState", listener); reject(abortSignal.reason); }); }); } ////////////////////////////// //// Pricing /** * Checks if the pricing for the swap is valid, according to max allowed price difference set in the ISwapPrice */ hasValidPrice() { return this.pricingInfo == null ? null : this.pricingInfo.isValid; } /** * Returns the price difference between offered price and current market price in PPM (parts per million) */ getPriceDifferencePPM() { return this.pricingInfo == null ? null : this.pricingInfo.differencePPM; } /** * Returns the price difference between offered price and current market price as a decimal number */ getPriceDifferencePct() { return this.pricingInfo == null ? null : this.pricingInfo.differencePPM == null ? null : Number(this.pricingInfo.differencePPM) / 1000000; } /** * Returns the escrow hash - i.e. hash of the escrow data */ getEscrowHash() { return this.data?.getEscrowHash(); } /** * Returns the claim data hash - i.e. hash passed to the claim handler */ getClaimHash() { return this.data?.getClaimHash(); } /** * Returns the identification hash of the swap, usually claim data hash, but can be overriden, e.g. for * lightning swaps the identifier hash is used instead of claim data hash */ getIdentifierHash() { const claimHashBuffer = buffer_1.Buffer.from(this.getClaimHash(), "hex"); if (this.randomNonce == null) return claimHashBuffer; return buffer_1.Buffer.concat([claimHashBuffer, buffer_1.Buffer.from(this.randomNonce, "hex")]); } /** * Returns the identification hash of the swap, usually claim data hash, but can be overriden, e.g. for * lightning swaps the identifier hash is used instead of claim data hash */ getIdentifierHashString() { const paymentHash = this.getIdentifierHash(); if (paymentHash == null) return null; return paymentHash.toString("hex"); } /** * Returns the ID of the swap, as used in the storage and getSwapById function */ getId() { return this.getIdentifierHashString(); } /** * Returns quote expiry in UNIX millis */ getExpiry() { return this.expiry; } /** * Returns the type of the swap */ getType() { return this.TYPE; } /** * Returns the direction of the swap */ getDirection() { return this.TYPE === SwapType_1.SwapType.FROM_BTCLN || this.TYPE === SwapType_1.SwapType.FROM_BTC ? SwapDirection_1.SwapDirection.FROM_BTC : SwapDirection_1.SwapDirection.TO_BTC; } /** * Returns the current state of the swap */ getState() { return this.state; } /** * @param signer Signer to check with this swap's initiator * @throws {Error} When signer's address doesn't match with the swap's initiator one */ checkSigner(signer) { if ((typeof (signer) === "string" ? signer : signer.getAddress()) !== this.getInitiator()) throw new Error("Invalid signer provided!"); } /** * Checks if the swap's quote is still valid */ async isQuoteValid() { try { await (0, Utils_1.tryWithRetries)(() => this.wrapper.contract.isValidInitAuthorization(this.data, this.signatureData, this.feeRate), null, base_1.SignatureVerificationError); return true; } catch (e) { if (e instanceof base_1.SignatureVerificationError) { return false; } } } isInitiated() { return this.initiated; } ////////////////////////////// //// Amounts & fees /** * Get the estimated smart chain fee of the commit transaction */ getCommitFee() { return this.wrapper.contract.getCommitFee(this.data, this.feeRate); } /** * Returns total fee for the swap, the fee is represented in source currency & destination currency, but is * paid only once */ getFee() { return this.getSwapFee(); } /** * Returns the transaction fee paid on the smart chain */ async getSmartChainNetworkFee() { const swapContract = this.wrapper.contract; return (0, Tokens_1.toTokenAmount)(await (swapContract.getRawCommitFee != null ? swapContract.getRawCommitFee(this.data, this.feeRate) : swapContract.getCommitFee(this.data, this.feeRate)), this.wrapper.getNativeToken(), this.wrapper.prices); } ////////////////////////////// //// Storage serialize() { if (this.pricingInfo == null) return {}; return { id: this.getIdentifierHashString(), type: this.getType(), escrowHash: this.getEscrowHash(), initiator: this.getInitiator(), _isValid: this.pricingInfo.isValid, _differencePPM: this.pricingInfo.differencePPM == null ? null : this.pricingInfo.differencePPM.toString(10), _satsBaseFee: this.pricingInfo.satsBaseFee == null ? null : this.pricingInfo.satsBaseFee.toString(10), _feePPM: this.pricingInfo.feePPM == null ? null : this.pricingInfo.feePPM.toString(10), _realPriceUSatPerToken: this.pricingInfo.realPriceUSatPerToken == null ? null : this.pricingInfo.realPriceUSatPerToken.toString(10), _swapPriceUSatPerToken: this.pricingInfo.swapPriceUSatPerToken == null ? null : this.pricingInfo.swapPriceUSatPerToken.toString(10), state: this.state, url: this.url, data: this.data != null ? this.data.serialize() : null, swapFee: this.swapFee == null ? null : this.swapFee.toString(10), swapFeeBtc: this.swapFeeBtc == null ? null : this.swapFeeBtc.toString(10), prefix: this.signatureData?.prefix, timeout: this.signatureData?.timeout, signature: this.signatureData?.signature, feeRate: this.feeRate == null ? null : this.feeRate.toString(), commitTxId: this.commitTxId, claimTxId: this.claimTxId, refundTxId: this.refundTxId, expiry: this.expiry, version: this.version, initiated: this.initiated, exactIn: this.exactIn, createdAt: this.createdAt, randomNonce: this.randomNonce }; } _save() { if (this.isQuoteExpired()) { return this.wrapper.removeSwapData(this); } else { return this.wrapper.saveSwapData(this); } } async _saveAndEmit(state) { if (state != null) this.state = state; await this._save(); this._emitEvent(); } ////////////////////////////// //// Events _emitEvent() { this.wrapper.events.emit("swapState", this); this.events.emit("swapState", this); } } exports.ISwap = ISwap;