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