@defisaver/positions-sdk 2.1.144-dev → 2.1.145
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/cjs/config/contracts.d.ts +249 -0
- package/cjs/config/contracts.js +11 -1
- package/cjs/constants/index.d.ts +1 -0
- package/cjs/constants/index.js +2 -1
- package/cjs/contracts.d.ts +1510 -0
- package/cjs/contracts.js +3 -2
- package/cjs/fluid/index.js +6 -2
- package/cjs/helpers/fluidHelpers/index.js +5 -4
- package/cjs/helpers/index.d.ts +1 -0
- package/cjs/helpers/index.js +2 -1
- package/cjs/helpers/morphoMidnightHelpers/headroom.d.ts +48 -0
- package/cjs/helpers/morphoMidnightHelpers/headroom.js +52 -0
- package/cjs/helpers/morphoMidnightHelpers/index.d.ts +113 -0
- package/cjs/helpers/morphoMidnightHelpers/index.js +298 -0
- package/cjs/helpers/morphoMidnightHelpers/rate.d.ts +15 -0
- package/cjs/helpers/morphoMidnightHelpers/rate.js +62 -0
- package/cjs/helpers/morphoMidnightHelpers/tenor.d.ts +129 -0
- package/cjs/helpers/morphoMidnightHelpers/tenor.js +254 -0
- package/cjs/index.d.ts +2 -1
- package/cjs/index.js +3 -1
- package/cjs/markets/index.d.ts +1 -0
- package/cjs/markets/index.js +6 -1
- package/cjs/markets/morphoMidnight/index.d.ts +60 -0
- package/cjs/markets/morphoMidnight/index.js +659 -0
- package/cjs/morphoMidnight/index.d.ts +14 -0
- package/cjs/morphoMidnight/index.js +261 -0
- package/cjs/portfolio/discovery.js +4 -0
- package/cjs/portfolio/index.js +44 -0
- package/cjs/services/viem.d.ts +11 -11
- package/cjs/staking/eligibility.d.ts +15 -1
- package/cjs/staking/eligibility.js +12 -1
- package/cjs/staking/staking.js +18 -22
- package/cjs/types/index.d.ts +1 -0
- package/cjs/types/index.js +1 -0
- package/cjs/types/morphoMidnight.d.ts +141 -0
- package/cjs/types/morphoMidnight.js +47 -0
- package/cjs/types/portfolio.d.ts +4 -0
- package/esm/config/contracts.d.ts +249 -0
- package/esm/config/contracts.js +9 -0
- package/esm/constants/index.d.ts +1 -0
- package/esm/constants/index.js +1 -0
- package/esm/contracts.d.ts +1510 -0
- package/esm/contracts.js +1 -0
- package/esm/fluid/index.js +6 -2
- package/esm/helpers/fluidHelpers/index.js +6 -5
- package/esm/helpers/index.d.ts +1 -0
- package/esm/helpers/index.js +1 -0
- package/esm/helpers/morphoMidnightHelpers/headroom.d.ts +48 -0
- package/esm/helpers/morphoMidnightHelpers/headroom.js +45 -0
- package/esm/helpers/morphoMidnightHelpers/index.d.ts +113 -0
- package/esm/helpers/morphoMidnightHelpers/index.js +271 -0
- package/esm/helpers/morphoMidnightHelpers/rate.d.ts +15 -0
- package/esm/helpers/morphoMidnightHelpers/rate.js +50 -0
- package/esm/helpers/morphoMidnightHelpers/tenor.d.ts +129 -0
- package/esm/helpers/morphoMidnightHelpers/tenor.js +238 -0
- package/esm/index.d.ts +2 -1
- package/esm/index.js +2 -1
- package/esm/markets/index.d.ts +1 -0
- package/esm/markets/index.js +1 -0
- package/esm/markets/morphoMidnight/index.d.ts +60 -0
- package/esm/markets/morphoMidnight/index.js +631 -0
- package/esm/morphoMidnight/index.d.ts +14 -0
- package/esm/morphoMidnight/index.js +248 -0
- package/esm/portfolio/discovery.js +5 -1
- package/esm/portfolio/index.js +45 -1
- package/esm/services/viem.d.ts +11 -11
- package/esm/staking/eligibility.d.ts +15 -1
- package/esm/staking/eligibility.js +10 -0
- package/esm/staking/staking.js +19 -23
- package/esm/types/index.d.ts +1 -0
- package/esm/types/index.js +1 -0
- package/esm/types/morphoMidnight.d.ts +141 -0
- package/esm/types/morphoMidnight.js +44 -0
- package/esm/types/portfolio.d.ts +4 -0
- package/package.json +1 -1
- package/src/config/contracts.ts +9 -0
- package/src/constants/index.ts +1 -0
- package/src/contracts.ts +1 -0
- package/src/fluid/index.ts +6 -2
- package/src/helpers/fluidHelpers/index.ts +6 -5
- package/src/helpers/index.ts +1 -0
- package/src/helpers/morphoMidnightHelpers/headroom.ts +82 -0
- package/src/helpers/morphoMidnightHelpers/index.ts +485 -0
- package/src/helpers/morphoMidnightHelpers/rate.ts +71 -0
- package/src/helpers/morphoMidnightHelpers/tenor.ts +407 -0
- package/src/index.ts +2 -0
- package/src/markets/index.ts +3 -0
- package/src/markets/morphoMidnight/index.ts +675 -0
- package/src/morphoMidnight/index.ts +276 -0
- package/src/portfolio/discovery.ts +6 -0
- package/src/portfolio/index.ts +44 -0
- package/src/staking/eligibility.ts +29 -2
- package/src/staking/staking.ts +20 -22
- package/src/types/index.ts +1 -0
- package/src/types/morphoMidnight.ts +163 -0
- package/src/types/portfolio.ts +4 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import Dec from 'decimal.js';
|
|
2
|
+
import { assetAmountInEth, getAssetInfoByAddress } from '@defisaver/tokens';
|
|
3
|
+
import { ZERO_ADDRESS, ZERO_BYTES32 } from '../../constants';
|
|
4
|
+
import {
|
|
5
|
+
MorphoMidnightBookOffer,
|
|
6
|
+
MorphoMidnightBookSide,
|
|
7
|
+
MorphoMidnightMarketData,
|
|
8
|
+
MorphoMidnightParsedBook,
|
|
9
|
+
NetworkNumber,
|
|
10
|
+
} from '../../types';
|
|
11
|
+
import { isTenorMidnightMarket, MIDNIGHT_BASE } from '../../markets/morphoMidnight';
|
|
12
|
+
import type {
|
|
13
|
+
MorphoMidnightBorrowQuote,
|
|
14
|
+
MorphoMidnightPaybackQuote,
|
|
15
|
+
MorphoMidnightPaybackUnitsQuote,
|
|
16
|
+
} from './index';
|
|
17
|
+
import {
|
|
18
|
+
buildMidnightParsedBook,
|
|
19
|
+
midnightApyFromPrice,
|
|
20
|
+
midnightBoundPrice,
|
|
21
|
+
midnightTimeToMaturityDays,
|
|
22
|
+
} from './rate';
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
// Notion doc by Rajko: https://app.notion.com/p/defisaver/Tenor-API-3ba0be682adc80dfad35c81a9a4cb442
|
|
26
|
+
|
|
27
|
+
const TENOR_QUOTES_URL = 'https://router.tenor.finance/v1/quotes';
|
|
28
|
+
const TENOR_ORDERBOOK_URL = 'https://router.tenor.finance/v1/orderbook';
|
|
29
|
+
const TENOR_TIMEOUT_MS = 15000;
|
|
30
|
+
const TENOR_ALGORITHM = 'branch_and_bound';
|
|
31
|
+
|
|
32
|
+
const tenorQuoteError = (reason?: string): string => (
|
|
33
|
+
reason ? `Morpho Midnight quote unavailable: ${reason}` : 'Morpho Midnight quote unavailable'
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
type TenorBookKey = 'asks' | 'bids';
|
|
37
|
+
export const tenorBookKeyFor = (side: MorphoMidnightBookSide): TenorBookKey => (side === 'bids' ? 'asks' : 'bids');
|
|
38
|
+
|
|
39
|
+
// Tenor order-book levels are annualized APR in basis points: 25 → 0.25%, 700 → 7%.
|
|
40
|
+
export const tenorBookRateToApyPercent = (rate: Dec.Value): string => new Dec(rate || 0).div(100).toString();
|
|
41
|
+
|
|
42
|
+
const tenorFillPrice = (assets: Dec.Value, units: Dec.Value): string => (
|
|
43
|
+
new Dec(units).lte(0) ? '0' : new Dec(assets).div(units).toString()
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
interface TenorOfferCollateral {
|
|
47
|
+
token: string;
|
|
48
|
+
lltv: string | number;
|
|
49
|
+
liquidation_cursor: string | number;
|
|
50
|
+
oracle: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface TenorOffer {
|
|
54
|
+
start: string | number;
|
|
55
|
+
group?: string;
|
|
56
|
+
callback?: string;
|
|
57
|
+
tick: string | number;
|
|
58
|
+
chain_id: string | number;
|
|
59
|
+
maturity: string | number;
|
|
60
|
+
buy: boolean;
|
|
61
|
+
maker: string;
|
|
62
|
+
loan_token_address: string;
|
|
63
|
+
callback_data?: string;
|
|
64
|
+
expiry: string | number;
|
|
65
|
+
ratifier?: string;
|
|
66
|
+
collaterals: TenorOfferCollateral[];
|
|
67
|
+
continuous_fee_cap: string | number;
|
|
68
|
+
enter_gate: string;
|
|
69
|
+
liquidator_gate: string;
|
|
70
|
+
max_assets: string | number;
|
|
71
|
+
max_units: string | number;
|
|
72
|
+
ratifier_data?: string;
|
|
73
|
+
rcf_threshold: string | number;
|
|
74
|
+
receiver_if_maker_is_seller?: string;
|
|
75
|
+
reduce_only: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface TenorOfferFill {
|
|
79
|
+
units: string | number;
|
|
80
|
+
offer: TenorOffer;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Tenor's offer JSON is flat (market fields live on the offer). Morpho's is nested, and that nested
|
|
85
|
+
* shape is what the app encodes for `Midnight.take`. Map Tenor into that shape so recipes stay on one encoder.
|
|
86
|
+
*/
|
|
87
|
+
export const tenorOfferToApiOffer = (offer: TenorOffer) => ({
|
|
88
|
+
market: {
|
|
89
|
+
chain_id: offer.chain_id,
|
|
90
|
+
midnight: MIDNIGHT_BASE,
|
|
91
|
+
loan_token: offer.loan_token_address,
|
|
92
|
+
collaterals: offer.collaterals || [],
|
|
93
|
+
maturity: offer.maturity,
|
|
94
|
+
rcf_threshold: offer.rcf_threshold,
|
|
95
|
+
enter_gate: offer.enter_gate,
|
|
96
|
+
liquidator_gate: offer.liquidator_gate,
|
|
97
|
+
},
|
|
98
|
+
buy: offer.buy,
|
|
99
|
+
maker: offer.maker,
|
|
100
|
+
start: offer.start,
|
|
101
|
+
expiry: offer.expiry,
|
|
102
|
+
tick: offer.tick,
|
|
103
|
+
group: offer.group || ZERO_BYTES32,
|
|
104
|
+
callback: offer.callback || ZERO_ADDRESS,
|
|
105
|
+
callback_data: offer.callback_data || '0x',
|
|
106
|
+
receiver_if_maker_is_seller: offer.receiver_if_maker_is_seller || ZERO_ADDRESS,
|
|
107
|
+
ratifier: offer.ratifier || ZERO_ADDRESS,
|
|
108
|
+
reduce_only: offer.reduce_only,
|
|
109
|
+
max_units: offer.max_units,
|
|
110
|
+
max_assets: offer.max_assets,
|
|
111
|
+
continuous_fee_cap: offer.continuous_fee_cap,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export const tenorOfferFillToApiFill = (fill: TenorOfferFill) => ({
|
|
115
|
+
units: fill.units,
|
|
116
|
+
offer: tenorOfferToApiOffer(fill.offer),
|
|
117
|
+
ratifier_data: fill.offer.ratifier_data || '0x',
|
|
118
|
+
market_id: '',
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
interface TenorBookBucket {
|
|
122
|
+
rate: number | string;
|
|
123
|
+
liquidity: number | string;
|
|
124
|
+
cumulative_liquidity?: number | string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface TenorBookSidePayload {
|
|
128
|
+
buckets?: TenorBookBucket[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface TenorBookResponse {
|
|
132
|
+
asks?: TenorBookSidePayload | TenorBookBucket[];
|
|
133
|
+
bids?: TenorBookSidePayload | TenorBookBucket[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const tenorBookBuckets = (side?: TenorBookSidePayload | TenorBookBucket[]): TenorBookBucket[] => {
|
|
137
|
+
if (!side) return [];
|
|
138
|
+
if (Array.isArray(side)) return side;
|
|
139
|
+
return side.buckets || [];
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/** The levels resting on the Tenor side a taker of `side` fills, in raw loan-token base units. */
|
|
143
|
+
export const parseTenorBookSide = (
|
|
144
|
+
json: TenorBookResponse,
|
|
145
|
+
side: MorphoMidnightBookSide,
|
|
146
|
+
) => tenorBookBuckets(json[tenorBookKeyFor(side)])
|
|
147
|
+
.map((bucket) => ({
|
|
148
|
+
rate: tenorBookRateToApyPercent(bucket.rate),
|
|
149
|
+
liquidityRaw: new Dec(bucket.liquidity || 0).toFixed(0),
|
|
150
|
+
}))
|
|
151
|
+
.filter((offer) => new Dec(offer.liquidityRaw).gt(0));
|
|
152
|
+
|
|
153
|
+
export const parseTenorOrderBook = (
|
|
154
|
+
json: TenorBookResponse,
|
|
155
|
+
side: MorphoMidnightBookSide,
|
|
156
|
+
loanSymbol: string,
|
|
157
|
+
): MorphoMidnightParsedBook | null => {
|
|
158
|
+
const offers: MorphoMidnightBookOffer[] = parseTenorBookSide(json, side).map((offer) => ({
|
|
159
|
+
rate: offer.rate,
|
|
160
|
+
liquidity: assetAmountInEth(offer.liquidityRaw, loanSymbol),
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
return buildMidnightParsedBook(offers, side);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const fetchTenorBook = async (marketId: string, network: NetworkNumber): Promise<TenorBookResponse> => {
|
|
167
|
+
const res = await fetch(
|
|
168
|
+
`${TENOR_ORDERBOOK_URL}/${marketId}?chain_id=${network}`,
|
|
169
|
+
{ signal: AbortSignal.timeout(TENOR_TIMEOUT_MS) },
|
|
170
|
+
);
|
|
171
|
+
if (!res.ok) throw new Error(`Midnight book request failed for ${marketId} (${res.status})`);
|
|
172
|
+
return res.json();
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const getTenorMarketBook = async (
|
|
176
|
+
market: MorphoMidnightMarketData,
|
|
177
|
+
network: NetworkNumber,
|
|
178
|
+
side: MorphoMidnightBookSide = 'bids',
|
|
179
|
+
): Promise<MorphoMidnightParsedBook | null> => {
|
|
180
|
+
const loanSymbol = getAssetInfoByAddress(market.loanToken, network).symbol;
|
|
181
|
+
return parseTenorOrderBook(await fetchTenorBook(market.marketId, network), side, loanSymbol);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const tenorBookAvailableAssetsRaw = async (
|
|
185
|
+
marketId: string,
|
|
186
|
+
network: NetworkNumber,
|
|
187
|
+
side: MorphoMidnightBookSide,
|
|
188
|
+
): Promise<string> => {
|
|
189
|
+
try {
|
|
190
|
+
const json = await fetchTenorBook(marketId, network);
|
|
191
|
+
return parseTenorBookSide(json, side).reduce((sum, offer) => sum.add(offer.liquidityRaw), new Dec(0)).toFixed(0);
|
|
192
|
+
} catch {
|
|
193
|
+
return '0';
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
interface TenorQuotePayload {
|
|
198
|
+
units?: string;
|
|
199
|
+
rate?: string;
|
|
200
|
+
buyer_assets?: string;
|
|
201
|
+
seller_assets?: string;
|
|
202
|
+
offers?: TenorOfferFill[];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
interface TenorQuoteResponse extends TenorQuotePayload {
|
|
206
|
+
quotes?: TenorQuotePayload[];
|
|
207
|
+
error?: string;
|
|
208
|
+
message?: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const fetchTenorQuote = async ({
|
|
212
|
+
marketId,
|
|
213
|
+
side,
|
|
214
|
+
assets,
|
|
215
|
+
units,
|
|
216
|
+
taker,
|
|
217
|
+
network = NetworkNumber.Base,
|
|
218
|
+
}: {
|
|
219
|
+
marketId: string,
|
|
220
|
+
side: MorphoMidnightBookSide,
|
|
221
|
+
assets?: string,
|
|
222
|
+
units?: string,
|
|
223
|
+
taker?: string,
|
|
224
|
+
network?: NetworkNumber,
|
|
225
|
+
}) => {
|
|
226
|
+
const hasAssets = assets !== undefined && assets !== null;
|
|
227
|
+
const hasUnits = units !== undefined && units !== null;
|
|
228
|
+
if (hasAssets === hasUnits) {
|
|
229
|
+
throw new Error(tenorQuoteError('Either assets or units must be provided'));
|
|
230
|
+
}
|
|
231
|
+
if (side !== 'asks' && side !== 'bids') {
|
|
232
|
+
throw new Error(tenorQuoteError(`Unsupported Tenor quote side: ${side}`));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const isBuy = side === 'asks';
|
|
236
|
+
const amount = (hasAssets ? assets : units) as string;
|
|
237
|
+
const res = await fetch(TENOR_QUOTES_URL, {
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: { 'Content-Type': 'application/json' },
|
|
240
|
+
body: JSON.stringify({
|
|
241
|
+
market_hashes: [marketId],
|
|
242
|
+
amount,
|
|
243
|
+
chain_id: network,
|
|
244
|
+
is_buy: isBuy,
|
|
245
|
+
is_exact_in: hasAssets ? isBuy : !isBuy,
|
|
246
|
+
limit_rate: null,
|
|
247
|
+
valid_for: null,
|
|
248
|
+
algorithm: TENOR_ALGORITHM,
|
|
249
|
+
allow_partial: false,
|
|
250
|
+
taker: taker || ZERO_ADDRESS,
|
|
251
|
+
}),
|
|
252
|
+
signal: AbortSignal.timeout(TENOR_TIMEOUT_MS),
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const result: TenorQuoteResponse = await res.json().catch(() => ({} as TenorQuoteResponse));
|
|
256
|
+
if (!res.ok) {
|
|
257
|
+
throw new Error(tenorQuoteError(result.error || result.message || `Tenor quote request failed with status ${res.status}`));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const payload: TenorQuotePayload = result.quotes?.[0] || result;
|
|
261
|
+
const quotedUnits = new Dec(payload.units || result.units || 0);
|
|
262
|
+
const offers = payload.offers || result.offers || [];
|
|
263
|
+
// A book that can't fill the size comes back 200 with everything zeroed rather than as an error.
|
|
264
|
+
if (quotedUnits.lte(0) || offers.length === 0) {
|
|
265
|
+
throw new Error(tenorQuoteError('INSUFFICIENT_LIQUIDITY'));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
units: quotedUnits.toFixed(0),
|
|
270
|
+
buyerAssets: (payload.buyer_assets || result.buyer_assets || '0').toString(),
|
|
271
|
+
offerFills: offers.map(tenorOfferFillToApiFill),
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const TENOR_NO_AVAILABLE_UNITS = '0';
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Tenor's router takes no slippage of its own — it prices the fill and hands back the offers. `slippagePercent`
|
|
279
|
+
* is therefore accepted only to keep the signature aligned with the Morpho-hosted quote that `index.ts`
|
|
280
|
+
* forwards to positionally; the guard comes from `rateSlippagePercent` via `midnightBoundPrice`.
|
|
281
|
+
*/
|
|
282
|
+
export const getTenorBorrowQuote = async (
|
|
283
|
+
marketId: string,
|
|
284
|
+
assetsRaw: string,
|
|
285
|
+
slippagePercent: Dec.Value,
|
|
286
|
+
maturity: number,
|
|
287
|
+
maxBorrowRate?: Dec.Value,
|
|
288
|
+
taker?: string,
|
|
289
|
+
rateSlippagePercent?: Dec.Value,
|
|
290
|
+
network: NetworkNumber = NetworkNumber.Base,
|
|
291
|
+
): Promise<MorphoMidnightBorrowQuote> => {
|
|
292
|
+
if (!isTenorMidnightMarket(marketId)) {
|
|
293
|
+
throw new Error(tenorQuoteError(`Not a Tenor Midnight market: ${marketId}`));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const [quote, availableAssets] = await Promise.all([
|
|
297
|
+
fetchTenorQuote({
|
|
298
|
+
marketId, side: 'bids', assets: assetsRaw, taker, network,
|
|
299
|
+
}),
|
|
300
|
+
tenorBookAvailableAssetsRaw(marketId, network, 'bids'),
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
304
|
+
const bestPrice = tenorFillPrice(assetsRaw, quote.units);
|
|
305
|
+
const estBorrowRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
306
|
+
|
|
307
|
+
const capPrice = midnightBoundPrice(estBorrowRate, ttmDays, 'ceiling', maxBorrowRate, rateSlippagePercent);
|
|
308
|
+
const maxRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
309
|
+
const maxUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0);
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
bestPrice,
|
|
313
|
+
worstPrice: capPrice,
|
|
314
|
+
estBorrowRate,
|
|
315
|
+
maxRate,
|
|
316
|
+
newUnits: quote.units,
|
|
317
|
+
maxUnits,
|
|
318
|
+
availableAssets,
|
|
319
|
+
availableUnits: TENOR_NO_AVAILABLE_UNITS,
|
|
320
|
+
takeableOffers: quote.offerFills,
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
export const getTenorPaybackQuote = async (
|
|
325
|
+
marketId: string,
|
|
326
|
+
assetsRaw: string,
|
|
327
|
+
slippagePercent: Dec.Value,
|
|
328
|
+
maturity: number,
|
|
329
|
+
minPaybackRate?: Dec.Value,
|
|
330
|
+
taker?: string,
|
|
331
|
+
rateSlippagePercent?: Dec.Value,
|
|
332
|
+
network: NetworkNumber = NetworkNumber.Base,
|
|
333
|
+
): Promise<MorphoMidnightPaybackQuote> => {
|
|
334
|
+
if (!isTenorMidnightMarket(marketId)) {
|
|
335
|
+
throw new Error(tenorQuoteError(`Not a Tenor Midnight market: ${marketId}`));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const [quote, availableAssets] = await Promise.all([
|
|
339
|
+
fetchTenorQuote({
|
|
340
|
+
marketId, side: 'asks', assets: assetsRaw, taker, network,
|
|
341
|
+
}),
|
|
342
|
+
tenorBookAvailableAssetsRaw(marketId, network, 'asks'),
|
|
343
|
+
]);
|
|
344
|
+
|
|
345
|
+
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
346
|
+
const bestPrice = tenorFillPrice(assetsRaw, quote.units);
|
|
347
|
+
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
348
|
+
|
|
349
|
+
const capPrice = midnightBoundPrice(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
350
|
+
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
351
|
+
const minUnits = new Dec(capPrice).lte(0) ? '0' : new Dec(assetsRaw).div(capPrice).toFixed(0, Dec.ROUND_DOWN);
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
bestPrice,
|
|
355
|
+
worstPrice: capPrice,
|
|
356
|
+
estPaybackRate,
|
|
357
|
+
minRate,
|
|
358
|
+
newUnits: quote.units,
|
|
359
|
+
minUnits,
|
|
360
|
+
availableAssets,
|
|
361
|
+
availableUnits: TENOR_NO_AVAILABLE_UNITS,
|
|
362
|
+
takeableOffers: quote.offerFills,
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
export const getTenorPaybackUnitsQuote = async (
|
|
367
|
+
marketId: string,
|
|
368
|
+
unitsRaw: string,
|
|
369
|
+
slippagePercent: Dec.Value,
|
|
370
|
+
maturity: number,
|
|
371
|
+
minPaybackRate?: Dec.Value,
|
|
372
|
+
taker?: string,
|
|
373
|
+
rateSlippagePercent?: Dec.Value,
|
|
374
|
+
network: NetworkNumber = NetworkNumber.Base,
|
|
375
|
+
): Promise<MorphoMidnightPaybackUnitsQuote> => {
|
|
376
|
+
if (!isTenorMidnightMarket(marketId)) {
|
|
377
|
+
throw new Error(tenorQuoteError(`Not a Tenor Midnight market: ${marketId}`));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const [quote, availableAssets] = await Promise.all([
|
|
381
|
+
fetchTenorQuote({
|
|
382
|
+
marketId, side: 'asks', units: unitsRaw, taker, network,
|
|
383
|
+
}),
|
|
384
|
+
tenorBookAvailableAssetsRaw(marketId, network, 'asks'),
|
|
385
|
+
]);
|
|
386
|
+
|
|
387
|
+
const ttmDays = midnightTimeToMaturityDays(maturity);
|
|
388
|
+
const newAssets = quote.buyerAssets;
|
|
389
|
+
const bestPrice = tenorFillPrice(newAssets, unitsRaw);
|
|
390
|
+
const estPaybackRate = midnightApyFromPrice(bestPrice, ttmDays);
|
|
391
|
+
|
|
392
|
+
const capPrice = midnightBoundPrice(estPaybackRate, ttmDays, 'floor', minPaybackRate, rateSlippagePercent);
|
|
393
|
+
const minRate = midnightApyFromPrice(capPrice, ttmDays);
|
|
394
|
+
const maxAssets = new Dec(unitsRaw).mul(capPrice).toFixed(0, Dec.ROUND_UP);
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
bestPrice,
|
|
398
|
+
worstPrice: capPrice,
|
|
399
|
+
estPaybackRate,
|
|
400
|
+
minRate,
|
|
401
|
+
newAssets,
|
|
402
|
+
maxAssets,
|
|
403
|
+
availableAssets,
|
|
404
|
+
availableUnits: TENOR_NO_AVAILABLE_UNITS,
|
|
405
|
+
takeableOffers: quote.offerFills,
|
|
406
|
+
};
|
|
407
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import * as markets from './markets';
|
|
|
17
17
|
import * as helpers from './helpers';
|
|
18
18
|
import * as exchange from './exchange';
|
|
19
19
|
import * as morphoBlue from './morphoBlue';
|
|
20
|
+
import * as morphoMidnight from './morphoMidnight';
|
|
20
21
|
import * as llamaLend from './llamaLend';
|
|
21
22
|
import * as portfolio from './portfolio';
|
|
22
23
|
import * as claiming from './claiming';
|
|
@@ -41,6 +42,7 @@ export {
|
|
|
41
42
|
markets,
|
|
42
43
|
helpers,
|
|
43
44
|
morphoBlue,
|
|
45
|
+
morphoMidnight,
|
|
44
46
|
llamaLend,
|
|
45
47
|
fluid,
|
|
46
48
|
portfolio,
|
package/src/markets/index.ts
CHANGED
|
@@ -17,6 +17,9 @@ export {
|
|
|
17
17
|
export { SparkMarkets } from './spark';
|
|
18
18
|
export { CrvUsdMarkets } from './curveUsd';
|
|
19
19
|
export { MorphoBlueMarkets, findMorphoBlueMarket } from './morphoBlue';
|
|
20
|
+
export {
|
|
21
|
+
MorphoMidnightMarkets, findMorphoMidnightMarket, isTenorMidnightMarket, morphoMidnightMarketCollateralParams,
|
|
22
|
+
} from './morphoMidnight';
|
|
20
23
|
export { LlamaLendMarkets } from './llamaLend';
|
|
21
24
|
export { LiquityV2Markets, findLiquityV2MarketByAddress } from './liquityV2';
|
|
22
25
|
export {
|