@shapeshiftoss/swapper 15.1.0 → 15.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +3 -0
- package/dist/manager/SwapperManager.d.ts +7 -2
- package/dist/manager/SwapperManager.js +47 -4
- package/dist/manager/testData.d.ts +10 -0
- package/dist/manager/testData.js +92 -0
- package/dist/manager/utils.d.ts +4 -0
- package/dist/manager/utils.js +40 -0
- package/dist/swappercli.js +16 -2
- package/dist/typeGuards.d.ts +2 -0
- package/dist/typeGuards.js +7 -0
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -67,6 +67,9 @@ export declare type ByPairInput = {
|
|
|
67
67
|
sellAssetId: AssetId;
|
|
68
68
|
buyAssetId: AssetId;
|
|
69
69
|
};
|
|
70
|
+
export declare type GetBestSwapperArgs = GetTradeQuoteInput & {
|
|
71
|
+
feeAsset: Asset;
|
|
72
|
+
};
|
|
70
73
|
export declare type BuyAssetBySellIdInput = {
|
|
71
74
|
sellAssetId: AssetId;
|
|
72
75
|
assetIds: AssetId[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChainId } from '@shapeshiftoss/caip';
|
|
2
|
-
import { BuyAssetBySellIdInput, ByPairInput, SupportedSellAssetsInput, Swapper } from '..';
|
|
2
|
+
import { BuyAssetBySellIdInput, ByPairInput, GetBestSwapperArgs, SupportedSellAssetsInput, Swapper } from '..';
|
|
3
3
|
import { SwapperType } from '../api';
|
|
4
4
|
export declare class SwapperManager {
|
|
5
5
|
swappers: Map<SwapperType, Swapper<ChainId>>;
|
|
@@ -22,7 +22,12 @@ export declare class SwapperManager {
|
|
|
22
22
|
* @returns {SwapperManager}
|
|
23
23
|
*/
|
|
24
24
|
removeSwapper(swapperType: SwapperType): this;
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @param args {GetBestSwapperArgs}
|
|
28
|
+
* @returns {Promise<Swapper<ChainId> | undefined>}
|
|
29
|
+
*/
|
|
30
|
+
getBestSwapper(args: GetBestSwapperArgs): Promise<Swapper<ChainId> | undefined>;
|
|
26
31
|
/**
|
|
27
32
|
*
|
|
28
33
|
* @param pair type {GetQuoteInput}
|
|
@@ -4,8 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.SwapperManager = void 0;
|
|
7
|
+
const lodash_1 = require("lodash");
|
|
7
8
|
const uniq_1 = __importDefault(require("lodash/uniq"));
|
|
8
9
|
const api_1 = require("../api");
|
|
10
|
+
const typeGuards_1 = require("../typeGuards");
|
|
11
|
+
const utils_1 = require("./utils");
|
|
9
12
|
function validateSwapper(swapper) {
|
|
10
13
|
if (!(typeof swapper === 'object' && typeof swapper.getType === 'function'))
|
|
11
14
|
throw new api_1.SwapError('[validateSwapper] - invalid swapper instance', {
|
|
@@ -57,10 +60,49 @@ class SwapperManager {
|
|
|
57
60
|
this.swappers.delete(swapperType);
|
|
58
61
|
return this;
|
|
59
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
*
|
|
65
|
+
* @param args {GetBestSwapperArgs}
|
|
66
|
+
* @returns {Promise<Swapper<ChainId> | undefined>}
|
|
67
|
+
*/
|
|
60
68
|
async getBestSwapper(args) {
|
|
61
|
-
|
|
62
|
-
//
|
|
63
|
-
|
|
69
|
+
const { sellAsset, buyAsset, feeAsset } = args;
|
|
70
|
+
// Get all swappers that support the pair
|
|
71
|
+
const supportedSwappers = this.getSwappersByPair({
|
|
72
|
+
sellAssetId: sellAsset.assetId,
|
|
73
|
+
buyAssetId: buyAsset.assetId,
|
|
74
|
+
});
|
|
75
|
+
// Get quotes from all swappers that support the pair
|
|
76
|
+
const quotePromises = supportedSwappers.map(async (swapper) => [swapper, await swapper.getTradeQuote(args)]);
|
|
77
|
+
const settledQuoteRequests = await Promise.allSettled(quotePromises);
|
|
78
|
+
// For each swapper, get receive amount/(input amount + gas fee), where all values are in fiat
|
|
79
|
+
const fulfilledQuoteTuples = settledQuoteRequests
|
|
80
|
+
.filter(typeGuards_1.isFulfilled)
|
|
81
|
+
.map((quoteRequest) => quoteRequest.value);
|
|
82
|
+
// The best swapper is the one with the highest ratio
|
|
83
|
+
const bestQuoteTuple = await fulfilledQuoteTuples.reduce(async (acc, currentQuoteTuple) => {
|
|
84
|
+
const resolvedAcc = await acc;
|
|
85
|
+
const [currentSwapper, currentQuote] = currentQuoteTuple;
|
|
86
|
+
const currentRatio = await (0, utils_1.getRatioFromQuote)(currentQuote, currentSwapper, feeAsset);
|
|
87
|
+
// It's our first iteration, so we just return the current SwapperQuoteTuple
|
|
88
|
+
if (!resolvedAcc)
|
|
89
|
+
return Promise.resolve([currentSwapper, currentRatio]);
|
|
90
|
+
const [, bestRatio] = resolvedAcc;
|
|
91
|
+
const isCurrentSwapperBestSwapper = (() => {
|
|
92
|
+
// Happy path - no div by 0's in getRatioFromQuote evaluations so we have both ratios
|
|
93
|
+
if (!(0, lodash_1.isUndefined)(currentRatio) && !(0, lodash_1.isUndefined)(bestRatio))
|
|
94
|
+
return currentRatio > bestRatio;
|
|
95
|
+
// We don't know, neither has a ratio, so we can't compare (big edge case div by 0 scenario)
|
|
96
|
+
if ((0, lodash_1.isUndefined)(currentRatio) && (0, lodash_1.isUndefined)(bestRatio))
|
|
97
|
+
return false;
|
|
98
|
+
// We don't have a best, but we do have a current, so current is the new best
|
|
99
|
+
return !(0, lodash_1.isUndefined)(currentRatio);
|
|
100
|
+
})();
|
|
101
|
+
const currentSwapperRatioTuple = [currentSwapper, currentRatio];
|
|
102
|
+
return Promise.resolve(isCurrentSwapperBestSwapper ? currentSwapperRatioTuple : resolvedAcc);
|
|
103
|
+
}, undefined);
|
|
104
|
+
const bestSwapper = bestQuoteTuple === null || bestQuoteTuple === void 0 ? void 0 : bestQuoteTuple[0];
|
|
105
|
+
return bestSwapper;
|
|
64
106
|
}
|
|
65
107
|
/**
|
|
66
108
|
*
|
|
@@ -69,7 +111,8 @@ class SwapperManager {
|
|
|
69
111
|
*/
|
|
70
112
|
getSwappersByPair(pair) {
|
|
71
113
|
const { sellAssetId, buyAssetId } = pair;
|
|
72
|
-
|
|
114
|
+
const availableSwappers = Array.from(this.swappers.values());
|
|
115
|
+
return availableSwappers.filter((swapper) => swapper.filterBuyAssetsBySellAssetId({ sellAssetId, assetIds: [buyAssetId] }).length);
|
|
73
116
|
}
|
|
74
117
|
getSupportedBuyAssetIdsFromSellId(args) {
|
|
75
118
|
return (0, uniq_1.default)(Array.from(this.swappers.values()).flatMap((swapper) => swapper.filterBuyAssetsBySellAssetId(args)));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { KnownChainIds } from '@shapeshiftoss/types';
|
|
2
|
+
import { TradeQuote } from '../api';
|
|
3
|
+
import { CowSwapper, ThorchainSwapper, ZrxSwapper } from '../swappers';
|
|
4
|
+
export declare const getZrxEthereumSwapper: () => ZrxSwapper<import("../api").EvmSupportedChainIds>;
|
|
5
|
+
export declare const getZrxAvalancheSwapper: () => ZrxSwapper<import("../api").EvmSupportedChainIds>;
|
|
6
|
+
export declare const getCowSwapper: () => CowSwapper;
|
|
7
|
+
export declare const getThorchainSwapper: () => ThorchainSwapper;
|
|
8
|
+
export declare const tradeQuote: TradeQuote<KnownChainIds.EthereumMainnet>;
|
|
9
|
+
export declare const goodTradeQuote: TradeQuote<KnownChainIds.EthereumMainnet>;
|
|
10
|
+
export declare const badTradeQuote: TradeQuote<KnownChainIds.EthereumMainnet>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.badTradeQuote = exports.goodTradeQuote = exports.tradeQuote = exports.getThorchainSwapper = exports.getCowSwapper = exports.getZrxAvalancheSwapper = exports.getZrxEthereumSwapper = void 0;
|
|
4
|
+
const types_1 = require("@shapeshiftoss/types");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
const swappers_1 = require("../swappers");
|
|
7
|
+
const assets_1 = require("../swappers/utils/test-data/assets");
|
|
8
|
+
const zrxEthereumSwapperDeps = {
|
|
9
|
+
web3: {},
|
|
10
|
+
adapter: {
|
|
11
|
+
getChainId: () => types_1.KnownChainIds.EthereumMainnet,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
const getZrxEthereumSwapper = () => new swappers_1.ZrxSwapper(zrxEthereumSwapperDeps);
|
|
15
|
+
exports.getZrxEthereumSwapper = getZrxEthereumSwapper;
|
|
16
|
+
const zrxAvalancheSwapperDeps = {
|
|
17
|
+
web3: {},
|
|
18
|
+
adapter: {
|
|
19
|
+
getChainId: () => types_1.KnownChainIds.AvalancheMainnet,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const getZrxAvalancheSwapper = () => new swappers_1.ZrxSwapper(zrxAvalancheSwapperDeps);
|
|
23
|
+
exports.getZrxAvalancheSwapper = getZrxAvalancheSwapper;
|
|
24
|
+
const cowSwapperDeps = {
|
|
25
|
+
apiUrl: 'https://api.cow.fi/mainnet/api/',
|
|
26
|
+
adapter: {
|
|
27
|
+
getChainId: () => types_1.KnownChainIds.EthereumMainnet,
|
|
28
|
+
},
|
|
29
|
+
web3: {},
|
|
30
|
+
};
|
|
31
|
+
const getCowSwapper = () => new swappers_1.CowSwapper(cowSwapperDeps);
|
|
32
|
+
exports.getCowSwapper = getCowSwapper;
|
|
33
|
+
const thorchainSwapperDeps = {
|
|
34
|
+
midgardUrl: '',
|
|
35
|
+
daemonUrl: '',
|
|
36
|
+
adapterManager: {},
|
|
37
|
+
web3: {},
|
|
38
|
+
};
|
|
39
|
+
const getThorchainSwapper = () => new swappers_1.ThorchainSwapper(thorchainSwapperDeps);
|
|
40
|
+
exports.getThorchainSwapper = getThorchainSwapper;
|
|
41
|
+
exports.tradeQuote = {
|
|
42
|
+
minimumCryptoHuman: '60',
|
|
43
|
+
maximum: '1000000000000000000000',
|
|
44
|
+
sellAmountBeforeFeesCryptoBaseUnit: '1000000000000000000000',
|
|
45
|
+
allowanceContract: '0x3624525075b88B24ecc29CE226b0CEc1fFcB6976',
|
|
46
|
+
buyAmountCryptoBaseUnit: '23448326921811747',
|
|
47
|
+
feeData: {
|
|
48
|
+
chainSpecific: {
|
|
49
|
+
estimatedGas: '100000',
|
|
50
|
+
approvalFeeCryptoBaseUnit: '700000',
|
|
51
|
+
gasPriceCryptoBaseUnit: '7',
|
|
52
|
+
},
|
|
53
|
+
buyAssetTradeFeeUsd: '7.656',
|
|
54
|
+
sellAssetTradeFeeUsd: '0',
|
|
55
|
+
networkFeeCryptoBaseUnit: '3246750000000000',
|
|
56
|
+
},
|
|
57
|
+
rate: '0.00002509060972289251',
|
|
58
|
+
sources: [{ name: api_1.SwapperName.Thorchain, proportion: '1' }],
|
|
59
|
+
buyAsset: assets_1.ETH,
|
|
60
|
+
sellAsset: assets_1.FOX,
|
|
61
|
+
accountNumber: 0,
|
|
62
|
+
};
|
|
63
|
+
exports.goodTradeQuote = {
|
|
64
|
+
...exports.tradeQuote,
|
|
65
|
+
buyAmountCryptoBaseUnit: '23000000000000000',
|
|
66
|
+
feeData: {
|
|
67
|
+
chainSpecific: {
|
|
68
|
+
estimatedGas: '100000',
|
|
69
|
+
approvalFeeCryptoBaseUnit: '700000',
|
|
70
|
+
gasPriceCryptoBaseUnit: '7',
|
|
71
|
+
},
|
|
72
|
+
buyAssetTradeFeeUsd: '7.656',
|
|
73
|
+
sellAssetTradeFeeUsd: '0',
|
|
74
|
+
networkFeeCryptoBaseUnit: '3246750000000000',
|
|
75
|
+
},
|
|
76
|
+
buyAsset: assets_1.WETH,
|
|
77
|
+
};
|
|
78
|
+
exports.badTradeQuote = {
|
|
79
|
+
...exports.tradeQuote,
|
|
80
|
+
buyAmountCryptoBaseUnit: '21000000000000000',
|
|
81
|
+
feeData: {
|
|
82
|
+
chainSpecific: {
|
|
83
|
+
estimatedGas: '100000',
|
|
84
|
+
approvalFeeCryptoBaseUnit: '700000',
|
|
85
|
+
gasPriceCryptoBaseUnit: '7',
|
|
86
|
+
},
|
|
87
|
+
buyAssetTradeFeeUsd: '10.656',
|
|
88
|
+
sellAssetTradeFeeUsd: '4',
|
|
89
|
+
networkFeeCryptoBaseUnit: '3446750000000000',
|
|
90
|
+
},
|
|
91
|
+
buyAsset: assets_1.WETH,
|
|
92
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Asset } from '@shapeshiftoss/asset-service';
|
|
2
|
+
import { ChainId } from '@shapeshiftoss/caip';
|
|
3
|
+
import { Swapper, TradeQuote } from '../api';
|
|
4
|
+
export declare const getRatioFromQuote: (quote: TradeQuote<ChainId>, swapper: Swapper<ChainId>, feeAsset: Asset) => Promise<number | undefined>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getRatioFromQuote = void 0;
|
|
4
|
+
const bignumber_1 = require("../swappers/utils/bignumber");
|
|
5
|
+
const typeGuards_1 = require("../typeGuards");
|
|
6
|
+
/*
|
|
7
|
+
The ratio is calculated by dividing the total fiat value of the receive amount
|
|
8
|
+
by the total fiat value of the sell amount (including network fees).
|
|
9
|
+
|
|
10
|
+
Higher ratios are better.
|
|
11
|
+
|
|
12
|
+
E.g. if the fiat value of the sell amount is 100 and the fiat value of the receive amount is 90,
|
|
13
|
+
the ratio is 0.9.
|
|
14
|
+
|
|
15
|
+
Negative ratios are possible when the fees exceed the sell amount.
|
|
16
|
+
This is allowed to let us choose 'the best from a bad bunch'.
|
|
17
|
+
*/
|
|
18
|
+
const getRatioFromQuote = async (quote, swapper, feeAsset) => {
|
|
19
|
+
/*
|
|
20
|
+
We make an assumption here that swappers return comparable asset rates.
|
|
21
|
+
If a swapper were to undervalue the outbound asset and overvalue the inbound asset
|
|
22
|
+
then the ratio would be incorrectly in its favour.
|
|
23
|
+
*/
|
|
24
|
+
const quoteAssets = [quote.sellAsset, quote.buyAsset, feeAsset];
|
|
25
|
+
const usdRatePromises = quoteAssets.map((asset) => swapper.getUsdRate(asset));
|
|
26
|
+
const [sellAssetUsdRate, buyAssetUsdRate, feeAssetUsdRate] = (await Promise.allSettled(usdRatePromises))
|
|
27
|
+
.filter(typeGuards_1.isFulfilled)
|
|
28
|
+
.map((p) => p.value);
|
|
29
|
+
const totalSellAmountFiat = (0, bignumber_1.bnOrZero)((0, bignumber_1.fromBaseUnit)(quote.sellAmountBeforeFeesCryptoBaseUnit, quote.sellAsset.precision))
|
|
30
|
+
.times(sellAssetUsdRate)
|
|
31
|
+
.plus((0, bignumber_1.bnOrZero)(quote.feeData.sellAssetTradeFeeUsd));
|
|
32
|
+
const totalReceiveAmountFiat = (0, bignumber_1.bnOrZero)((0, bignumber_1.fromBaseUnit)(quote.buyAmountCryptoBaseUnit, quote.buyAsset.precision))
|
|
33
|
+
.times(buyAssetUsdRate)
|
|
34
|
+
.minus((0, bignumber_1.bnOrZero)(quote.feeData.buyAssetTradeFeeUsd));
|
|
35
|
+
const networkFeeFiat = (0, bignumber_1.bnOrZero)((0, bignumber_1.fromBaseUnit)(quote.feeData.networkFeeCryptoBaseUnit, feeAsset.precision)).times(feeAssetUsdRate);
|
|
36
|
+
const totalSendAmountFiat = totalSellAmountFiat.plus(networkFeeFiat);
|
|
37
|
+
const ratio = totalReceiveAmountFiat.div(totalSendAmountFiat);
|
|
38
|
+
return ratio.isFinite() ? ratio.toNumber() : undefined;
|
|
39
|
+
};
|
|
40
|
+
exports.getRatioFromQuote = getRatioFromQuote;
|
package/dist/swappercli.js
CHANGED
|
@@ -15,6 +15,7 @@ const adapters_1 = require("./adapters");
|
|
|
15
15
|
const manager_1 = require("./manager");
|
|
16
16
|
const swappers_1 = require("./swappers");
|
|
17
17
|
const bignumber_1 = require("./swappers/utils/bignumber");
|
|
18
|
+
const setupSwapQuote_1 = require("./swappers/utils/test-data/setupSwapQuote");
|
|
18
19
|
dotenv_1.default.config();
|
|
19
20
|
const { ETH_NODE_URL = 'http://localhost:3000', MIDGARD_URL = 'https://dev-indexer.thorchain.shapeshift.com/v2', DEVICE_ID = 'device123', MNEMONIC = 'all all all all all all all all all all all all', } = process.env;
|
|
20
21
|
const toBaseUnit = (amount, precision) => {
|
|
@@ -91,9 +92,22 @@ const main = async () => {
|
|
|
91
92
|
const tc = new swappers_1.ThorchainSwapper(tcDeps);
|
|
92
93
|
await tc.initialize();
|
|
93
94
|
swapManager.addSwapper(tc);
|
|
95
|
+
const ethereumAsset = {
|
|
96
|
+
assetId: caip_1.ethAssetId,
|
|
97
|
+
chainId: caip_1.ethChainId,
|
|
98
|
+
symbol: 'ETH',
|
|
99
|
+
name: 'Ethereum',
|
|
100
|
+
precision: 18,
|
|
101
|
+
color: '#5C6BC0',
|
|
102
|
+
icon: 'https://assets.coincap.io/assets/icons/256/eth.png',
|
|
103
|
+
explorer: 'https://etherscan.io',
|
|
104
|
+
explorerAddressLink: 'https://etherscan.io/address/',
|
|
105
|
+
explorerTxLink: 'https://etherscan.io/tx/',
|
|
106
|
+
};
|
|
107
|
+
const { quoteInput } = (0, setupSwapQuote_1.setupQuote)();
|
|
94
108
|
const swapper = await swapManager.getBestSwapper({
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
...quoteInput,
|
|
110
|
+
feeAsset: ethereumAsset,
|
|
97
111
|
});
|
|
98
112
|
console.info(`using swapper ${swapper === null || swapper === void 0 ? void 0 : swapper.getType()}`);
|
|
99
113
|
if (!swapper) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isRejected = exports.isFulfilled = void 0;
|
|
4
|
+
const isFulfilled = (promise) => promise.status === 'fulfilled';
|
|
5
|
+
exports.isFulfilled = isFulfilled;
|
|
6
|
+
const isRejected = (promise) => promise.status === 'rejected';
|
|
7
|
+
exports.isRejected = isRejected;
|
package/package.json
CHANGED