@defisaver/ethena-sdk 0.0.4 → 0.0.5

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.
@@ -0,0 +1,4 @@
1
+ export declare const STABLE_PAIR_FEE_DIVIDER = "10000";
2
+ export declare const DFS_API_URL = "https://fe.defisaver.com";
3
+ export declare const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
4
+ export declare const SLIPPAGE_PERCENT = 0.05;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SLIPPAGE_PERCENT = exports.ZERO_ADDRESS = exports.DFS_API_URL = exports.STABLE_PAIR_FEE_DIVIDER = void 0;
4
+ exports.STABLE_PAIR_FEE_DIVIDER = '10000';
5
+ exports.DFS_API_URL = 'https://fe.defisaver.com';
6
+ exports.ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
7
+ exports.SLIPPAGE_PERCENT = 0.05;
@@ -0,0 +1,10 @@
1
+ import { NetworkNumber } from '@defisaver/positions-sdk';
2
+ import { OffchainExchanges, PriceData } from '../types';
3
+ export declare const numStringToBytes: (num: number) => string;
4
+ export declare const getBestPrice: (fromAsset: string, toAsset: string, amount: string, userAddress: string, network?: NetworkNumber) => Promise<PriceData>;
5
+ export declare const getExchangeOrder: (fromAsset: string, toAsset: string, amount: string, userAddress: string, minPrice: string, network?: NetworkNumber) => Promise<{
6
+ orderData: (string | string[])[];
7
+ value: string;
8
+ source: OffchainExchanges | "None";
9
+ price: string;
10
+ }>;
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.getExchangeOrder = exports.getBestPrice = exports.numStringToBytes = void 0;
16
+ const decimal_js_1 = __importDefault(require("decimal.js"));
17
+ const bn_js_1 = __importDefault(require("bn.js"));
18
+ const positions_sdk_1 = require("@defisaver/positions-sdk");
19
+ const tokens_1 = require("@defisaver/tokens");
20
+ const types_1 = require("../types");
21
+ const constants_1 = require("../constants");
22
+ const getOffchainEmptyData = (source = 'None') => ({
23
+ wrapper: constants_1.ZERO_ADDRESS,
24
+ to: constants_1.ZERO_ADDRESS,
25
+ allowanceTarget: constants_1.ZERO_ADDRESS,
26
+ price: '0',
27
+ priceWithFee: '0',
28
+ protocolFee: '0',
29
+ data: '0x00',
30
+ value: '0',
31
+ gas: '0',
32
+ source,
33
+ });
34
+ const parsePriceWithDecimals = (price, fromDecimals, toDecimals) => new decimal_js_1.default(price)
35
+ .div(Math.pow(10, toDecimals))
36
+ .div(Math.pow(10, (18 - fromDecimals)))
37
+ .toString();
38
+ const formatPriceWithDecimalForContract = (price, fromDecimals, toDecimals) => new decimal_js_1.default(price)
39
+ .mul(Math.pow(10, toDecimals))
40
+ .mul(Math.pow(10, (18 - fromDecimals)))
41
+ .floor()
42
+ .toString();
43
+ const includeFeeInPrice = (price, from, to, fee) => {
44
+ if (from === to)
45
+ return price;
46
+ return new decimal_js_1.default(price).mul(new decimal_js_1.default(1).sub(fee)).toString();
47
+ };
48
+ const excludeFeeFromPrice = (price, from, to, fee) => {
49
+ if (from === to)
50
+ return price;
51
+ return new decimal_js_1.default(price).mul(new decimal_js_1.default(1).add(fee)).toString();
52
+ };
53
+ const parseOffchainPrice = (fromTokenSymbol, fromTokenDecimals, toTokenSymbol, toTokenDecimals, amount, feeDecimal) => {
54
+ const _price = parsePriceWithDecimals(amount, fromTokenDecimals, toTokenDecimals);
55
+ return includeFeeInPrice(_price, fromTokenSymbol, toTokenSymbol, feeDecimal);
56
+ };
57
+ const getFeeDecimal = () => new decimal_js_1.default(1).div(constants_1.STABLE_PAIR_FEE_DIVIDER).toString();
58
+ const numStringToBytes = (num) => {
59
+ const bn = new bn_js_1.default(num.toString()).toTwos(256);
60
+ return bn.toString(16);
61
+ };
62
+ exports.numStringToBytes = numStringToBytes;
63
+ const getPriceFromServer = (fromAsset_1, toAsset_1, amount_1, userAddress_1, ...args_1) => __awaiter(void 0, [fromAsset_1, toAsset_1, amount_1, userAddress_1, ...args_1], void 0, function* (fromAsset, toAsset, amount, userAddress, network = positions_sdk_1.NetworkNumber.Eth, infoOnly = true) {
64
+ const fromAssetData = (0, tokens_1.getAssetInfo)(fromAsset, network);
65
+ const toAssetData = (0, tokens_1.getAssetInfo)(toAsset, network);
66
+ const feeDecimal = getFeeDecimal();
67
+ const excludedSources = ['Balancer_V2', 'Beethovenx'];
68
+ const allSources = Object.values(types_1.OffchainExchanges);
69
+ try {
70
+ const res = yield fetch(`${constants_1.DFS_API_URL}/api/exchange/get-best-price`, {
71
+ method: 'POST',
72
+ headers: {
73
+ 'Content-Type': 'application/json',
74
+ },
75
+ body: JSON.stringify({
76
+ fromAsset: fromAssetData.address,
77
+ fromAssetDecimals: fromAssetData.decimals,
78
+ fromAssetSymbol: fromAssetData.symbol,
79
+ toAsset: toAssetData.address,
80
+ toAssetDecimals: toAssetData.decimals,
81
+ toAssetSymbol: toAssetData.symbol,
82
+ sources: [...allSources.map(s => s.toLowerCase())],
83
+ chainId: network,
84
+ amount,
85
+ excludedSources,
86
+ infoOnly,
87
+ takerAddress: userAddress,
88
+ account: userAddress,
89
+ noFee: false,
90
+ feeDecimal,
91
+ // temporary fix for paraswap until old exchange service is removed
92
+ shouldFormatParaswapPrice: true,
93
+ }),
94
+ });
95
+ if (!res.ok)
96
+ throw new Error(yield res.text());
97
+ const data = (yield res.json());
98
+ const formattedData = data.map((d, i) => {
99
+ const source = allSources[i];
100
+ if (typeof d === 'string')
101
+ return getOffchainEmptyData(source);
102
+ return {
103
+ wrapper: d.wrapper || constants_1.ZERO_ADDRESS,
104
+ to: d.to || constants_1.ZERO_ADDRESS,
105
+ allowanceTarget: d.allowanceTarget || constants_1.ZERO_ADDRESS,
106
+ protocolFee: d.protocolFee || '0',
107
+ data: d.data || '0x00',
108
+ value: d.value || '0',
109
+ gas: d.gas || '0',
110
+ source: allSources[i],
111
+ price: d.price,
112
+ priceWithFee: +(d.price || '0') > 0
113
+ ? parseOffchainPrice(fromAssetData.symbol, fromAssetData.decimals, toAssetData.symbol, toAssetData.decimals, d.price, feeDecimal)
114
+ : '0',
115
+ };
116
+ }).filter((d) => d.wrapper !== constants_1.ZERO_ADDRESS && d.price !== '0').sort((a, b) => (new decimal_js_1.default(a.price).gt(b.price) ? -1 : 1));
117
+ return formattedData;
118
+ }
119
+ catch (_a) {
120
+ return allSources.map(source => getOffchainEmptyData(source));
121
+ }
122
+ });
123
+ const getBestPrice = (fromAsset_1, toAsset_1, amount_1, userAddress_1, ...args_1) => __awaiter(void 0, [fromAsset_1, toAsset_1, amount_1, userAddress_1, ...args_1], void 0, function* (fromAsset, toAsset, amount, userAddress, network = positions_sdk_1.NetworkNumber.Eth) {
124
+ try {
125
+ const formattedData = yield getPriceFromServer(fromAsset, toAsset, amount, userAddress, network);
126
+ return formattedData[0] || getOffchainEmptyData();
127
+ }
128
+ catch (e) {
129
+ console.error('Error fetching best price:', e);
130
+ return getOffchainEmptyData();
131
+ }
132
+ });
133
+ exports.getBestPrice = getBestPrice;
134
+ const getExchangeOrder = (fromAsset_1, toAsset_1, amount_1, userAddress_1, minPrice_1, ...args_1) => __awaiter(void 0, [fromAsset_1, toAsset_1, amount_1, userAddress_1, minPrice_1, ...args_1], void 0, function* (fromAsset, toAsset, amount, userAddress, minPrice, network = positions_sdk_1.NetworkNumber.Eth) {
135
+ const fromAssetData = (0, tokens_1.getAssetInfo)(fromAsset, network);
136
+ const toAssetData = (0, tokens_1.getAssetInfo)(toAsset, network);
137
+ const feeDecimal = getFeeDecimal();
138
+ const offchainQuotes = yield getPriceFromServer(fromAsset, toAsset, amount, userAddress, network, false);
139
+ const formattedOffchainQuotes = offchainQuotes.map((quote) => ({
140
+ source: quote.source,
141
+ price: quote.priceWithFee,
142
+ wrapper: quote.wrapper,
143
+ wrapperData: quote.data,
144
+ offchainData: quote,
145
+ }));
146
+ const bestQuote = formattedOffchainQuotes[0];
147
+ const { offchainData, source, price } = bestQuote;
148
+ const minPriceFormatted = new decimal_js_1.default(excludeFeeFromPrice(minPrice, fromAssetData.address, toAssetData.address, feeDecimal))
149
+ .mul(100 - constants_1.SLIPPAGE_PERCENT)
150
+ .div(100)
151
+ .toString();
152
+ const minPriceForContract = formatPriceWithDecimalForContract(minPriceFormatted, fromAssetData.decimals, toAssetData.decimals);
153
+ const offchainDataArray = [
154
+ offchainData.wrapper,
155
+ offchainData.to,
156
+ offchainData.allowanceTarget,
157
+ offchainData.price,
158
+ offchainData.protocolFee,
159
+ offchainData.data,
160
+ ];
161
+ const value = offchainData.protocolFee;
162
+ const wrapper = constants_1.ZERO_ADDRESS;
163
+ const wrapperData = `0x${(0, exports.numStringToBytes)(Math.floor(Date.now() / 1000))}`;
164
+ const amountWei = (0, tokens_1.assetAmountInWei)(amount, fromAsset).toString();
165
+ if (offchainData.data === '0x00')
166
+ throw new Error('Offchain data is empty');
167
+ return {
168
+ orderData: [
169
+ fromAssetData.address,
170
+ toAssetData.address,
171
+ amountWei,
172
+ '0',
173
+ minPriceForContract,
174
+ constants_1.STABLE_PAIR_FEE_DIVIDER,
175
+ '0x0000000000000000000000000000000000000000', // set by contract
176
+ wrapper,
177
+ wrapperData,
178
+ offchainDataArray,
179
+ ],
180
+ value,
181
+ source,
182
+ price,
183
+ };
184
+ });
185
+ exports.getExchangeOrder = getExchangeOrder;
package/cjs/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import './setup';
2
2
  import * as positionData from './positionData';
