@morpho-org/midnight-sdk 2.1.0-next.0 → 2.1.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.
@@ -1,2 +1,2 @@
1
1
  /** @internal Installed Midnight SDK package version. */
2
- export declare const MIDNIGHT_SDK_VERSION = "2.1.0-next.0";
2
+ export declare const MIDNIGHT_SDK_VERSION = "2.1.0";
@@ -3,4 +3,4 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.MIDNIGHT_SDK_VERSION = void 0;
5
5
  /** @internal Installed Midnight SDK package version. */
6
- exports.MIDNIGHT_SDK_VERSION = "2.1.0-next.0";
6
+ exports.MIDNIGHT_SDK_VERSION = "2.1.0";
@@ -1,4 +1,3 @@
1
- import { InvalidPositionAccrualStateError as SharedInvalidPositionAccrualStateError, InvalidPositionAccrualTimestampError as SharedInvalidPositionAccrualTimestampError, InvalidPositionLossFactorError as SharedInvalidPositionLossFactorError } from "@morpho-org/morpho-ts";
2
1
  import type { Address } from "viem";
3
2
  import type { MempoolPayloadValidationIssue } from "./api/types.js";
4
3
  /**
@@ -71,27 +70,42 @@ export declare class SettlementFeeExceedsPriceError extends Error {
71
70
  /**
72
71
  * Thrown when a local position accrual is requested for an impossible timestamp.
73
72
  *
74
- * @deprecated Import `InvalidPositionAccrualTimestampError` from `@morpho-org/morpho-ts`. Removed in the next major.
73
+ * @example
74
+ * ```ts
75
+ * import { InvalidPositionAccrualTimestampError } from "@morpho-org/midnight-sdk";
76
+ *
77
+ * throw new InvalidPositionAccrualTimestampError(1n, 2n);
78
+ * ```
75
79
  */
76
- export declare const InvalidPositionAccrualTimestampError: typeof SharedInvalidPositionAccrualTimestampError;
77
- /** @deprecated Import `InvalidPositionAccrualTimestampError` from `@morpho-org/morpho-ts`. Removed in the next major. */
78
- export type InvalidPositionAccrualTimestampError = SharedInvalidPositionAccrualTimestampError;
80
+ export declare class InvalidPositionAccrualTimestampError extends Error {
81
+ constructor(timestamp: bigint, lastAccrual: bigint);
82
+ }
79
83
  /**
80
84
  * Thrown when a local position accrual sees a market loss factor older than the position.
81
85
  *
82
- * @deprecated Import `InvalidPositionLossFactorError` from `@morpho-org/morpho-ts`. Removed in the next major.
86
+ * @example
87
+ * ```ts
88
+ * import { InvalidPositionLossFactorError } from "@morpho-org/midnight-sdk";
89
+ *
90
+ * throw new InvalidPositionLossFactorError(1n, 2n);
91
+ * ```
83
92
  */
84
- export declare const InvalidPositionLossFactorError: typeof SharedInvalidPositionLossFactorError;
85
- /** @deprecated Import `InvalidPositionLossFactorError` from `@morpho-org/morpho-ts`. Removed in the next major. */
86
- export type InvalidPositionLossFactorError = SharedInvalidPositionLossFactorError;
93
+ export declare class InvalidPositionLossFactorError extends Error {
94
+ constructor(marketLossFactor: bigint, positionLossFactor: bigint);
95
+ }
87
96
  /**
88
97
  * Thrown when local position accrual inputs violate Midnight accounting invariants.
89
98
  *
90
- * @deprecated Import `InvalidPositionAccrualStateError` from `@morpho-org/morpho-ts`. Removed in the next major.
99
+ * @example
100
+ * ```ts
101
+ * import { InvalidPositionAccrualStateError } from "@morpho-org/midnight-sdk";
102
+ *
103
+ * throw new InvalidPositionAccrualStateError("Pending fee exceeds credit.");
104
+ * ```
91
105
  */
92
- export declare const InvalidPositionAccrualStateError: typeof SharedInvalidPositionAccrualStateError;
93
- /** @deprecated Import `InvalidPositionAccrualStateError` from `@morpho-org/morpho-ts`. Removed in the next major. */
94
- export type InvalidPositionAccrualStateError = SharedInvalidPositionAccrualStateError;
106
+ export declare class InvalidPositionAccrualStateError extends Error {
107
+ constructor(reason: string);
108
+ }
95
109
  /**
96
110
  * Thrown when a market parameter cannot satisfy Midnight protocol rules.
97
111
  *
package/lib/cjs/errors.js CHANGED
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InvalidMidnightApiQuoteTargetError = exports.InvalidMidnightApiResponseError = exports.MidnightApiError = exports.InvalidEcrecoverSignatureVError = exports.InvalidTypedDataSignatureError = exports.MidnightMempoolValidationError = exports.ChainIdMismatchError = exports.RatifierV1TakerNotAllowedError = exports.InvalidRatifierV1AddressError = exports.InvalidRateRatifierV1TimeError = exports.InvalidRateRatifierV1TickError = exports.RateRatifierV1BoundOverflowError = exports.InvalidRateRatifierV1RateError = exports.InvalidTreeError = exports.InvalidTreeHeightError = exports.PayloadDecodeError = exports.InvalidOfferParameterError = exports.UnknownCollateralIndexError = exports.InvalidMarketParameterError = exports.InvalidPositionAccrualStateError = exports.InvalidPositionLossFactorError = exports.InvalidPositionAccrualTimestampError = exports.SettlementFeeExceedsPriceError = exports.InvalidTickSpacingError = exports.PriceGreaterThanOneError = exports.TickOutOfRangeError = exports.InvalidOfferGroupError = void 0;
4
- const morpho_ts_1 = require("@morpho-org/morpho-ts");
5
4
  /**
6
5
  * Thrown when a Midnight offer group violates protocol-level group mechanics.
7
6
  *
@@ -90,25 +89,57 @@ class SettlementFeeExceedsPriceError extends Error {
90
89
  }
91
90
  }
92
91
  exports.SettlementFeeExceedsPriceError = SettlementFeeExceedsPriceError;
93
- // Temporary compatibility exception for these shared morpho-ts errors.
94
92
  /**
95
93
  * Thrown when a local position accrual is requested for an impossible timestamp.
96
94
  *
97
- * @deprecated Import `InvalidPositionAccrualTimestampError` from `@morpho-org/morpho-ts`. Removed in the next major.
95
+ * @example
96
+ * ```ts
97
+ * import { InvalidPositionAccrualTimestampError } from "@morpho-org/midnight-sdk";
98
+ *
99
+ * throw new InvalidPositionAccrualTimestampError(1n, 2n);
100
+ * ```
98
101
  */
99
- exports.InvalidPositionAccrualTimestampError = morpho_ts_1.InvalidPositionAccrualTimestampError;
102
+ class InvalidPositionAccrualTimestampError extends Error {
103
+ constructor(timestamp, lastAccrual) {
104
+ super(`Position accrual timestamp "${timestamp}" is before last accrual "${lastAccrual}". Use a timestamp greater than or equal to last accrual.`);
105
+ this.name = "InvalidPositionAccrualTimestampError";
106
+ }
107
+ }
108
+ exports.InvalidPositionAccrualTimestampError = InvalidPositionAccrualTimestampError;
100
109
  /**
101
110
  * Thrown when a local position accrual sees a market loss factor older than the position.
102
111
  *
103
- * @deprecated Import `InvalidPositionLossFactorError` from `@morpho-org/morpho-ts`. Removed in the next major.
112
+ * @example
113
+ * ```ts
114
+ * import { InvalidPositionLossFactorError } from "@morpho-org/midnight-sdk";
115
+ *
116
+ * throw new InvalidPositionLossFactorError(1n, 2n);
117
+ * ```
104
118
  */
