@morpho-org/midnight-sdk 0.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.
Files changed (157) hide show
  1. package/README.md +252 -0
  2. package/lib/cjs/abis.d.ts +3170 -0
  3. package/lib/cjs/abis.js +4073 -0
  4. package/lib/cjs/api/MidnightApi.d.ts +532 -0
  5. package/lib/cjs/api/MidnightApi.js +713 -0
  6. package/lib/cjs/api/helpers.d.ts +20 -0
  7. package/lib/cjs/api/helpers.js +189 -0
  8. package/lib/cjs/api/index.d.ts +2 -0
  9. package/lib/cjs/api/index.js +21 -0
  10. package/lib/cjs/api/types.d.ts +453 -0
  11. package/lib/cjs/api/types.js +2 -0
  12. package/lib/cjs/constants.d.ts +168 -0
  13. package/lib/cjs/constants.js +187 -0
  14. package/lib/cjs/errors.d.ts +253 -0
  15. package/lib/cjs/errors.js +309 -0
  16. package/lib/cjs/fetch/Market.d.ts +65 -0
  17. package/lib/cjs/fetch/Market.js +117 -0
  18. package/lib/cjs/fetch/Offer.d.ts +74 -0
  19. package/lib/cjs/fetch/Offer.js +119 -0
  20. package/lib/cjs/fetch/Position.d.ts +85 -0
  21. package/lib/cjs/fetch/Position.js +148 -0
  22. package/lib/cjs/fetch/Ratifier.d.ts +38 -0
  23. package/lib/cjs/fetch/Ratifier.js +57 -0
  24. package/lib/cjs/fetch/index.d.ts +5 -0
  25. package/lib/cjs/fetch/index.js +21 -0
  26. package/lib/cjs/fetch/types.d.ts +57 -0
  27. package/lib/cjs/fetch/types.js +2 -0
  28. package/lib/cjs/fetch/utils.d.ts +8 -0
  29. package/lib/cjs/fetch/utils.js +18 -0
  30. package/lib/cjs/index.d.ts +18 -0
  31. package/lib/cjs/index.js +57 -0
  32. package/lib/cjs/market/Market.d.ts +613 -0
  33. package/lib/cjs/market/Market.js +503 -0
  34. package/lib/cjs/market/MarketUtils.d.ts +189 -0
  35. package/lib/cjs/market/MarketUtils.js +303 -0
  36. package/lib/cjs/market/Position.d.ts +424 -0
  37. package/lib/cjs/market/Position.js +425 -0
  38. package/lib/cjs/market/PositionUtils.d.ts +115 -0
  39. package/lib/cjs/market/PositionUtils.js +194 -0
  40. package/lib/cjs/market/index.d.ts +4 -0
  41. package/lib/cjs/market/index.js +20 -0
  42. package/lib/cjs/math/ConsumableUnitsLib.d.ts +57 -0
  43. package/lib/cjs/math/ConsumableUnitsLib.js +77 -0
  44. package/lib/cjs/math/TakeAmountsLib.d.ts +175 -0
  45. package/lib/cjs/math/TakeAmountsLib.js +199 -0
  46. package/lib/cjs/math/TickLib.d.ts +200 -0
  47. package/lib/cjs/math/TickLib.js +272 -0
  48. package/lib/cjs/math/index.d.ts +3 -0
  49. package/lib/cjs/math/index.js +19 -0
  50. package/lib/cjs/offers/Offer.d.ts +534 -0
  51. package/lib/cjs/offers/Offer.js +343 -0
  52. package/lib/cjs/offers/OfferUtils.d.ts +565 -0
  53. package/lib/cjs/offers/OfferUtils.js +659 -0
  54. package/lib/cjs/offers/index.d.ts +2 -0
  55. package/lib/cjs/offers/index.js +18 -0
  56. package/lib/cjs/package.json +1 -0
  57. package/lib/cjs/queries/GetPosition.d.ts +55 -0
  58. package/lib/cjs/queries/GetPosition.js +74 -0
  59. package/lib/cjs/queries/index.d.ts +1 -0
  60. package/lib/cjs/queries/index.js +37 -0
  61. package/lib/cjs/signatures/EcrecoverRatifierUtils.d.ts +733 -0
  62. package/lib/cjs/signatures/EcrecoverRatifierUtils.js +618 -0
  63. package/lib/cjs/signatures/Group.d.ts +178 -0
  64. package/lib/cjs/signatures/Group.js +193 -0
  65. package/lib/cjs/signatures/GroupUtils.d.ts +191 -0
  66. package/lib/cjs/signatures/GroupUtils.js +127 -0
  67. package/lib/cjs/signatures/Payload.d.ts +283 -0
  68. package/lib/cjs/signatures/Payload.js +570 -0
  69. package/lib/cjs/signatures/RatifierUtils.d.ts +109 -0
  70. package/lib/cjs/signatures/RatifierUtils.js +76 -0
  71. package/lib/cjs/signatures/SetterRatifierUtils.d.ts +243 -0
  72. package/lib/cjs/signatures/SetterRatifierUtils.js +217 -0
  73. package/lib/cjs/signatures/Tree.d.ts +241 -0
  74. package/lib/cjs/signatures/Tree.js +263 -0
  75. package/lib/cjs/signatures/TreeUtils.d.ts +573 -0
  76. package/lib/cjs/signatures/TreeUtils.js +461 -0
  77. package/lib/cjs/signatures/index.d.ts +7 -0
  78. package/lib/cjs/signatures/index.js +23 -0
  79. package/lib/esm/abis.d.ts +3170 -0
  80. package/lib/esm/abis.js +4070 -0
  81. package/lib/esm/api/MidnightApi.d.ts +532 -0
  82. package/lib/esm/api/MidnightApi.js +709 -0
  83. package/lib/esm/api/helpers.d.ts +20 -0
  84. package/lib/esm/api/helpers.js +180 -0
  85. package/lib/esm/api/index.d.ts +2 -0
  86. package/lib/esm/api/index.js +2 -0
  87. package/lib/esm/api/types.d.ts +453 -0
  88. package/lib/esm/api/types.js +1 -0
  89. package/lib/esm/constants.d.ts +168 -0
  90. package/lib/esm/constants.js +184 -0
  91. package/lib/esm/errors.d.ts +253 -0
  92. package/lib/esm/errors.js +291 -0
  93. package/lib/esm/fetch/Market.d.ts +65 -0
  94. package/lib/esm/fetch/Market.js +113 -0
  95. package/lib/esm/fetch/Offer.d.ts +74 -0
  96. package/lib/esm/fetch/Offer.js +116 -0
  97. package/lib/esm/fetch/Position.d.ts +85 -0
  98. package/lib/esm/fetch/Position.js +144 -0
  99. package/lib/esm/fetch/Ratifier.d.ts +38 -0
  100. package/lib/esm/fetch/Ratifier.js +54 -0
  101. package/lib/esm/fetch/index.d.ts +5 -0
  102. package/lib/esm/fetch/index.js +5 -0
  103. package/lib/esm/fetch/types.d.ts +57 -0
  104. package/lib/esm/fetch/types.js +1 -0
  105. package/lib/esm/fetch/utils.d.ts +8 -0
  106. package/lib/esm/fetch/utils.js +12 -0
  107. package/lib/esm/index.d.ts +18 -0
  108. package/lib/esm/index.js +18 -0
  109. package/lib/esm/market/Market.d.ts +613 -0
  110. package/lib/esm/market/Market.js +498 -0
  111. package/lib/esm/market/MarketUtils.d.ts +189 -0
  112. package/lib/esm/market/MarketUtils.js +300 -0
  113. package/lib/esm/market/Position.d.ts +424 -0
  114. package/lib/esm/market/Position.js +420 -0
  115. package/lib/esm/market/PositionUtils.d.ts +115 -0
  116. package/lib/esm/market/PositionUtils.js +191 -0
  117. package/lib/esm/market/index.d.ts +4 -0
  118. package/lib/esm/market/index.js +4 -0
  119. package/lib/esm/math/ConsumableUnitsLib.d.ts +57 -0
  120. package/lib/esm/math/ConsumableUnitsLib.js +74 -0
  121. package/lib/esm/math/TakeAmountsLib.d.ts +175 -0
  122. package/lib/esm/math/TakeAmountsLib.js +196 -0
  123. package/lib/esm/math/TickLib.d.ts +200 -0
  124. package/lib/esm/math/TickLib.js +269 -0
  125. package/lib/esm/math/index.d.ts +3 -0
  126. package/lib/esm/math/index.js +3 -0
  127. package/lib/esm/offers/Offer.d.ts +534 -0
  128. package/lib/esm/offers/Offer.js +339 -0
  129. package/lib/esm/offers/OfferUtils.d.ts +565 -0
  130. package/lib/esm/offers/OfferUtils.js +656 -0
  131. package/lib/esm/offers/index.d.ts +2 -0
  132. package/lib/esm/offers/index.js +2 -0
  133. package/lib/esm/package.json +1 -0
  134. package/lib/esm/queries/GetPosition.d.ts +55 -0
  135. package/lib/esm/queries/GetPosition.js +71 -0
  136. package/lib/esm/queries/index.d.ts +1 -0
  137. package/lib/esm/queries/index.js +1 -0
  138. package/lib/esm/signatures/EcrecoverRatifierUtils.d.ts +733 -0
  139. package/lib/esm/signatures/EcrecoverRatifierUtils.js +615 -0
  140. package/lib/esm/signatures/Group.d.ts +178 -0
  141. package/lib/esm/signatures/Group.js +189 -0
  142. package/lib/esm/signatures/GroupUtils.d.ts +191 -0
  143. package/lib/esm/signatures/GroupUtils.js +124 -0
  144. package/lib/esm/signatures/Payload.d.ts +283 -0
  145. package/lib/esm/signatures/Payload.js +564 -0
  146. package/lib/esm/signatures/RatifierUtils.d.ts +109 -0
  147. package/lib/esm/signatures/RatifierUtils.js +73 -0
  148. package/lib/esm/signatures/SetterRatifierUtils.d.ts +243 -0
  149. package/lib/esm/signatures/SetterRatifierUtils.js +214 -0
  150. package/lib/esm/signatures/Tree.d.ts +241 -0
  151. package/lib/esm/signatures/Tree.js +259 -0
  152. package/lib/esm/signatures/TreeUtils.d.ts +573 -0
  153. package/lib/esm/signatures/TreeUtils.js +458 -0
  154. package/lib/esm/signatures/index.d.ts +7 -0
  155. package/lib/esm/signatures/index.js +7 -0
  156. package/package.json +69 -0
  157. package/src/index.ts +19 -0