3
3
  import * as marketData from './marketData';
4
+ import * as exchange from './exchange';
5
+ import * as constants from './constants';
4
6
  export * from './types';
5
- export { positionData, marketData, };
7
+ export { positionData, marketData, exchange, constants, };
package/cjs/index.js CHANGED
@@ -36,10 +36,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
36
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.marketData = exports.positionData = void 0;
39
+ exports.constants = exports.exchange = exports.marketData = exports.positionData = void 0;
40
40
  require("./setup");
41
41
  const positionData = __importStar(require("./positionData"));
42
42
  exports.positionData = positionData;
43
43
  const marketData = __importStar(require("./marketData"));
44
44
  exports.marketData = marketData;
45
+ const exchange = __importStar(require("./exchange"));
46
+ exports.exchange = exchange;
47
+ const constants = __importStar(require("./constants"));
48
+ exports.constants = constants;
45
49
  __exportStar(require("./types"), exports);
@@ -1,3 +1,3 @@
1
1
  import { MarketData, NetworkNumber, PositionData } from '../types';
2
2
  export declare const getMaxLeverageForSupplyAmount: (marketData: MarketData, supplyAmount: string) => number;
3
- export declare const getResultingPosition: (marketData: MarketData, supplyAmount: string, leverage: number, rpcUrl: string, network: NetworkNumber) => Promise<PositionData>;
3
+ export declare const getResultingPosition: (marketData: MarketData, supplyAmount: string, leverage: number, userAddress: string, rpcUrl: string, network: NetworkNumber) => Promise<PositionData>;
@@ -22,10 +22,10 @@ const getMaxLeverageForSupplyAmount = (marketData, supplyAmount) => {
22
22
  }
23
23
  };
24
24
  exports.getMaxLeverageForSupplyAmount = getMaxLeverageForSupplyAmount;