105
- exports.InvalidPositionLossFactorError = morpho_ts_1.InvalidPositionLossFactorError;
119
+ class InvalidPositionLossFactorError extends Error {
120
+ constructor(marketLossFactor, positionLossFactor) {
121
+ super(`Market loss factor "${marketLossFactor}" is below position loss factor "${positionLossFactor}". Fetch a consistent market and position at the same block.`);
122
+ this.name = "InvalidPositionLossFactorError";
123
+ }
124
+ }
125
+ exports.InvalidPositionLossFactorError = InvalidPositionLossFactorError;
106
126
  /**
107
127
  * Thrown when local position accrual inputs violate Midnight accounting invariants.
108
128
  *
109
- * @deprecated Import `InvalidPositionAccrualStateError` from `@morpho-org/morpho-ts`. Removed in the next major.
129
+ * @example
130
+ * ```ts
131
+ * import { InvalidPositionAccrualStateError } from "@morpho-org/midnight-sdk";
132
+ *
133
+ * throw new InvalidPositionAccrualStateError("Pending fee exceeds credit.");
134
+ * ```
110
135
  */
111
- exports.InvalidPositionAccrualStateError = morpho_ts_1.InvalidPositionAccrualStateError;
136
+ class InvalidPositionAccrualStateError extends Error {
137
+ constructor(reason) {
138
+ super(`Invalid Midnight position accrual state. ${reason}`);
139
+ this.name = "InvalidPositionAccrualStateError";
140
+ }
141
+ }
142
+ exports.InvalidPositionAccrualStateError = InvalidPositionAccrualStateError;
112
143
  /**
113
144
  * Thrown when a market parameter cannot satisfy Midnight protocol rules.
114
145
  *
@@ -150,6 +150,7 @@ export declare class Offer {
150
150
  readonly tick: bigint;
151
151
  private cachedGroup;
152
152
  private cachedHash;
153
+ private cachedOnchainHash;
153
154
  /**
154
155
  * Whether the group id was supplied at construction rather than derived.
155
156
  *
@@ -329,6 +330,55 @@ export declare class Offer {
329
330
  * ```
330
331
  */
331
332
  get hash(): Hash;
333
+ /**
334
+ * Returns the on-chain offer identifier emitted by Midnight's `Take` event.
335
+ *
336
+ * This `keccak256(abi.encode(offer))` hash identifies offers on-chain and is
337
+ * distinct from `hash`, the typehash-prefixed ratifier hash.
338
+ * It matches a `Take` event only for the offer exactly as published. For
339
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
340
+ * `offers`: `buildDescriptor` replaces both the default group and a
341
+ * `Group.create` group.
342
+ *
343
+ * @returns The cached on-chain offer hash.
344
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
345
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
346
+ * @example
347
+ * ```ts
348
+ * import { Offer } from "@morpho-org/midnight-sdk";
349
+ * import { zeroAddress } from "viem";
350
+ *
351
+ * const offer = Offer.create({
352
+ * market: {
353
+ * chainId: 8453,
354
+ * midnight: "0x0000000000000000000000000000000000001000",
355
+ * loanToken: "0x0000000000000000000000000000000000006000",
356
+ * collateralParams: [
357
+ * {
358
+ * token: "0x0000000000000000000000000000000000007000",
359
+ * lltv: 770000000000000000n,
360
+ * liquidationCursor: 250000000000000000n,
361
+ * oracle: "0x0000000000000000000000000000000000008000",
362
+ * },
363
+ * ],
364
+ * maturity: 54_000n,
365
+ * rcfThreshold: 0n,
366
+ * enterGate: zeroAddress,
367
+ * liquidatorGate: zeroAddress,
368
+ * },
369
+ * buy: true,
370
+ * maker: "0x0000000000000000000000000000000000009000",
371
+ * tick: 5_000n,
372
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
373
+ * expiry: 3_600n,
374
+ * ratifier: "0x0000000000000000000000000000000000004000",
375
+ * maxUnits: 100n,
376
+ * });
377
+ * const offerHash = offer.onchainHash;
378
+ * console.log(offerHash);
379
+ * ```
380
+ */
381
+ get onchainHash(): Hash;
332
382
  /**
333
383
  * WAD zero-coupon price at this offer's tick.
334
384
  *
@@ -71,6 +71,7 @@ class Offer {
71
71
  tick;
72
72
  cachedGroup;
73
73
  cachedHash;
74
+ cachedOnchainHash;
74
75
  /**
75
76
  * Whether the group id was supplied at construction rather than derived.
76
77
  *
@@ -281,6 +282,58 @@ class Offer {
281
282
  this.cachedHash ??= OfferUtils_js_1.OfferUtils.hash(this);
282
283
  return this.cachedHash;
283
284
  }
285
+ /**
286
+ * Returns the on-chain offer identifier emitted by Midnight's `Take` event.
287
+ *
288
+ * This `keccak256(abi.encode(offer))` hash identifies offers on-chain and is
289
+ * distinct from `hash`, the typehash-prefixed ratifier hash.
290
+ * It matches a `Take` event only for the offer exactly as published. For
291
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
292
+ * `offers`: `buildDescriptor` replaces both the default group and a
293
+ * `Group.create` group.
294
+ *
295
+ * @returns The cached on-chain offer hash.
296
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
297
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
298
+ * @example
299
+ * ```ts
300
+ * import { Offer } from "@morpho-org/midnight-sdk";
301
+ * import { zeroAddress } from "viem";
302
+ *
303
+ * const offer = Offer.create({
304
+ * market: {
305
+ * chainId: 8453,
306
+ * midnight: "0x0000000000000000000000000000000000001000",
307
+ * loanToken: "0x0000000000000000000000000000000000006000",
308
+ * collateralParams: [
309
+ * {
310
+ * token: "0x0000000000000000000000000000000000007000",
311
+ * lltv: 770000000000000000n,
312
+ * liquidationCursor: 250000000000000000n,
313
+ * oracle: "0x0000000000000000000000000000000000008000",
314
+ * },
315
+ * ],
316
+ * maturity: 54_000n,
317
+ * rcfThreshold: 0n,
318
+ * enterGate: zeroAddress,
319
+ * liquidatorGate: zeroAddress,
320
+ * },
321
+ * buy: true,
322
+ * maker: "0x0000000000000000000000000000000000009000",
323
+ * tick: 5_000n,
324
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
325
+ * expiry: 3_600n,
326
+ * ratifier: "0x0000000000000000000000000000000000004000",
327
+ * maxUnits: 100n,
328
+ * });
329
+ * const offerHash = offer.onchainHash;
330
+ * console.log(offerHash);
331
+ * ```
332
+ */
333
+ get onchainHash() {
334
+ this.cachedOnchainHash ??= OfferUtils_js_1.OfferUtils.onchainHash(this);
335
+ return this.cachedOnchainHash;
336
+ }
284
337
  /**
285
338
  * WAD zero-coupon price at this offer's tick.
286
339
  *
@@ -160,6 +160,114 @@ export declare namespace OfferUtils {
160
160
  * ```
161
161
  */
162
162
  function hash(offer: IOffer & Required<Pick<IOffer, "group">>): Hash;