@@ -0,0 +1,570 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DecodeError = exports.MAX_REQUEST_BODY_BYTES = exports.MAX_PAYLOAD_HEX_LENGTH = exports.MAX_DECOMPRESSED_ITEMS_BYTES = exports.MAX_COMPRESSED_ITEMS_BYTES = exports.MAX_PAYLOAD_BYTES = exports.MAX_ATTRIBUTION_SUFFIX_BYTES = exports.CURRENT_VERSION = void 0;
4
+ exports.encode = encode;
5
+ exports.decode = decode;
6
+ const morpho_ts_1 = require("@morpho-org/morpho-ts");
7
+ const viem_1 = require("viem");
8
+ const constants_js_1 = require("../constants.js");
9
+ const errors_js_1 = require("../errors.js");
10
+ const index_js_1 = require("../market/index.js");
11
+ const index_js_2 = require("../offers/index.js");
12
+ /**
13
+ * Current Midnight mempool payload wire version.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { Payload } from "@morpho-org/midnight-sdk";
18
+ *
19
+ * console.log(Payload.CURRENT_VERSION);
20
+ * ```
21
+ */
22
+ exports.CURRENT_VERSION = 1;
23
+ const VERSION_PREFIX_BYTES = 1;
24
+ const LENGTH_PREFIX_BYTES = 4;
25
+ const HEADER_BYTES = VERSION_PREFIX_BYTES + LENGTH_PREFIX_BYTES;
26
+ /**
27
+ * Upper bound on opaque bytes appended after the gzip stream (e.g. an app
28
+ * attribution tag). The suffix is meant to be small, so `decode` rejects a
29
+ * larger one: this keeps the `MAX_PAYLOAD_BYTES` ceiling meaningful for the
30
+ * whole input. Without it a tiny declared `gzipLen` could smuggle an
31
+ * arbitrarily large suffix past validation and into the indexer while still
32
+ * forcing the API to receive and hex-decode the whole blob.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * import { Payload } from "@morpho-org/midnight-sdk";
37
+ *
38
+ * console.log(Payload.MAX_ATTRIBUTION_SUFFIX_BYTES);
39
+ * ```
40
+ */
41
+ exports.MAX_ATTRIBUTION_SUFFIX_BYTES = 256;
42
+ /**
43
+ * Largest fully framed wire payload, in bytes, matching the router/onchain
44
+ * mempool cap.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { Payload } from "@morpho-org/midnight-sdk";
49
+ *
50
+ * console.log(Payload.MAX_PAYLOAD_BYTES);
51
+ * ```
52
+ */
53
+ exports.MAX_PAYLOAD_BYTES = 1_000_000;
54
+ /**
55
+ * Maximum gzip-compressed item bytes accepted by the payload codec.
56
+ *
57
+ * The budget reserves room for the fixed header and the largest accepted
58
+ * attribution suffix so the full wire payload never exceeds
59
+ * {@link MAX_PAYLOAD_BYTES}.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * import { Payload } from "@morpho-org/midnight-sdk";
64
+ *
65
+ * console.log(Payload.MAX_COMPRESSED_ITEMS_BYTES);
66
+ * ```
67
+ */
68
+ exports.MAX_COMPRESSED_ITEMS_BYTES = exports.MAX_PAYLOAD_BYTES - HEADER_BYTES - exports.MAX_ATTRIBUTION_SUFFIX_BYTES;
69
+ /**
70
+ * Maximum ABI-decoded item bytes accepted by the payload codec.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * import { Payload } from "@morpho-org/midnight-sdk";
75
+ *
76
+ * console.log(Payload.MAX_DECOMPRESSED_ITEMS_BYTES);
77
+ * ```
78
+ */
79
+ exports.MAX_DECOMPRESSED_ITEMS_BYTES = 6_000_000;
80
+ const ABI_ARRAY_HEAD_BYTES = 64;
81
+ const ABI_LENGTH_LOW_OFFSET = 60;
82
+ const MAX_TIMESTAMP_SECONDS = 1000000000000n - 1n;
83
+ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
84
+ const ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
85
+ /**
86
+ * Largest `0x`-prefixed hex string that can encode a wire payload.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * import { Payload } from "@morpho-org/midnight-sdk";
91
+ *
92
+ * console.log(Payload.MAX_PAYLOAD_HEX_LENGTH);
93
+ * ```
94
+ */
95
+ exports.MAX_PAYLOAD_HEX_LENGTH = "0x".length + exports.MAX_PAYLOAD_BYTES * 2;
96
+ /**
97
+ * Largest inbound HTTP request body, in bytes, accepted by the API and
98
+ * gatekeeper APIs.
99
+ *
100
+ * @example
101
+ * ```ts
102
+ * import { Payload } from "@morpho-org/midnight-sdk";
103
+ *
104
+ * console.log(Payload.MAX_REQUEST_BODY_BYTES);
105
+ * ```
106
+ */
107
+ exports.MAX_REQUEST_BODY_BYTES = exports.MAX_PAYLOAD_HEX_LENGTH + 1_024;
108
+ const offerStructAbiComponents = [
109
+ {
110
+ name: "market",
111
+ type: "tuple",
112
+ components: [
113
+ { name: "loanToken", type: "address" },
114
+ {
115
+ name: "collateralParams",
116
+ type: "tuple[]",
117
+ components: [
118
+ { name: "token", type: "address" },
119
+ { name: "lltv", type: "uint256" },
120
+ { name: "maxLif", type: "uint256" },
121
+ { name: "oracle", type: "address" },
122
+ ],
123
+ },
124
+ { name: "maturity", type: "uint256" },
125
+ { name: "rcfThreshold", type: "uint256" },
126
+ { name: "enterGate", type: "address" },
127
+ { name: "liquidatorGate", type: "address" },
128
+ ],
129
+ },
130
+ { name: "buy", type: "bool" },
131
+ { name: "maker", type: "address" },
132
+ { name: "start", type: "uint256" },
133
+ { name: "expiry", type: "uint256" },
134
+ { name: "tick", type: "uint256" },
135
+ { name: "group", type: "bytes32" },
136
+ { name: "callback", type: "address" },
137
+ { name: "callbackData", type: "bytes" },
138
+ { name: "receiverIfMakerIsSeller", type: "address" },
139
+ { name: "ratifier", type: "address" },
140
+ { name: "reduceOnly", type: "bool" },
141
+ { name: "maxUnits", type: "uint256" },
142
+ { name: "maxAssets", type: "uint256" },
143
+ { name: "continuousFeeCap", type: "uint256" },
144
+ ];
145
+ const itemsAbi = [
146
+ {
147
+ name: "items",
148
+ type: "tuple[]",
149
+ components: [
150
+ { name: "offer", type: "tuple", components: offerStructAbiComponents },
151
+ { name: "ratifierData", type: "bytes" },
152
+ ],
153
+ },
154
+ ];
155
+ /**
156
+ * ABI-encode `items` as `(Offer, bytes ratifierData)[]` and gzip the result,
157
+ * then emit the wire payload `version || uint32(gzipLen) || gzip(...)`. The
158
+ * single-byte version prefix lets future formats coexist on the wire; the
159
+ * 4-byte big-endian length delimits the gzip stream so `decode` finds its end
160
+ * without scanning. That delimiter is what lets a publisher append a small
161
+ * opaque suffix (e.g. an attribution tag) after the gzip stream — `decode`
162
+ * ignores up to `MAX_ATTRIBUTION_SUFFIX_BYTES` bytes past `gzipLen` and rejects
163
+ * a larger suffix. Item order is preserved verbatim and is the order a consumer
164
+ * sees on decode.
165
+ *
166
+ * Use after ratifier utilities have produced payload-ready items and before an
167
+ * onchain mempool submission. Normal SDK maker flows should validate the tree
168
+ * with `Tree.mempoolValidate` before ratification; use
169
+ * `MidnightApi.validateMempoolPayload` only when validating already encoded
170
+ * payload bytes.
171
+ *
172
+ * @param items - Per-leaf items in the order the maker committed to. Must
173
+ * contain at least one item and fit within payload byte-size limits.
174
+ * @returns The encoded payload as a `Hex` string, ready to include in onchain
175
+ * mempool submission calldata.
176
+ * @throws {DecodeError} when the item list is empty, encoding or compression
177
+ * exceeds SDK byte-size limits, or a non-empty offer fails payload validation
178
+ * for collateral requirements, maturity timing, expiry range, tick bounds, or
179
+ * `maxUnits`/`maxAssets` cap shape.
180
+ * @example
181
+ * ```ts
182
+ * import { Offer, Payload } from "@morpho-org/midnight-sdk";
183
+ * import { zeroAddress } from "viem";
184
+ *
185
+ * const offer = Offer.create({
186
+ * market: {
187
+ * loanToken: "0x0000000000000000000000000000000000006000",
188
+ * collateralParams: [
189
+ * {
190
+ * token: "0x0000000000000000000000000000000000007000",
191
+ * lltv: 770000000000000000n,
192
+ * maxLif: 1061007957559681697n,
193
+ * oracle: "0x0000000000000000000000000000000000008000",
194
+ * },
195
+ * ],
196
+ * maturity: 54_000n,
197
+ * rcfThreshold: 0n,
198
+ * enterGate: zeroAddress,
199
+ * liquidatorGate: zeroAddress,
200
+ * },
201
+ * buy: true,
202
+ * maker: "0x0000000000000000000000000000000000009000",
203
+ * tick: 5_000n,
204
+ * expiry: 3_600n,
205
+ * ratifier: "0x0000000000000000000000000000000000004000",
206
+ * maxUnits: 100n,
207
+ * });
208
+ * const encoded = await Payload.encode([{ offer, ratifierData: "0x" }]);
209
+ * console.log(encoded);
210
+ * ```
211
+ */
212
+ async function encode(items) {
213
+ if (exports.CURRENT_VERSION > 0xff) {
214
+ throw new DecodeError(`version overflow: ${exports.CURRENT_VERSION} exceeds 255`);
215
+ }
216
+ assertNonEmptyItemCount(items.length);
217
+ const payloadItems = items.map((item) => {
218
+ const offer = index_js_2.OfferUtils.toStruct({ offer: item.offer });
219
+ assertApiValidOfferStruct(offer);
220
+ return {
221
+ offer,
222
+ ratifierData: item.ratifierData,
223
+ };
224
+ });
225
+ const itemBytes = (0, viem_1.hexToBytes)((0, viem_1.encodeAbiParameters)(itemsAbi, [payloadItems]));
226
+ const compressed = await compressItemsPayload(itemBytes);
227
+ const encoded = new Uint8Array(HEADER_BYTES + compressed.length);
228
+ encoded[0] = exports.CURRENT_VERSION;
229
+ new DataView(encoded.buffer, encoded.byteOffset, encoded.byteLength).setUint32(VERSION_PREFIX_BYTES, compressed.length);
230
+ encoded.set(compressed, HEADER_BYTES);
231
+ return (0, viem_1.bytesToHex)(encoded);
232
+ }
233
+ /**
234
+ * Parse a wire payload back into its `(offer, ratifierData)` items. The version
235
+ * byte is validated against `CURRENT_VERSION` and not surfaced — any payload
236
+ * that decodes successfully is on the current wire format. The 4-byte length
237
+ * prefix bounds the gzip stream; up to `MAX_ATTRIBUTION_SUFFIX_BYTES` bytes
238
+ * after it (e.g. an attribution suffix appended by the publishing app) are
239
+ * ignored, so a tagged payload decodes identically to an untagged one. A larger
240
+ * trailing suffix is rejected so an oversized blob cannot ride a small payload
241
+ * past validation and into the indexer.
242
+ *
243
+ * Use on the take-side, in indexers, or in diagnostics when you need to inspect
244
+ * the offer structs and ratifier data that a maker published.
245
+ *
246
+ * @param payload - Hex payload bytes to decode.
247
+ * @param options - Optional decode bounds.
248
+ * @returns The decoded items in encoded order.
249
+ * @throws {DecodeError} when the payload is malformed or exceeds protocol limits.
250
+ * @example
251
+ * ```ts
252
+ * import { Offer, Payload } from "@morpho-org/midnight-sdk";
253
+ * import { zeroAddress } from "viem";
254
+ *
255
+ * const offer = Offer.create({
256
+ * market: {
257
+ * loanToken: "0x0000000000000000000000000000000000006000",
258
+ * collateralParams: [
259
+ * {
260
+ * token: "0x0000000000000000000000000000000000007000",
261
+ * lltv: 770000000000000000n,
262
+ * maxLif: 1061007957559681697n,
263
+ * oracle: "0x0000000000000000000000000000000000008000",
264
+ * },
265
+ * ],
266
+ * maturity: 54_000n,
267
+ * rcfThreshold: 0n,
268
+ * enterGate: zeroAddress,
269
+ * liquidatorGate: zeroAddress,
270
+ * },
271
+ * buy: true,
272
+ * maker: "0x0000000000000000000000000000000000009000",
273
+ * tick: 5_000n,
274
+ * expiry: 3_600n,
275
+ * ratifier: "0x0000000000000000000000000000000000004000",
276
+ * maxUnits: 100n,
277
+ * });
278
+ * const encoded = await Payload.encode([{ offer, ratifierData: "0x" }]);
279
+ * const items = await Payload.decode(encoded);
280
+ * console.log(items.length);
281
+ * ```
282
+ */
283
+ async function decode(payload, options) {
284
+ const maxItems = resolveMaxItems(options?.maxItems);
285
+ if (payload.length > exports.MAX_PAYLOAD_HEX_LENGTH) {
286
+ throw new DecodeError(`payload hex exceeds ${exports.MAX_PAYLOAD_HEX_LENGTH} characters`);
287
+ }
288
+ const bytes = (0, viem_1.hexToBytes)(payload);
289
+ if (bytes.length < HEADER_BYTES) {
290
+ throw new DecodeError("payload too short for header");
291
+ }
292
+ const version = bytes[0];
293
+ if (version !== (exports.CURRENT_VERSION & 0xff)) {
294
+ throw new DecodeError(`invalid version: expected ${exports.CURRENT_VERSION}, got ${version}`);
295
+ }
296
+ const compressedLength = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(VERSION_PREFIX_BYTES);
297
+ const compressedEnd = HEADER_BYTES + compressedLength;
298
+ if (compressedEnd > bytes.length) {
299
+ throw new DecodeError("payload truncated before declared gzip length");
300
+ }
301
+ const suffixLength = bytes.length - compressedEnd;
302
+ if (suffixLength > exports.MAX_ATTRIBUTION_SUFFIX_BYTES) {
303
+ throw new DecodeError(`trailing suffix exceeds ${exports.MAX_ATTRIBUTION_SUFFIX_BYTES} bytes: got ${suffixLength}`);
304
+ }
305
+ const compressed = bytes.subarray(HEADER_BYTES, compressedEnd);
306
+ const decoded = await decompressItemsPayload(compressed);
307
+ assertAbiItemCountWithinCallerLimit(decoded, maxItems);
308
+ const items = decodeItemsBytes(decoded);
309
+ assertNonEmptyItemCount(items.length);
310
+ assertCallerItemCountLimit(items.length, maxItems);
311
+ assertCanonicalItemsBytes(items, decoded);
312
+ return items;
313
+ }
314
+ function decodeItemsBytes(decoded) {
315
+ let raw;
316
+ try {
317
+ [raw] = (0, viem_1.decodeAbiParameters)(itemsAbi, (0, viem_1.bytesToHex)(decoded));
318
+ }
319
+ catch (error) {
320
+ throw new DecodeError("items abi decode failed", error instanceof Error ? error : undefined);
321
+ }
322
+ try {
323
+ return raw.map((entry) => {
324
+ assertApiValidOfferStruct(entry.offer);
325
+ return {
326
+ offer: index_js_2.Offer.from(entry.offer),
327
+ ratifierData: entry.ratifierData,
328
+ };
329
+ });
330
+ }
331
+ catch (error) {
332
+ if (error instanceof DecodeError)
333
+ throw error;
334
+ if (error instanceof Error) {
335
+ throw new DecodeError(`invalid offer bytes: ${error.message}`, error);
336
+ }
337
+ throw error;
338
+ }
339
+ }
340
+ function assertCanonicalItemsBytes(items, decoded) {
341
+ const canonical = (0, viem_1.encodeAbiParameters)(itemsAbi, [
342
+ items.map((item) => ({
343
+ offer: index_js_2.OfferUtils.toStruct({ offer: item.offer }),
344
+ ratifierData: item.ratifierData,
345
+ })),
346
+ ]);
347
+ if (canonical !== (0, viem_1.bytesToHex)(decoded)) {
348
+ throw new DecodeError("items payload has non-canonical ABI bytes");
349
+ }
350
+ }
351
+ function assertApiValidOfferStruct(offer) {
352
+ if (isEmptyOfferStruct(offer))
353
+ return;
354
+ assertCollateralParams(offer);
355
+ assertSafeTimestamp("market.maturity", offer.market.maturity);
356
+ assertMaturityAt15Utc(offer.market.maturity);
357
+ assertSafeTimestamp("start", offer.start);
358
+ assertSafeTimestamp("expiry", offer.expiry);
359
+ assertApiValidOfferTimeRange(offer);
360
+ if (offer.buy && !isZeroAddress(offer.receiverIfMakerIsSeller)) {
361
+ throw new DecodeError("invalid offer bytes: receiverIfMakerIsSeller must be zero for buy offers");
362
+ }
363
+ if (offer.expiry > offer.market.maturity) {
364
+ throw new DecodeError("invalid offer bytes: expiry must be before or equal to maturity");
365
+ }
366
+ if (offer.tick > constants_js_1.MAX_TICK) {
367
+ throw new DecodeError(`invalid offer bytes: tick exceeds maximum ${constants_js_1.MAX_TICK}`);
368
+ }
369
+ if ((offer.maxUnits === 0n) === (offer.maxAssets === 0n)) {
370
+ throw new DecodeError("invalid offer bytes: exactly one of maxUnits and maxAssets must be non-zero");
371
+ }
372
+ if (offer.continuousFeeCap < 0n) {
373
+ throw new DecodeError("invalid offer bytes: continuousFeeCap must be non-negative");
374
+ }
375
+ }
376
+ function assertApiValidOfferTimeRange(offer) {
377
+ try {
378
+ index_js_2.OfferUtils.validateOfferTimeRange(offer);
379
+ }
380
+ catch (error) {
381
+ if (error instanceof errors_js_1.InvalidOfferParameterError) {
382
+ throw new DecodeError(`invalid offer bytes: ${error.message}`, error);
383
+ }
384
+ throw error;
385
+ }
386
+ }
387
+ function assertCollateralParams(offer) {
388
+ const collateralParams = offer.market.collateralParams;
389
+ if (collateralParams.length === 0) {
390
+ throw new DecodeError("invalid offer bytes: at least one collateral required");
391
+ }
392
+ if (collateralParams.length > Number(constants_js_1.MAX_COLLATERALS)) {
393
+ throw new DecodeError(`invalid offer bytes: market.collateralParams has ${collateralParams.length} collaterals, exceeding the maximum of ${constants_js_1.MAX_COLLATERALS}`);
394
+ }
395
+ let previousToken;
396
+ for (const params of collateralParams) {
397
+ if (params.lltv < 0n || params.lltv > morpho_ts_1.MathLib.WAD) {
398
+ throw new DecodeError(`invalid offer bytes: collateral lltv must be between 0 and WAD, got ${params.lltv}`);
399
+ }
400
+ const lowMaxLif = index_js_1.MarketUtils.getLiquidationIncentiveFactor(params, constants_js_1.LIQUIDATION_CURSOR_LOW);
401
+ const highMaxLif = index_js_1.MarketUtils.getLiquidationIncentiveFactor(params, constants_js_1.LIQUIDATION_CURSOR_HIGH);
402
+ if (params.maxLif !== lowMaxLif && params.maxLif !== highMaxLif) {
403
+ throw new DecodeError(`invalid offer bytes: invalid collateral maxLif ${params.maxLif}`);
404
+ }
405
+ const token = params.token.toLowerCase();
406
+ if (isZeroAddress(token)) {
407
+ throw new DecodeError("invalid offer bytes: collateral token must be non-zero");
408
+ }
409
+ if (previousToken != null && previousToken >= token) {
410
+ throw new DecodeError("invalid offer bytes: collaterals must be sorted and unique");
411
+ }
412
+ previousToken = token;
413
+ }
414
+ }
415
+ function assertSafeTimestamp(field, value) {
416
+ if (value > MAX_TIMESTAMP_SECONDS) {
417
+ throw new DecodeError(`invalid offer bytes: ${field} exceeds ${MAX_TIMESTAMP_SECONDS}`);
418
+ }
419
+ }
420
+ function assertMaturityAt15Utc(value) {
421
+ const date = new Date(Number(value) * 1000);
422
+ if (date.getUTCHours() !== 15 ||
423
+ date.getUTCMinutes() !== 0 ||
424
+ date.getUTCSeconds() !== 0 ||
425
+ date.getUTCMilliseconds() !== 0) {
426
+ throw new DecodeError("invalid offer bytes: maturity must be at 15:00:00 UTC");
427
+ }
428
+ }
429
+ function isEmptyOfferStruct(offer) {
430
+ return (isZeroAddress(offer.market.loanToken) &&
431
+ offer.market.collateralParams.length === 0 &&
432
+ offer.market.maturity === 0n &&
433
+ offer.market.rcfThreshold === 0n &&
434
+ isZeroAddress(offer.market.enterGate) &&
435
+ isZeroAddress(offer.market.liquidatorGate) &&
436
+ offer.buy === false &&
437
+ isZeroAddress(offer.maker) &&
438
+ offer.start === 0n &&
439
+ offer.expiry === 0n &&
440
+ offer.tick === 0n &&
441
+ offer.group === ZERO_BYTES32 &&
442
+ isZeroAddress(offer.callback) &&
443
+ offer.callbackData === "0x" &&
444
+ isZeroAddress(offer.receiverIfMakerIsSeller) &&
445
+ isZeroAddress(offer.ratifier) &&
446
+ offer.reduceOnly === false &&
447
+ offer.maxUnits === 0n &&
448
+ offer.maxAssets === 0n &&
449
+ offer.continuousFeeCap === 0n);
450
+ }
451
+ function isZeroAddress(value) {
452
+ return value.toLowerCase() === ZERO_ADDRESS;
453
+ }
454
+ function resolveMaxItems(maxItems) {
455
+ if (maxItems === undefined)
456
+ return undefined;
457
+ if (!Number.isInteger(maxItems) || maxItems < 1) {
458
+ throw new DecodeError(`maxItems must be a positive integer: got ${maxItems}`);
459
+ }
460
+ return maxItems;
461
+ }
462
+ function assertNonEmptyItemCount(count) {
463
+ if (count === 0)
464
+ throw new DecodeError("items payload is empty");
465
+ }
466
+ function assertCallerItemCountLimit(count, maxItems) {
467
+ if (maxItems === undefined)
468
+ return;
469
+ if (count > maxItems) {
470
+ throw new DecodeError(`items payload exceeds ${maxItems} items`);
471
+ }
472
+ }
473
+ async function compressItemsPayload(payload) {
474
+ if (payload.length > exports.MAX_DECOMPRESSED_ITEMS_BYTES) {
475
+ throw new DecodeError(`decompressed items exceed ${exports.MAX_DECOMPRESSED_ITEMS_BYTES} bytes`);
476
+ }
477
+ let compressed;
478
+ try {
479
+ const stream = singleChunkStream(payload).pipeThrough(new CompressionStream("gzip"));
480
+ compressed = new Uint8Array(await new Response(stream).arrayBuffer());
481
+ }
482
+ catch (error) {
483
+ throw new DecodeError("compression failed", error instanceof Error ? error : undefined);
484
+ }
485
+ if (compressed.length > exports.MAX_COMPRESSED_ITEMS_BYTES) {
486
+ throw new DecodeError(`compressed items exceed ${exports.MAX_COMPRESSED_ITEMS_BYTES} bytes`);
487
+ }
488
+ return compressed;
489
+ }
490
+ async function decompressItemsPayload(compressed) {
491
+ if (compressed.length === 0)
492
+ throw new DecodeError("decompression failed");
493
+ if (compressed.length > exports.MAX_COMPRESSED_ITEMS_BYTES) {
494
+ throw new DecodeError(`compressed items exceed ${exports.MAX_COMPRESSED_ITEMS_BYTES} bytes`);
495
+ }
496
+ const reader = singleChunkStream(compressed)
497
+ .pipeThrough(new DecompressionStream("gzip"))
498
+ .getReader();
499
+ const chunks = [];
500
+ let totalLength = 0;
501
+ try {
502
+ while (true) {
503
+ const { done, value } = await reader.read();
504
+ if (done)
505
+ break;
506
+ totalLength += value.length;
507
+ if (totalLength > exports.MAX_DECOMPRESSED_ITEMS_BYTES) {
508
+ await reader.cancel();
509
+ throw new DecodeError(`decompressed items exceed ${exports.MAX_DECOMPRESSED_ITEMS_BYTES} bytes`);
510
+ }
511
+ chunks.push(value);
512
+ }
513
+ }
514
+ catch (error) {
515
+ if (error instanceof DecodeError)
516
+ throw error;
517
+ throw new DecodeError("decompression failed", error instanceof Error ? error : undefined);
518
+ }
519
+ const result = new Uint8Array(totalLength);
520
+ let offset = 0;
521
+ for (const chunk of chunks) {
522
+ result.set(chunk, offset);
523
+ offset += chunk.length;
524
+ }
525
+ return result;
526
+ }
527
+ function singleChunkStream(bytes) {
528
+ return new ReadableStream({
529
+ start(controller) {
530
+ controller.enqueue(new Uint8Array(bytes));
531
+ controller.close();
532
+ },
533
+ });
534
+ }
535
+ function assertAbiItemCountWithinCallerLimit(decoded, maxItems) {
536
+ if (decoded.length < ABI_ARRAY_HEAD_BYTES) {
537
+ throw new DecodeError("items payload truncated before ABI array head");
538
+ }
539
+ if (maxItems === undefined)
540
+ return;
541
+ for (let i = 32; i < ABI_LENGTH_LOW_OFFSET; i++) {
542
+ if (decoded[i] !== 0) {
543
+ throw new DecodeError(`items payload exceeds ${maxItems} items`);
544
+ }
545
+ }
546
+ const length = new DataView(decoded.buffer, decoded.byteOffset, decoded.byteLength).getUint32(ABI_LENGTH_LOW_OFFSET);
547
+ if (length > maxItems) {
548
+ throw new DecodeError(`items payload exceeds ${maxItems} items`);
549
+ }
550
+ }
551
+ /**
552
+ * Error thrown when a Midnight mempool payload cannot be decoded.
553
+ *
554
+ * @example
555
+ * ```ts
556
+ * import { Payload } from "@morpho-org/midnight-sdk";
557
+ *
558
+ * throw new Payload.DecodeError("payload too short for header");
559
+ * ```
560
+ */
561
+ class DecodeError extends Error {
562
+ /** Machine-readable decode failure reason without the class message prefix. */
563
+ reason;
564
+ constructor(reason, cause) {
565
+ super(`Failed to decode payload: ${reason}`, cause ? { cause } : undefined);
566
+ this.name = "Payload.DecodeError";
567
+ this.reason = reason;
568
+ }
569
+ }
570
+ exports.DecodeError = DecodeError;
@@ -0,0 +1,109 @@
1
+ import type { Address, Hex } from "viem";
2
+ /**
3
+ * Parameters for {@link RatifierUtils.getRatifierInfo}.
4
+ *
5
+ * Pass the maker account bytecode read at the same block context used to build
6
+ * the offer. The bytecode is used only to choose the ratifier address to put on
7
+ * the offer.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import type { GetRatifierInfoParams } from "@morpho-org/midnight-sdk";
12
+ *
13
+ * const params: GetRatifierInfoParams = {
14
+ * bytecode: "0x",
15
+ * ecrecoverRatifier: "0x0000000000000000000000000000000000000001",
16
+ * setterRatifier: "0x0000000000000000000000000000000000000002",
17
+ * };
18
+ * console.log(params.bytecode);
19
+ * ```
20
+ */
21
+ export interface GetRatifierInfoParams {
22
+ /** Maker account bytecode returned by viem `getBytecode`; `undefined`, `null`, and `0x` mean no deployed code. */
23
+ readonly bytecode?: Hex | null;
24
+ /** Ecrecover ratifier address. */
25
+ readonly ecrecoverRatifier: Address;
26
+ /** Setter ratifier address. */
27
+ readonly setterRatifier: Address;
28
+ }
29
+ /**
30
+ * Classification of the ratifier route for a maker account.
31
+ *
32
+ * Put `ratifier` on each new `Offer.create` call for this maker. Use `type` to
33
+ * decide whether the tree later needs an Ecrecover signature or a Setter root
34
+ * approval before payload encoding.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import type { RatifierInfo } from "@morpho-org/midnight-sdk";
39
+ *
40
+ * const info: RatifierInfo = {
41
+ * type: "ecrecover",
42
+ * ratifier: "0x0000000000000000000000000000000000000001",
43
+ * };
44
+ * ```
45
+ */
46
+ export interface RatifierInfo {
47
+ /** Ratifier family selected for the maker account. */
48
+ readonly type: "ecrecover" | "setter";
49
+ /** Ratifier contract address to put on the offer. */
50
+ readonly ratifier: Address;
51
+ }
52
+ /**
53
+ * Utilities for selecting Midnight ratifier routes.
54
+ *
55
+ * Call these during make-side preparation, before `Offer.create`, when an app
56
+ * must decide whether a maker can sign an Ecrecover root or must approve a
57
+ * Setter root onchain. Fetch helpers read bytecode for you; this namespace is
58
+ * the pure classification logic.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { RatifierUtils } from "@morpho-org/midnight-sdk";
63
+ *
64
+ * console.log(RatifierUtils.isEip7702Designator("0xef0100"));
65
+ * ```
66
+ */
67
+ export declare namespace RatifierUtils {
68
+ /**
69
+ * Checks whether bytecode is an EIP-7702 designator.
70
+ *
71
+ * EIP-7702 accounts have code-like bytecode but still sign as the owning EOA,
72
+ * so they use the Ecrecover ratifier route instead of the Setter route.
73
+ *
74
+ * @param bytecode - Account bytecode.
75
+ * @returns Whether the bytecode starts with `0xef0100`.
76
+ * @example
77
+ * ```ts
78
+ * import { RatifierUtils } from "@morpho-org/midnight-sdk";
79
+ *
80
+ * console.log(RatifierUtils.isEip7702Designator("0xef0100"));
81
+ * ```
82
+ */
83
+ function isEip7702Designator(bytecode: Hex): boolean;
84
+ /**
85
+ * Selects Ecrecover for EOAs/EIP-7702 accounts and Setter for deployed-code
86
+ * accounts.
87
+ *
88
+ * Use the returned `ratifier` address in `Offer.create`. Later, use
89
+ * `EcrecoverRatifierUtils.ratify` when `type` is `ecrecover`, or approve the
90
+ * root and call `SetterRatifierUtils.ratify` when `type` is `setter`.
91
+ *
92
+ * @param params.bytecode - Maker bytecode returned by `eth_getCode`.
93
+ * @param params.ecrecoverRatifier - Ratifier address used for EOAs and EIP-7702 accounts.
94
+ * @param params.setterRatifier - Ratifier address used for deployed-code accounts.
95
+ * @returns Ratifier information for the maker.
96
+ * @example
97
+ * ```ts
98
+ * import { RatifierUtils } from "@morpho-org/midnight-sdk";
99
+ *
100
+ * const info = RatifierUtils.getRatifierInfo({
101
+ * bytecode: "0x",
102
+ * ecrecoverRatifier: "0x0000000000000000000000000000000000000001",
103
+ * setterRatifier: "0x0000000000000000000000000000000000000002",
104
+ * });
105
+ * console.log(info.type);
106
+ * ```
107
+ */
108
+ function getRatifierInfo(params: GetRatifierInfoParams): RatifierInfo;
109
+ }