25
- const getResultingPosition = (marketData, supplyAmount, leverage, rpcUrl, network) => __awaiter(void 0, void 0, void 0, function* () {
25
+ const getResultingPosition = (marketData, supplyAmount, leverage, userAddress, rpcUrl, network) => __awaiter(void 0, void 0, void 0, function* () {
26
26
  switch (marketData.market) {
27
27
  case types_1.SupportedMarkets.MorphoBlueSUSDeUSDtb_915: {
28
- return (0, morpho_1.getMorphoResultingPosition)(marketData, supplyAmount, leverage, rpcUrl, network);
28
+ return (0, morpho_1.getMorphoResultingPosition)(marketData, supplyAmount, leverage, userAddress, rpcUrl, network);
29
29
  }
30
30
  default:
31
31
  throw new Error(`Unsupported market: ${marketData.market}`);
@@ -1,4 +1,4 @@
1
1
  import { NetworkNumber } from '@defisaver/positions-sdk';
2
2
  import { MarketData, PositionData } from '../types';
3
3
  export declare const getMorphoMaxLeverageForSupplyAmount: (marketData: MarketData, supplyAmount: string) => number;
4
- export declare const getMorphoResultingPosition: (marketData: MarketData, supplyAmount: string, leverage: number, rpcUrl: string, network: NetworkNumber) => Promise<PositionData>;
4
+ export declare const getMorphoResultingPosition: (marketData: MarketData, supplyAmount: string, leverage: number, userAddress: string, rpcUrl: string, network: NetworkNumber) => Promise<PositionData>;
@@ -17,6 +17,7 @@ const decimal_js_1 = __importDefault(require("decimal.js"));
17
17
  const tokens_1 = require("@defisaver/tokens");
18
18
  const positions_sdk_1 = require("@defisaver/positions-sdk");
19
19
  const viem_1 = require("../services/viem");
20
+ const exchange_1 = require("../exchange");
20
21
  const getMaxBoostUsd = (lltv, borrowLimit, debt, targetRatio = 1.01, bufferPercent = 1) => new decimal_js_1.default(targetRatio).mul(debt).sub(borrowLimit)
21
22
  .div(new decimal_js_1.default(lltv).sub(targetRatio).toString())
22
23
  .mul((100 - bufferPercent) / 100)
@@ -37,19 +38,19 @@ const getMorphoMaxLeverageForSupplyAmount = (marketData, supplyAmount) => {
37
38
  return maxLeverage;
38
39
  };
39
40
  exports.getMorphoMaxLeverageForSupplyAmount = getMorphoMaxLeverageForSupplyAmount;
40
- const getMorphoResultingPosition = (marketData, supplyAmount, leverage, rpcUrl, network) => __awaiter(void 0, void 0, void 0, function* () {
41
+ const getMorphoResultingPosition = (marketData, supplyAmount, leverage, userAddress, rpcUrl, network) => __awaiter(void 0, void 0, void 0, function* () {
41
42
  const provider = (0, viem_1.getViemProvider)(rpcUrl, network);
42
43
  const morphoMarket = positions_sdk_1.markets.MorphoBlueMarkets(network)[positions_sdk_1.MorphoBlueVersions.MorphoBlueSUSDeUSDtb_915];
43
44
  const { rate: oracle, assetsData, } = marketData;
45
+ const supplyAsset = Object.values(assetsData).find((asset) => !asset.isDebtAsset);
46
+ const borrowAsset = Object.values(assetsData).find((asset) => asset.isDebtAsset);
44
47
  const debtAmount = new decimal_js_1.default(leverage)
45
48
  .times(supplyAmount).minus(supplyAmount).times(oracle)
46
49
  .toString();
47
- // TODO: add price fetching logic
48
- const priceForAmount = new decimal_js_1.default(1).div(oracle).toString();
49
- const leveragedAmount = new decimal_js_1.default(debtAmount).times(priceForAmount);
50
+ const debtAmountWei = (0, tokens_1.assetAmountInWei)(debtAmount, borrowAsset.symbol);
51
+ const { priceWithFee, source } = yield (0, exchange_1.getBestPrice)(borrowAsset.symbol, supplyAsset.symbol, debtAmountWei, userAddress, network);
52
+ const leveragedAmount = new decimal_js_1.default(debtAmount).times(priceWithFee);
50
53
  const collIncrease = new decimal_js_1.default(supplyAmount).plus(leveragedAmount).toString();
51
- const supplyAsset = Object.values(assetsData).find((asset) => !asset.isDebtAsset);
52
- const borrowAsset = Object.values(assetsData).find((asset) => asset.isDebtAsset);
53
54
  const morphoMarketData = yield positions_sdk_1.morphoBlue._getMorphoBlueMarketData(provider, network, morphoMarket);
54
55
  const usedAssets = {};
55
56
  usedAssets[borrowAsset.symbol] = {
@@ -73,6 +74,13 @@ const getMorphoResultingPosition = (marketData, supplyAmount, leverage, rpcUrl,
73
74
  borrowedUsd: '0',
74
75
  };
75
76
  const aggregatedPosition = positions_sdk_1.helpers.morphoBlueHelpers.getMorphoBlueAggregatedPositionData({ usedAssets, assetsData: morphoMarketData.assetsData, marketInfo: morphoMarketData });
76
- return Object.assign({ usedAssets }, aggregatedPosition);
77
+ return Object.assign({ exchangeInfo: {
78
+ price: priceWithFee,
79
+ source,
80
+ sellAsset: borrowAsset.symbol,
81
+ sellAmount: debtAmount,
82
+ buyAsset: supplyAsset.symbol,
83
+ buyAmount: leveragedAmount.toString(),
84
+ }, usedAssets }, aggregatedPosition);
77
85
  });
78
86
  exports.getMorphoResultingPosition = getMorphoResultingPosition;
@@ -1,5 +1,6 @@
1
1
  import { IncentiveData, MMUsedAssets, MorphoBlueAggregatedPositionData, NetworkNumber } from '@defisaver/positions-sdk';
2
2
  import { SupportedMarkets } from './markets';
3
+ import { OffchainExchanges } from './exchange';
3
4
  export interface AssetData {
4
5
  symbol: string;
5
6
  address: string;
@@ -18,7 +19,16 @@ export interface MarketData {
18
19
  lltv: string;
19
20
  rate: string;
20
21
  }
22
+ export interface ExchangeInfo {
23
+ price: string;
24
+ source: OffchainExchanges | 'None';
25
+ sellAsset: string;
26
+ sellAmount: string;
27
+ buyAsset: string;
28
+ buyAmount: string;
29
+ }
21
30
  export interface PositionData extends MorphoBlueAggregatedPositionData {
22
31
  usedAssets: MMUsedAssets;
32
+ exchangeInfo: ExchangeInfo;
23
33
  }
24
34
  export { NetworkNumber, MMUsedAssets, IncentiveData, };
@@ -0,0 +1,19 @@
1
+ export declare enum OffchainExchanges {
2
+ ZeroX = "0x",
3
+ Paraswap = "Paraswap",
4
+ Kyberswap = "Kyberswap",
5
+ OneInch = "1Inch",
6
+ Bebop = "Bebop"
7
+ }
8
+ export interface PriceData {
9
+ price: string;
10
+ priceWithFee: string;
11
+ source: OffchainExchanges | 'None';
12
+ wrapper: string;
13
+ to: string;
14
+ allowanceTarget: string;
15
+ protocolFee: string;
16
+ data: string;
17
+ value: string;
18
+ gas: string;
19
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OffchainExchanges = void 0;
4
+ var OffchainExchanges;
5
+ (function (OffchainExchanges) {
6
+ OffchainExchanges["ZeroX"] = "0x";
7
+ OffchainExchanges["Paraswap"] = "Paraswap";
8
+ OffchainExchanges["Kyberswap"] = "Kyberswap";
9
+ OffchainExchanges["OneInch"] = "1Inch";
10
+ // Odos = 'Odos',
11
+ OffchainExchanges["Bebop"] = "Bebop";
12
+ })(OffchainExchanges || (exports.OffchainExchanges = OffchainExchanges = {}));
@@ -1,2 +1,3 @@
1
1
  export * from './common';
2
2
  export * from './markets';
3
+ export * from './exchange';
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./common"), exports);
18
18
  __exportStar(require("./markets"), exports);
19
+ __exportStar(require("./exchange"), exports);
@@ -0,0 +1,4 @@
1
+ export declare const STABLE_PAIR_FEE_DIVIDER = "10000";
2
+ export declare const DFS_API_URL = "https://fe.defisaver.com";
3
+ export declare const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
4
+ export declare const SLIPPAGE_PERCENT = 0.05;
@@ -0,0 +1,4 @@
1
+ export const STABLE_PAIR_FEE_DIVIDER = '10000';
2
+ export const DFS_API_URL = 'https://fe.defisaver.com';
3
+ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
4
+ export const SLIPPAGE_PERCENT = 0.05;
@@ -0,0 +1,10 @@
1
+ import { NetworkNumber } from '@defisaver/positions-sdk';
2
+ import { OffchainExchanges, PriceData } from '../types';
3
+ export declare const numStringToBytes: (num: number) => string;
4
+ export declare const getBestPrice: (fromAsset: string, toAsset: string, amount: string, userAddress: string, network?: NetworkNumber) => Promise<PriceData>;
5
+ export declare const getExchangeOrder: (fromAsset: string, toAsset: string, amount: string, userAddress: string, minPrice: string, network?: NetworkNumber) => Promise<{
6
+ orderData: (string | string[])[];
7
+ value: string;
8
+ source: OffchainExchanges | "None";
9
+ price: string;
10
+ }>;
@@ -0,0 +1,176 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import Dec from 'decimal.js';
11
+ import BN from 'bn.js';
12
+ import { NetworkNumber } from '@defisaver/positions-sdk';
13
+ import { assetAmountInWei, getAssetInfo } from '@defisaver/tokens';
14
+ import { OffchainExchanges } from '../types';
15
+ import { DFS_API_URL, SLIPPAGE_PERCENT, STABLE_PAIR_FEE_DIVIDER, ZERO_ADDRESS, } from '../constants';
16
+ const getOffchainEmptyData = (source = 'None') => ({
17
+ wrapper: ZERO_ADDRESS,
18
+ to: ZERO_ADDRESS,
19
+ allowanceTarget: ZERO_ADDRESS,
20
+ price: '0',
21
+ priceWithFee: '0',
22
+ protocolFee: '0',
23
+ data: '0x00',
24
+ value: '0',
25
+ gas: '0',
26
+ source,
27
+ });
28
+ const parsePriceWithDecimals = (price, fromDecimals, toDecimals) => new Dec(price)
29
+ .div(Math.pow(10, toDecimals))
30
+ .div(Math.pow(10, (18 - fromDecimals)))
31
+ .toString();
32
+ const formatPriceWithDecimalForContract = (price, fromDecimals, toDecimals) => new Dec(price)
33
+ .mul(Math.pow(10, toDecimals))
34
+ .mul(Math.pow(10, (18 - fromDecimals)))
35
+ .floor()
36
+ .toString();
37
+ const includeFeeInPrice = (price, from, to, fee) => {
38
+ if (from === to)
39
+ return price;
40
+ return new Dec(price).mul(new Dec(1).sub(fee)).toString();
41
+ };
42
+ const excludeFeeFromPrice = (price, from, to, fee) => {
43
+ if (from === to)
44
+ return price;
45
+ return new Dec(price).mul(new Dec(1).add(fee)).toString();
46
+ };
47
+ const parseOffchainPrice = (fromTokenSymbol, fromTokenDecimals, toTokenSymbol, toTokenDecimals, amount, feeDecimal) => {
48
+ const _price = parsePriceWithDecimals(amount, fromTokenDecimals, toTokenDecimals);
49
+ return includeFeeInPrice(_price, fromTokenSymbol, toTokenSymbol, feeDecimal);
50
+ };
51
+ const getFeeDecimal = () => new Dec(1).div(STABLE_PAIR_FEE_DIVIDER).toString();
52
+ export const numStringToBytes = (num) => {
53
+ const bn = new BN(num.toString()).toTwos(256);
54
+ return bn.toString(16);
55
+ };
56
+ const getPriceFromServer = (fromAsset_1, toAsset_1, amount_1, userAddress_1, ...args_1) => __awaiter(void 0, [fromAsset_1, toAsset_1, amount_1, userAddress_1, ...args_1], void 0, function* (fromAsset, toAsset, amount, userAddress, network = NetworkNumber.Eth, infoOnly = true) {
57
+ const fromAssetData = getAssetInfo(fromAsset, network);
58
+ const toAssetData = getAssetInfo(toAsset, network);
59
+ const feeDecimal = getFeeDecimal();
60
+ const excludedSources = ['Balancer_V2', 'Beethovenx'];
61
+ const allSources = Object.values(OffchainExchanges);
62
+ try {
63
+ const res = yield fetch(`${DFS_API_URL}/api/exchange/get-best-price`, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ },
68
+ body: JSON.stringify({
69
+ fromAsset: fromAssetData.address,
70
+ fromAssetDecimals: fromAssetData.decimals,
71
+ fromAssetSymbol: fromAssetData.symbol,
72
+ toAsset: toAssetData.address,
73
+ toAssetDecimals: toAssetData.decimals,
74
+ toAssetSymbol: toAssetData.symbol,
75
+ sources: [...allSources.map(s => s.toLowerCase())],
76
+ chainId: network,
77
+ amount,
78
+ excludedSources,
79
+ infoOnly,
80
+ takerAddress: userAddress,
81
+ account: userAddress,
82
+ noFee: false,
83
+ feeDecimal,
84
+ // temporary fix for paraswap until old exchange service is removed
85
+ shouldFormatParaswapPrice: true,
86
+ }),
87
+ });
88
+ if (!res.ok)
89
+ throw new Error(yield res.text());
90
+ const data = (yield res.json());
91
+ const formattedData = data.map((d, i) => {
92
+ const source = allSources[i];
93
+ if (typeof d === 'string')
94
+ return getOffchainEmptyData(source);
95
+ return {
96
+ wrapper: d.wrapper || ZERO_ADDRESS,
97
+ to: d.to || ZERO_ADDRESS,
98
+ allowanceTarget: d.allowanceTarget || ZERO_ADDRESS,
99
+ protocolFee: d.protocolFee || '0',
100
+ data: d.data || '0x00',
101
+ value: d.value || '0',
102
+ gas: d.gas || '0',
103
+ source: allSources[i],
104
+ price: d.price,
105
+ priceWithFee: +(d.price || '0') > 0
106
+ ? parseOffchainPrice(fromAssetData.symbol, fromAssetData.decimals, toAssetData.symbol, toAssetData.decimals, d.price, feeDecimal)
107
+ : '0',
108
+ };
109
+ }).filter((d) => d.wrapper !== ZERO_ADDRESS && d.price !== '0').sort((a, b) => (new Dec(a.price).gt(b.price) ? -1 : 1));
110
+ return formattedData;
111
+ }
112
+ catch (_a) {
113
+ return allSources.map(source => getOffchainEmptyData(source));
114
+ }
115
+ });
116
+ export const getBestPrice = (fromAsset_1, toAsset_1, amount_1, userAddress_1, ...args_1) => __awaiter(void 0, [fromAsset_1, toAsset_1, amount_1, userAddress_1, ...args_1], void 0, function* (fromAsset, toAsset, amount, userAddress, network = NetworkNumber.Eth) {
117
+ try {
118
+ const formattedData = yield getPriceFromServer(fromAsset, toAsset, amount, userAddress, network);
119
+ return formattedData[0] || getOffchainEmptyData();
120
+ }
121
+ catch (e) {
122
+ console.error('Error fetching best price:', e);
123
+ return getOffchainEmptyData();
124
+ }
125
+ });
126
+ export const getExchangeOrder = (fromAsset_1, toAsset_1, amount_1, userAddress_1, minPrice_1, ...args_1) => __awaiter(void 0, [fromAsset_1, toAsset_1, amount_1, userAddress_1, minPrice_1, ...args_1], void 0, function* (fromAsset, toAsset, amount, userAddress, minPrice, network = NetworkNumber.Eth) {
127
+ const fromAssetData = getAssetInfo(fromAsset, network);
128
+ const toAssetData = getAssetInfo(toAsset, network);
129
+ const feeDecimal = getFeeDecimal();
130
+ const offchainQuotes = yield getPriceFromServer(fromAsset, toAsset, amount, userAddress, network, false);
131
+ const formattedOffchainQuotes = offchainQuotes.map((quote) => ({
132
+ source: quote.source,
133
+ price: quote.priceWithFee,
134
+ wrapper: quote.wrapper,
135
+ wrapperData: quote.data,
136
+ offchainData: quote,
137
+ }));
138
+ const bestQuote = formattedOffchainQuotes[0];
139
+ const { offchainData, source, price } = bestQuote;
140
+ const minPriceFormatted = new Dec(excludeFeeFromPrice(minPrice, fromAssetData.address, toAssetData.address, feeDecimal))
141
+ .mul(100 - SLIPPAGE_PERCENT)
142
+ .div(100)
143
+ .toString();
144
+ const minPriceForContract = formatPriceWithDecimalForContract(minPriceFormatted, fromAssetData.decimals, toAssetData.decimals);
145
+ const offchainDataArray = [
146
+ offchainData.wrapper,
147
+ offchainData.to,
148
+ offchainData.allowanceTarget,
149
+ offchainData.price,
150
+ offchainData.protocolFee,
151
+ offchainData.data,
152
+ ];
153
+ const value = offchainData.protocolFee;
154
+ const wrapper = ZERO_ADDRESS;
155
+ const wrapperData = `0x${numStringToBytes(Math.floor(Date.now() / 1000))}`;
156
+ const amountWei = assetAmountInWei(amount, fromAsset).toString();
157
+ if (offchainData.data === '0x00')
158
+ throw new Error('Offchain data is empty');
159
+ return {
160
+ orderData: [
161
+ fromAssetData.address,
162
+ toAssetData.address,
163
+ amountWei,
164
+ '0',
165
+ minPriceForContract,
166
+ STABLE_PAIR_FEE_DIVIDER,
167
+ '0x0000000000000000000000000000000000000000', // set by contract
168
+ wrapper,
169
+ wrapperData,
170
+ offchainDataArray,
171
+ ],
172
+ value,
173
+ source,
174
+ price,
175
+ };
176
+ });
package/esm/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import './setup';
2
2
  import * as positionData from './positionData';
3
3
  import * as marketData from './marketData';
4
+ import * as exchange from './exchange';
5
+ import * as constants from './constants';
4
6
  export * from './types';
5
- export { positionData, marketData, };
7
+ export { positionData, marketData, exchange, constants, };
package/esm/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import './setup';
2
2
  import * as positionData from './positionData';
3
3
  import * as marketData from './marketData';
4
+ import * as exchange from './exchange';
5
+ import * as constants from './constants';
4
6
  export * from './types';
5
- export { positionData, marketData, };
7
+ export { positionData, marketData, exchange, constants, };
@@ -1,3 +1,3 @@
1
1
  import { MarketData, NetworkNumber, PositionData } from '../types';
2
2
  export declare const getMaxLeverageForSupplyAmount: (marketData: MarketData, supplyAmount: string) => number;
3
- export declare const getResultingPosition: (marketData: MarketData, supplyAmount: string, leverage: number, rpcUrl: string, network: NetworkNumber) => Promise<PositionData>;
3
+ export declare const getResultingPosition: (marketData: MarketData, supplyAmount: string, leverage: number, userAddress: string, rpcUrl: string, network: NetworkNumber) => Promise<PositionData>;
@@ -18,10 +18,10 @@ export const getMaxLeverageForSupplyAmount = (marketData, supplyAmount) => {
18
18
  throw new Error(`Unsupported market: ${marketData.market}`);
19
19
  }
20
20
  };
21
- export const getResultingPosition = (marketData, supplyAmount, leverage, rpcUrl, network) => __awaiter(void 0, void 0, void 0, function* () {
21
+ export const getResultingPosition = (marketData, supplyAmount, leverage, userAddress, rpcUrl, network) => __awaiter(void 0, void 0, void 0, function* () {
22
22
  switch (marketData.market) {
23
23
  case SupportedMarkets.MorphoBlueSUSDeUSDtb_915: {
24
- return getMorphoResultingPosition(marketData, supplyAmount, leverage, rpcUrl, network);
24
+ return getMorphoResultingPosition(marketData, supplyAmount, leverage, userAddress, rpcUrl, network);
25
25
  }
26
26
  default:
27
27
  throw new Error(`Unsupported market: ${marketData.market}`);
@@ -1,4 +1,4 @@
1
1
  import { NetworkNumber } from '@defisaver/positions-sdk';
2
2
  import { MarketData, PositionData } from '../types';
3
3
  export declare const getMorphoMaxLeverageForSupplyAmount: (marketData: MarketData, supplyAmount: string) => number;
4
- export declare const getMorphoResultingPosition: (marketData: MarketData, supplyAmount: string, leverage: number, rpcUrl: string, network: NetworkNumber) => Promise<PositionData>;
4
+ export declare const getMorphoResultingPosition: (marketData: MarketData, supplyAmount: string, leverage: number, userAddress: string, rpcUrl: string, network: NetworkNumber) => Promise<PositionData>;
@@ -8,9 +8,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import Dec from 'decimal.js';
11
- import { getAssetInfo } from '@defisaver/tokens';
11
+ import { assetAmountInWei, getAssetInfo } from '@defisaver/tokens';
12
12
  import { helpers, markets, morphoBlue, MorphoBlueVersions, } from '@defisaver/positions-sdk';
13
13
  import { getViemProvider } from '../services/viem';
14
+ import { getBestPrice } from '../exchange';
14
15
  const getMaxBoostUsd = (lltv, borrowLimit, debt, targetRatio = 1.01, bufferPercent = 1) => new Dec(targetRatio).mul(debt).sub(borrowLimit)
15
16
  .div(new Dec(lltv).sub(targetRatio).toString())
16
17
  .mul((100 - bufferPercent) / 100)
@@ -30,19 +31,19 @@ export const getMorphoMaxLeverageForSupplyAmount = (marketData, supplyAmount) =>
30
31
  const maxLeverage = new Dec(supplyAmount).plus(new Dec(maxDebt).times(rate)).div(supplyAmount).toNumber();
31
32
  return maxLeverage;
32
33
  };
33
- export const getMorphoResultingPosition = (marketData, supplyAmount, leverage, rpcUrl, network) => __awaiter(void 0, void 0, void 0, function* () {
34
+ export const getMorphoResultingPosition = (marketData, supplyAmount, leverage, userAddress, rpcUrl, network) => __awaiter(void 0, void 0, void 0, function* () {
34
35
  const provider = getViemProvider(rpcUrl, network);
35
36
  const morphoMarket = markets.MorphoBlueMarkets(network)[MorphoBlueVersions.MorphoBlueSUSDeUSDtb_915];
36
37
  const { rate: oracle, assetsData, } = marketData;
38
+ const supplyAsset = Object.values(assetsData).find((asset) => !asset.isDebtAsset);
39
+ const borrowAsset = Object.values(assetsData).find((asset) => asset.isDebtAsset);
37
40
  const debtAmount = new Dec(leverage)
38
41
  .times(supplyAmount).minus(supplyAmount).times(oracle)
39
42
  .toString();
40
- // TODO: add price fetching logic
41
- const priceForAmount = new Dec(1).div(oracle).toString();
42
- const leveragedAmount = new Dec(debtAmount).times(priceForAmount);
43
+ const debtAmountWei = assetAmountInWei(debtAmount, borrowAsset.symbol);
44
+ const { priceWithFee, source } = yield getBestPrice(borrowAsset.symbol, supplyAsset.symbol, debtAmountWei, userAddress, network);
45
+ const leveragedAmount = new Dec(debtAmount).times(priceWithFee);
43
46
  const collIncrease = new Dec(supplyAmount).plus(leveragedAmount).toString();
44
- const supplyAsset = Object.values(assetsData).find((asset) => !asset.isDebtAsset);
45
- const borrowAsset = Object.values(assetsData).find((asset) => asset.isDebtAsset);
46
47
  const morphoMarketData = yield morphoBlue._getMorphoBlueMarketData(provider, network, morphoMarket);
47
48
  const usedAssets = {};
48
49
  usedAssets[borrowAsset.symbol] = {
@@ -66,5 +67,12 @@ export const getMorphoResultingPosition = (marketData, supplyAmount, leverage, r
66
67
  borrowedUsd: '0',
67
68
  };
68
69
  const aggregatedPosition = helpers.morphoBlueHelpers.getMorphoBlueAggregatedPositionData({ usedAssets, assetsData: morphoMarketData.assetsData, marketInfo: morphoMarketData });
69
- return Object.assign({ usedAssets }, aggregatedPosition);
70
+ return Object.assign({ exchangeInfo: {
71
+ price: priceWithFee,
72
+ source,
73
+ sellAsset: borrowAsset.symbol,
74
+ sellAmount: debtAmount,
75
+ buyAsset: supplyAsset.symbol,
76
+ buyAmount: leveragedAmount.toString(),
77
+ }, usedAssets }, aggregatedPosition);
70
78
  });
@@ -1,5 +1,6 @@
1
1
  import { IncentiveData, MMUsedAssets, MorphoBlueAggregatedPositionData, NetworkNumber } from '@defisaver/positions-sdk';
2
2
  import { SupportedMarkets } from './markets';
3
+ import { OffchainExchanges } from './exchange';
3
4
  export interface AssetData {
4
5
  symbol: string;
5
6
  address: string;
@@ -18,7 +19,16 @@ export interface MarketData {
18
19
  lltv: string;
19
20
  rate: string;
20
21
  }
22
+ export interface ExchangeInfo {
23
+ price: string;
24
+ source: OffchainExchanges | 'None';
25
+ sellAsset: string;
26
+ sellAmount: string;
27
+ buyAsset: string;
28
+ buyAmount: string;
29
+ }
21
30
  export interface PositionData extends MorphoBlueAggregatedPositionData {
22
31
  usedAssets: MMUsedAssets;
32
+ exchangeInfo: ExchangeInfo;
23
33
  }
24
34
  export { NetworkNumber, MMUsedAssets, IncentiveData, };
@@ -0,0 +1,19 @@
1
+ export declare enum OffchainExchanges {
2
+ ZeroX = "0x",
3
+ Paraswap = "Paraswap",
4
+ Kyberswap = "Kyberswap",
5
+ OneInch = "1Inch",
6
+ Bebop = "Bebop"
7
+ }
8
+ export interface PriceData {
9
+ price: string;
10
+ priceWithFee: string;
11
+ source: OffchainExchanges | 'None';
12
+ wrapper: string;
13
+ to: string;
14
+ allowanceTarget: string;
15
+ protocolFee: string;
16
+ data: string;
17
+ value: string;
18
+ gas: string;
19
+ }
@@ -0,0 +1,9 @@
1
+ export var OffchainExchanges;
2
+ (function (OffchainExchanges) {
3
+ OffchainExchanges["ZeroX"] = "0x";
4
+ OffchainExchanges["Paraswap"] = "Paraswap";
5
+ OffchainExchanges["Kyberswap"] = "Kyberswap";
6
+ OffchainExchanges["OneInch"] = "1Inch";
7
+ // Odos = 'Odos',
8
+ OffchainExchanges["Bebop"] = "Bebop";
9
+ })(OffchainExchanges || (OffchainExchanges = {}));
@@ -1,2 +1,3 @@
1
1
  export * from './common';
2
2
  export * from './markets';
3
+ export * from './exchange';
@@ -1,2 +1,3 @@
1
1
  export * from './common';
2
2
  export * from './markets';
3
+ export * from './exchange';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defisaver/ethena-sdk",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "SDK for ethena lev create",
5
5
  "main": "./cjs/index.js",
6
6
  "module": "./esm/index.js",
@@ -27,6 +27,7 @@
27
27
  "dependencies": {
28
28
  "@defisaver/positions-sdk": "^2.1.57",
29
29
  "@defisaver/tokens": "^1.7.22",
30
+ "bn.js": "^5.1.3",
30
31
  "decimal.js": "^10.6.0",
31
32
  "viem": "^2.37.9"
32
33
  },
@@ -0,0 +1,4 @@
1
+ export const STABLE_PAIR_FEE_DIVIDER = '10000';
2
+ export const DFS_API_URL = 'https://fe.defisaver.com';
3
+ export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
4
+ export const SLIPPAGE_PERCENT = 0.05;
@@ -0,0 +1,195 @@
1
+ import Dec from 'decimal.js';
2
+ import BN from 'bn.js';
3
+ import { NetworkNumber } from '@defisaver/positions-sdk';
4
+ import { assetAmountInWei, getAssetInfo } from '@defisaver/tokens';
5
+ import { OffchainExchanges, PriceData } from '../types';
6
+ import {
7
+ DFS_API_URL, SLIPPAGE_PERCENT, STABLE_PAIR_FEE_DIVIDER, ZERO_ADDRESS,
8
+ } from '../constants';
9
+
10
+ const getOffchainEmptyData = (source: OffchainExchanges | 'None' = 'None'): PriceData => ({
11
+ wrapper: ZERO_ADDRESS,
12
+ to: ZERO_ADDRESS,
13
+ allowanceTarget: ZERO_ADDRESS,
14
+ price: '0',
15
+ priceWithFee: '0',
16
+ protocolFee: '0',
17
+ data: '0x00',
18
+ value: '0',
19
+ gas: '0',
20
+ source,
21
+ });
22
+
23
+ const parsePriceWithDecimals = (price: string, fromDecimals: number, toDecimals: number) => new Dec(price)
24
+ .div(10 ** toDecimals)
25
+ .div(10 ** (18 - fromDecimals))
26
+ .toString();
27
+
28
+ const formatPriceWithDecimalForContract = (price: string, fromDecimals: number, toDecimals: number) => new Dec(price)
29
+ .mul(10 ** toDecimals)
30
+ .mul(10 ** (18 - fromDecimals))
31
+ .floor()
32
+ .toString();
33
+
34
+ const includeFeeInPrice = (price: string, from: string, to: string, fee: string) => {
35
+ if (from === to) return price;
36
+ return new Dec(price).mul(new Dec(1).sub(fee)).toString();
37
+ };
38
+
39
+ const excludeFeeFromPrice = (price: string, from: string, to: string, fee: string) => {
40
+ if (from === to) return price;
41
+ return new Dec(price).mul(new Dec(1).add(fee)).toString();
42
+ };
43
+
44
+ const parseOffchainPrice = (
45
+ fromTokenSymbol: string,
46
+ fromTokenDecimals: number,
47
+ toTokenSymbol: string,
48
+ toTokenDecimals: number,
49
+ amount: string,
50
+ feeDecimal: string,
51
+ ): string => {
52
+ const _price = parsePriceWithDecimals(amount, fromTokenDecimals, toTokenDecimals);
53
+ return includeFeeInPrice(_price, fromTokenSymbol, toTokenSymbol, feeDecimal);
54
+ };
55
+
56
+ const getFeeDecimal = () => new Dec(1).div(STABLE_PAIR_FEE_DIVIDER).toString();
57
+
58
+ export const numStringToBytes = (num: number) => {
59
+ const bn = new BN(num.toString()).toTwos(256);
60
+ return bn.toString(16);
61
+ };
62
+
63
+ const getPriceFromServer = async (fromAsset: string, toAsset: string, amount: string, userAddress: string, network: NetworkNumber = NetworkNumber.Eth, infoOnly: boolean = true) => {
64
+ const fromAssetData = getAssetInfo(fromAsset, network);
65
+ const toAssetData = getAssetInfo(toAsset, network);
66
+ const feeDecimal = getFeeDecimal();
67
+ const excludedSources = ['Balancer_V2', 'Beethovenx'];
68
+
69
+ const allSources = Object.values(OffchainExchanges);
70
+
71
+ try {
72
+ const res = await fetch(`${DFS_API_URL}/api/exchange/get-best-price`, {
73
+ method: 'POST',
74
+ headers: {
75
+ 'Content-Type': 'application/json',
76
+ },
77
+ body: JSON.stringify({
78
+ fromAsset: fromAssetData.address,
79
+ fromAssetDecimals: fromAssetData.decimals,
80
+ fromAssetSymbol: fromAssetData.symbol,
81
+ toAsset: toAssetData.address,
82
+ toAssetDecimals: toAssetData.decimals,
83
+ toAssetSymbol: toAssetData.symbol,
84
+ sources: [...allSources.map(s => s.toLowerCase())],
85
+ chainId: network,
86
+ amount,
87
+ excludedSources,
88
+ infoOnly,
89
+ takerAddress: userAddress,
90
+ account: userAddress,
91
+ noFee: false,
92
+ feeDecimal,
93
+ // temporary fix for paraswap until old exchange service is removed
94
+ shouldFormatParaswapPrice: true,
95
+ }),
96
+ });
97
+
98
+ if (!res.ok) throw new Error(await res.text());
99
+ const data = (await res.json());
100
+
101
+ const formattedData: PriceData[] = data.map((d: any, i: number) => {
102
+ const source = allSources[i];
103
+ if (typeof d === 'string') return getOffchainEmptyData(source);
104
+ return {
105
+ wrapper: d.wrapper || ZERO_ADDRESS,
106
+ to: d.to || ZERO_ADDRESS,
107
+ allowanceTarget: d.allowanceTarget || ZERO_ADDRESS,
108
+ protocolFee: d.protocolFee || '0',
109
+ data: d.data || '0x00',
110
+ value: d.value || '0',
111
+ gas: d.gas || '0',
112
+ source: allSources[i],
113
+ price: d.price,
114
+ priceWithFee: +(d.price || '0') > 0
115
+ ? parseOffchainPrice(fromAssetData.symbol, fromAssetData.decimals, toAssetData.symbol, toAssetData.decimals, d.price, feeDecimal)
116
+ : '0',
117
+ };
118
+ }).filter((d: PriceData) => d.wrapper !== ZERO_ADDRESS && d.price !== '0').sort((a: PriceData, b: PriceData) => (new Dec(a.price).gt(b.price) ? -1 : 1));
119
+
120
+ return formattedData;
121
+ } catch {
122
+ return allSources.map(source => getOffchainEmptyData(source));
123
+ }
124
+ };
125
+
126
+ export const getBestPrice = async (fromAsset: string, toAsset: string, amount: string, userAddress: string, network: NetworkNumber = NetworkNumber.Eth): Promise<PriceData> => {
127
+ try {
128
+ const formattedData: PriceData[] = await getPriceFromServer(fromAsset, toAsset, amount, userAddress, network);
129
+
130
+ return formattedData[0] || getOffchainEmptyData();
131
+ } catch (e) {
132
+ console.error('Error fetching best price:', e);
133
+ return getOffchainEmptyData();
134
+ }
135
+ };
136
+
137
+ export const getExchangeOrder = async (fromAsset: string, toAsset: string, amount: string, userAddress: string, minPrice: string, network: NetworkNumber = NetworkNumber.Eth) => {
138
+ const fromAssetData = getAssetInfo(fromAsset, network);
139
+ const toAssetData = getAssetInfo(toAsset, network);
140
+ const feeDecimal = getFeeDecimal();
141
+
142
+ const offchainQuotes: PriceData[] = await getPriceFromServer(fromAsset, toAsset, amount, userAddress, network, false);
143
+
144
+ const formattedOffchainQuotes = offchainQuotes.map((quote) => ({
145
+ source: quote.source,
146
+ price: quote.priceWithFee,
147
+ wrapper: quote.wrapper,
148
+ wrapperData: quote.data,
149
+ offchainData: quote,
150
+ }));
151
+
152
+ const bestQuote = formattedOffchainQuotes[0];
153
+ const { offchainData, source, price } = bestQuote;
154
+
155
+ const minPriceFormatted = new Dec(excludeFeeFromPrice(minPrice, fromAssetData.address, toAssetData.address, feeDecimal))
156
+ .mul(100 - SLIPPAGE_PERCENT)
157
+ .div(100)
158
+ .toString();
159
+ const minPriceForContract = formatPriceWithDecimalForContract(minPriceFormatted, fromAssetData.decimals, toAssetData.decimals);
160
+
161
+ const offchainDataArray = [
162
+ offchainData.wrapper,
163
+ offchainData.to,
164
+ offchainData.allowanceTarget,
165
+ offchainData.price,
166
+ offchainData.protocolFee,
167
+ offchainData.data,
168
+ ];
169
+
170
+ const value = offchainData.protocolFee;
171
+
172
+ const wrapper = ZERO_ADDRESS;
173
+ const wrapperData = `0x${numStringToBytes(Math.floor(Date.now() / 1000))}`;
174
+ const amountWei = assetAmountInWei(amount, fromAsset).toString();
175
+
176
+ if (offchainData.data === '0x00') throw new Error('Offchain data is empty');
177
+
178
+ return {
179
+ orderData: [
180
+ fromAssetData.address,
181
+ toAssetData.address,
182
+ amountWei,
183
+ '0',
184
+ minPriceForContract,
185
+ STABLE_PAIR_FEE_DIVIDER,
186
+ '0x0000000000000000000000000000000000000000', // set by contract
187
+ wrapper,
188
+ wrapperData,
189
+ offchainDataArray,
190
+ ],
191
+ value,
192
+ source,
193
+ price,
194
+ };
195
+ };
package/src/index.ts CHANGED
@@ -2,10 +2,14 @@ import './setup';
2
2
 
3
3
  import * as positionData from './positionData';
4
4
  import * as marketData from './marketData';
5
+ import * as exchange from './exchange';
6
+ import * as constants from './constants';
5
7
 
6
8
  export * from './types';
7
9
 
8
10
  export {
9
11
  positionData,
10
12
  marketData,
13
+ exchange,
14
+ constants,
11
15
  };
@@ -13,10 +13,10 @@ export const getMaxLeverageForSupplyAmount = (marketData: MarketData, supplyAmou
13
13
  }
14
14
  };
15
15
 
16
- export const getResultingPosition = async (marketData: MarketData, supplyAmount: string, leverage: number, rpcUrl: string, network: NetworkNumber): Promise<PositionData> => {
16
+ export const getResultingPosition = async (marketData: MarketData, supplyAmount: string, leverage: number, userAddress: string, rpcUrl: string, network: NetworkNumber): Promise<PositionData> => {
17
17
  switch (marketData.market) {
18
18
  case SupportedMarkets.MorphoBlueSUSDeUSDtb_915: {
19
- return getMorphoResultingPosition(marketData, supplyAmount, leverage, rpcUrl, network);
19
+ return getMorphoResultingPosition(marketData, supplyAmount, leverage, userAddress, rpcUrl, network);
20
20
  }
21
21
  default:
22
22
  throw new Error(`Unsupported market: ${marketData.market}`);
@@ -1,10 +1,11 @@
1
1
  import Dec from 'decimal.js';
2
- import { getAssetInfo } from '@defisaver/tokens';
2
+ import { assetAmountInWei, getAssetInfo } from '@defisaver/tokens';
3
3
  import {
4
4
  helpers, markets, MMUsedAssets, morphoBlue, MorphoBlueVersions, NetworkNumber,
5
5
  } from '@defisaver/positions-sdk';
6
6
  import { AssetData, MarketData, PositionData } from '../types';
7
7
  import { getViemProvider } from '../services/viem';
8
+ import { getBestPrice } from '../exchange';
8
9
 
9
10
  const getMaxBoostUsd = (lltv: string, borrowLimit: string, debt: string, targetRatio = 1.01, bufferPercent = 1) => new Dec(targetRatio).mul(debt).sub(borrowLimit)
10
11
  .div(new Dec(lltv).sub(targetRatio).toString())
@@ -38,24 +39,24 @@ export const getMorphoMaxLeverageForSupplyAmount = (marketData: MarketData, supp
38
39
  return maxLeverage;
39
40
  };
40
41
 
41
- export const getMorphoResultingPosition = async (marketData: MarketData, supplyAmount: string, leverage: number, rpcUrl: string, network: NetworkNumber): Promise<PositionData> => {
42
+ export const getMorphoResultingPosition = async (marketData: MarketData, supplyAmount: string, leverage: number, userAddress: string, rpcUrl: string, network: NetworkNumber): Promise<PositionData> => {
42
43
  const provider = getViemProvider(rpcUrl, network);
43
44
 
44
45
  const morphoMarket = markets.MorphoBlueMarkets(network)[MorphoBlueVersions.MorphoBlueSUSDeUSDtb_915];
45
46
  const {
46
47
  rate: oracle, assetsData,
47
48
  } = marketData;
49
+ const supplyAsset: AssetData = Object.values(assetsData).find((asset) => !asset.isDebtAsset)!;
50
+ const borrowAsset: AssetData = Object.values(assetsData).find((asset) => asset.isDebtAsset)!;
48
51
  const debtAmount = new Dec(leverage)
49
52
  .times(supplyAmount).minus(supplyAmount).times(oracle)
50
53
  .toString();
51
54
 
52
- // TODO: add price fetching logic
53
- const priceForAmount = new Dec(1).div(oracle).toString();
54
- const leveragedAmount = new Dec(debtAmount).times(priceForAmount);
55
- const collIncrease = new Dec(supplyAmount).plus(leveragedAmount).toString();
55
+ const debtAmountWei = assetAmountInWei(debtAmount, borrowAsset.symbol);
56
+ const { priceWithFee, source } = await getBestPrice(borrowAsset.symbol, supplyAsset.symbol, debtAmountWei, userAddress, network);
56
57
 
57
- const supplyAsset: AssetData = Object.values(assetsData).find((asset) => !asset.isDebtAsset)!;
58
- const borrowAsset: AssetData = Object.values(assetsData).find((asset) => asset.isDebtAsset)!;
58
+ const leveragedAmount = new Dec(debtAmount).times(priceWithFee);
59
+ const collIncrease = new Dec(supplyAmount).plus(leveragedAmount).toString();
59
60
 
60
61
  const morphoMarketData = await morphoBlue._getMorphoBlueMarketData(provider, network, morphoMarket);
61
62
  const usedAssets: MMUsedAssets = {};
@@ -84,6 +85,14 @@ export const getMorphoResultingPosition = async (marketData: MarketData, supplyA
84
85
 
85
86
  const aggregatedPosition = helpers.morphoBlueHelpers.getMorphoBlueAggregatedPositionData({ usedAssets, assetsData: morphoMarketData.assetsData, marketInfo: morphoMarketData });
86
87
  return {
88
+ exchangeInfo: {
89
+ price: priceWithFee,
90
+ source,
91
+ sellAsset: borrowAsset.symbol,
92
+ sellAmount: debtAmount,
93
+ buyAsset: supplyAsset.symbol,
94
+ buyAmount: leveragedAmount.toString(),
95
+ },
87
96
  usedAssets,
88
97
  ...aggregatedPosition,
89
98
  };
@@ -2,6 +2,7 @@ import {
2
2
  IncentiveData, MMUsedAssets, MorphoBlueAggregatedPositionData, NetworkNumber,
3
3
  } from '@defisaver/positions-sdk';
4
4
  import { SupportedMarkets } from './markets';
5
+ import { OffchainExchanges } from './exchange';
5
6
 
6
7
  export interface AssetData {
7
8
  symbol: string;
@@ -23,8 +24,18 @@ export interface MarketData {
23
24
  rate: string;
24
25
  }
25
26
 
27
+ export interface ExchangeInfo {
28
+ price: string;
29
+ source: OffchainExchanges | 'None';
30
+ sellAsset: string;
31
+ sellAmount: string;
32
+ buyAsset: string;
33
+ buyAmount: string;
34
+ }
35
+
26
36
  export interface PositionData extends MorphoBlueAggregatedPositionData {
27
37
  usedAssets: MMUsedAssets;
38
+ exchangeInfo: ExchangeInfo;
28
39
  }
29
40
 
30
41
  export {
@@ -0,0 +1,21 @@
1
+ export enum OffchainExchanges {
2
+ ZeroX = '0x',
3
+ Paraswap = 'Paraswap',
4
+ Kyberswap = 'Kyberswap',
5
+ OneInch = '1Inch',
6
+ // Odos = 'Odos',
7
+ Bebop = 'Bebop',
8
+ }
9
+
10
+ export interface PriceData {
11
+ price: string;
12
+ priceWithFee: string;
13
+ source: OffchainExchanges | 'None';
14
+ wrapper: string;
15
+ to: string;
16
+ allowanceTarget: string;
17
+ protocolFee: string;
18
+ data: string;
19
+ value: string;
20
+ gas: string;
21
+ }
@@ -1,2 +1,3 @@
1
1
  export * from './common';
2
- export * from './markets';
2
+ export * from './markets';
3
+ export * from './exchange';