163
+ /**
164
+ * Computes the on-chain offer identifier from the supplied offer struct.
165
+ *
166
+ * This is `keccak256(abi.encode(offer))`, the hash emitted by Midnight's
167
+ * `Take` event and used to identify offers on-chain. It is distinct from
168
+ * `hashStruct` and `hash`, which produce the typehash-prefixed ratifier hash.
169
+ * Collaterals are hashed in the supplied order; Midnight rejects unsorted
170
+ * collateral params with `CollateralParamsNotSorted`, so pass a sorted struct,
171
+ * for example one returned by `toStruct`.
172
+ * It matches a `Take` event only for the offer exactly as published. For
173
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
174
+ * `offers`: `buildDescriptor` replaces both the default group and a
175
+ * `Group.create` group.
176
+ *
177
+ * @param offerStruct - ABI-compatible offer to hash without normalization.
178
+ * @returns The offer hash emitted by Midnight's `Take` event.
179
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
180
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
181
+ * @example
182
+ * ```ts
183
+ * import { Offer, OfferUtils } from "@morpho-org/midnight-sdk";
184
+ * import { zeroAddress } from "viem";
185
+ *
186
+ * const offer = Offer.create({
187
+ * market: {
188
+ * chainId: 8453,
189
+ * midnight: "0x0000000000000000000000000000000000001000",
190
+ * loanToken: "0x0000000000000000000000000000000000006000",
191
+ * collateralParams: [
192
+ * {
193
+ * token: "0x0000000000000000000000000000000000007000",
194
+ * lltv: 770000000000000000n,
195
+ * liquidationCursor: 250000000000000000n,
196
+ * oracle: "0x0000000000000000000000000000000000008000",
197
+ * },
198
+ * ],
199
+ * maturity: 54_000n,
200
+ * rcfThreshold: 0n,
201
+ * enterGate: zeroAddress,
202
+ * liquidatorGate: zeroAddress,
203
+ * },
204
+ * buy: true,
205
+ * maker: "0x0000000000000000000000000000000000009000",
206
+ * tick: 5_000n,
207
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
208
+ * expiry: 3_600n,
209
+ * ratifier: "0x0000000000000000000000000000000000004000",
210
+ * maxUnits: 100n,
211
+ * });
212
+ * const offerHash = OfferUtils.onchainHashStruct(
213
+ * OfferUtils.toStruct({ offer }),
214
+ * );
215
+ * console.log(offerHash);
216
+ * ```
217
+ */
218
+ function onchainHashStruct(offerStruct: OfferStruct): Hash;
219
+ /**
220
+ * Computes the on-chain offer identifier from an offer with a group id.
221
+ *
222
+ * This is `keccak256(abi.encode(offer))`, the hash emitted by Midnight's
223
+ * `Take` event and used to identify offers on-chain. It is distinct from
224
+ * `hash`, which returns the typehash-prefixed ratifier hash. `toStruct`
225
+ * normalizes the offer and sorts collateral params as required by Midnight.
226
+ * It matches a `Take` event only for the offer exactly as published. For
227
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
228
+ * `offers`: `buildDescriptor` replaces both the default group and a
229
+ * `Group.create` group.
230
+ *
231
+ * @param offer - Offer with the protocol group id encoded in its struct.
232
+ * @returns The offer hash emitted by Midnight's `Take` event.
233
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
234
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
235
+ * @example
236
+ * ```ts
237
+ * import { Offer, OfferUtils } from "@morpho-org/midnight-sdk";
238
+ * import { zeroAddress } from "viem";
239
+ *
240
+ * const offer = Offer.create({
241
+ * market: {
242
+ * chainId: 8453,
243
+ * midnight: "0x0000000000000000000000000000000000001000",
244
+ * loanToken: "0x0000000000000000000000000000000000006000",
245
+ * collateralParams: [
246
+ * {
247
+ * token: "0x0000000000000000000000000000000000007000",
248
+ * lltv: 770000000000000000n,
249
+ * liquidationCursor: 250000000000000000n,
250
+ * oracle: "0x0000000000000000000000000000000000008000",
251
+ * },
252
+ * ],
253
+ * maturity: 54_000n,
254
+ * rcfThreshold: 0n,
255
+ * enterGate: zeroAddress,
256
+ * liquidatorGate: zeroAddress,
257
+ * },
258
+ * buy: true,
259
+ * maker: "0x0000000000000000000000000000000000009000",
260
+ * tick: 5_000n,
261
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
262
+ * expiry: 3_600n,
263
+ * ratifier: "0x0000000000000000000000000000000000004000",
264
+ * maxUnits: 100n,
265
+ * });
266
+ * const offerHash = OfferUtils.onchainHash(offer);
267
+ * console.log(offerHash);
268
+ * ```
269
+ */
270
+ function onchainHash(offer: IOffer & Required<Pick<IOffer, "group">>): Hash;
163
271
  /**
164
272
  * Computes the offer hash used to derive a content-addressed group id.
165
273
  *
@@ -3,11 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OfferUtils = void 0;
4
4
  const morpho_ts_1 = require("@morpho-org/morpho-ts");
5
5
  const viem_1 = require("viem");
6
+ const abis_js_1 = require("../abis.js");
6
7
  const constants_js_1 = require("../constants.js");
7
8
  const errors_js_1 = require("../errors.js");
8
9
  const index_js_1 = require("../market/index.js");
9
10
  const index_js_2 = require("../math/index.js");
10
11
  const Offer_js_1 = require("./Offer.js");
12
+ const takeOfferParam = (0, viem_1.getAbiItem)({ abi: abis_js_1.midnightAbi, name: "take" }).inputs[0];
11
13
  const offerHashParams = [
12
14
  { name: "typehash", type: "bytes32" },
13
15
  { name: "marketHash", type: "bytes32" },
@@ -226,6 +228,120 @@ var OfferUtils;
226
228
  return hashStruct(toStruct({ offer }));
227
229
  }
228
230
  OfferUtils.hash = hash;
231
+ /**
232
+ * Computes the on-chain offer identifier from the supplied offer struct.
233
+ *
234
+ * This is `keccak256(abi.encode(offer))`, the hash emitted by Midnight's
235
+ * `Take` event and used to identify offers on-chain. It is distinct from
236
+ * `hashStruct` and `hash`, which produce the typehash-prefixed ratifier hash.
237
+ * Collaterals are hashed in the supplied order; Midnight rejects unsorted
238
+ * collateral params with `CollateralParamsNotSorted`, so pass a sorted struct,
239
+ * for example one returned by `toStruct`.
240
+ * It matches a `Take` event only for the offer exactly as published. For
241
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
242
+ * `offers`: `buildDescriptor` replaces both the default group and a
243
+ * `Group.create` group.
244
+ *
245
+ * @param offerStruct - ABI-compatible offer to hash without normalization.
246
+ * @returns The offer hash emitted by Midnight's `Take` event.
247
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
248
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
249
+ * @example
250
+ * ```ts
251
+ * import { Offer, OfferUtils } from "@morpho-org/midnight-sdk";
252
+ * import { zeroAddress } from "viem";
253
+ *
254
+ * const offer = Offer.create({
255
+ * market: {
256
+ * chainId: 8453,
257
+ * midnight: "0x0000000000000000000000000000000000001000",
258
+ * loanToken: "0x0000000000000000000000000000000000006000",
259
+ * collateralParams: [
260
+ * {
261
+ * token: "0x0000000000000000000000000000000000007000",
262
+ * lltv: 770000000000000000n,
263
+ * liquidationCursor: 250000000000000000n,
264
+ * oracle: "0x0000000000000000000000000000000000008000",
265
+ * },
266
+ * ],
267
+ * maturity: 54_000n,
268
+ * rcfThreshold: 0n,
269
+ * enterGate: zeroAddress,
270
+ * liquidatorGate: zeroAddress,
271
+ * },
272
+ * buy: true,
273
+ * maker: "0x0000000000000000000000000000000000009000",
274
+ * tick: 5_000n,
275
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
276
+ * expiry: 3_600n,
277
+ * ratifier: "0x0000000000000000000000000000000000004000",
278
+ * maxUnits: 100n,
279
+ * });
280
+ * const offerHash = OfferUtils.onchainHashStruct(
281
+ * OfferUtils.toStruct({ offer }),
282
+ * );
283
+ * console.log(offerHash);
284
+ * ```
285
+ */
286
+ function onchainHashStruct(offerStruct) {
287
+ return (0, viem_1.keccak256)((0, viem_1.encodeAbiParameters)([takeOfferParam], [offerStruct]));
288
+ }
289
+ OfferUtils.onchainHashStruct = onchainHashStruct;
290
+ /**
291
+ * Computes the on-chain offer identifier from an offer with a group id.
292
+ *
293
+ * This is `keccak256(abi.encode(offer))`, the hash emitted by Midnight's
294
+ * `Take` event and used to identify offers on-chain. It is distinct from
295
+ * `hash`, which returns the typehash-prefixed ratifier hash. `toStruct`
296
+ * normalizes the offer and sorts collateral params as required by Midnight.
297
+ * It matches a `Take` event only for the offer exactly as published. For
298
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
299
+ * `offers`: `buildDescriptor` replaces both the default group and a
300
+ * `Group.create` group.
301
+ *
302
+ * @param offer - Offer with the protocol group id encoded in its struct.
303
+ * @returns The offer hash emitted by Midnight's `Take` event.
304
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
305
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
306
+ * @example
307
+ * ```ts
308
+ * import { Offer, OfferUtils } from "@morpho-org/midnight-sdk";
309
+ * import { zeroAddress } from "viem";
310
+ *
311
+ * const offer = Offer.create({
312
+ * market: {
313
+ * chainId: 8453,
314
+ * midnight: "0x0000000000000000000000000000000000001000",
315
+ * loanToken: "0x0000000000000000000000000000000000006000",
316
+ * collateralParams: [
317
+ * {
318
+ * token: "0x0000000000000000000000000000000000007000",
319
+ * lltv: 770000000000000000n,
320
+ * liquidationCursor: 250000000000000000n,
321
+ * oracle: "0x0000000000000000000000000000000000008000",
322
+ * },
323
+ * ],
324
+ * maturity: 54_000n,
325
+ * rcfThreshold: 0n,
326
+ * enterGate: zeroAddress,
327
+ * liquidatorGate: zeroAddress,
328
+ * },
329
+ * buy: true,
330
+ * maker: "0x0000000000000000000000000000000000009000",
331
+ * tick: 5_000n,
332
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
333
+ * expiry: 3_600n,
334
+ * ratifier: "0x0000000000000000000000000000000000004000",
335
+ * maxUnits: 100n,
336
+ * });
337
+ * const offerHash = OfferUtils.onchainHash(offer);
338
+ * console.log(offerHash);
339
+ * ```
340
+ */
341
+ function onchainHash(offer) {
342
+ return onchainHashStruct(toStruct({ offer }));
343
+ }
344
+ OfferUtils.onchainHash = onchainHash;
229
345
  /**
230
346
  * Computes the offer hash used to derive a content-addressed group id.
231
347
  *
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PositionUtils = void 0;
4
4
  const morpho_ts_1 = require("@morpho-org/morpho-ts");
5
+ const errors_js_1 = require("../errors.js");
5
6
  /**
6
7
  * Domain helpers for Midnight positions.
7
8
  *
@@ -134,17 +135,40 @@ var PositionUtils;
134
135
  */
