@yoroi/swap
Version:
The Swap package of Yoroi SDK
245 lines (244 loc) • 9.05 kB
JavaScript
;
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