@morpho-org/midnight-sdk 1.0.0 → 1.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/lib/cjs/api/MidnightApi.js +67 -3
- package/lib/cjs/api/helpers.d.ts +11 -1
- package/lib/cjs/api/helpers.js +48 -1
- package/lib/cjs/errors.d.ts +15 -0
- package/lib/cjs/errors.js +21 -1
- package/lib/cjs/fetch/Market.js +37 -2
- package/lib/cjs/fetch/Position.d.ts +1 -1
- package/lib/cjs/fetch/Position.js +5 -1
- package/lib/cjs/fetch/utils.d.ts +1 -1
- package/lib/cjs/fetch/utils.js +9 -2
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +1 -0
- package/lib/cjs/market/index.d.ts +0 -2
- package/lib/cjs/market/index.js +0 -2
- package/lib/cjs/offers/Offer.d.ts +1 -1
- package/lib/cjs/offers/OfferChainUtils.d.ts +179 -0
- package/lib/cjs/offers/OfferChainUtils.js +446 -0
- package/lib/cjs/offers/OfferUtils.d.ts +5 -5
- package/lib/cjs/offers/OfferUtils.js +26 -13
- package/lib/cjs/offers/index.d.ts +1 -0
- package/lib/cjs/offers/index.js +1 -0
- package/lib/cjs/{market → position}/Position.d.ts +83 -6
- package/lib/cjs/{market → position}/Position.js +83 -5
- package/lib/{esm/market → cjs/position}/PositionUtils.d.ts +26 -1
- package/lib/cjs/{market → position}/PositionUtils.js +27 -0
- package/lib/cjs/position/index.d.ts +2 -0
- package/lib/cjs/position/index.js +18 -0
- package/lib/cjs/signatures/EcrecoverRatifierUtils.d.ts +118 -0
- package/lib/cjs/signatures/EcrecoverRatifierUtils.js +149 -1
- package/lib/cjs/signatures/Group.js +5 -3
- package/lib/cjs/signatures/GroupUtils.d.ts +48 -0
- package/lib/cjs/signatures/GroupUtils.js +51 -0
- package/lib/cjs/signatures/RatifierUtils.js +15 -3
- package/lib/cjs/signatures/SetterRatifierUtils.d.ts +31 -0
- package/lib/cjs/signatures/SetterRatifierUtils.js +38 -0
- package/lib/cjs/signatures/Tree.js +13 -3
- package/lib/cjs/signatures/TreeUtils.d.ts +6 -3
- package/lib/cjs/signatures/TreeUtils.js +35 -13
- package/lib/cjs/signatures/eip712.d.ts +22 -0
- package/lib/cjs/signatures/eip712.js +26 -0
- package/lib/cjs/signatures/index.d.ts +1 -0
- package/lib/cjs/signatures/index.js +1 -0
- package/lib/esm/api/MidnightApi.js +68 -4
- package/lib/esm/api/helpers.d.ts +11 -1
- package/lib/esm/api/helpers.js +48 -2
- package/lib/esm/errors.d.ts +15 -0
- package/lib/esm/errors.js +19 -0
- package/lib/esm/fetch/Market.js +38 -3
- package/lib/esm/fetch/Position.d.ts +1 -1
- package/lib/esm/fetch/Position.js +5 -1
- package/lib/esm/fetch/utils.d.ts +1 -1
- package/lib/esm/fetch/utils.js +9 -2
- package/lib/esm/index.d.ts +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/market/index.d.ts +0 -2
- package/lib/esm/market/index.js +0 -2
- package/lib/esm/offers/Offer.d.ts +1 -1
- package/lib/esm/offers/OfferChainUtils.d.ts +179 -0
- package/lib/esm/offers/OfferChainUtils.js +443 -0
- package/lib/esm/offers/OfferUtils.d.ts +5 -5
- package/lib/esm/offers/OfferUtils.js +28 -15
- package/lib/esm/offers/index.d.ts +1 -0
- package/lib/esm/offers/index.js +1 -0
- package/lib/esm/{market → position}/Position.d.ts +83 -6
- package/lib/esm/{market → position}/Position.js +83 -5
- package/lib/{cjs/market → esm/position}/PositionUtils.d.ts +26 -1
- package/lib/esm/{market → position}/PositionUtils.js +27 -0
- package/lib/esm/position/index.d.ts +2 -0
- package/lib/esm/position/index.js +2 -0
- package/lib/esm/signatures/EcrecoverRatifierUtils.d.ts +118 -0
- package/lib/esm/signatures/EcrecoverRatifierUtils.js +151 -3
- package/lib/esm/signatures/Group.js +5 -3
- package/lib/esm/signatures/GroupUtils.d.ts +48 -0
- package/lib/esm/signatures/GroupUtils.js +51 -0
- package/lib/esm/signatures/RatifierUtils.js +16 -4
- package/lib/esm/signatures/SetterRatifierUtils.d.ts +31 -0
- package/lib/esm/signatures/SetterRatifierUtils.js +38 -0
- package/lib/esm/signatures/Tree.js +14 -4
- package/lib/esm/signatures/TreeUtils.d.ts +6 -3
- package/lib/esm/signatures/TreeUtils.js +35 -13
- package/lib/esm/signatures/eip712.d.ts +22 -0
- package/lib/esm/signatures/eip712.js +22 -0
- package/lib/esm/signatures/index.d.ts +1 -0
- package/lib/esm/signatures/index.js +1 -0
- package/package.json +8 -7
- package/src/index.ts +0 -19
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { MathLib } from "@morpho-org/morpho-ts";
|
|
2
|
+
import { InvalidMidnightApiResponseError } from "../errors.js";
|
|
3
|
+
import { TickLib } from "../math/index.js";
|
|
1
4
|
import { Payload } from "../signatures/Payload.js";
|
|
2
|
-
import { buildBookPath, mapBookMarket,
|
|
5
|
+
import { buildBookPath, mapBookMarket, mapBoundTakeableOffers, mapPriceLevel, parseValidationResponse, requestMidnightApi, } from "./helpers.js";
|
|
3
6
|
/**
|
|
4
7
|
* Midnight API client and stateless helper surface for books and mempool data.
|
|
5
8
|
*
|
|
@@ -217,7 +220,10 @@ export class MidnightApi {
|
|
|
217
220
|
}),
|
|
218
221
|
});
|
|
219
222
|
return {
|
|
220
|
-
data: response.data
|
|
223
|
+
data: mapBoundTakeableOffers(response.data, {
|
|
224
|
+
marketId: input.marketId,
|
|
225
|
+
side: input.side,
|
|
226
|
+
}),
|
|
221
227
|
};
|
|
222
228
|
}
|
|
223
229
|
/**
|
|
@@ -267,7 +273,61 @@ export class MidnightApi {
|
|
|
267
273
|
assets: input.assets,
|
|
268
274
|
},
|
|
269
275
|
});
|
|
270
|
-
const takeableOffers = response.data.takeable_offers
|
|
276
|
+
const takeableOffers = mapBoundTakeableOffers(response.data.takeable_offers, {
|
|
277
|
+
marketId: input.marketId,
|
|
278
|
+
side: input.side,
|
|
279
|
+
});
|
|
280
|
+
const averageWorstPrice = "averageWorstPrice" in input && input.averageWorstPrice != null
|
|
281
|
+
? input.averageWorstPrice
|
|
282
|
+
: "slippage" in input && input.slippage != null
|
|
283
|
+
? response.data.average_worst_price
|
|
284
|
+
: undefined;
|
|
285
|
+
if (averageWorstPrice != null) {
|
|
286
|
+
const guard = BigInt(averageWorstPrice);
|
|
287
|
+
let filledUnits = 0n;
|
|
288
|
+
let weightedPrice = 0n;
|
|
289
|
+
if ("units" in input && input.units != null) {
|
|
290
|
+
let remainingUnits = BigInt(input.units);
|
|
291
|
+
for (const take of takeableOffers) {
|
|
292
|
+
if (remainingUnits === 0n)
|
|
293
|
+
break;
|
|
294
|
+
const price = TickLib.tickToPrice(take.offer.tick);
|
|
295
|
+
const filled = take.units < remainingUnits ? take.units : remainingUnits;
|
|
296
|
+
filledUnits += filled;
|
|
297
|
+
weightedPrice += filled * price;
|
|
298
|
+
remainingUnits -= filled;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
let remainingAssets = BigInt(input.assets);
|
|
303
|
+
for (const take of takeableOffers) {
|
|
304
|
+
if (remainingAssets === 0n)
|
|
305
|
+
break;
|
|
306
|
+
const price = TickLib.tickToPrice(take.offer.tick);
|
|
307
|
+
const takeAssets = input.side === "asks"
|
|
308
|
+
? MathLib.mulDivUp(take.units, price, MathLib.WAD)
|
|
309
|
+
: MathLib.mulDivDown(take.units, price, MathLib.WAD);
|
|
310
|
+
if (takeAssets === 0n)
|
|
311
|
+
continue;
|
|
312
|
+
const fillsEntireTake = takeAssets <= remainingAssets;
|
|
313
|
+
const filled = fillsEntireTake
|
|
314
|
+
? take.units
|
|
315
|
+
: MathLib.mulDiv(remainingAssets, MathLib.WAD, price, input.side === "asks" ? "Down" : "Up");
|
|
316
|
+
if (filled === 0n)
|
|
317
|
+
continue;
|
|
318
|
+
filledUnits += filled;
|
|
319
|
+
weightedPrice += filled * price;
|
|
320
|
+
remainingAssets = fillsEntireTake ? remainingAssets - takeAssets : 0n;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (filledUnits > 0n) {
|
|
324
|
+
const averagePrice = MathLib.mulDivDown(weightedPrice, 1n, filledUnits);
|
|
325
|
+
const violatesGuard = input.side === "asks" ? averagePrice > guard : averagePrice < guard;
|
|
326
|
+
if (violatesGuard) {
|
|
327
|
+
throw new InvalidMidnightApiResponseError(`Midnight API quote takeable offers imply average price "${averagePrice}" outside average_worst_price guard "${guard}".`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
271
331
|
return {
|
|
272
332
|
data: {
|
|
273
333
|
averageBestPrice: response.data.average_best_price,
|
|
@@ -320,7 +380,11 @@ export class MidnightApi {
|
|
|
320
380
|
});
|
|
321
381
|
return {
|
|
322
382
|
cursor: response.cursor,
|
|
323
|
-
data: response.data
|
|
383
|
+
data: mapBoundTakeableOffers(response.data, {
|
|
384
|
+
maker: input.maker,
|
|
385
|
+
marketIds: input.marketIds,
|
|
386
|
+
groups: input.groups,
|
|
387
|
+
}),
|
|
324
388
|
};
|
|
325
389
|
}
|
|
326
390
|
/**
|
package/lib/esm/api/helpers.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Address, type Hash } from "viem";
|
|
2
2
|
import type { ApiBookMarketResponse, ApiCollateralResponse, ApiPriceLevelResponse, ApiRequestParams, ApiTakeableOfferResponse, MempoolPayloadValidationResult, MidnightApiBookMarket, MidnightApiBookSide, MidnightApiCollateral, MidnightApiPriceLevel, MidnightApiTake } from "./types.js";
|
|
3
3
|
/** @internal Sends one Midnight API request and maps non-2xx responses to SDK errors. */
|
|
4
4
|
export declare function requestMidnightApi<Response = unknown>(params: ApiRequestParams): Promise<Response>;
|
|
@@ -16,5 +16,15 @@ export declare function mapCollateral(collateral: ApiCollateralResponse): Midnig
|
|
|
16
16
|
export declare function mapPriceLevel(level: ApiPriceLevelResponse): MidnightApiPriceLevel;
|
|
17
17
|
/** @internal Maps a takeable-offer API payload to the SDK response shape. */
|
|
18
18
|
export declare function mapTakeableOffer(takeableOffer: ApiTakeableOfferResponse): MidnightApiTake;
|
|
19
|
+
/** @internal API response context required for a takeable offer to be executable for the caller's request. */
|
|
20
|
+
export interface TakeableOfferContext {
|
|
21
|
+
readonly marketId?: Hash;
|
|
22
|
+
readonly side?: MidnightApiBookSide;
|
|
23
|
+
readonly maker?: Address;
|
|
24
|
+
readonly marketIds?: readonly Hash[];
|
|
25
|
+
readonly groups?: readonly Hash[];
|
|
26
|
+
}
|
|
27
|
+
/** @internal Maps, validates, and best-price-sorts API takeable offers. */
|
|
28
|
+
export declare function mapBoundTakeableOffers(takeableOffers: readonly ApiTakeableOfferResponse[], context: TakeableOfferContext): MidnightApiTake[];
|
|
19
29
|
/** @internal Parses the mempool validation response envelope. */
|
|
20
30
|
export declare function parseValidationResponse(response: unknown): MempoolPayloadValidationResult;
|
package/lib/esm/api/helpers.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { BLUE_API_BASE_URL } from "@morpho-org/morpho-ts";
|
|
1
|
+
import { BLUE_API_BASE_URL, isHexEqual } from "@morpho-org/morpho-ts";
|
|
2
|
+
import { isAddressEqual } from "viem";
|
|
2
3
|
import { InvalidMidnightApiResponseError, MidnightApiError, } from "../errors.js";
|
|
4
|
+
import { MarketUtils } from "../market/index.js";
|
|
3
5
|
const DEFAULT_MIDNIGHT_API_URL = new URL("/v0/midnight", BLUE_API_BASE_URL);
|
|
4
6
|
// Keep this literal in source so the browser ESM build does not import package.json
|
|
5
7
|
// through Node-only createRequire/module shims.
|
|
6
|
-
const MIDNIGHT_SDK_VERSION = "0.1
|
|
8
|
+
const MIDNIGHT_SDK_VERSION = "1.0.1";
|
|
7
9
|
/** @internal Sends one Midnight API request and maps non-2xx responses to SDK errors. */
|
|
8
10
|
export async function requestMidnightApi(params) {
|
|
9
11
|
const baseUrl = new URL(params.baseUrl ?? DEFAULT_MIDNIGHT_API_URL);
|
|
@@ -153,6 +155,50 @@ export function mapTakeableOffer(takeableOffer) {
|
|
|
153
155
|
ratifierData: takeableOffer.ratifier_data,
|
|
154
156
|
};
|
|
155
157
|
}
|
|
158
|
+
/** @internal Maps, validates, and best-price-sorts API takeable offers. */
|
|
159
|
+
export function mapBoundTakeableOffers(takeableOffers, context) {
|
|
160
|
+
const mapped = takeableOffers.map((takeableOffer) => {
|
|
161
|
+
const take = mapTakeableOffer(takeableOffer);
|
|
162
|
+
const embeddedMarketId = MarketUtils.toId(take.offer.market);
|
|
163
|
+
if (!isHexEqual(embeddedMarketId, take.marketId)) {
|
|
164
|
+
throw new InvalidMidnightApiResponseError(`Midnight API takeable offer market_id "${take.marketId}" does not match embedded offer market "${embeddedMarketId}".`);
|
|
165
|
+
}
|
|
166
|
+
if (context.marketId != null &&
|
|
167
|
+
!isHexEqual(take.marketId, context.marketId)) {
|
|
168
|
+
throw new InvalidMidnightApiResponseError(`Midnight API takeable offer market_id "${take.marketId}" does not match requested market "${context.marketId}".`);
|
|
169
|
+
}
|
|
170
|
+
if (context.marketIds != null &&
|
|
171
|
+
context.marketIds.length > 0 &&
|
|
172
|
+
!context.marketIds.some((marketId) => isHexEqual(marketId, take.marketId))) {
|
|
173
|
+
throw new InvalidMidnightApiResponseError(`Midnight API takeable offer market_id "${take.marketId}" is outside the requested market_ids filter.`);
|
|
174
|
+
}
|
|
175
|
+
if (context.side != null) {
|
|
176
|
+
const expectedBuy = context.side === "bids";
|
|
177
|
+
if (take.offer.buy !== expectedBuy) {
|
|
178
|
+
throw new InvalidMidnightApiResponseError(`Midnight API ${context.side} takeable offer returned offer.buy "${take.offer.buy}".`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (context.maker != null &&
|
|
182
|
+
!isAddressEqual(take.offer.maker, context.maker)) {
|
|
183
|
+
throw new InvalidMidnightApiResponseError(`Midnight API takeable offer maker "${take.offer.maker}" does not match requested maker "${context.maker}".`);
|
|
184
|
+
}
|
|
185
|
+
if (context.groups != null &&
|
|
186
|
+
context.groups.length > 0 &&
|
|
187
|
+
!context.groups.some((group) => isHexEqual(group, take.offer.group))) {
|
|
188
|
+
throw new InvalidMidnightApiResponseError(`Midnight API takeable offer group "${take.offer.group}" is outside the requested groups filter.`);
|
|
189
|
+
}
|
|
190
|
+
return take;
|
|
191
|
+
});
|
|
192
|
+
if (context.side == null)
|
|
193
|
+
return mapped;
|
|
194
|
+
return [...mapped].sort((a, b) => {
|
|
195
|
+
if (a.offer.tick === b.offer.tick)
|
|
196
|
+
return 0;
|
|
197
|
+
if (context.side === "asks")
|
|
198
|
+
return a.offer.tick < b.offer.tick ? -1 : 1;
|
|
199
|
+
return a.offer.tick > b.offer.tick ? -1 : 1;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
156
202
|
/** @internal Parses the mempool validation response envelope. */
|
|
157
203
|
export function parseValidationResponse(response) {
|
|
158
204
|
const parsedResponse = requireRecord(response, "validation response");
|
package/lib/esm/errors.d.ts
CHANGED
|
@@ -277,6 +277,21 @@ export declare class InvalidTypedDataSignatureError extends Error {
|
|
|
277
277
|
readonly signer: Address;
|
|
278
278
|
constructor(signer: Address, cause?: unknown);
|
|
279
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Thrown when decoded Ecrecover ratifier data contains a non-canonical signature `v` value.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```ts
|
|
285
|
+
* import { InvalidEcrecoverSignatureVError } from "@morpho-org/midnight-sdk";
|
|
286
|
+
*
|
|
287
|
+
* throw new InvalidEcrecoverSignatureVError(29);
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
export declare class InvalidEcrecoverSignatureVError extends Error {
|
|
291
|
+
/** Invalid ECDSA recovery id. */
|
|
292
|
+
readonly v: number;
|
|
293
|
+
constructor(v: number);
|
|
294
|
+
}
|
|
280
295
|
/**
|
|
281
296
|
* Thrown when the Midnight API returns a non-2xx response.
|
|
282
297
|
*
|
package/lib/esm/errors.js
CHANGED
|
@@ -325,6 +325,25 @@ export class InvalidTypedDataSignatureError extends Error {
|
|
|
325
325
|
this.signer = signer;
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Thrown when decoded Ecrecover ratifier data contains a non-canonical signature `v` value.
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```ts
|
|
333
|
+
* import { InvalidEcrecoverSignatureVError } from "@morpho-org/midnight-sdk";
|
|
334
|
+
*
|
|
335
|
+
* throw new InvalidEcrecoverSignatureVError(29);
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
export class InvalidEcrecoverSignatureVError extends Error {
|
|
339
|
+
/** Invalid ECDSA recovery id. */
|
|
340
|
+
v;
|
|
341
|
+
constructor(v) {
|
|
342
|
+
super(`Ecrecover signature v "${v}" is invalid. Use canonical Ethereum v 27 or 28.`);
|
|
343
|
+
this.name = "InvalidEcrecoverSignatureVError";
|
|
344
|
+
this.v = v;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
328
347
|
/**
|
|
329
348
|
* Thrown when the Midnight API returns a non-2xx response.
|
|
330
349
|
*
|
package/lib/esm/fetch/Market.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getChainAddress } from "@morpho-org/morpho-ts";
|
|
2
2
|
import { readContract } from "viem/actions";
|
|
3
3
|
import { midnightAbi } from "../abis.js";
|
|
4
|
-
import {
|
|
4
|
+
import { InvalidMarketParameterError } from "../errors.js";
|
|
5
|
+
import { Market, MarketParams, MarketUtils } from "../market/index.js";
|
|
5
6
|
import { callParameters, resolveChainId } from "./utils.js";
|
|
6
7
|
/**
|
|
7
8
|
* Fetches immutable market params by id.
|
|
@@ -41,7 +42,11 @@ export async function fetchMarketParams(client, params) {
|
|
|
41
42
|
functionName: "toMarket",
|
|
42
43
|
args: [params.marketId],
|
|
43
44
|
});
|
|
44
|
-
return
|
|
45
|
+
return normalizeFetchedMarketParams(market, {
|
|
46
|
+
marketId: params.marketId,
|
|
47
|
+
chainId,
|
|
48
|
+
midnight,
|
|
49
|
+
});
|
|
45
50
|
}
|
|
46
51
|
/**
|
|
47
52
|
* Fetches a hydrated market by id.
|
|
@@ -92,7 +97,11 @@ export async function fetchMarket(client, params) {
|
|
|
92
97
|
]);
|
|
93
98
|
const [totalUnits, lossFactor, withdrawable, continuousFeeCredit, settlementFeeCbps0, settlementFeeCbps1, settlementFeeCbps2, settlementFeeCbps3, settlementFeeCbps4, settlementFeeCbps5, settlementFeeCbps6, continuousFee, tickSpacing,] = state;
|
|
94
99
|
return new Market({
|
|
95
|
-
params:
|
|
100
|
+
params: normalizeFetchedMarketParams(market, {
|
|
101
|
+
marketId: params.marketId,
|
|
102
|
+
chainId,
|
|
103
|
+
midnight,
|
|
104
|
+
}),
|
|
96
105
|
totalUnits,
|
|
97
106
|
lossFactor,
|
|
98
107
|
withdrawable,
|
|
@@ -110,3 +119,29 @@ export async function fetchMarket(client, params) {
|
|
|
110
119
|
tickSpacing,
|
|
111
120
|
});
|
|
112
121
|
}
|
|
122
|
+
function normalizeFetchedMarketParams(market, expected) {
|
|
123
|
+
const marketParams = new MarketParams(market);
|
|
124
|
+
if (marketParams.chainId !== BigInt(expected.chainId)) {
|
|
125
|
+
throw new InvalidMarketParameterError({
|
|
126
|
+
parameter: "chainId",
|
|
127
|
+
value: marketParams.chainId,
|
|
128
|
+
instruction: `Fetched market must use client chain id "${expected.chainId}".`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (marketParams.midnight.toLowerCase() !== expected.midnight.toLowerCase()) {
|
|
132
|
+
throw new InvalidMarketParameterError({
|
|
133
|
+
parameter: "midnight",
|
|
134
|
+
value: marketParams.midnight,
|
|
135
|
+
instruction: `Fetched market must use Midnight contract "${expected.midnight}".`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
const actualMarketId = MarketUtils.toId(marketParams);
|
|
139
|
+
if (actualMarketId.toLowerCase() !== expected.marketId.toLowerCase()) {
|
|
140
|
+
throw new InvalidMarketParameterError({
|
|
141
|
+
parameter: "marketId",
|
|
142
|
+
value: actualMarketId,
|
|
143
|
+
instruction: `Fetched market must match requested market id "${expected.marketId}".`,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return marketParams;
|
|
147
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Address, Client, Hash } from "viem";
|
|
2
|
-
import { AccrualPosition, Position } from "../
|
|
2
|
+
import { AccrualPosition, Position } from "../position/index.js";
|
|
3
3
|
import type { MidnightFetchParams } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* Fetches a Midnight position by id and user.
|
|
@@ -2,7 +2,7 @@ import { getChainAddress } from "@morpho-org/morpho-ts";
|
|
|
2
2
|
import { readContract } from "viem/actions";
|
|
3
3
|
import { midnightAbi } from "../abis.js";
|
|
4
4
|
import { MAX_COLLATERALS } from "../constants.js";
|
|
5
|
-
import { AccrualPosition, Position } from "../
|
|
5
|
+
import { AccrualPosition, Position } from "../position/index.js";
|
|
6
6
|
import { abi as getPositionAbi, code as getPositionCode, } from "../queries/GetPosition.js";
|
|
7
7
|
import { fetchMarket } from "./Market.js";
|
|
8
8
|
import { callParameters, resolveChainId, shouldUseDeployless, } from "./utils.js";
|
|
@@ -60,6 +60,8 @@ export async function fetchPosition(client, params) {
|
|
|
60
60
|
});
|
|
61
61
|
const collateral = position.collateral;
|
|
62
62
|
return new Position({
|
|
63
|
+
user: params.user,
|
|
64
|
+
marketId: params.marketId,
|
|
63
65
|
credit: position.credit,
|
|
64
66
|
pendingFee: position.pendingFee,
|
|
65
67
|
lastLossFactor: position.lastLossFactor,
|
|
@@ -92,6 +94,8 @@ export async function fetchPosition(client, params) {
|
|
|
92
94
|
});
|
|
93
95
|
}));
|
|
94
96
|
return new Position({
|
|
97
|
+
user: params.user,
|
|
98
|
+
marketId: params.marketId,
|
|
95
99
|
credit: position[0],
|
|
96
100
|
pendingFee: position[1],
|
|
97
101
|
lastLossFactor: position[2],
|
package/lib/esm/fetch/utils.d.ts
CHANGED
|
@@ -4,5 +4,5 @@ import type { DeploylessFetchParameters, MidnightCallParameters } from "./types.
|
|
|
4
4
|
export declare const callParameters: (params: MidnightCallParameters) => MidnightCallParameters;
|
|
5
5
|
/** @internal Returns the configured deployless mode, defaulting to enabled. */
|
|
6
6
|
export declare const shouldUseDeployless: (params: DeploylessFetchParameters) => boolean | "force";
|
|
7
|
-
/** @internal Returns the
|
|
7
|
+
/** @internal Returns the RPC chain id and rejects configured-chain drift. */
|
|
8
8
|
export declare const resolveChainId: (client: Client) => Promise<number>;
|
package/lib/esm/fetch/utils.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getChainId } from "viem/actions";
|
|
2
|
+
import { ChainIdMismatchError } from "../errors.js";
|
|
2
3
|
/** @internal Extracts viem call parameters from Midnight fetch inputs. */
|
|
3
4
|
export const callParameters = (params) => ({
|
|
4
5
|
account: params.account,
|
|
@@ -8,5 +9,11 @@ export const callParameters = (params) => ({
|
|
|
8
9
|
});
|
|
9
10
|
/** @internal Returns the configured deployless mode, defaulting to enabled. */
|
|
10
11
|
export const shouldUseDeployless = (params) => params.deployless ?? true;
|
|
11
|
-
/** @internal Returns the
|
|
12
|
-
export const resolveChainId = async (client) =>
|
|
12
|
+
/** @internal Returns the RPC chain id and rejects configured-chain drift. */
|
|
13
|
+
export const resolveChainId = async (client) => {
|
|
14
|
+
const rpcChainId = await getChainId(client);
|
|
15
|
+
if (client.chain?.id != null && client.chain.id !== rpcChainId) {
|
|
16
|
+
throw new ChainIdMismatchError(client.chain.id, BigInt(rpcChainId));
|
|
17
|
+
}
|
|
18
|
+
return rpcChainId;
|
|
19
|
+
};
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./fetch/index.js";
|
|
|
5
5
|
export * from "./market/index.js";
|
|
6
6
|
export * from "./math/index.js";
|
|
7
7
|
export * from "./offers/index.js";
|
|
8
|
+
export * from "./position/index.js";
|
|
8
9
|
export * from "./signatures/index.js";
|
|
9
10
|
/**
|
|
10
11
|
* Namespace for Midnight mempool payload utilities.
|
package/lib/esm/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./fetch/index.js";
|
|
|
5
5
|
export * from "./market/index.js";
|
|
6
6
|
export * from "./math/index.js";
|
|
7
7
|
export * from "./offers/index.js";
|
|
8
|
+
export * from "./position/index.js";
|
|
8
9
|
export * from "./signatures/index.js";
|
|
9
10
|
/**
|
|
10
11
|
* Namespace for Midnight mempool payload utilities.
|
package/lib/esm/market/index.js
CHANGED
|
@@ -721,7 +721,7 @@ export interface BuildOfferParams {
|
|
|
721
721
|
readonly maxUnits?: BigIntish;
|
|
722
722
|
/** Maximum buyer or seller assets; defaults to zero. Exactly one of `maxUnits` and `maxAssets` must be non-zero. */
|
|
723
723
|
readonly maxAssets?: BigIntish;
|
|
724
|
-
/** Maximum market continuous fee accepted by this offer; defaults to
|
|
724
|
+
/** Maximum market continuous fee accepted by this offer; defaults to zero. */
|
|
725
725
|
readonly continuousFeeCap?: BigIntish;
|
|
726
726
|
/** Offer start timestamp; defaults to zero. */
|
|
727
727
|
readonly start?: BigIntish;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { type BigIntish } from "@morpho-org/morpho-ts";
|
|
2
|
+
/** One time-bounded offer leg to convert into a Midnight `Offer.create` input. */
|
|
3
|
+
export interface FixedRateOfferChainLeg {
|
|
4
|
+
/** Spacing-aligned Midnight tick for this offer. */
|
|
5
|
+
readonly tick: bigint;
|
|
6
|
+
/** First timestamp at which the offer leg is active. */
|
|
7
|
+
readonly startTimestamp: bigint;
|
|
8
|
+
/** Last timestamp at which the offer leg is active. */
|
|
9
|
+
readonly expiryTimestamp: bigint;
|
|
10
|
+
}
|
|
11
|
+
/** Parameters shared by lend-only and borrow-only fixed-rate offer-chain builders. */
|
|
12
|
+
export interface BuildFixedRateOfferChainParams {
|
|
13
|
+
/** Target yearly fixed rate as a decimal number, for example `0.05` for 5%. */
|
|
14
|
+
readonly targetRate: number;
|
|
15
|
+
/** Tick spacing enforced by the market. */
|
|
16
|
+
readonly tickSpacing: BigIntish;
|
|
17
|
+
/** Market maturity timestamp in seconds. */
|
|
18
|
+
readonly maturityTimestamp: BigIntish;
|
|
19
|
+
/** First timestamp the chain should cover. */
|
|
20
|
+
readonly chainStartTimestamp: BigIntish;
|
|
21
|
+
/** Latest timestamp the chain may cover. */
|
|
22
|
+
readonly chainEndTimestamp: BigIntish;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Utilities for building time-bounded Midnight offer chains.
|
|
26
|
+
*
|
|
27
|
+
* A Midnight offer has one fixed price, so its displayed yearly rate changes
|
|
28
|
+
* as maturity approaches. The markets app uses these helpers when a maker wants
|
|
29
|
+
* to post, for example, a 5% order for several months: the app builds several
|
|
30
|
+
* adjacent offers sharing the same reserve, each with a different tick and time
|
|
31
|
+
* window, so the order reviews and renders as a stable 5% order across the
|
|
32
|
+
* selected window instead of drifting upward as time passes.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { OfferChainUtils } from "@morpho-org/midnight-sdk";
|
|
37
|
+
*
|
|
38
|
+
* const legs = OfferChainUtils.buildLendFixedRateOfferChain({
|
|
39
|
+
* targetRate: 0.05,
|
|
40
|
+
* tickSpacing: 4n,
|
|
41
|
+
* maturityTimestamp: 1_798_761_600n,
|
|
42
|
+
* chainStartTimestamp: 1_767_225_600n,
|
|
43
|
+
* chainEndTimestamp: 1_791_153_600n,
|
|
44
|
+
* });
|
|
45
|
+
* console.log(legs[0]?.tick);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare namespace OfferChainUtils {
|
|
49
|
+
/**
|
|
50
|
+
* Maximum fraction of the initial time-to-maturity covered by a fixed-rate offer chain.
|
|
51
|
+
*
|
|
52
|
+
* Use this cap before displaying an expiry picker for a fixed-rate maker
|
|
53
|
+
* order. The last part of the maturity window is intentionally excluded
|
|
54
|
+
* because tiny time changes cause large displayed-rate drift.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* import { OfferChainUtils } from "@morpho-org/midnight-sdk";
|
|
59
|
+
*
|
|
60
|
+
* console.log(OfferChainUtils.MAX_EXPIRY_TTM_FRACTION);
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
const MAX_EXPIRY_TTM_FRACTION = 0.75;
|
|
64
|
+
/**
|
|
65
|
+
* Builds borrow-side sell-offer legs that approximate one fixed maker rate over time.
|
|
66
|
+
*
|
|
67
|
+
* Use before a make-borrow or supply-collateral-and-make-borrow flow when a
|
|
68
|
+
* borrower wants one displayed fixed borrow rate across a longer window. Map
|
|
69
|
+
* the returned legs to `Offer.create({ buy: false, ... })`, then group and
|
|
70
|
+
* submit them through the maker flow.
|
|
71
|
+
*
|
|
72
|
+
* Borrow chains read their target rate at each leg's expiry edge. If a leg
|
|
73
|
+
* would need to extend past `chainEndTimestamp`, it is dropped rather than
|
|
74
|
+
* clamped so the expiry edge remains the recoverable target-rate edge.
|
|
75
|
+
*
|
|
76
|
+
* @param params - Fixed-rate offer-chain parameters without a side field.
|
|
77
|
+
* @param params.targetRate - Target yearly fixed rate expressed as a decimal number.
|
|
78
|
+
* @param params.tickSpacing - Tick spacing enforced by the market.
|
|
79
|
+
* @param params.maturityTimestamp - Market maturity timestamp in seconds.
|
|
80
|
+
* @param params.chainStartTimestamp - First timestamp the chain should cover.
|
|
81
|
+
* @param params.chainEndTimestamp - Latest timestamp the chain may cover.
|
|
82
|
+
* @returns Borrow-side offer legs that can be mapped to `Offer.create({ buy: false, ... })`.
|
|
83
|
+
* @throws {InvalidOfferParameterError} when an input is invalid or the end timestamp exceeds the supported horizon.
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* import { Offer, OfferChainUtils } from "@morpho-org/midnight-sdk";
|
|
87
|
+
*
|
|
88
|
+
* const legs = OfferChainUtils.buildBorrowFixedRateOfferChain({
|
|
89
|
+
* targetRate: 0.08,
|
|
90
|
+
* tickSpacing: market.tickSpacing,
|
|
91
|
+
* maturityTimestamp: market.params.maturity,
|
|
92
|
+
* chainStartTimestamp: now,
|
|
93
|
+
* chainEndTimestamp: expiry,
|
|
94
|
+
* });
|
|
95
|
+
* const offers = legs.map((leg) =>
|
|
96
|
+
* Offer.create({
|
|
97
|
+
* ...baseOffer,
|
|
98
|
+
* buy: false,
|
|
99
|
+
* tick: leg.tick,
|
|
100
|
+
* start: leg.startTimestamp,
|
|
101
|
+
* expiry: leg.expiryTimestamp,
|
|
102
|
+
* }),
|
|
103
|
+
* );
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
function buildBorrowFixedRateOfferChain(params: BuildFixedRateOfferChainParams): readonly FixedRateOfferChainLeg[];
|
|
107
|
+
/**
|
|
108
|
+
* Builds lend-side buy-offer legs that approximate one fixed maker rate over time.
|
|
109
|
+
*
|
|
110
|
+
* Use before a make-lend flow when a lender wants one displayed fixed lend
|
|
111
|
+
* rate across a longer window. Map the returned legs to
|
|
112
|
+
* `Offer.create({ buy: true, ... })`, then group and submit them through the
|
|
113
|
+
* maker flow.
|
|
114
|
+
*
|
|
115
|
+
* Lend chains read their target rate at each leg's start edge. The first leg
|
|
116
|
+
* may start before `chainStartTimestamp`; clamping that edge would move the
|
|
117
|
+
* recoverable display rate below the maker's target.
|
|
118
|
+
*
|
|
119
|
+
* @param params - Fixed-rate offer-chain parameters without a side field.
|
|
120
|
+
* @param params.targetRate - Target yearly fixed rate expressed as a decimal number.
|
|
121
|
+
* @param params.tickSpacing - Tick spacing enforced by the market.
|
|
122
|
+
* @param params.maturityTimestamp - Market maturity timestamp in seconds.
|
|
123
|
+
* @param params.chainStartTimestamp - First timestamp the chain should cover.
|
|
124
|
+
* @param params.chainEndTimestamp - Latest timestamp the chain may cover.
|
|
125
|
+
* @returns Lend-side offer legs that can be mapped to `Offer.create({ buy: true, ... })`.
|
|
126
|
+
* @throws {InvalidOfferParameterError} when an input is invalid or the end timestamp exceeds the supported horizon.
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* import { Offer, OfferChainUtils } from "@morpho-org/midnight-sdk";
|
|
130
|
+
*
|
|
131
|
+
* const legs = OfferChainUtils.buildLendFixedRateOfferChain({
|
|
132
|
+
* targetRate: 0.05,
|
|
133
|
+
* tickSpacing: market.tickSpacing,
|
|
134
|
+
* maturityTimestamp: market.params.maturity,
|
|
135
|
+
* chainStartTimestamp: now,
|
|
136
|
+
* chainEndTimestamp: expiry,
|
|
137
|
+
* });
|
|
138
|
+
* const offers = legs.map((leg) =>
|
|
139
|
+
* Offer.create({
|
|
140
|
+
* ...baseOffer,
|
|
141
|
+
* buy: true,
|
|
142
|
+
* tick: leg.tick,
|
|
143
|
+
* start: leg.startTimestamp,
|
|
144
|
+
* expiry: leg.expiryTimestamp,
|
|
145
|
+
* }),
|
|
146
|
+
* );
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
function buildLendFixedRateOfferChain(params: BuildFixedRateOfferChainParams): readonly FixedRateOfferChainLeg[];
|
|
150
|
+
/**
|
|
151
|
+
* Returns the latest supported end timestamp for a fixed-rate offer chain.
|
|
152
|
+
*
|
|
153
|
+
* Chains intentionally stop before the final part of the maturity window
|
|
154
|
+
* because rate sensitivity accelerates near maturity and would require too
|
|
155
|
+
* many short-lived offers for a stable maker quote.
|
|
156
|
+
* Call this when constraining a make-offer expiry selector, then pass the
|
|
157
|
+
* selected timestamp to the lend-only or borrow-only chain builder.
|
|
158
|
+
*
|
|
159
|
+
* @param params - Maturity and chain-start timestamps.
|
|
160
|
+
* @param params.maturityTimestamp - Market maturity timestamp in seconds.
|
|
161
|
+
* @param params.chainStartTimestamp - First timestamp the chain should cover.
|
|
162
|
+
* @returns Latest accepted chain end timestamp.
|
|
163
|
+
* @throws {InvalidOfferParameterError} when a timestamp is invalid.
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* import { OfferChainUtils } from "@morpho-org/midnight-sdk";
|
|
167
|
+
*
|
|
168
|
+
* const maxExpiry = OfferChainUtils.getMaxFixedRateOfferChainEndTimestamp({
|
|
169
|
+
* maturityTimestamp: market.params.maturity,
|
|
170
|
+
* chainStartTimestamp: now,
|
|
171
|
+
* });
|
|
172
|
+
* console.log(maxExpiry);
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
function getMaxFixedRateOfferChainEndTimestamp(params: {
|
|
176
|
+
readonly maturityTimestamp: BigIntish;
|
|
177
|
+
readonly chainStartTimestamp: BigIntish;
|
|
178
|
+
}): bigint;
|
|
179
|
+
}
|