UNPKG

@yoroi/swap

Version:
245 lines (244 loc) 9.05 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.swapManagerMaker = exports.standarizeError = void 0; var _types = require("@yoroi/types"); var _common = require("@yoroi/common"); var _immer = require("immer"); var _apiMaker = require("./adapters/api/dexhunter/api-maker"); var _apiMaker2 = require("./adapters/api/muesliswap/api-maker"); var _getBestSwap = require("./helpers/getBestSwap"); var _getPtPrice = require("./helpers/getPtPrice"); const swapManagerMaker = ({ address, addressHex, network, primaryTokenInfo, isPrimaryToken, stakingKey, storage, partners }) => { const dexhunterApi = (0, _apiMaker.dexhunterApiMaker)({ address, network, primaryTokenInfo, isPrimaryToken, partner: partners?.[_types.Swap.Aggregator.Dexhunter] }); const muesliswapApi = (0, _apiMaker2.muesliswapApiMaker)({ address, addressHex, network, primaryTokenInfo, stakingKey, isPrimaryToken, partner: partners?.[_types.Swap.Aggregator.Muesliswap] }); const settings = { routingPreference: 'auto', slippage: 1 }; const assignSettings = v => { const newSettings = Object.assign(settings, v); storage.settings.save(newSettings); return newSettings; }; storage.settings.read().then(assignSettings); const api = apiManagerMaker({ [_types.Swap.Aggregator.Dexhunter]: dexhunterApi, [_types.Swap.Aggregator.Muesliswap]: muesliswapApi }, settings, (0, _getPtPrice.getPtPrice)(primaryTokenInfo, dexhunterApi)); return { api, assignSettings, settings, clearStorage: storage.clear }; }; exports.swapManagerMaker = swapManagerMaker; const apiManagerMaker = (adapters, settings, getPrice) => { return (0, _immer.freeze)({ async tokens() { const aggregatorPromises = { dexhunter: adapters.dexhunter.tokens(), muesliswap: adapters.muesliswap.tokens() }; const responses = await Promise.all(Object.entries(aggregatorPromises).map(([key, promise]) => settings.routingPreference === 'auto' || settings.routingPreference.includes(key) ? promise : excluded)); warnAllLeft(...responses); if (responses.every(_common.isLeft)) return invalid; const merged = {}; const append = tokenInfo => { if (merged[tokenInfo.id] === undefined) merged[tokenInfo.id] = tokenInfo; }; responses.filter(_common.isRight).flatMap(({ value }) => value.data).forEach(append); return { tag: 'right', value: { status: _types.Api.HttpStatusCode.Ok, data: Object.values(merged) } }; }, async orders() { const responses = await Promise.all([adapters.muesliswap.orders(), adapters.dexhunter.orders()]); warnAllLeft(...responses); if (responses.every(_common.isLeft)) return invalid; const merged = {}; const append = order => { const key = `${order.txHash}#${order.outputIndex}`; /* istanbul ignore next */ if ( // TODO: refactor to avoid istanbul ignore merged[key] === undefined || order.aggregator === _types.Swap.Aggregator.Dexhunter) merged[key] = order; }; responses.filter(_common.isRight).flatMap(({ value }) => value.data).forEach(append); return { tag: 'right', value: { status: _types.Api.HttpStatusCode.Ok, data: Object.values(merged).sort(({ lastUpdate: A, placedAt: A2 }, { lastUpdate: B, placedAt: B2 }) => (B ?? B2 ?? 0) - (A ?? A2 ?? 0)) } }; }, /* istanbul ignore next */ async limitOptions(body) { const aggregatorPromises = { dexhunter: adapters.dexhunter.limitOptions(body), muesliswap: adapters.muesliswap.limitOptions(body) }; const responses = await Promise.all(Object.entries(aggregatorPromises).map(([key, promise]) => settings.routingPreference === 'auto' || settings.routingPreference.includes(key) ? promise : excluded)); warnAllLeft(...responses); if (responses.every(_common.isLeft)) return responses.find(res => res.error.status !== -3) ?? invalid; const validResponses = responses.filter(_common.isRight).map(({ value }) => value.data); if (validResponses.length === 0) return invalid; const mergedOptions = {}; const append = res => { mergedOptions[res.protocol] = res; }; validResponses.forEach(({ options }) => options.forEach(append)); const data = { defaultProtocol: validResponses[0].defaultProtocol, wantedPrice: Math.min(...validResponses.map(({ wantedPrice }) => wantedPrice)), options: Object.values(mergedOptions) }; return { tag: 'right', value: { status: _types.Api.HttpStatusCode.Ok, data } }; }, async estimate(body) { const aggregatorPromises = { dexhunter: adapters.dexhunter.estimate(body), muesliswap: adapters.muesliswap.estimate(body) }; const responses = await Promise.all(Object.entries(aggregatorPromises).map(([key, promise]) => settings.routingPreference === 'auto' || settings.routingPreference.includes(key) ? promise : excluded)); warnAllLeft(...responses); if (responses.every(_common.isLeft)) return standarizeError(responses.find(res => res.error.status !== -3 && res.error.message !== '' && !res.error.message.includes('DOCTYPE html')) ?? invalid); const estimates = responses.filter(_common.isRight).flatMap(({ value }) => value.data); const bestEstimate = estimates.reduce((0, _getBestSwap.getBestSwap)(await getPrice(body.tokenOut)), estimates[0]); return { tag: 'right', value: { status: _types.Api.HttpStatusCode.Ok, data: bestEstimate } }; }, async create(body) { const aggregatorPromises = { dexhunter: adapters.dexhunter.create(body), muesliswap: adapters.muesliswap.create(body) }; const responses = await Promise.all(Object.entries(aggregatorPromises).map(([key, promise]) => settings.routingPreference === 'auto' || settings.routingPreference.includes(key) ? promise : excluded)); warnAllLeft(...responses); if (responses.every(_common.isLeft)) return standarizeError(responses.find(res => res.error.status !== -3 && res.error.message !== '' && !res.error.message.includes('DOCTYPE html')) ?? invalid); const creates = responses.filter(_common.isRight).map(({ value }) => value.data); const bestCreate = creates.reduce((0, _getBestSwap.getBestSwap)(await getPrice(body.tokenOut)), creates[0]); return { tag: 'right', value: { status: _types.Api.HttpStatusCode.Ok, data: bestCreate } }; }, async cancel(body) { return body.order.aggregator === _types.Swap.Aggregator.Muesliswap ? adapters.muesliswap.cancel(body) : adapters.dexhunter.cancel(body); } }, true); }; const excluded = (0, _immer.freeze)({ tag: 'left', error: { status: -3, message: 'Aggregator excluded from call', responseData: {} } }, true); const invalid = (0, _immer.freeze)({ tag: 'left', error: { status: -3, message: 'Unknown error', responseData: {} } }, true); const warnAllLeft = (...responses) => { if (responses.every(_common.isLeft)) console.warn('Swap Manager all left >> ', responses.map(response => response.error.message)); }; const standarizeError = input => { if ((0, _common.isRight)(input)) return input; const response = { ...input, error: { ...input.error } }; switch (true) { case response.error.message.includes('Unable to build transaction due to insufficient user balance'): case response.error.message.includes('Transaction Building Errornot enough funds'): case response.error.message.includes('Transaction Building ErrorNo Remaining UTxOs'): response.error.message = 'Insufficient balance: consider fees, assets blocked by staking or multiaddress holdings'; break; case response.error.message.includes('amount_in_invalid'): case response.error.message.includes('Buy and sell amounts must be positive'): response.error.message = 'Buy and sell amounts must be positive'; break; case response.error.message.includes('No liquidity available for this token pair'): case response.error.message.includes('pool_not_found'): response.error.message = 'This pair is not available in any liquidity pool.'; break; case response.error.message.includes('DOCTYPE html'): case response.error.message.includes('Could not find the number of decimals'): response.error.message = 'Unknown error'; break; } return (0, _immer.freeze)(response, true); }; exports.standarizeError = standarizeError; //# sourceMappingURL=manager.js.map