@morpho-dev/router 0.2.1 → 0.3.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/dist/cli.js +1200 -559
- package/dist/drizzle/migrations/0015_add-lots-table.sql +12 -0
- package/dist/drizzle/migrations/0016_merkle-metadata.sql +26 -0
- package/dist/drizzle/migrations/meta/0015_snapshot.json +1365 -0
- package/dist/drizzle/migrations/meta/0016_snapshot.json +1531 -0
- package/dist/drizzle/migrations/meta/_journal.json +14 -0
- package/dist/index.browser.d.mts +443 -229
- package/dist/index.browser.d.mts.map +1 -1
- package/dist/index.browser.d.ts +439 -229
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +533 -202
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +534 -203
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.node.d.mts +737 -67
- package/dist/index.node.d.mts.map +1 -1
- package/dist/index.node.d.ts +737 -67
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +1240 -513
- package/dist/index.node.js.map +1 -1
- package/dist/index.node.mjs +1241 -514
- package/dist/index.node.mjs.map +1 -1
- package/package.json +1 -2
package/dist/index.browser.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __export } from "./chunk-jass6xSI.mjs";
|
|
2
2
|
import { z } from "zod/v4";
|
|
3
|
-
import { bytesToHex, decodeAbiParameters, encodeAbiParameters, getAddress, hashTypedData, hexToBytes, isAddress, isHex, keccak256, maxUint256, parseAbi, publicActions, zeroAddress } from "viem";
|
|
3
|
+
import { bytesToHex, decodeAbiParameters, encodeAbiParameters, getAddress, hashMessage, hashTypedData, hexToBytes, isAddress, isHex, keccak256, maxUint256, parseAbi, publicActions, recoverAddress, zeroAddress } from "viem";
|
|
4
4
|
import "reflect-metadata";
|
|
5
5
|
import { generateDocument } from "openapi-metadata";
|
|
6
6
|
import { ApiBody, ApiOperation, ApiProperty, ApiQuery, ApiResponse, ApiTags } from "openapi-metadata/decorators";
|
|
@@ -9,7 +9,6 @@ import { Base64 } from "js-base64";
|
|
|
9
9
|
import createOpenApiFetchClient from "openapi-fetch";
|
|
10
10
|
import { getBlock, getLogs, multicall } from "viem/actions";
|
|
11
11
|
import { anvil, base, mainnet } from "viem/chains";
|
|
12
|
-
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
|
|
13
12
|
import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
|
|
14
13
|
import { gzip, ungzip } from "pako";
|
|
15
14
|
|
|
@@ -117,13 +116,16 @@ var OfferResponse_exports = /* @__PURE__ */ __export({ from: () => from$11 });
|
|
|
117
116
|
* Creates an `OfferResponse` from an `Offer`.
|
|
118
117
|
* @constructor
|
|
119
118
|
* @param offer - {@link Offer}
|
|
119
|
+
* @param attestation - {@link Attestation}
|
|
120
120
|
* @returns The created `OfferResponse`. {@link OfferResponse}
|
|
121
121
|
*/
|
|
122
|
-
function from$11(offer) {
|
|
123
|
-
const
|
|
122
|
+
function from$11(offer, attestation) {
|
|
123
|
+
const { signature: _, ...rest } = toSnakeCase$1(offer);
|
|
124
124
|
return {
|
|
125
|
-
...
|
|
126
|
-
|
|
125
|
+
...rest,
|
|
126
|
+
root: attestation?.root.toLowerCase() ?? null,
|
|
127
|
+
proof: attestation?.proof.map((p) => p.toLowerCase()) ?? null,
|
|
128
|
+
signature: attestation?.signature.toLowerCase() ?? null
|
|
127
129
|
};
|
|
128
130
|
}
|
|
129
131
|
|
|
@@ -172,10 +174,12 @@ const offerExample = {
|
|
|
172
174
|
data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000034cf890db685fc536e05652fb41f02090c3fb751000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000108e644e3ab01184155270aa92a00000000000",
|
|
173
175
|
gas_limit: "500000"
|
|
174
176
|
},
|
|
175
|
-
signature: "0x1234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123400",
|
|
176
177
|
consumed: "0",
|
|
177
178
|
takeable: "369216000000000000000000",
|
|
178
|
-
block_number: 0xa7495128adfb1
|
|
179
|
+
block_number: 0xa7495128adfb1,
|
|
180
|
+
root: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
|
181
|
+
proof: ["0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba"],
|
|
182
|
+
signature: "0x1234567890123456789012345678901234567890123456789012345678901234123456789012345678901234567890123456789012345678901234567890123400"
|
|
179
183
|
};
|
|
180
184
|
const collectorsHealthExample = {
|
|
181
185
|
name: "offers",
|
|
@@ -320,6 +324,16 @@ __decorate([ApiProperty({
|
|
|
320
324
|
type: "number",
|
|
321
325
|
example: offerExample.block_number
|
|
322
326
|
})], OfferListItemResponse.prototype, "block_number", void 0);
|
|
327
|
+
__decorate([ApiProperty({
|
|
328
|
+
type: "string",
|
|
329
|
+
nullable: true,
|
|
330
|
+
example: offerExample.root
|
|
331
|
+
})], OfferListItemResponse.prototype, "root", void 0);
|
|
332
|
+
__decorate([ApiProperty({
|
|
333
|
+
type: [String],
|
|
334
|
+
nullable: true,
|
|
335
|
+
example: offerExample.proof
|
|
336
|
+
})], OfferListItemResponse.prototype, "proof", void 0);
|
|
323
337
|
__decorate([ApiProperty({
|
|
324
338
|
type: "string",
|
|
325
339
|
nullable: true,
|
|
@@ -503,44 +517,61 @@ __decorate([ApiProperty({
|
|
|
503
517
|
var ValidateOffersRequest = class {};
|
|
504
518
|
__decorate([ApiProperty({
|
|
505
519
|
type: () => [ValidateOfferRequest],
|
|
506
|
-
description: "Array of offers in snake_case format.
|
|
507
|
-
required:
|
|
520
|
+
description: "Array of offers in snake_case format. Required, non-empty.",
|
|
521
|
+
required: true
|
|
508
522
|
})], ValidateOffersRequest.prototype, "offers", void 0);
|
|
523
|
+
var ValidationSuccessDataResponse = class {};
|
|
509
524
|
__decorate([ApiProperty({
|
|
510
525
|
type: "string",
|
|
511
|
-
description: "
|
|
512
|
-
example: "
|
|
513
|
-
|
|
514
|
-
})], ValidateOffersRequest.prototype, "calldata", void 0);
|
|
515
|
-
var ValidateOfferResultResponse = class {};
|
|
526
|
+
description: "Unsigned payload: version (1B) + gzip(offers) + root (32B).",
|
|
527
|
+
example: "0x01789c..."
|
|
528
|
+
})], ValidationSuccessDataResponse.prototype, "payload", void 0);
|
|
516
529
|
__decorate([ApiProperty({
|
|
517
530
|
type: "string",
|
|
518
|
-
|
|
519
|
-
|
|
531
|
+
description: "Merkle tree root to sign with EIP-191.",
|
|
532
|
+
example: "0xac4bd8318ec914f89f8af913f162230575b0ac0696a19256bc12138c5cfe1427"
|
|
533
|
+
})], ValidationSuccessDataResponse.prototype, "root", void 0);
|
|
534
|
+
var ValidationSuccessResponse = class extends SuccessResponse {};
|
|
520
535
|
__decorate([ApiProperty({
|
|
521
|
-
type: "
|
|
522
|
-
|
|
523
|
-
|
|
536
|
+
type: "string",
|
|
537
|
+
nullable: true,
|
|
538
|
+
example: null
|
|
539
|
+
})], ValidationSuccessResponse.prototype, "cursor", void 0);
|
|
540
|
+
__decorate([ApiProperty({
|
|
541
|
+
type: () => ValidationSuccessDataResponse,
|
|
542
|
+
description: "Payload and root for client-side signing."
|
|
543
|
+
})], ValidationSuccessResponse.prototype, "data", void 0);
|
|
544
|
+
var ValidationIssueResponse = class {};
|
|
545
|
+
__decorate([ApiProperty({
|
|
546
|
+
type: "number",
|
|
547
|
+
description: "0-indexed position of the failed offer in the request array.",
|
|
548
|
+
example: 0
|
|
549
|
+
})], ValidationIssueResponse.prototype, "index", void 0);
|
|
524
550
|
__decorate([ApiProperty({
|
|
525
551
|
type: "string",
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
})],
|
|
552
|
+
description: "Gatekeeper rule name that rejected the offer.",
|
|
553
|
+
example: "no_buy"
|
|
554
|
+
})], ValidationIssueResponse.prototype, "rule", void 0);
|
|
529
555
|
__decorate([ApiProperty({
|
|
530
556
|
type: "string",
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
})],
|
|
534
|
-
var
|
|
557
|
+
description: "Human-readable rejection reason.",
|
|
558
|
+
example: "Buy offers are not supported"
|
|
559
|
+
})], ValidationIssueResponse.prototype, "message", void 0);
|
|
560
|
+
var ValidationFailureDataResponse = class {};
|
|
561
|
+
__decorate([ApiProperty({
|
|
562
|
+
type: () => [ValidationIssueResponse],
|
|
563
|
+
description: "List of validation issues. Returned when any offer fails validation."
|
|
564
|
+
})], ValidationFailureDataResponse.prototype, "issues", void 0);
|
|
565
|
+
var ValidationFailureResponse = class extends SuccessResponse {};
|
|
535
566
|
__decorate([ApiProperty({
|
|
536
567
|
type: "string",
|
|
537
568
|
nullable: true,
|
|
538
569
|
example: null
|
|
539
|
-
})],
|
|
570
|
+
})], ValidationFailureResponse.prototype, "cursor", void 0);
|
|
540
571
|
__decorate([ApiProperty({
|
|
541
|
-
type: () =>
|
|
542
|
-
description: "
|
|
543
|
-
})],
|
|
572
|
+
type: () => ValidationFailureDataResponse,
|
|
573
|
+
description: "List of validation issues. Returned when any offer fails validation."
|
|
574
|
+
})], ValidationFailureResponse.prototype, "data", void 0);
|
|
544
575
|
var BookLevelResponse = class {};
|
|
545
576
|
__decorate([ApiProperty({
|
|
546
577
|
type: "string",
|
|
@@ -605,13 +636,18 @@ __decorate([
|
|
|
605
636
|
methods: ["post"],
|
|
606
637
|
path: "/v1/validate",
|
|
607
638
|
summary: "Validate offers",
|
|
608
|
-
description: "Validates offers against router validation rules. Returns
|
|
639
|
+
description: "Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure."
|
|
609
640
|
}),
|
|
610
641
|
ApiBody({ type: ValidateOffersRequest }),
|
|
611
642
|
ApiResponse({
|
|
612
643
|
status: 200,
|
|
613
644
|
description: "Success",
|
|
614
|
-
type:
|
|
645
|
+
type: ValidationSuccessResponse
|
|
646
|
+
}),
|
|
647
|
+
ApiResponse({
|
|
648
|
+
status: 200,
|
|
649
|
+
description: "Validation issues",
|
|
650
|
+
type: ValidationFailureResponse
|
|
615
651
|
})
|
|
616
652
|
], ValidateController.prototype, "validateOffers", null);
|
|
617
653
|
ValidateController = __decorate([ApiTags("Validate"), ApiResponse({
|
|
@@ -780,7 +816,7 @@ const OpenApi = async (options = {}) => {
|
|
|
780
816
|
if (options.rules && options.rules.length > 0) {
|
|
781
817
|
const rulesDescription = options.rules.map((rule) => `- **${rule.name}**: ${rule.description}`).join("\n");
|
|
782
818
|
const validatePath = document.paths?.["/v1/validate"];
|
|
783
|
-
if (validatePath && "post" in validatePath && validatePath.post) validatePath.post.description = `Validates offers against router validation rules. Returns
|
|
819
|
+
if (validatePath && "post" in validatePath && validatePath.post) validatePath.post.description = `Validates offers against router validation rules. Returns unsigned payload + root on success, or issues only on validation failure.\n\n**Available validation rules:**\n${rulesDescription}`;
|
|
784
820
|
}
|
|
785
821
|
return document;
|
|
786
822
|
};
|
|
@@ -792,17 +828,23 @@ var Cursor_exports = /* @__PURE__ */ __export({
|
|
|
792
828
|
encode: () => encode$3,
|
|
793
829
|
validate: () => validate
|
|
794
830
|
});
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
const c = cursor;
|
|
798
|
-
if (![
|
|
831
|
+
const isSort = (value) => {
|
|
832
|
+
return [
|
|
799
833
|
"rate",
|
|
800
834
|
"maturity",
|
|
801
835
|
"expiry",
|
|
802
836
|
"amount"
|
|
803
|
-
].includes(
|
|
804
|
-
|
|
805
|
-
|
|
837
|
+
].includes(value);
|
|
838
|
+
};
|
|
839
|
+
function validate(cursor) {
|
|
840
|
+
if (!cursor || typeof cursor !== "object") throw new Error("Cursor must be an object");
|
|
841
|
+
const c = cursor;
|
|
842
|
+
const sort = c.sort;
|
|
843
|
+
const dir = c.dir;
|
|
844
|
+
const hash$1 = c.hash;
|
|
845
|
+
if (typeof sort !== "string" || !isSort(sort)) throw new Error(`Invalid sort field: ${String(sort)}. Must be one of: rate, maturity, expiry, amount`);
|
|
846
|
+
if (typeof dir !== "string" || !["asc", "desc"].includes(dir)) throw new Error(`Invalid direction: ${String(dir)}. Must be one of: asc, desc`);
|
|
847
|
+
if (typeof hash$1 !== "string" || !/^0x[a-fA-F0-9]{64}$/.test(hash$1)) throw new Error(`Invalid hash format: ${String(hash$1)}. Must be a 64-character hex string starting with 0x`);
|
|
806
848
|
const validation = {
|
|
807
849
|
rate: {
|
|
808
850
|
field: "rate",
|
|
@@ -819,24 +861,30 @@ function validate(cursor) {
|
|
|
819
861
|
maturity: {
|
|
820
862
|
field: "maturity",
|
|
821
863
|
type: "number",
|
|
822
|
-
|
|
864
|
+
min: 1,
|
|
823
865
|
error: "positive number"
|
|
824
866
|
},
|
|
825
867
|
expiry: {
|
|
826
868
|
field: "expiry",
|
|
827
869
|
type: "number",
|
|
828
|
-
|
|
870
|
+
min: 1,
|
|
829
871
|
error: "positive number"
|
|
830
872
|
}
|
|
831
|
-
}[
|
|
832
|
-
if (!validation) throw new Error(`Invalid sort field: ${
|
|
873
|
+
}[sort];
|
|
874
|
+
if (!validation) throw new Error(`Invalid sort field: ${sort}`);
|
|
833
875
|
const fieldValue = c[validation.field];
|
|
834
|
-
if (
|
|
835
|
-
if (
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
876
|
+
if (fieldValue === void 0 || fieldValue === null) throw new Error(`${sort} sort requires '${validation.field}' field to be present`);
|
|
877
|
+
if (validation.type === "string") {
|
|
878
|
+
if (typeof fieldValue !== "string") throw new Error(`${sort} sort requires '${validation.field}' field of type ${validation.type}`);
|
|
879
|
+
if (!validation.pattern.test(fieldValue)) throw new Error(`Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`);
|
|
880
|
+
}
|
|
881
|
+
if (validation.type === "number") {
|
|
882
|
+
if (typeof fieldValue !== "number") throw new Error(`${sort} sort requires '${validation.field}' field of type ${validation.type}`);
|
|
883
|
+
if (fieldValue < validation.min) throw new Error(`Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`);
|
|
884
|
+
}
|
|
885
|
+
const page = c.page;
|
|
886
|
+
if (page !== void 0) {
|
|
887
|
+
if (typeof page !== "number" || !Number.isInteger(page) || page < 1) throw new Error("Invalid page: must be a positive integer");
|
|
840
888
|
}
|
|
841
889
|
return true;
|
|
842
890
|
}
|
|
@@ -952,21 +1000,7 @@ const schemas = {
|
|
|
952
1000
|
get_obligations: GetObligationsQueryParams,
|
|
953
1001
|
get_obligation: GetObligationParams,
|
|
954
1002
|
get_book: GetBookParams,
|
|
955
|
-
validate_offers: z$1.object({
|
|
956
|
-
offers: z$1.any().refine((val) => val === void 0 || Array.isArray(val), { message: "'offers' must be an array" }),
|
|
957
|
-
calldata: z$1.string().regex(/^0x[a-fA-F0-9]*$/, { message: "'calldata' must be a hex string starting with '0x'" }).optional()
|
|
958
|
-
}).superRefine((val, ctx) => {
|
|
959
|
-
const hasOffers = val.offers !== void 0;
|
|
960
|
-
const hasCalldata = val.calldata !== void 0;
|
|
961
|
-
if (hasOffers && hasCalldata) ctx.addIssue({
|
|
962
|
-
code: "custom",
|
|
963
|
-
message: "Request body must contain either 'offers' or 'calldata', not both"
|
|
964
|
-
});
|
|
965
|
-
if (!hasOffers && !hasCalldata) ctx.addIssue({
|
|
966
|
-
code: "custom",
|
|
967
|
-
message: "Request body must contain either 'offers' array or 'calldata' hex string"
|
|
968
|
-
});
|
|
969
|
-
})
|
|
1003
|
+
validate_offers: z$1.object({ offers: z$1.array(z$1.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict()
|
|
970
1004
|
};
|
|
971
1005
|
function parse(action, query) {
|
|
972
1006
|
return schemas[action].parse(query);
|
|
@@ -1193,8 +1227,12 @@ function decode$2(type, data) {
|
|
|
1193
1227
|
}
|
|
1194
1228
|
function encode$2(type, data) {
|
|
1195
1229
|
switch (type) {
|
|
1196
|
-
case CallbackType.BuyVaultV1Callback:
|
|
1197
|
-
|
|
1230
|
+
case CallbackType.BuyVaultV1Callback:
|
|
1231
|
+
if (!("vaults" in data)) throw new Error("Invalid callback data");
|
|
1232
|
+
return encodeBuyVaultV1Callback(data);
|
|
1233
|
+
case CallbackType.SellERC20Callback:
|
|
1234
|
+
if (!("collaterals" in data)) throw new Error("Invalid callback data");
|
|
1235
|
+
return encodeSellERC20Callback(data);
|
|
1198
1236
|
default: throw new Error("Invalid callback type");
|
|
1199
1237
|
}
|
|
1200
1238
|
}
|
|
@@ -1522,22 +1560,22 @@ const DEFAULT_BATCH_SIZE$1 = 2500;
|
|
|
1522
1560
|
const MAX_BLOCK_WINDOW = 1e4;
|
|
1523
1561
|
const DEFAULT_BLOCK_WINDOW = 8e3;
|
|
1524
1562
|
async function* streamLogs(parameters) {
|
|
1525
|
-
const { client, contractAddress, event, blockNumberGte, blockNumberLte, order
|
|
1563
|
+
const { client, contractAddress, event, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE$1, blockWindow = DEFAULT_BLOCK_WINDOW } = {} } = parameters;
|
|
1526
1564
|
if (maxBatchSize > MAX_BATCH_SIZE) throw new InvalidBatchSizeError(maxBatchSize);
|
|
1527
1565
|
if (blockWindow > MAX_BLOCK_WINDOW) throw new InvalidBlockWindowError(blockWindow);
|
|
1528
|
-
if (order
|
|
1566
|
+
if (order === "asc" && blockNumberGte === void 0) throw new MissingBlockNumberError();
|
|
1529
1567
|
const latestBlock = (await getBlock(client, {
|
|
1530
1568
|
blockTag: "latest",
|
|
1531
1569
|
includeTransactions: false
|
|
1532
1570
|
})).number;
|
|
1533
1571
|
let toBlock = 0n;
|
|
1534
|
-
if (order
|
|
1535
|
-
if (order
|
|
1572
|
+
if (order === "asc") toBlock = min(BigInt(blockNumberGte) + BigInt(blockWindow), blockNumberLte ? BigInt(blockNumberLte) : latestBlock);
|
|
1573
|
+
if (order === "desc") toBlock = blockNumberLte === void 0 ? latestBlock : min(BigInt(blockNumberLte), latestBlock);
|
|
1536
1574
|
let fromBlock = 0n;
|
|
1537
|
-
if (order
|
|
1538
|
-
if (order
|
|
1539
|
-
if (order
|
|
1540
|
-
if (order
|
|
1575
|
+
if (order === "asc") fromBlock = min(BigInt(blockNumberGte), latestBlock);
|
|
1576
|
+
if (order === "desc") fromBlock = max$1(BigInt(blockNumberGte || toBlock - BigInt(blockWindow)), 0n);
|
|
1577
|
+
if (order === "asc") toBlock = min(toBlock, fromBlock + BigInt(blockWindow));
|
|
1578
|
+
if (order === "desc") fromBlock = max$1(fromBlock, toBlock - BigInt(blockWindow));
|
|
1541
1579
|
if (fromBlock > toBlock) throw new InvalidBlockRangeError(fromBlock, toBlock);
|
|
1542
1580
|
let streaming = true;
|
|
1543
1581
|
while (streaming) {
|
|
@@ -1547,29 +1585,29 @@ async function* streamLogs(parameters) {
|
|
|
1547
1585
|
fromBlock,
|
|
1548
1586
|
toBlock
|
|
1549
1587
|
});
|
|
1550
|
-
streaming = order
|
|
1588
|
+
streaming = order === "asc" ? toBlock < (blockNumberLte || latestBlock) : fromBlock > (blockNumberGte || 0n);
|
|
1551
1589
|
if (logs.length === 0 && !streaming) break;
|
|
1552
1590
|
if (logs.length === 0 && streaming) yield {
|
|
1553
1591
|
logs: [],
|
|
1554
|
-
blockNumber: order
|
|
1592
|
+
blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock)
|
|
1555
1593
|
};
|
|
1556
1594
|
logs.sort((a, b) => {
|
|
1557
|
-
if (a.blockNumber !== b.blockNumber) return order
|
|
1558
|
-
if (a.transactionIndex !== b.transactionIndex) return order
|
|
1559
|
-
return order
|
|
1595
|
+
if (a.blockNumber !== b.blockNumber) return order === "asc" ? Number(a.blockNumber - b.blockNumber) : Number(b.blockNumber - a.blockNumber);
|
|
1596
|
+
if (a.transactionIndex !== b.transactionIndex) return order === "asc" ? a.transactionIndex - b.transactionIndex : b.transactionIndex - a.transactionIndex;
|
|
1597
|
+
return order === "asc" ? a.logIndex - b.logIndex : b.logIndex - a.logIndex;
|
|
1560
1598
|
});
|
|
1561
1599
|
for (const logBatch of batch(logs, maxBatchSize)) yield {
|
|
1562
1600
|
logs: logBatch,
|
|
1563
|
-
blockNumber: logBatch.length === maxBatchSize ? Number(logBatch[logBatch.length - 1]?.blockNumber) : order
|
|
1601
|
+
blockNumber: logBatch.length === maxBatchSize ? Number(logBatch[logBatch.length - 1]?.blockNumber) : order === "asc" ? Number(toBlock) : Number(fromBlock)
|
|
1564
1602
|
};
|
|
1565
|
-
if (order
|
|
1603
|
+
if (order === "asc") {
|
|
1566
1604
|
const upperBound = BigInt(blockNumberLte || latestBlock);
|
|
1567
1605
|
const nextFromBlock = min(BigInt(toBlock) + 1n, upperBound);
|
|
1568
1606
|
const nextToBlock = min(toBlock + BigInt(blockWindow) + 1n, upperBound);
|
|
1569
1607
|
fromBlock = nextFromBlock;
|
|
1570
1608
|
toBlock = nextToBlock;
|
|
1571
1609
|
}
|
|
1572
|
-
if (order
|
|
1610
|
+
if (order === "desc") {
|
|
1573
1611
|
const lowerBound = BigInt(blockNumberGte || 0);
|
|
1574
1612
|
const nextToBlock = max$1(fromBlock - 1n, lowerBound);
|
|
1575
1613
|
const nextFromBlock = max$1(fromBlock - BigInt(blockWindow) - 1n, lowerBound);
|
|
@@ -1579,7 +1617,7 @@ async function* streamLogs(parameters) {
|
|
|
1579
1617
|
}
|
|
1580
1618
|
yield {
|
|
1581
1619
|
logs: [],
|
|
1582
|
-
blockNumber: order
|
|
1620
|
+
blockNumber: order === "asc" ? Number(toBlock) : Number(fromBlock)
|
|
1583
1621
|
};
|
|
1584
1622
|
}
|
|
1585
1623
|
var InvalidBlockRangeError = class extends BaseError {
|
|
@@ -1607,6 +1645,96 @@ var MissingBlockNumberError = class extends BaseError {
|
|
|
1607
1645
|
}
|
|
1608
1646
|
};
|
|
1609
1647
|
|
|
1648
|
+
//#endregion
|
|
1649
|
+
//#region src/utils/Random.ts
|
|
1650
|
+
var Random_exports = /* @__PURE__ */ __export({
|
|
1651
|
+
address: () => address,
|
|
1652
|
+
bool: () => bool,
|
|
1653
|
+
bytes: () => bytes,
|
|
1654
|
+
float: () => float,
|
|
1655
|
+
hex: () => hex,
|
|
1656
|
+
int: () => int,
|
|
1657
|
+
seed: () => seed,
|
|
1658
|
+
withSeed: () => withSeed
|
|
1659
|
+
});
|
|
1660
|
+
let currentRng = Math.random;
|
|
1661
|
+
const FNV_OFFSET_BASIS = 2166136261;
|
|
1662
|
+
const FNV_PRIME = 16777619;
|
|
1663
|
+
const hashSeed = (seed$1) => {
|
|
1664
|
+
let hash$1 = FNV_OFFSET_BASIS;
|
|
1665
|
+
for (let i = 0; i < seed$1.length; i += 1) {
|
|
1666
|
+
hash$1 ^= seed$1.charCodeAt(i);
|
|
1667
|
+
hash$1 = Math.imul(hash$1, FNV_PRIME);
|
|
1668
|
+
}
|
|
1669
|
+
return hash$1 >>> 0;
|
|
1670
|
+
};
|
|
1671
|
+
const createSeededRng = (seed$1) => {
|
|
1672
|
+
let state = hashSeed(seed$1);
|
|
1673
|
+
return () => {
|
|
1674
|
+
state += 1831565813;
|
|
1675
|
+
let t = Math.imul(state ^ state >>> 15, state | 1);
|
|
1676
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
1677
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
1678
|
+
};
|
|
1679
|
+
};
|
|
1680
|
+
/**
|
|
1681
|
+
* Runs a function with a deterministic RNG derived from the given seed.
|
|
1682
|
+
*/
|
|
1683
|
+
function withSeed(seed$1, fn) {
|
|
1684
|
+
const previous = currentRng;
|
|
1685
|
+
currentRng = createSeededRng(seed$1);
|
|
1686
|
+
try {
|
|
1687
|
+
return fn();
|
|
1688
|
+
} finally {
|
|
1689
|
+
currentRng = previous;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Seeds the global RNG for deterministic test runs.
|
|
1694
|
+
*/
|
|
1695
|
+
function seed(seed$1) {
|
|
1696
|
+
currentRng = createSeededRng(seed$1);
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* Returns a deterministic random float in [0, 1).
|
|
1700
|
+
*/
|
|
1701
|
+
function float() {
|
|
1702
|
+
return currentRng();
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Returns a deterministic random integer in [min, maxExclusive).
|
|
1706
|
+
*/
|
|
1707
|
+
function int(maxExclusive, min$1 = 0) {
|
|
1708
|
+
return Math.floor(float() * (maxExclusive - min$1)) + min$1;
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Returns a deterministic random boolean.
|
|
1712
|
+
*/
|
|
1713
|
+
function bool(probability = .5) {
|
|
1714
|
+
return float() < probability;
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Returns deterministic random bytes.
|
|
1718
|
+
*/
|
|
1719
|
+
function bytes(length) {
|
|
1720
|
+
const output = new Uint8Array(length);
|
|
1721
|
+
for (let i = 0; i < length; i += 1) output[i] = int(256);
|
|
1722
|
+
return output;
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
1725
|
+
* Returns a deterministic random hex string for the given byte length.
|
|
1726
|
+
*/
|
|
1727
|
+
function hex(byteLength) {
|
|
1728
|
+
const output = bytes(byteLength);
|
|
1729
|
+
return `0x${Array.from(output, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Returns a deterministic random address.
|
|
1733
|
+
*/
|
|
1734
|
+
function address() {
|
|
1735
|
+
return hex(20);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1610
1738
|
//#endregion
|
|
1611
1739
|
//#region src/utils/zod.ts
|
|
1612
1740
|
const transformHex = (val, ctx) => {
|
|
@@ -1728,8 +1856,8 @@ const from$9 = (parameters) => {
|
|
|
1728
1856
|
*/
|
|
1729
1857
|
function random$3() {
|
|
1730
1858
|
return from$9({
|
|
1731
|
-
asset:
|
|
1732
|
-
oracle:
|
|
1859
|
+
asset: address(),
|
|
1860
|
+
oracle: address(),
|
|
1733
1861
|
lltv: .965
|
|
1734
1862
|
});
|
|
1735
1863
|
}
|
|
@@ -2144,12 +2272,8 @@ function id(obligation) {
|
|
|
2144
2272
|
function random$2() {
|
|
2145
2273
|
return from$7({
|
|
2146
2274
|
chainId: 1,
|
|
2147
|
-
loanToken:
|
|
2148
|
-
collaterals: [
|
|
2149
|
-
asset: privateKeyToAccount(generatePrivateKey()).address,
|
|
2150
|
-
oracle: privateKeyToAccount(generatePrivateKey()).address,
|
|
2151
|
-
lltv: .965
|
|
2152
|
-
})],
|
|
2275
|
+
loanToken: address(),
|
|
2276
|
+
collaterals: [random$3()],
|
|
2153
2277
|
maturity: from$8("end_of_next_quarter")
|
|
2154
2278
|
});
|
|
2155
2279
|
}
|
|
@@ -2169,101 +2293,249 @@ var CollateralsAreNotSortedError = class extends BaseError {
|
|
|
2169
2293
|
//#endregion
|
|
2170
2294
|
//#region src/core/Tree.ts
|
|
2171
2295
|
var Tree_exports = /* @__PURE__ */ __export({
|
|
2296
|
+
DecodeError: () => DecodeError,
|
|
2297
|
+
EncodeError: () => EncodeError,
|
|
2298
|
+
TreeError: () => TreeError,
|
|
2172
2299
|
VERSION: () => VERSION,
|
|
2173
2300
|
decode: () => decode$1,
|
|
2174
2301
|
encode: () => encode$1,
|
|
2175
|
-
|
|
2302
|
+
encodeUnsigned: () => encodeUnsigned,
|
|
2303
|
+
from: () => from$6,
|
|
2304
|
+
proofs: () => proofs
|
|
2176
2305
|
});
|
|
2177
2306
|
const VERSION = 1;
|
|
2307
|
+
const normalizeHash = (hash$1) => hash$1.toLowerCase();
|
|
2178
2308
|
/**
|
|
2179
2309
|
* Builds a Merkle tree from a list of offers.
|
|
2180
2310
|
*
|
|
2181
2311
|
* Leaves are the offer `hash` values as `bytes32` and are deterministically
|
|
2182
|
-
* ordered
|
|
2183
|
-
* regardless of the input order.
|
|
2312
|
+
* ordered following the StandardMerkleTree leaf ordering so that the resulting
|
|
2313
|
+
* root is stable regardless of the input order.
|
|
2184
2314
|
*
|
|
2185
2315
|
* @param offers - Offers to include in the tree.
|
|
2186
2316
|
* @returns A `StandardMerkleTree` of `bytes32` leaves representing the offers.
|
|
2317
|
+
* @throws {TreeError} If tree building fails due to offer inconsistencies.
|
|
2187
2318
|
*/
|
|
2188
2319
|
const from$6 = (offers) => {
|
|
2189
|
-
const leaves =
|
|
2190
|
-
return [offer.hash];
|
|
2191
|
-
});
|
|
2320
|
+
const leaves = offers.map((offer) => [offer.hash]);
|
|
2192
2321
|
const tree = StandardMerkleTree.of(leaves, ["bytes32"]);
|
|
2193
|
-
|
|
2322
|
+
const orderedOffers = orderOffers(tree, offers);
|
|
2323
|
+
return Object.assign(tree, { offers: orderedOffers });
|
|
2194
2324
|
};
|
|
2195
|
-
const
|
|
2196
|
-
const
|
|
2197
|
-
|
|
2325
|
+
const orderOffers = (tree, offers) => {
|
|
2326
|
+
const offerByHash = /* @__PURE__ */ new Map();
|
|
2327
|
+
for (const offer of offers) offerByHash.set(normalizeHash(offer.hash), offer);
|
|
2328
|
+
const entries = tree.dump().values.map((value) => {
|
|
2329
|
+
const hash$1 = normalizeHash(value.value[0]);
|
|
2330
|
+
const offer = offerByHash.get(hash$1);
|
|
2331
|
+
if (!offer) throw new TreeError(`missing offer for leaf ${hash$1}`);
|
|
2332
|
+
return {
|
|
2333
|
+
offer,
|
|
2334
|
+
treeIndex: value.treeIndex
|
|
2335
|
+
};
|
|
2336
|
+
});
|
|
2337
|
+
entries.sort((a, b) => b.treeIndex - a.treeIndex);
|
|
2338
|
+
return entries.map((item) => item.offer);
|
|
2198
2339
|
};
|
|
2199
2340
|
/**
|
|
2200
|
-
*
|
|
2341
|
+
* Generates merkle proofs for all offers in a tree.
|
|
2201
2342
|
*
|
|
2202
|
-
*
|
|
2203
|
-
*
|
|
2343
|
+
* Each proof allows independent verification that an offer is included in the tree
|
|
2344
|
+
* without requiring the full tree. Proofs are ordered by StandardMerkleTree leaf ordering.
|
|
2204
2345
|
*
|
|
2205
|
-
* @param tree - The
|
|
2206
|
-
* @returns
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2346
|
+
* @param tree - The {@link Tree} to generate proofs for.
|
|
2347
|
+
* @returns Array of proofs - {@link Proof}
|
|
2348
|
+
*/
|
|
2349
|
+
const proofs = (tree) => {
|
|
2350
|
+
return tree.offers.map((offer) => {
|
|
2351
|
+
return {
|
|
2352
|
+
offer,
|
|
2353
|
+
path: tree.getProof([offer.hash])
|
|
2354
|
+
};
|
|
2355
|
+
});
|
|
2356
|
+
};
|
|
2357
|
+
const assertHex = (value, expectedBytes, name) => {
|
|
2358
|
+
if (typeof value !== "string" || !isHex(value)) throw new DecodeError(`${name} is not a valid hex string`);
|
|
2359
|
+
if (hexToBytes(value).length !== expectedBytes) throw new DecodeError(`${name}: expected ${expectedBytes} bytes`);
|
|
2360
|
+
};
|
|
2361
|
+
const verifySignatureAndRecoverAddress = async (params) => {
|
|
2362
|
+
const { root, signature } = params;
|
|
2363
|
+
assertHex(signature, 65, "signature");
|
|
2364
|
+
const hash$1 = hashMessage({ raw: root });
|
|
2365
|
+
try {
|
|
2366
|
+
return await recoverAddress({
|
|
2367
|
+
hash: hash$1,
|
|
2368
|
+
signature
|
|
2369
|
+
});
|
|
2370
|
+
} catch {
|
|
2371
|
+
throw new DecodeError("signature recovery failed");
|
|
2372
|
+
}
|
|
2373
|
+
};
|
|
2374
|
+
/**
|
|
2375
|
+
* Encodes a merkle tree with signature into hex calldata for onchain broadcast.
|
|
2376
|
+
*
|
|
2377
|
+
* Layout: `0x{vv}{gzip([...offers])}{root}{signature}` where:
|
|
2378
|
+
* - `{vv}`: 1-byte version (currently 0x01)
|
|
2379
|
+
* - `{gzip([...offers])}`: gzipped JSON array of serialized offers
|
|
2380
|
+
* - `{root}`: 32-byte merkle root
|
|
2381
|
+
* - `{signature}`: 65-byte EIP-191 signature over raw root bytes
|
|
2382
|
+
*
|
|
2383
|
+
* Validates signature authenticity and root integrity before encoding.
|
|
2384
|
+
*
|
|
2385
|
+
* @example
|
|
2386
|
+
* ```typescript
|
|
2387
|
+
* const tree = Tree.from(offers);
|
|
2388
|
+
* const signature = await wallet.signMessage({ message: { raw: tree.root } });
|
|
2389
|
+
* const calldata = await Tree.encode(tree, signature);
|
|
2390
|
+
* await broadcast(calldata);
|
|
2391
|
+
* ```
|
|
2392
|
+
*
|
|
2393
|
+
* @example
|
|
2394
|
+
* Manual construction (for advanced users):
|
|
2395
|
+
* ```typescript
|
|
2396
|
+
* const tree = Tree.from(offers);
|
|
2397
|
+
* const compressed = gzip(JSON.stringify(tree.offers.map(Offer.serialize)));
|
|
2398
|
+
* const partial = `0x01${bytesToHex(compressed)}${tree.root.slice(2)}`;
|
|
2399
|
+
* const signature = await wallet.signMessage({ message: { raw: tree.root } });
|
|
2400
|
+
* const calldata = `${partial}${signature.slice(2)}`;
|
|
2401
|
+
* ```
|
|
2402
|
+
*
|
|
2403
|
+
* @param tree - Merkle tree of offers
|
|
2404
|
+
* @param signature - EIP-191 signature over raw root bytes
|
|
2405
|
+
* @returns Hex-encoded calldata ready for onchain broadcast
|
|
2406
|
+
* @throws {EncodeError} If signature verification fails or root mismatch
|
|
2407
|
+
*/
|
|
2408
|
+
const encode$1 = async (tree, signature) => {
|
|
2409
|
+
validateTreeForEncoding(tree);
|
|
2410
|
+
await verifySignatureAndRecoverAddress({
|
|
2411
|
+
root: tree.root,
|
|
2412
|
+
signature
|
|
2413
|
+
});
|
|
2414
|
+
const unsigned = encodeUnsignedBytes(tree);
|
|
2415
|
+
const sigBytes = hexToBytes(signature);
|
|
2416
|
+
const encoded = new Uint8Array(unsigned.length + sigBytes.length);
|
|
2417
|
+
encoded.set(unsigned, 0);
|
|
2418
|
+
encoded.set(sigBytes, unsigned.length);
|
|
2240
2419
|
return bytesToHex(encoded);
|
|
2241
2420
|
};
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2421
|
+
/**
|
|
2422
|
+
* Encodes a merkle tree without a signature into hex payload for client-side signing.
|
|
2423
|
+
*
|
|
2424
|
+
* Layout: `0x{vv}{gzip([...offers])}{root}` where:
|
|
2425
|
+
* - `{vv}`: 1-byte version (currently 0x01)
|
|
2426
|
+
* - `{gzip([...offers])}`: gzipped JSON array of serialized offers
|
|
2427
|
+
* - `{root}`: 32-byte merkle root
|
|
2428
|
+
*
|
|
2429
|
+
* Validates root integrity before encoding.
|
|
2430
|
+
*
|
|
2431
|
+
* @param tree - Merkle tree of offers
|
|
2432
|
+
* @returns Hex-encoded unsigned payload
|
|
2433
|
+
* @throws {EncodeError} If root mismatch
|
|
2434
|
+
*/
|
|
2435
|
+
const encodeUnsigned = (tree) => {
|
|
2436
|
+
validateTreeForEncoding(tree);
|
|
2437
|
+
return bytesToHex(encodeUnsignedBytes(tree));
|
|
2438
|
+
};
|
|
2439
|
+
const validateTreeForEncoding = (tree) => {
|
|
2440
|
+
if (VERSION > 255) throw new EncodeError(`version overflow: ${VERSION} exceeds 255`);
|
|
2441
|
+
const computed = from$6(tree.offers);
|
|
2442
|
+
if (tree.root !== computed.root) throw new EncodeError(`root mismatch: expected ${computed.root}, got ${tree.root}`);
|
|
2443
|
+
};
|
|
2444
|
+
const encodeUnsignedBytes = (tree) => {
|
|
2445
|
+
const offersPayload = tree.offers.map(serialize);
|
|
2446
|
+
const compressed = gzip(JSON.stringify(offersPayload));
|
|
2447
|
+
const rootBytes = hexToBytes(tree.root);
|
|
2448
|
+
const encoded = new Uint8Array(1 + compressed.length + 32);
|
|
2449
|
+
encoded[0] = VERSION;
|
|
2450
|
+
encoded.set(compressed, 1);
|
|
2451
|
+
encoded.set(rootBytes, 1 + compressed.length);
|
|
2452
|
+
return encoded;
|
|
2245
2453
|
};
|
|
2246
2454
|
/**
|
|
2247
|
-
* Decodes
|
|
2455
|
+
* Decodes hex calldata into a validated merkle tree.
|
|
2456
|
+
*
|
|
2457
|
+
* Validates signature before decompression for fail-fast rejection of invalid payloads.
|
|
2458
|
+
* Returns the tree with separately validated signature and recovered signer address.
|
|
2459
|
+
*
|
|
2460
|
+
* Validation order:
|
|
2461
|
+
* 1. Version check
|
|
2462
|
+
* 2. Signature verification (fail-fast, before decompression)
|
|
2463
|
+
* 3. Decompression (only if signature valid)
|
|
2464
|
+
* 4. Root verification (computed from offers vs embedded root)
|
|
2248
2465
|
*
|
|
2249
|
-
*
|
|
2250
|
-
*
|
|
2466
|
+
* @example
|
|
2467
|
+
* ```typescript
|
|
2468
|
+
* const { tree, signature, signer } = await Tree.decode(calldata);
|
|
2469
|
+
* console.log(`Tree signed by ${signer} with ${tree.offers.length} offers`);
|
|
2470
|
+
* ```
|
|
2251
2471
|
*
|
|
2252
|
-
* @param encoded - Hex
|
|
2253
|
-
* @returns
|
|
2254
|
-
* @throws
|
|
2255
|
-
*/
|
|
2256
|
-
const decode$1 = (encoded) => {
|
|
2257
|
-
const bytes = hexToBytes(encoded);
|
|
2258
|
-
if (bytes.length <
|
|
2259
|
-
const version = bytes[0];
|
|
2260
|
-
if (version !== (VERSION & 255)) throw new
|
|
2261
|
-
const
|
|
2262
|
-
const
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2472
|
+
* @param encoded - Hex calldata in format `0x{vv}{gzip}{root}{signature}`
|
|
2473
|
+
* @returns Validated tree, signature, and recovered signer address
|
|
2474
|
+
* @throws {DecodeError} If version invalid, signature invalid, or root mismatch
|
|
2475
|
+
*/
|
|
2476
|
+
const decode$1 = async (encoded) => {
|
|
2477
|
+
const bytes$1 = hexToBytes(encoded);
|
|
2478
|
+
if (bytes$1.length < 98) throw new DecodeError("payload too short");
|
|
2479
|
+
const version = bytes$1[0];
|
|
2480
|
+
if (version !== (VERSION & 255)) throw new DecodeError(`invalid version: expected ${VERSION}, got ${version ?? 0}`);
|
|
2481
|
+
const signature = bytesToHex(bytes$1.slice(-65));
|
|
2482
|
+
const root = bytesToHex(bytes$1.slice(-97, -65));
|
|
2483
|
+
assertHex(root, 32, "root");
|
|
2484
|
+
assertHex(signature, 65, "signature");
|
|
2485
|
+
const signer = await verifySignatureAndRecoverAddress({
|
|
2486
|
+
root,
|
|
2487
|
+
signature
|
|
2488
|
+
});
|
|
2489
|
+
const compressed = bytes$1.slice(1, -97);
|
|
2490
|
+
let decoded;
|
|
2491
|
+
try {
|
|
2492
|
+
decoded = ungzip(compressed, { to: "string" });
|
|
2493
|
+
} catch {
|
|
2494
|
+
throw new DecodeError("decompression failed");
|
|
2495
|
+
}
|
|
2496
|
+
let rawOffers;
|
|
2497
|
+
try {
|
|
2498
|
+
rawOffers = JSON.parse(decoded);
|
|
2499
|
+
} catch {
|
|
2500
|
+
throw new DecodeError("JSON parse failed");
|
|
2501
|
+
}
|
|
2502
|
+
const tree = from$6(rawOffers.map((o) => OfferSchema().parse(o)));
|
|
2503
|
+
if (root !== tree.root) throw new DecodeError(`root mismatch: expected ${tree.root}, got ${root}`);
|
|
2504
|
+
return {
|
|
2505
|
+
tree,
|
|
2506
|
+
signature,
|
|
2507
|
+
signer
|
|
2508
|
+
};
|
|
2509
|
+
};
|
|
2510
|
+
/**
|
|
2511
|
+
* Error thrown during tree building operations.
|
|
2512
|
+
* Indicates structural issues with the tree (missing offers, inconsistent state).
|
|
2513
|
+
*/
|
|
2514
|
+
var TreeError = class extends BaseError {
|
|
2515
|
+
constructor(reason) {
|
|
2516
|
+
super(`Tree error: ${reason}`);
|
|
2517
|
+
_defineProperty(this, "name", "Tree.TreeError");
|
|
2518
|
+
}
|
|
2519
|
+
};
|
|
2520
|
+
/**
|
|
2521
|
+
* Error thrown during tree encoding.
|
|
2522
|
+
* Indicates validation failures (signature, root mismatch, mixed makers).
|
|
2523
|
+
*/
|
|
2524
|
+
var EncodeError = class extends BaseError {
|
|
2525
|
+
constructor(reason) {
|
|
2526
|
+
super(`Failed to encode tree: ${reason}`);
|
|
2527
|
+
_defineProperty(this, "name", "Tree.EncodeError");
|
|
2528
|
+
}
|
|
2529
|
+
};
|
|
2530
|
+
/**
|
|
2531
|
+
* Error thrown during tree decoding.
|
|
2532
|
+
* Indicates payload corruption, version mismatch, or validation failures.
|
|
2533
|
+
*/
|
|
2534
|
+
var DecodeError = class extends BaseError {
|
|
2535
|
+
constructor(reason) {
|
|
2536
|
+
super(`Failed to decode tree: ${reason}`);
|
|
2537
|
+
_defineProperty(this, "name", "Tree.DecodeError");
|
|
2538
|
+
}
|
|
2267
2539
|
};
|
|
2268
2540
|
|
|
2269
2541
|
//#endregion
|
|
@@ -2283,6 +2555,7 @@ var Offer_exports = /* @__PURE__ */ __export({
|
|
|
2283
2555
|
hash: () => hash,
|
|
2284
2556
|
obligationId: () => obligationId,
|
|
2285
2557
|
random: () => random$1,
|
|
2558
|
+
serialize: () => serialize,
|
|
2286
2559
|
sign: () => sign,
|
|
2287
2560
|
signatureMsg: () => signatureMsg,
|
|
2288
2561
|
toSnakeCase: () => toSnakeCase,
|
|
@@ -2363,16 +2636,47 @@ function toSnakeCase(offer) {
|
|
|
2363
2636
|
return toSnakeCase$1(offer);
|
|
2364
2637
|
}
|
|
2365
2638
|
/**
|
|
2639
|
+
* Serializes an offer for merkle tree encoding.
|
|
2640
|
+
* Converts BigInt fields to strings for JSON compatibility.
|
|
2641
|
+
*
|
|
2642
|
+
* @param offer - Offer to serialize
|
|
2643
|
+
* @returns JSON-serializable offer object
|
|
2644
|
+
*/
|
|
2645
|
+
const serialize = (offer) => ({
|
|
2646
|
+
offering: offer.offering,
|
|
2647
|
+
assets: offer.assets.toString(),
|
|
2648
|
+
rate: offer.rate.toString(),
|
|
2649
|
+
maturity: Number(offer.maturity),
|
|
2650
|
+
expiry: Number(offer.expiry),
|
|
2651
|
+
start: Number(offer.start),
|
|
2652
|
+
nonce: offer.nonce.toString(),
|
|
2653
|
+
buy: offer.buy,
|
|
2654
|
+
chainId: offer.chainId,
|
|
2655
|
+
loanToken: offer.loanToken,
|
|
2656
|
+
collaterals: offer.collaterals.map((c) => ({
|
|
2657
|
+
asset: c.asset,
|
|
2658
|
+
oracle: c.oracle,
|
|
2659
|
+
lltv: c.lltv.toString()
|
|
2660
|
+
})),
|
|
2661
|
+
callback: {
|
|
2662
|
+
address: offer.callback.address,
|
|
2663
|
+
data: offer.callback.data,
|
|
2664
|
+
gasLimit: offer.callback.gasLimit.toString()
|
|
2665
|
+
},
|
|
2666
|
+
signature: offer.signature,
|
|
2667
|
+
hash: offer.hash
|
|
2668
|
+
});
|
|
2669
|
+
/**
|
|
2366
2670
|
* Generates a random Offer.
|
|
2367
2671
|
* The returned Offer contains randomly generated values.
|
|
2368
2672
|
* @warning The generated Offer should not be used for production usage.
|
|
2369
2673
|
* @returns {Offer} A randomly generated Offer object.
|
|
2370
2674
|
*/
|
|
2371
2675
|
function random$1(config) {
|
|
2372
|
-
const chain = config?.chains ? config.chains[
|
|
2373
|
-
const loanToken = config?.loanTokens ? config.loanTokens[
|
|
2374
|
-
const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [
|
|
2375
|
-
const collateralAsset = collateralCandidates[
|
|
2676
|
+
const chain = config?.chains ? config.chains[int(config.chains.length)] : chains$1.ethereum;
|
|
2677
|
+
const loanToken = config?.loanTokens ? config.loanTokens[int(config.loanTokens.length)] : address();
|
|
2678
|
+
const collateralCandidates = config?.collateralTokens ? config.collateralTokens.filter((a) => a !== loanToken) : [address()];
|
|
2679
|
+
const collateralAsset = collateralCandidates[int(collateralCandidates.length)];
|
|
2376
2680
|
const maturityOption = weightedChoice([["end_of_month", 1], ["end_of_next_month", 1]]);
|
|
2377
2681
|
const maturity$1 = config?.maturity ?? from$8(maturityOption);
|
|
2378
2682
|
const lltv = from$10(weightedChoice([
|
|
@@ -2386,7 +2690,7 @@ function random$1(config) {
|
|
|
2386
2690
|
[.965, 4],
|
|
2387
2691
|
[.98, 2]
|
|
2388
2692
|
]));
|
|
2389
|
-
const buy = config?.buy !== void 0 ? config.buy :
|
|
2693
|
+
const buy = config?.buy !== void 0 ? config.buy : bool();
|
|
2390
2694
|
const ONE = 1000000000000000000n;
|
|
2391
2695
|
const qMin = buy ? 16 : 4;
|
|
2392
2696
|
const len = (buy ? 32 : 16) - qMin + 1;
|
|
@@ -2397,9 +2701,9 @@ function random$1(config) {
|
|
|
2397
2701
|
const rate = config?.rate ?? weightedChoice(ratePairs);
|
|
2398
2702
|
const loanTokenDecimals = config?.assetsDecimals?.[loanToken] ?? 18;
|
|
2399
2703
|
const unit = BigInt(10) ** BigInt(loanTokenDecimals);
|
|
2400
|
-
const amountBase = BigInt(100 +
|
|
2704
|
+
const amountBase = BigInt(100 + int(999901));
|
|
2401
2705
|
const assetsScaled = config?.assets ?? amountBase * unit;
|
|
2402
|
-
const consumed = config?.consumed !== void 0 ? config.consumed :
|
|
2706
|
+
const consumed = config?.consumed !== void 0 ? config.consumed : float() < .8 ? 0n : assetsScaled * BigInt(1 + int(900)) / 1000n;
|
|
2403
2707
|
const callbackBySide = (() => {
|
|
2404
2708
|
if (buy) return {
|
|
2405
2709
|
address: zeroAddress,
|
|
@@ -2418,29 +2722,29 @@ function random$1(config) {
|
|
|
2418
2722
|
};
|
|
2419
2723
|
})();
|
|
2420
2724
|
return from$5({
|
|
2421
|
-
offering: config?.offering ??
|
|
2725
|
+
offering: config?.offering ?? address(),
|
|
2422
2726
|
assets: assetsScaled,
|
|
2423
2727
|
rate,
|
|
2424
2728
|
maturity: maturity$1,
|
|
2425
2729
|
expiry: config?.expiry ?? maturity$1 - 1,
|
|
2426
2730
|
start: config?.start ?? maturity$1 - 10,
|
|
2427
|
-
nonce: BigInt(
|
|
2731
|
+
nonce: BigInt(int(1e6)),
|
|
2428
2732
|
buy,
|
|
2429
2733
|
chainId: chain.id,
|
|
2430
2734
|
loanToken,
|
|
2431
|
-
collaterals: config?.collaterals ?? Array.from({ length:
|
|
2735
|
+
collaterals: config?.collaterals ?? Array.from({ length: int(3) + 1 }, () => ({
|
|
2432
2736
|
...random$3(),
|
|
2433
2737
|
lltv
|
|
2434
2738
|
})).sort((a, b) => a.asset.localeCompare(b.asset)),
|
|
2435
2739
|
callback: config?.callback ?? callbackBySide,
|
|
2436
2740
|
consumed,
|
|
2437
2741
|
takeable: config?.takeable ?? assetsScaled - consumed,
|
|
2438
|
-
blockNumber: config?.blockNumber ??
|
|
2742
|
+
blockNumber: config?.blockNumber ?? int(Number.MAX_SAFE_INTEGER)
|
|
2439
2743
|
});
|
|
2440
2744
|
}
|
|
2441
2745
|
const weightedChoice = (pairs) => {
|
|
2442
2746
|
const total = pairs.reduce((sum, [, weight]) => sum + weight, 0);
|
|
2443
|
-
let roll =
|
|
2747
|
+
let roll = float() * total;
|
|
2444
2748
|
for (const [value, weight] of pairs) {
|
|
2445
2749
|
roll -= weight;
|
|
2446
2750
|
if (roll < 0) return value;
|
|
@@ -2908,8 +3212,8 @@ function fromSnakeCase(snake) {
|
|
|
2908
3212
|
function random() {
|
|
2909
3213
|
return from$2({
|
|
2910
3214
|
obligationId: id(random$2()),
|
|
2911
|
-
ask: { rate: BigInt(
|
|
2912
|
-
bid: { rate: BigInt(
|
|
3215
|
+
ask: { rate: BigInt(int(1e6)) },
|
|
3216
|
+
bid: { rate: BigInt(int(1e6)) }
|
|
2913
3217
|
});
|
|
2914
3218
|
}
|
|
2915
3219
|
var InvalidQuoteError = class extends BaseError {
|
|
@@ -3009,24 +3313,28 @@ async function getOffers(apiClient, parameters) {
|
|
|
3009
3313
|
throw new HttpGetApiFailedError(`GET request returned ${response.status}`, { details: JSON.stringify(error) });
|
|
3010
3314
|
}
|
|
3011
3315
|
const offers = data?.data.map((item) => {
|
|
3012
|
-
const { signature, ...rest } = item;
|
|
3013
|
-
return
|
|
3014
|
-
...
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3316
|
+
const { root, proof, signature, ...rest } = item;
|
|
3317
|
+
return {
|
|
3318
|
+
...fromSnakeCase$1({
|
|
3319
|
+
...rest,
|
|
3320
|
+
offering: item.offering,
|
|
3321
|
+
maturity: from$8(item.maturity),
|
|
3322
|
+
loan_token: item.loan_token,
|
|
3323
|
+
collaterals: item.collaterals.map((collateral) => ({
|
|
3324
|
+
asset: collateral.asset,
|
|
3325
|
+
oracle: collateral.oracle,
|
|
3326
|
+
lltv: collateral.lltv
|
|
3327
|
+
})),
|
|
3328
|
+
callback: {
|
|
3329
|
+
...item.callback,
|
|
3330
|
+
address: item.callback.address,
|
|
3331
|
+
data: item.callback.data
|
|
3332
|
+
},
|
|
3333
|
+
signature: signature?.toLowerCase()
|
|
3334
|
+
}),
|
|
3335
|
+
root: root?.toLowerCase() || void 0,
|
|
3336
|
+
proof: proof?.map((p) => p.toLowerCase()) || void 0
|
|
3337
|
+
};
|
|
3030
3338
|
}) ?? [];
|
|
3031
3339
|
return {
|
|
3032
3340
|
cursor: data?.cursor ?? null,
|
|
@@ -3217,8 +3525,8 @@ function getCallback(chain, type) {
|
|
|
3217
3525
|
* @param address - Callback contract address
|
|
3218
3526
|
* @returns The callback type when found, otherwise undefined
|
|
3219
3527
|
*/
|
|
3220
|
-
function getCallbackType(chain, address) {
|
|
3221
|
-
return configs[chain].callbacks?.find((c) => c.type !== CallbackType.BuyWithEmptyCallback && c.addresses.includes(address?.toLowerCase()))?.type;
|
|
3528
|
+
function getCallbackType(chain, address$1) {
|
|
3529
|
+
return configs[chain].callbacks?.find((c) => c.type !== CallbackType.BuyWithEmptyCallback && c.addresses.includes(address$1?.toLowerCase()))?.type;
|
|
3222
3530
|
}
|
|
3223
3531
|
/**
|
|
3224
3532
|
* Returns the callback addresses for a given chain and callback type, if it exists.
|
|
@@ -3351,6 +3659,7 @@ var Rules_exports = /* @__PURE__ */ __export({
|
|
|
3351
3659
|
callback: () => callback,
|
|
3352
3660
|
chains: () => chains,
|
|
3353
3661
|
maturity: () => maturity,
|
|
3662
|
+
sameMaker: () => sameMaker,
|
|
3354
3663
|
token: () => token,
|
|
3355
3664
|
validity: () => validity
|
|
3356
3665
|
});
|
|
@@ -3487,10 +3796,29 @@ const token = ({ assets: assets$1 }) => single("token", "Validates that offer lo
|
|
|
3487
3796
|
if (!allowedAssets.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
|
|
3488
3797
|
if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
|
|
3489
3798
|
});
|
|
3799
|
+
/**
|
|
3800
|
+
* A batch validation rule that ensures all offers in a tree have the same maker (offering address).
|
|
3801
|
+
* Returns an issue only for the first non-conforming offer.
|
|
3802
|
+
* This rule is signing-agnostic; signer verification is handled at the collector level.
|
|
3803
|
+
*/
|
|
3804
|
+
const sameMaker = () => batch$1("mixed_maker", "Validates that all offers in a batch have the same maker (offering address)", (offers) => {
|
|
3805
|
+
const issues = /* @__PURE__ */ new Map();
|
|
3806
|
+
if (offers.length === 0) return issues;
|
|
3807
|
+
const firstMaker = offers[0].offering.toLowerCase();
|
|
3808
|
+
for (let i = 1; i < offers.length; i++) {
|
|
3809
|
+
const offer = offers[i];
|
|
3810
|
+
if (offer.offering.toLowerCase() !== firstMaker) {
|
|
3811
|
+
issues.set(i, { message: `Offer has different maker ${offer.offering} than first offer ${offers[0].offering}` });
|
|
3812
|
+
return issues;
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
return issues;
|
|
3816
|
+
});
|
|
3490
3817
|
|
|
3491
3818
|
//#endregion
|
|
3492
3819
|
//#region src/gatekeeper/morphoRules.ts
|
|
3493
3820
|
const morphoRules = (chains$2) => [
|
|
3821
|
+
sameMaker(),
|
|
3494
3822
|
chains({ chains: chains$2 }),
|
|
3495
3823
|
maturity({ maturities: [MaturityType.EndOfMonth, MaturityType.EndOfNextMonth] }),
|
|
3496
3824
|
callback({
|
|
@@ -3531,22 +3859,24 @@ async function add(config, offers) {
|
|
|
3531
3859
|
const tree = from$6(offers.map((o) => from$5(o)));
|
|
3532
3860
|
const chainId = await getChainId(config.client);
|
|
3533
3861
|
for (const offer of tree.offers) if (chainId !== offer.chainId) throw new ChainIdMismatchError(offer.chainId, chainId);
|
|
3862
|
+
const signature = await sign(tree.offers, config.client);
|
|
3863
|
+
const encoded = await encode$1(tree, signature);
|
|
3534
3864
|
try {
|
|
3535
3865
|
return await config.client.sendTransaction({
|
|
3536
3866
|
chain: config.client.chain,
|
|
3537
3867
|
account: config.client.account,
|
|
3538
3868
|
to: config.mempoolAddress,
|
|
3539
|
-
data:
|
|
3869
|
+
data: encoded
|
|
3540
3870
|
});
|
|
3541
3871
|
} catch (error) {
|
|
3542
3872
|
throw new ViemClientError(error instanceof Error ? error.message : "Unknown error");
|
|
3543
3873
|
}
|
|
3544
3874
|
}
|
|
3545
3875
|
async function* get(config, parameters) {
|
|
3546
|
-
const { loanToken, blockNumberGte, blockNumberLte, order
|
|
3876
|
+
const { loanToken, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE } = {} } = parameters || {};
|
|
3547
3877
|
yield* streamOffers(config, {
|
|
3548
3878
|
loanToken,
|
|
3549
|
-
order
|
|
3879
|
+
order,
|
|
3550
3880
|
blockNumberGte,
|
|
3551
3881
|
blockNumberLte,
|
|
3552
3882
|
options: {
|
|
@@ -3568,7 +3898,7 @@ const getChainId = async (client) => {
|
|
|
3568
3898
|
return chainId;
|
|
3569
3899
|
};
|
|
3570
3900
|
async function* streamOffers(config, parameters) {
|
|
3571
|
-
const { loanToken, blockNumberGte, blockNumberLte, order
|
|
3901
|
+
const { loanToken, blockNumberGte, blockNumberLte, order = "desc", options: { maxBatchSize = DEFAULT_BATCH_SIZE, blockWindow = config.blockWindow } = {} } = parameters;
|
|
3572
3902
|
const stream = streamLogs({
|
|
3573
3903
|
client: config.client.extend(publicActions),
|
|
3574
3904
|
contractAddress: config.mempoolAddress,
|
|
@@ -3585,13 +3915,13 @@ async function* streamOffers(config, parameters) {
|
|
|
3585
3915
|
},
|
|
3586
3916
|
blockNumberGte,
|
|
3587
3917
|
blockNumberLte,
|
|
3588
|
-
order
|
|
3918
|
+
order,
|
|
3589
3919
|
options: {
|
|
3590
3920
|
maxBatchSize,
|
|
3591
3921
|
blockWindow
|
|
3592
3922
|
}
|
|
3593
3923
|
});
|
|
3594
|
-
let blockNumber = order
|
|
3924
|
+
let blockNumber = order === "asc" ? blockNumberGte : blockNumberLte;
|
|
3595
3925
|
for await (const { logs, blockNumber: newBlockNumber } of stream) {
|
|
3596
3926
|
blockNumber = newBlockNumber;
|
|
3597
3927
|
if (logs.length === 0) continue;
|
|
@@ -3600,7 +3930,7 @@ async function* streamOffers(config, parameters) {
|
|
|
3600
3930
|
if (!log) continue;
|
|
3601
3931
|
const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
|
|
3602
3932
|
try {
|
|
3603
|
-
const tree = decode$1(payload);
|
|
3933
|
+
const { tree } = await decode$1(payload);
|
|
3604
3934
|
for (const offer of tree.offers) {
|
|
3605
3935
|
if (loanToken && offer.loanToken.toLowerCase() !== loanToken.toLowerCase()) continue;
|
|
3606
3936
|
offers.push({
|
|
@@ -3769,6 +4099,7 @@ function max() {
|
|
|
3769
4099
|
//#region src/utils/index.ts
|
|
3770
4100
|
var utils_exports = /* @__PURE__ */ __export({
|
|
3771
4101
|
BaseError: () => BaseError,
|
|
4102
|
+
Random: () => Random_exports,
|
|
3772
4103
|
ReorgError: () => ReorgError,
|
|
3773
4104
|
Time: () => time_exports,
|
|
3774
4105
|
batch: () => batch,
|