135
136
  function accrueInterest(params) {
136
137
  const timestamp = BigInt(params.timestamp);
138
+ const credit = BigInt(params.position.credit);
139
+ const pendingFee = BigInt(params.position.pendingFee);
140
+ const lastLossFactor = BigInt(params.position.lastLossFactor);
141
+ const lastAccrual = BigInt(params.position.lastAccrual);
137
142
  const marketLossFactor = BigInt(params.market.lossFactor);
143
+ if (timestamp < lastAccrual) {
144
+ throw new errors_js_1.InvalidPositionAccrualTimestampError(timestamp, lastAccrual);
145
+ }
146
+ if (marketLossFactor < lastLossFactor) {
147
+ throw new errors_js_1.InvalidPositionLossFactorError(marketLossFactor, lastLossFactor);
148
+ }
149
+ if (pendingFee > credit) {
150
+ throw new errors_js_1.InvalidPositionAccrualStateError("Pending fee must be less than or equal to credit.");
151
+ }
152
+ const postSlashCredit = lastLossFactor < morpho_ts_1.MathLib.MAX_UINT_128
153
+ ? morpho_ts_1.MathLib.mulDivDown(credit, morpho_ts_1.MathLib.MAX_UINT_128 - marketLossFactor, morpho_ts_1.MathLib.MAX_UINT_128 - lastLossFactor)
154
+ : 0n;
155
+ if (postSlashCredit > credit) {
156
+ throw new errors_js_1.InvalidPositionAccrualStateError("Post-slash credit cannot exceed stored credit.");
157
+ }
158
+ const creditDecrease = credit - postSlashCredit;
159
+ const pendingFeeDecrease = credit > 0n ? morpho_ts_1.MathLib.mulDivUp(pendingFee, creditDecrease, credit) : 0n;
160
+ if (pendingFeeDecrease > pendingFee) {
161
+ throw new errors_js_1.InvalidPositionAccrualStateError("Pending-fee decrease cannot exceed pending fee.");
162
+ }
163
+ const postSlashPendingFee = pendingFee - pendingFeeDecrease;
138
164
  const maturity = BigInt(params.market.params.maturity);
139
- const { credit, pendingFee, accruedFee } = morpho_ts_1.MidnightPositionLib.accrue({
140
- credit: params.position.credit,
141
- pendingFee: params.position.pendingFee,
142
- lastLossFactor: params.position.lastLossFactor,
143
- lastAccrual: params.position.lastAccrual,
144
- marketLossFactor,
145
- maturity,
146
- timestamp,
147
- });
165
+ const accrualEnd = timestamp < maturity ? timestamp : maturity;
166
+ const accruedFee = lastAccrual < maturity
167
+ ? morpho_ts_1.MathLib.mulDivDown(postSlashPendingFee, accrualEnd - lastAccrual, maturity - lastAccrual)
168
+ : 0n;
169
+ if (accruedFee > postSlashCredit || accruedFee > postSlashPendingFee) {
170
+ throw new errors_js_1.InvalidPositionAccrualStateError("Accrued fee cannot exceed post-slash credit or pending fee.");
171
+ }
148
172
  const collateral = params.position.collateral.map((assets) => BigInt(assets));
149
173
  const collateralParams = params.market.params.collateralParams.map((collateralParam) => ({
150
174
  token: collateralParam.token,
@@ -175,8 +199,8 @@ var PositionUtils;
175
199
  position: {
176
200
  user: params.position.user,
177
201
  marketId: params.position.marketId,
178
- credit,
179
- pendingFee,
202
+ credit: postSlashCredit - accruedFee,
203
+ pendingFee: postSlashPendingFee - accruedFee,
180
204
  lastLossFactor: marketLossFactor,
181
205
  lastAccrual: timestamp,
182
206
  debt: BigInt(params.position.debt),
@@ -1,2 +1,2 @@
1
1
  /** @internal Installed Midnight SDK package version. */
2
- export declare const MIDNIGHT_SDK_VERSION = "2.1.0-next.0";
2
+ export declare const MIDNIGHT_SDK_VERSION = "2.1.0";
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/generate-midnight-package-version.ts. Do not edit.
2
2
  /** @internal Installed Midnight SDK package version. */
3
- export const MIDNIGHT_SDK_VERSION = "2.1.0-next.0";
3
+ export const MIDNIGHT_SDK_VERSION = "2.1.0";
@@ -1,4 +1,3 @@
1
- import { InvalidPositionAccrualStateError as SharedInvalidPositionAccrualStateError, InvalidPositionAccrualTimestampError as SharedInvalidPositionAccrualTimestampError, InvalidPositionLossFactorError as SharedInvalidPositionLossFactorError } from "@morpho-org/morpho-ts";
2
1
  import type { Address } from "viem";
3
2
  import type { MempoolPayloadValidationIssue } from "./api/types.js";
4
3
  /**
@@ -71,27 +70,42 @@ export declare class SettlementFeeExceedsPriceError extends Error {
71
70
  /**
72
71
  * Thrown when a local position accrual is requested for an impossible timestamp.
73
72
  *
74
- * @deprecated Import `InvalidPositionAccrualTimestampError` from `@morpho-org/morpho-ts`. Removed in the next major.
73
+ * @example
74
+ * ```ts
75
+ * import { InvalidPositionAccrualTimestampError } from "@morpho-org/midnight-sdk";
76
+ *
77
+ * throw new InvalidPositionAccrualTimestampError(1n, 2n);
78
+ * ```
75
79
  */
76
- export declare const InvalidPositionAccrualTimestampError: typeof SharedInvalidPositionAccrualTimestampError;
77
- /** @deprecated Import `InvalidPositionAccrualTimestampError` from `@morpho-org/morpho-ts`. Removed in the next major. */
78
- export type InvalidPositionAccrualTimestampError = SharedInvalidPositionAccrualTimestampError;
80
+ export declare class InvalidPositionAccrualTimestampError extends Error {
81
+ constructor(timestamp: bigint, lastAccrual: bigint);
82
+ }
79
83
  /**
80
84
  * Thrown when a local position accrual sees a market loss factor older than the position.
81
85
  *
82
- * @deprecated Import `InvalidPositionLossFactorError` from `@morpho-org/morpho-ts`. Removed in the next major.
86
+ * @example
87
+ * ```ts
88
+ * import { InvalidPositionLossFactorError } from "@morpho-org/midnight-sdk";
89
+ *
90
+ * throw new InvalidPositionLossFactorError(1n, 2n);
91
+ * ```
83
92
  */
84
- export declare const InvalidPositionLossFactorError: typeof SharedInvalidPositionLossFactorError;
85
- /** @deprecated Import `InvalidPositionLossFactorError` from `@morpho-org/morpho-ts`. Removed in the next major. */
86
- export type InvalidPositionLossFactorError = SharedInvalidPositionLossFactorError;
93
+ export declare class InvalidPositionLossFactorError extends Error {
94
+ constructor(marketLossFactor: bigint, positionLossFactor: bigint);
95
+ }
87
96
  /**
88
97
  * Thrown when local position accrual inputs violate Midnight accounting invariants.
89
98
  *
90
- * @deprecated Import `InvalidPositionAccrualStateError` from `@morpho-org/morpho-ts`. Removed in the next major.
99
+ * @example
100
+ * ```ts
101
+ * import { InvalidPositionAccrualStateError } from "@morpho-org/midnight-sdk";
102
+ *
103
+ * throw new InvalidPositionAccrualStateError("Pending fee exceeds credit.");
104
+ * ```
91
105
  */
92
- export declare const InvalidPositionAccrualStateError: typeof SharedInvalidPositionAccrualStateError;
93
- /** @deprecated Import `InvalidPositionAccrualStateError` from `@morpho-org/morpho-ts`. Removed in the next major. */
94
- export type InvalidPositionAccrualStateError = SharedInvalidPositionAccrualStateError;
106
+ export declare class InvalidPositionAccrualStateError extends Error {
107
+ constructor(reason: string);
108
+ }
95
109
  /**
96
110
  * Thrown when a market parameter cannot satisfy Midnight protocol rules.
97
111
  *
package/lib/esm/errors.js CHANGED
@@ -1,4 +1,3 @@
1
- import { InvalidPositionAccrualStateError as SharedInvalidPositionAccrualStateError, InvalidPositionAccrualTimestampError as SharedInvalidPositionAccrualTimestampError, InvalidPositionLossFactorError as SharedInvalidPositionLossFactorError, } from "@morpho-org/morpho-ts";
2
1
  /**
3
2
  * Thrown when a Midnight offer group violates protocol-level group mechanics.
4
3
  *
@@ -82,25 +81,54 @@ export class SettlementFeeExceedsPriceError extends Error {
82
81
  this.name = "SettlementFeeExceedsPriceError";
83
82
  }
84
83
  }
85
- // Temporary compatibility exception for these shared morpho-ts errors.
86
84
  /**
87
85
  * Thrown when a local position accrual is requested for an impossible timestamp.
88
86
  *
89
- * @deprecated Import `InvalidPositionAccrualTimestampError` from `@morpho-org/morpho-ts`. Removed in the next major.
87
+ * @example
88
+ * ```ts
89
+ * import { InvalidPositionAccrualTimestampError } from "@morpho-org/midnight-sdk";
90
+ *
91
+ * throw new InvalidPositionAccrualTimestampError(1n, 2n);
92
+ * ```
90
93
  */
91
- export const InvalidPositionAccrualTimestampError = SharedInvalidPositionAccrualTimestampError;
94
+ export class InvalidPositionAccrualTimestampError extends Error {
95
+ constructor(timestamp, lastAccrual) {
96
+ super(`Position accrual timestamp "${timestamp}" is before last accrual "${lastAccrual}". Use a timestamp greater than or equal to last accrual.`);
97
+ this.name = "InvalidPositionAccrualTimestampError";
98
+ }
99
+ }
92
100
  /**
93
101
  * Thrown when a local position accrual sees a market loss factor older than the position.
94
102
  *
95
- * @deprecated Import `InvalidPositionLossFactorError` from `@morpho-org/morpho-ts`. Removed in the next major.
103
+ * @example
104
+ * ```ts
105
+ * import { InvalidPositionLossFactorError } from "@morpho-org/midnight-sdk";
106
+ *
107
+ * throw new InvalidPositionLossFactorError(1n, 2n);
108
+ * ```
96
109
  */
97
- export const InvalidPositionLossFactorError = SharedInvalidPositionLossFactorError;
110
+ export class InvalidPositionLossFactorError extends Error {
111
+ constructor(marketLossFactor, positionLossFactor) {
112
+ super(`Market loss factor "${marketLossFactor}" is below position loss factor "${positionLossFactor}". Fetch a consistent market and position at the same block.`);
113
+ this.name = "InvalidPositionLossFactorError";
114
+ }
115
+ }
98
116
  /**
99
117
  * Thrown when local position accrual inputs violate Midnight accounting invariants.
100
118
  *
101
- * @deprecated Import `InvalidPositionAccrualStateError` from `@morpho-org/morpho-ts`. Removed in the next major.
119
+ * @example
120
+ * ```ts
121
+ * import { InvalidPositionAccrualStateError } from "@morpho-org/midnight-sdk";
122
+ *
123
+ * throw new InvalidPositionAccrualStateError("Pending fee exceeds credit.");
124
+ * ```
102
125
  */
103
- export const InvalidPositionAccrualStateError = SharedInvalidPositionAccrualStateError;
126
+ export class InvalidPositionAccrualStateError extends Error {
127
+ constructor(reason) {
128
+ super(`Invalid Midnight position accrual state. ${reason}`);
129
+ this.name = "InvalidPositionAccrualStateError";
130
+ }
131
+ }
104
132
  /**
105
133
  * Thrown when a market parameter cannot satisfy Midnight protocol rules.
106
134
  *
@@ -150,6 +150,7 @@ export declare class Offer {
150
150
  readonly tick: bigint;
151
151
  private cachedGroup;
152
152
  private cachedHash;
153
+ private cachedOnchainHash;
153
154
  /**
154
155
  * Whether the group id was supplied at construction rather than derived.
155
156
  *
@@ -329,6 +330,55 @@ export declare class Offer {
329
330
  * ```
330
331
  */
331
332
  get hash(): Hash;
333
+ /**
334
+ * Returns the on-chain offer identifier emitted by Midnight's `Take` event.
335
+ *
336
+ * This `keccak256(abi.encode(offer))` hash identifies offers on-chain and is
337
+ * distinct from `hash`, the typehash-prefixed ratifier hash.
338
+ * It matches a `Take` event only for the offer exactly as published. For
339
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
340
+ * `offers`: `buildDescriptor` replaces both the default group and a
341
+ * `Group.create` group.
342
+ *
343
+ * @returns The cached on-chain offer hash.
344
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
345
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
346
+ * @example
347
+ * ```ts
348
+ * import { Offer } from "@morpho-org/midnight-sdk";
349
+ * import { zeroAddress } from "viem";
350
+ *
351
+ * const offer = Offer.create({
352
+ * market: {
353
+ * chainId: 8453,
354
+ * midnight: "0x0000000000000000000000000000000000001000",
355
+ * loanToken: "0x0000000000000000000000000000000000006000",
356
+ * collateralParams: [
357
+ * {
358
+ * token: "0x0000000000000000000000000000000000007000",
359
+ * lltv: 770000000000000000n,
360
+ * liquidationCursor: 250000000000000000n,
361
+ * oracle: "0x0000000000000000000000000000000000008000",
362
+ * },
363
+ * ],
364
+ * maturity: 54_000n,
365
+ * rcfThreshold: 0n,
366
+ * enterGate: zeroAddress,
367
+ * liquidatorGate: zeroAddress,
368
+ * },
369
+ * buy: true,
370
+ * maker: "0x0000000000000000000000000000000000009000",
371
+ * tick: 5_000n,
372
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
373
+ * expiry: 3_600n,
374
+ * ratifier: "0x0000000000000000000000000000000000004000",
375
+ * maxUnits: 100n,
376
+ * });
377
+ * const offerHash = offer.onchainHash;
378
+ * console.log(offerHash);
379
+ * ```
380
+ */
381
+ get onchainHash(): Hash;
332
382
  /**
333
383
  * WAD zero-coupon price at this offer's tick.
334
384
  *
@@ -68,6 +68,7 @@ export class Offer {
68
68
  tick;
69
69
  cachedGroup;
70
70
  cachedHash;
71
+ cachedOnchainHash;
71
72
  /**
72
73
  * Whether the group id was supplied at construction rather than derived.
73
74
  *
@@ -278,6 +279,58 @@ export class Offer {
278
279
  this.cachedHash ??= OfferUtils.hash(this);
279
280
  return this.cachedHash;
280
281
  }
282
+ /**
283
+ * Returns the on-chain offer identifier emitted by Midnight's `Take` event.
284
+ *
285
+ * This `keccak256(abi.encode(offer))` hash identifies offers on-chain and is
286
+ * distinct from `hash`, the typehash-prefixed ratifier hash.
287
+ * It matches a `Take` event only for the offer exactly as published. For
288
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
289
+ * `offers`: `buildDescriptor` replaces both the default group and a
290
+ * `Group.create` group.
291
+ *
292
+ * @returns The cached on-chain offer hash.
293
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
294
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
295
+ * @example
296
+ * ```ts
297
+ * import { Offer } from "@morpho-org/midnight-sdk";
298
+ * import { zeroAddress } from "viem";
299
+ *
300
+ * const offer = Offer.create({
301
+ * market: {
302
+ * chainId: 8453,
303
+ * midnight: "0x0000000000000000000000000000000000001000",
304
+ * loanToken: "0x0000000000000000000000000000000000006000",
305
+ * collateralParams: [
306
+ * {
307
+ * token: "0x0000000000000000000000000000000000007000",
308
+ * lltv: 770000000000000000n,
309
+ * liquidationCursor: 250000000000000000n,
310
+ * oracle: "0x0000000000000000000000000000000000008000",
311
+ * },
312
+ * ],
313
+ * maturity: 54_000n,
314
+ * rcfThreshold: 0n,
315
+ * enterGate: zeroAddress,
316
+ * liquidatorGate: zeroAddress,
317
+ * },
318
+ * buy: true,
319
+ * maker: "0x0000000000000000000000000000000000009000",
320
+ * tick: 5_000n,
321
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
322
+ * expiry: 3_600n,
323
+ * ratifier: "0x0000000000000000000000000000000000004000",
324
+ * maxUnits: 100n,
325
+ * });
326
+ * const offerHash = offer.onchainHash;
327
+ * console.log(offerHash);
328
+ * ```
329
+ */
330
+ get onchainHash() {
331
+ this.cachedOnchainHash ??= OfferUtils.onchainHash(this);
332
+ return this.cachedOnchainHash;
333
+ }
281
334
  /**
282
335
  * WAD zero-coupon price at this offer's tick.
283
336
  *
@@ -160,6 +160,114 @@ export declare namespace OfferUtils {
160
160
  * ```
161
161
  */
162
162
  function hash(offer: IOffer & Required<Pick<IOffer, "group">>): Hash;
163
+ /**
164
+ * Computes the on-chain offer identifier from the supplied offer struct.
165
+ *
166
+ * This is `keccak256(abi.encode(offer))`, the hash emitted by Midnight's
167
+ * `Take` event and used to identify offers on-chain. It is distinct from
168
+ * `hashStruct` and `hash`, which produce the typehash-prefixed ratifier hash.
169
+ * Collaterals are hashed in the supplied order; Midnight rejects unsorted
170
+ * collateral params with `CollateralParamsNotSorted`, so pass a sorted struct,
171
+ * for example one returned by `toStruct`.
172
+ * It matches a `Take` event only for the offer exactly as published. For
173
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
174
+ * `offers`: `buildDescriptor` replaces both the default group and a
175
+ * `Group.create` group.
176
+ *
177
+ * @param offerStruct - ABI-compatible offer to hash without normalization.
178
+ * @returns The offer hash emitted by Midnight's `Take` event.
179
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
180
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
181
+ * @example
182
+ * ```ts
183
+ * import { Offer, OfferUtils } from "@morpho-org/midnight-sdk";
184
+ * import { zeroAddress } from "viem";
185
+ *
186
+ * const offer = Offer.create({
187
+ * market: {
188
+ * chainId: 8453,
189
+ * midnight: "0x0000000000000000000000000000000000001000",
190
+ * loanToken: "0x0000000000000000000000000000000000006000",
191
+ * collateralParams: [
192
+ * {
193
+ * token: "0x0000000000000000000000000000000000007000",
194
+ * lltv: 770000000000000000n,
195
+ * liquidationCursor: 250000000000000000n,
196
+ * oracle: "0x0000000000000000000000000000000000008000",
197
+ * },
198
+ * ],
199
+ * maturity: 54_000n,
200
+ * rcfThreshold: 0n,
201
+ * enterGate: zeroAddress,
202
+ * liquidatorGate: zeroAddress,
203
+ * },
204
+ * buy: true,
205
+ * maker: "0x0000000000000000000000000000000000009000",
206
+ * tick: 5_000n,
207
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
208
+ * expiry: 3_600n,
209
+ * ratifier: "0x0000000000000000000000000000000000004000",
210
+ * maxUnits: 100n,
211
+ * });
212
+ * const offerHash = OfferUtils.onchainHashStruct(
213
+ * OfferUtils.toStruct({ offer }),
214
+ * );
215
+ * console.log(offerHash);
216
+ * ```
217
+ */
218
+ function onchainHashStruct(offerStruct: OfferStruct): Hash;
219
+ /**
220
+ * Computes the on-chain offer identifier from an offer with a group id.
221
+ *
222
+ * This is `keccak256(abi.encode(offer))`, the hash emitted by Midnight's
223
+ * `Take` event and used to identify offers on-chain. It is distinct from
224
+ * `hash`, which returns the typehash-prefixed ratifier hash. `toStruct`
225
+ * normalizes the offer and sorts collateral params as required by Midnight.
226
+ * It matches a `Take` event only for the offer exactly as published. For
227
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
228
+ * `offers`: `buildDescriptor` replaces both the default group and a
229
+ * `Group.create` group.
230
+ *
231
+ * @param offer - Offer with the protocol group id encoded in its struct.
232
+ * @returns The offer hash emitted by Midnight's `Take` event.
233
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
234
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
235
+ * @example
236
+ * ```ts
237
+ * import { Offer, OfferUtils } from "@morpho-org/midnight-sdk";
238
+ * import { zeroAddress } from "viem";
239
+ *
240
+ * const offer = Offer.create({
241
+ * market: {
242
+ * chainId: 8453,
243
+ * midnight: "0x0000000000000000000000000000000000001000",
244
+ * loanToken: "0x0000000000000000000000000000000000006000",
245
+ * collateralParams: [
246
+ * {
247
+ * token: "0x0000000000000000000000000000000000007000",
248
+ * lltv: 770000000000000000n,
249
+ * liquidationCursor: 250000000000000000n,
250
+ * oracle: "0x0000000000000000000000000000000000008000",
251
+ * },
252
+ * ],
253
+ * maturity: 54_000n,
254
+ * rcfThreshold: 0n,
255
+ * enterGate: zeroAddress,
256
+ * liquidatorGate: zeroAddress,
257
+ * },
258
+ * buy: true,
259
+ * maker: "0x0000000000000000000000000000000000009000",
260
+ * tick: 5_000n,
261
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
262
+ * expiry: 3_600n,
263
+ * ratifier: "0x0000000000000000000000000000000000004000",
264
+ * maxUnits: 100n,
265
+ * });
266
+ * const offerHash = OfferUtils.onchainHash(offer);
267
+ * console.log(offerHash);
268
+ * ```
269
+ */
270
+ function onchainHash(offer: IOffer & Required<Pick<IOffer, "group">>): Hash;
163
271
  /**
164
272
  * Computes the offer hash used to derive a content-addressed group id.
165
273
  *
@@ -1,10 +1,12 @@
1
1
  import { assertNonNegative, DivisionByZeroError, MathLib, } from "@morpho-org/morpho-ts";
2
- import { encodeAbiParameters, isAddressEqual, keccak256, zeroAddress, zeroHash, } from "viem";
2
+ import { encodeAbiParameters, getAbiItem, isAddressEqual, keccak256, zeroAddress, zeroHash, } from "viem";
3
+ import { midnightAbi } from "../abis.js";
3
4
  import { DEFAULT_TICK_SPACING, MAX_OFFER_CAP, MAX_TICK, OFFER_TYPEHASH, } from "../constants.js";
4
5
  import { InvalidOfferGroupError, InvalidOfferParameterError, } from "../errors.js";
5
6
  import { Market, MarketParams, MarketUtils } from "../market/index.js";
6
7
  import { TakeAmountsLib, TickLib } from "../math/index.js";
7
8
  import { Offer, } from "./Offer.js";
9
+ const takeOfferParam = getAbiItem({ abi: midnightAbi, name: "take" }).inputs[0];
8
10
  const offerHashParams = [
9
11
  { name: "typehash", type: "bytes32" },
10
12
  { name: "marketHash", type: "bytes32" },
@@ -223,6 +225,120 @@ export var OfferUtils;
223
225
  return hashStruct(toStruct({ offer }));
224
226
  }
225
227
  OfferUtils.hash = hash;
228
+ /**
229
+ * Computes the on-chain offer identifier from the supplied offer struct.
230
+ *
231
+ * This is `keccak256(abi.encode(offer))`, the hash emitted by Midnight's
232
+ * `Take` event and used to identify offers on-chain. It is distinct from
233
+ * `hashStruct` and `hash`, which produce the typehash-prefixed ratifier hash.
234
+ * Collaterals are hashed in the supplied order; Midnight rejects unsorted
235
+ * collateral params with `CollateralParamsNotSorted`, so pass a sorted struct,
236
+ * for example one returned by `toStruct`.
237
+ * It matches a `Take` event only for the offer exactly as published. For
238
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
239
+ * `offers`: `buildDescriptor` replaces both the default group and a
240
+ * `Group.create` group.
241
+ *
242
+ * @param offerStruct - ABI-compatible offer to hash without normalization.
243
+ * @returns The offer hash emitted by Midnight's `Take` event.
244
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
245
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
246
+ * @example
247
+ * ```ts
248
+ * import { Offer, OfferUtils } from "@morpho-org/midnight-sdk";
249
+ * import { zeroAddress } from "viem";
250
+ *
251
+ * const offer = Offer.create({
252
+ * market: {
253
+ * chainId: 8453,
254
+ * midnight: "0x0000000000000000000000000000000000001000",
255
+ * loanToken: "0x0000000000000000000000000000000000006000",
256
+ * collateralParams: [
257
+ * {
258
+ * token: "0x0000000000000000000000000000000000007000",
259
+ * lltv: 770000000000000000n,
260
+ * liquidationCursor: 250000000000000000n,
261
+ * oracle: "0x0000000000000000000000000000000000008000",
262
+ * },
263
+ * ],
264
+ * maturity: 54_000n,
265
+ * rcfThreshold: 0n,
266
+ * enterGate: zeroAddress,
267
+ * liquidatorGate: zeroAddress,
268
+ * },
269
+ * buy: true,
270
+ * maker: "0x0000000000000000000000000000000000009000",
271
+ * tick: 5_000n,
272
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
273
+ * expiry: 3_600n,
274
+ * ratifier: "0x0000000000000000000000000000000000004000",
275
+ * maxUnits: 100n,
276
+ * });
277
+ * const offerHash = OfferUtils.onchainHashStruct(
278
+ * OfferUtils.toStruct({ offer }),
279
+ * );
280
+ * console.log(offerHash);
281
+ * ```
282
+ */
283
+ function onchainHashStruct(offerStruct) {
284
+ return keccak256(encodeAbiParameters([takeOfferParam], [offerStruct]));
285
+ }
286
+ OfferUtils.onchainHashStruct = onchainHashStruct;
287
+ /**
288
+ * Computes the on-chain offer identifier from an offer with a group id.
289
+ *
290
+ * This is `keccak256(abi.encode(offer))`, the hash emitted by Midnight's
291
+ * `Take` event and used to identify offers on-chain. It is distinct from
292
+ * `hash`, which returns the typehash-prefixed ratifier hash. `toStruct`
293
+ * normalizes the offer and sorts collateral params as required by Midnight.
294
+ * It matches a `Take` event only for the offer exactly as published. For
295
+ * RateRatifierV1 and PriceRatifierV1, hash the offers in the descriptor's
296
+ * `offers`: `buildDescriptor` replaces both the default group and a
297
+ * `Group.create` group.
298
+ *
299
+ * @param offer - Offer with the protocol group id encoded in its struct.
300
+ * @returns The offer hash emitted by Midnight's `Take` event.
301
+ * @throws {viem.InvalidAddressError} when an address has an invalid mixed-case checksum.
302
+ * @throws {viem.IntegerOutOfRangeError} when a uint field overflows its ABI size.
303
+ * @example
304
+ * ```ts
305
+ * import { Offer, OfferUtils } from "@morpho-org/midnight-sdk";
306
+ * import { zeroAddress } from "viem";
307
+ *
308
+ * const offer = Offer.create({
309
+ * market: {
310
+ * chainId: 8453,
311
+ * midnight: "0x0000000000000000000000000000000000001000",
312
+ * loanToken: "0x0000000000000000000000000000000000006000",
313
+ * collateralParams: [
314
+ * {
315
+ * token: "0x0000000000000000000000000000000000007000",
316
+ * lltv: 770000000000000000n,
317
+ * liquidationCursor: 250000000000000000n,
318
+ * oracle: "0x0000000000000000000000000000000000008000",
319
+ * },
320
+ * ],
321
+ * maturity: 54_000n,
322
+ * rcfThreshold: 0n,
323
+ * enterGate: zeroAddress,
324
+ * liquidatorGate: zeroAddress,
325
+ * },
326
+ * buy: true,
327
+ * maker: "0x0000000000000000000000000000000000009000",
328
+ * tick: 5_000n,
329
+ * group: "0x1111111111111111111111111111111111111111111111111111111111111111",
330
+ * expiry: 3_600n,
331
+ * ratifier: "0x0000000000000000000000000000000000004000",
332
+ * maxUnits: 100n,
333
+ * });
334
+ * const offerHash = OfferUtils.onchainHash(offer);
335
+ * console.log(offerHash);
336
+ * ```
337
+ */
338
+ function onchainHash(offer) {
339
+ return onchainHashStruct(toStruct({ offer }));
340
+ }
341
+ OfferUtils.onchainHash = onchainHash;
226
342
  /**
227
343
  * Computes the offer hash used to derive a content-addressed group id.
228
344
  *
@@ -1,4 +1,5 @@
1
- import { MathLib, MidnightPositionLib, } from "@morpho-org/morpho-ts";
1
+ import { MathLib } from "@morpho-org/morpho-ts";
2
+ import { InvalidPositionAccrualStateError, InvalidPositionAccrualTimestampError, InvalidPositionLossFactorError, } from "../errors.js";
2
3
  /**
3
4
  * Domain helpers for Midnight positions.
4
5
  *
@@ -131,17 +132,40 @@ export var PositionUtils;
131
132
  */
132
133
  function accrueInterest(params) {
133
134
  const timestamp = BigInt(params.timestamp);
135
+ const credit = BigInt(params.position.credit);
136
+ const pendingFee = BigInt(params.position.pendingFee);
137
+ const lastLossFactor = BigInt(params.position.lastLossFactor);
138
+ const lastAccrual = BigInt(params.position.lastAccrual);
134
139
  const marketLossFactor = BigInt(params.market.lossFactor);
140
+ if (timestamp < lastAccrual) {
141
+ throw new InvalidPositionAccrualTimestampError(timestamp, lastAccrual);
142
+ }
143
+ if (marketLossFactor < lastLossFactor) {
144
+ throw new InvalidPositionLossFactorError(marketLossFactor, lastLossFactor);
145
+ }
146
+ if (pendingFee > credit) {
147
+ throw new InvalidPositionAccrualStateError("Pending fee must be less than or equal to credit.");
148
+ }
149
+ const postSlashCredit = lastLossFactor < MathLib.MAX_UINT_128
150
+ ? MathLib.mulDivDown(credit, MathLib.MAX_UINT_128 - marketLossFactor, MathLib.MAX_UINT_128 - lastLossFactor)
151
+ : 0n;
152
+ if (postSlashCredit > credit) {
153
+ throw new InvalidPositionAccrualStateError("Post-slash credit cannot exceed stored credit.");
154
+ }
155
+ const creditDecrease = credit - postSlashCredit;
156
+ const pendingFeeDecrease = credit > 0n ? MathLib.mulDivUp(pendingFee, creditDecrease, credit) : 0n;
157
+ if (pendingFeeDecrease > pendingFee) {
158
+ throw new InvalidPositionAccrualStateError("Pending-fee decrease cannot exceed pending fee.");
159
+ }
160
+ const postSlashPendingFee = pendingFee - pendingFeeDecrease;
135
161
  const maturity = BigInt(params.market.params.maturity);
136
- const { credit, pendingFee, accruedFee } = MidnightPositionLib.accrue({
137
- credit: params.position.credit,
138
- pendingFee: params.position.pendingFee,
139
- lastLossFactor: params.position.lastLossFactor,
140
- lastAccrual: params.position.lastAccrual,
141
- marketLossFactor,
142
- maturity,
143
- timestamp,
144
- });
162
+ const accrualEnd = timestamp < maturity ? timestamp : maturity;
163
+ const accruedFee = lastAccrual < maturity
164
+ ? MathLib.mulDivDown(postSlashPendingFee, accrualEnd - lastAccrual, maturity - lastAccrual)
165
+ : 0n;
166
+ if (accruedFee > postSlashCredit || accruedFee > postSlashPendingFee) {
167
+ throw new InvalidPositionAccrualStateError("Accrued fee cannot exceed post-slash credit or pending fee.");
168
+ }
145
169
  const collateral = params.position.collateral.map((assets) => BigInt(assets));
146
170
  const collateralParams = params.market.params.collateralParams.map((collateralParam) => ({
147
171
  token: collateralParam.token,
@@ -172,8 +196,8 @@ export var PositionUtils;
172
196
  position: {
173
197
  user: params.position.user,
174
198
  marketId: params.position.marketId,
175
- credit,
176
- pendingFee,
199
+ credit: postSlashCredit - accruedFee,
200
+ pendingFee: postSlashPendingFee - accruedFee,
177
201
  lastLossFactor: marketLossFactor,
178
202
  lastAccrual: timestamp,
179
203
  debt: BigInt(params.position.debt),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@morpho-org/midnight-sdk",
3
3
  "description": "Viem-based package for Morpho Midnight that exports protocol utilities, fetch helpers, and Midnight API utilities under a dedicated API subpath.",
4
- "version": "2.1.0-next.0",
4
+ "version": "2.1.0",
5
5
  "author": "Morpho Association <contact@morpho.org>",
6
6
  "repository": {
7
7
  "type": "git",
@@ -41,12 +41,12 @@
41
41
  "MIGRATION-v1-to-v2.md"
42
42
  ],
43
43
  "peerDependencies": {
44
- "@morpho-org/morpho-ts": "^4.1.0-0",
44
+ "@morpho-org/morpho-ts": "^2.8.0 || ^3.0.0 || ^4.0.0",
45
45
  "viem": "^2.0.0"
46
46
  },
47
47
  "devDependencies": {
48
- "@morpho-org/morpho-ts": "^4.1.0-next.0",
49
- "@morpho-org/test": "^2.11.0-next.0",
48
+ "@morpho-org/morpho-ts": "^4.0.1",
49
+ "@morpho-org/test": "^2.11.0",
50
50
  "fast-check": "^4.9.0",
51
51
  "typescript": "7.0.2",
52
52
  "viem": "^2.55.10",