@morpho-dev/router 0.5.0 → 0.7.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 +2353 -1043
- package/dist/drizzle/migrations/0023_remove-block-number-for-collaterals.sql +1 -0
- package/dist/drizzle/migrations/meta/0023_snapshot.json +1436 -0
- package/dist/drizzle/migrations/meta/_journal.json +7 -0
- package/dist/index.browser.d.mts +540 -169
- package/dist/index.browser.d.mts.map +1 -1
- package/dist/index.browser.d.ts +540 -169
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +956 -411
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +958 -407
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.node.d.mts +628 -187
- package/dist/index.node.d.mts.map +1 -1
- package/dist/index.node.d.ts +627 -186
- package/dist/index.node.d.ts.map +1 -1
- package/dist/index.node.js +6776 -5605
- package/dist/index.node.js.map +1 -1
- package/dist/index.node.mjs +6767 -5602
- package/dist/index.node.mjs.map +1 -1
- package/docs/integrator.md +7 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ import os from "node:os";
|
|
|
16
16
|
import path, { dirname, resolve } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
19
|
-
import { bytesToHex, createPublicClient, createWalletClient, decodeAbiParameters, encodeAbiParameters, erc20Abi, getAddress,
|
|
19
|
+
import { bytesToHex, createPublicClient, createWalletClient, decodeAbiParameters, encodeAbiParameters, erc20Abi, getAddress, hashTypedData, hexToBytes, http, isAddress, isHex, keccak256, maxUint256, numberToHex, pad, parseAbi, parseEventLogs, publicActions, recoverAddress, stringify, toHex, zeroAddress } from "viem";
|
|
20
20
|
import { mnemonicToAccount, privateKeyToAccount } from "viem/accounts";
|
|
21
21
|
import { getBlock, getBlockNumber, getLogs, multicall } from "viem/actions";
|
|
22
22
|
import { anvil, base, mainnet } from "viem/chains";
|
|
@@ -26,13 +26,13 @@ import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
|
|
|
26
26
|
import { gzip, ungzip } from "pako";
|
|
27
27
|
import { serve } from "@hono/node-server";
|
|
28
28
|
import { Hono } from "hono";
|
|
29
|
-
import {
|
|
29
|
+
import crypto, { createHash } from "node:crypto";
|
|
30
30
|
import { z as z$1 } from "zod/v4";
|
|
31
31
|
import "reflect-metadata";
|
|
32
32
|
import { generateDocument } from "openapi-metadata";
|
|
33
33
|
import { ApiBody, ApiOperation, ApiParam, ApiProperty, ApiQuery, ApiResponse, ApiTags } from "openapi-metadata/decorators";
|
|
34
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
34
35
|
import dotenv from "dotenv";
|
|
35
|
-
import crypto from "node:crypto";
|
|
36
36
|
import { PGlite } from "@electric-sql/pglite";
|
|
37
37
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
38
38
|
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
@@ -152,7 +152,7 @@ function startActiveSpan(tracer, name, fn) {
|
|
|
152
152
|
//#endregion
|
|
153
153
|
//#region package.json
|
|
154
154
|
var name = "@morpho-dev/router";
|
|
155
|
-
var version = "0.
|
|
155
|
+
var version = "0.7.0";
|
|
156
156
|
var description = "Router package for Morpho protocol";
|
|
157
157
|
|
|
158
158
|
//#endregion
|
|
@@ -1469,20 +1469,14 @@ async function run(parameters) {
|
|
|
1469
1469
|
* @param parameters - Gatekeeper parameters. {@link GatekeeperParameters}
|
|
1470
1470
|
* @returns Gatekeeper instance. {@link Gatekeeper}
|
|
1471
1471
|
*/
|
|
1472
|
-
function create$
|
|
1472
|
+
function create$20(parameters) {
|
|
1473
1473
|
const { rules } = parameters;
|
|
1474
|
-
return {
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
},
|
|
1481
|
-
getRules: async () => rules.map((rule) => ({
|
|
1482
|
-
name: rule.name,
|
|
1483
|
-
description: rule.description
|
|
1484
|
-
}))
|
|
1485
|
-
};
|
|
1474
|
+
return { isAllowed: async (offers) => {
|
|
1475
|
+
return await run({
|
|
1476
|
+
items: offers,
|
|
1477
|
+
rules
|
|
1478
|
+
});
|
|
1479
|
+
} };
|
|
1486
1480
|
}
|
|
1487
1481
|
|
|
1488
1482
|
//#endregion
|
|
@@ -1689,17 +1683,6 @@ function getCallbackType(chain, address) {
|
|
|
1689
1683
|
return configs[chain].callbacks?.find((c) => c.type !== Type$1.BuyWithEmptyCallback && c.addresses.includes(address?.toLowerCase()))?.type;
|
|
1690
1684
|
}
|
|
1691
1685
|
/**
|
|
1692
|
-
* Returns the callback addresses for a given chain and callback type, if it exists.
|
|
1693
|
-
* @param chain - Chain name for which to read the validation configuration
|
|
1694
|
-
* @param type - Callback type to retrieve
|
|
1695
|
-
* @returns The matching callback addresses or an empty array if not configured
|
|
1696
|
-
*/
|
|
1697
|
-
function getCallbackTypeAddresses(chain, type) {
|
|
1698
|
-
if (type === Type$1.BuyWithEmptyCallback) return [];
|
|
1699
|
-
const match = configs[chain].callbacks?.find((c) => c.type === type);
|
|
1700
|
-
return match && "addresses" in match ? match.addresses : [];
|
|
1701
|
-
}
|
|
1702
|
-
/**
|
|
1703
1686
|
* Returns the list of allowed non-empty callback addresses for a chain.
|
|
1704
1687
|
*
|
|
1705
1688
|
* @param chain - Chain name
|
|
@@ -1713,13 +1696,18 @@ const assets = {
|
|
|
1713
1696
|
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
1714
1697
|
"0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
1715
1698
|
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
1716
|
-
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
|
1699
|
+
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
|
1700
|
+
"0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c",
|
|
1701
|
+
"0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"
|
|
1717
1702
|
],
|
|
1718
1703
|
[ChainId.BASE.toString()]: [
|
|
1719
1704
|
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
1720
1705
|
"0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
|
|
1721
1706
|
"0x4200000000000000000000000000000000000006",
|
|
1722
|
-
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
|
1707
|
+
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
|
|
1708
|
+
"0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
|
|
1709
|
+
"0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452",
|
|
1710
|
+
"0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42"
|
|
1723
1711
|
],
|
|
1724
1712
|
[ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
|
|
1725
1713
|
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
@@ -1735,6 +1723,43 @@ const assets = {
|
|
|
1735
1723
|
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
|
|
1736
1724
|
]
|
|
1737
1725
|
};
|
|
1726
|
+
const oracles$1 = {
|
|
1727
|
+
[ChainId.ETHEREUM.toString()]: [
|
|
1728
|
+
"0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
1729
|
+
"0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
|
|
1730
|
+
"0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
|
|
1731
|
+
"0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
|
|
1732
|
+
"0xbD60A6770b27E084E8617335ddE769241B0e71D8",
|
|
1733
|
+
"0xAe12416c1F21B0698c27fe042D9309C83baC6597"
|
|
1734
|
+
],
|
|
1735
|
+
[ChainId.BASE.toString()]: [
|
|
1736
|
+
"0xD09048c8B568Dbf5f189302beA26c9edABFC4858",
|
|
1737
|
+
"0xFEa2D58cEfCb9fcb597723c6bAE66fFE4193aFE4",
|
|
1738
|
+
"0x05D2618404668D725B66c0f32B39e4EC15B393dC",
|
|
1739
|
+
"0xE1bb8E5b4930eC9FeC7f7943FCF6227649F14B37",
|
|
1740
|
+
"0x663BECd10daE6C4A3Dcd89F1d76c1174199639B9",
|
|
1741
|
+
"0x10b95702a0ce895972C91e432C4f7E19811D320E",
|
|
1742
|
+
"0x8C87DbD7A0c647A4291592Bc2994dbF95880fE2F",
|
|
1743
|
+
"0x4A11590e5326138B514E08A9B52202D42077Ca65",
|
|
1744
|
+
"0xa54122f0E0766258377Ffe732e454A3248f454F4"
|
|
1745
|
+
],
|
|
1746
|
+
[ChainId["ETHEREUM-VIRTUAL-TESTNET"].toString()]: [
|
|
1747
|
+
"0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
1748
|
+
"0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
|
|
1749
|
+
"0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
|
|
1750
|
+
"0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
|
|
1751
|
+
"0xbD60A6770b27E084E8617335ddE769241B0e71D8",
|
|
1752
|
+
"0xAe12416c1F21B0698c27fe042D9309C83baC6597"
|
|
1753
|
+
],
|
|
1754
|
+
[ChainId.ANVIL.toString()]: [
|
|
1755
|
+
"0xDddd770BADd886dF3864029e4B377B5F6a2B6b83",
|
|
1756
|
+
"0x9CB3f4276bcD149b3668e1a645a964bC12877b89",
|
|
1757
|
+
"0x48F7E36EB6B826B2dF4B2E630B62Cd25e89E40e2",
|
|
1758
|
+
"0x6Eb9F4128CeBc8B885A4d8562Db1Addf097f7348",
|
|
1759
|
+
"0xbD60A6770b27E084E8617335ddE769241B0e71D8",
|
|
1760
|
+
"0xAe12416c1F21B0698c27fe042D9309C83baC6597"
|
|
1761
|
+
]
|
|
1762
|
+
};
|
|
1738
1763
|
const configs = {
|
|
1739
1764
|
ethereum: {
|
|
1740
1765
|
callbacks: [
|
|
@@ -1836,7 +1861,7 @@ const Oracle = [{
|
|
|
1836
1861
|
* @param chains - Array of chain objects to register.
|
|
1837
1862
|
* @returns A registry for looking up chains by ID. {@link ChainRegistry}
|
|
1838
1863
|
*/
|
|
1839
|
-
function create$
|
|
1864
|
+
function create$19(chains) {
|
|
1840
1865
|
const byId = /* @__PURE__ */ new Map();
|
|
1841
1866
|
for (const chain of chains) byId.set(chain.id, chain);
|
|
1842
1867
|
return {
|
|
@@ -2158,196 +2183,6 @@ var CollateralsAreNotSortedError = class extends BaseError {
|
|
|
2158
2183
|
}
|
|
2159
2184
|
};
|
|
2160
2185
|
|
|
2161
|
-
//#endregion
|
|
2162
|
-
//#region src/core/Tree.ts
|
|
2163
|
-
const VERSION$1 = 1;
|
|
2164
|
-
const normalizeHash = (hash) => hash.toLowerCase();
|
|
2165
|
-
/**
|
|
2166
|
-
* Builds a Merkle tree from a list of offers.
|
|
2167
|
-
*
|
|
2168
|
-
* Leaves are the offer `hash` values as `bytes32` and are deterministically
|
|
2169
|
-
* ordered following the StandardMerkleTree leaf ordering so that the resulting
|
|
2170
|
-
* root is stable regardless of the input order.
|
|
2171
|
-
*
|
|
2172
|
-
* @param offers - Offers to include in the tree.
|
|
2173
|
-
* @returns A `StandardMerkleTree` of `bytes32` leaves representing the offers.
|
|
2174
|
-
* @throws {TreeError} If tree building fails due to offer inconsistencies.
|
|
2175
|
-
*/
|
|
2176
|
-
const from$12 = (offers) => {
|
|
2177
|
-
const leaves = offers.map((offer) => [hash(offer)]);
|
|
2178
|
-
const tree = StandardMerkleTree.of(leaves, ["bytes32"]);
|
|
2179
|
-
const orderedOffers = orderOffers(tree, offers);
|
|
2180
|
-
return Object.assign(tree, { offers: orderedOffers });
|
|
2181
|
-
};
|
|
2182
|
-
const orderOffers = (tree, offers) => {
|
|
2183
|
-
const offerByHash = /* @__PURE__ */ new Map();
|
|
2184
|
-
for (const offer of offers) offerByHash.set(normalizeHash(hash(offer)), offer);
|
|
2185
|
-
const entries = tree.dump().values.map((value) => {
|
|
2186
|
-
const hash = normalizeHash(value.value[0]);
|
|
2187
|
-
const offer = offerByHash.get(hash);
|
|
2188
|
-
if (!offer) throw new TreeError(`missing offer for leaf ${hash}`);
|
|
2189
|
-
return {
|
|
2190
|
-
offer,
|
|
2191
|
-
treeIndex: value.treeIndex
|
|
2192
|
-
};
|
|
2193
|
-
});
|
|
2194
|
-
entries.sort((a, b) => b.treeIndex - a.treeIndex);
|
|
2195
|
-
return entries.map((item) => item.offer);
|
|
2196
|
-
};
|
|
2197
|
-
/**
|
|
2198
|
-
* Generates merkle proofs for all offers in a tree.
|
|
2199
|
-
*
|
|
2200
|
-
* Each proof allows independent verification that an offer is included in the tree
|
|
2201
|
-
* without requiring the full tree. Proofs are ordered by StandardMerkleTree leaf ordering.
|
|
2202
|
-
*
|
|
2203
|
-
* @param tree - The {@link Tree} to generate proofs for.
|
|
2204
|
-
* @returns Array of proofs - {@link Proof}
|
|
2205
|
-
*/
|
|
2206
|
-
const proofs = (tree) => {
|
|
2207
|
-
return tree.offers.map((offer) => {
|
|
2208
|
-
return {
|
|
2209
|
-
offer,
|
|
2210
|
-
path: tree.getProof([hash(offer)])
|
|
2211
|
-
};
|
|
2212
|
-
});
|
|
2213
|
-
};
|
|
2214
|
-
const assertHex = (value, expectedBytes, name) => {
|
|
2215
|
-
if (typeof value !== "string" || !isHex(value)) throw new DecodeError(`${name} is not a valid hex string`);
|
|
2216
|
-
if (hexToBytes(value).length !== expectedBytes) throw new DecodeError(`${name}: expected ${expectedBytes} bytes`);
|
|
2217
|
-
};
|
|
2218
|
-
const verifySignatureAndRecoverAddress = async (params) => {
|
|
2219
|
-
const { root, signature } = params;
|
|
2220
|
-
assertHex(signature, 65, "signature");
|
|
2221
|
-
const hash = hashMessage({ raw: root });
|
|
2222
|
-
try {
|
|
2223
|
-
return await recoverAddress({
|
|
2224
|
-
hash,
|
|
2225
|
-
signature
|
|
2226
|
-
});
|
|
2227
|
-
} catch {
|
|
2228
|
-
throw new DecodeError("signature recovery failed");
|
|
2229
|
-
}
|
|
2230
|
-
};
|
|
2231
|
-
/**
|
|
2232
|
-
* Encodes a merkle tree without a signature into hex payload for client-side signing.
|
|
2233
|
-
*
|
|
2234
|
-
* Layout: `0x{vv}{gzip([...offers])}{root}` where:
|
|
2235
|
-
* - `{vv}`: 1-byte version (currently 0x01)
|
|
2236
|
-
* - `{gzip([...offers])}`: gzipped JSON array of serialized offers
|
|
2237
|
-
* - `{root}`: 32-byte merkle root
|
|
2238
|
-
*
|
|
2239
|
-
* Validates root integrity before encoding.
|
|
2240
|
-
*
|
|
2241
|
-
* @param tree - Merkle tree of offers
|
|
2242
|
-
* @returns Hex-encoded unsigned payload
|
|
2243
|
-
* @throws {EncodeError} If root mismatch
|
|
2244
|
-
*/
|
|
2245
|
-
const encodeUnsigned = (tree) => {
|
|
2246
|
-
validateTreeForEncoding(tree);
|
|
2247
|
-
return bytesToHex(encodeUnsignedBytes(tree));
|
|
2248
|
-
};
|
|
2249
|
-
const validateTreeForEncoding = (tree) => {
|
|
2250
|
-
if (VERSION$1 > 255) throw new EncodeError(`version overflow: ${VERSION$1} exceeds 255`);
|
|
2251
|
-
const computed = from$12(tree.offers);
|
|
2252
|
-
if (tree.root !== computed.root) throw new EncodeError(`root mismatch: expected ${computed.root}, got ${tree.root}`);
|
|
2253
|
-
};
|
|
2254
|
-
const encodeUnsignedBytes = (tree) => {
|
|
2255
|
-
const offersPayload = tree.offers.map(serialize);
|
|
2256
|
-
const compressed = gzip(JSON.stringify(offersPayload));
|
|
2257
|
-
const rootBytes = hexToBytes(tree.root);
|
|
2258
|
-
const encoded = new Uint8Array(1 + compressed.length + 32);
|
|
2259
|
-
encoded[0] = VERSION$1;
|
|
2260
|
-
encoded.set(compressed, 1);
|
|
2261
|
-
encoded.set(rootBytes, 1 + compressed.length);
|
|
2262
|
-
return encoded;
|
|
2263
|
-
};
|
|
2264
|
-
/**
|
|
2265
|
-
* Decodes hex calldata into a validated merkle tree.
|
|
2266
|
-
*
|
|
2267
|
-
* Validates signature before decompression for fail-fast rejection of invalid payloads.
|
|
2268
|
-
* Returns the tree with separately validated signature and recovered signer address.
|
|
2269
|
-
*
|
|
2270
|
-
* Validation order:
|
|
2271
|
-
* 1. Version check
|
|
2272
|
-
* 2. Signature verification (fail-fast, before decompression)
|
|
2273
|
-
* 3. Decompression (only if signature valid)
|
|
2274
|
-
* 4. Root verification (computed from offers vs embedded root)
|
|
2275
|
-
*
|
|
2276
|
-
* @example
|
|
2277
|
-
* ```typescript
|
|
2278
|
-
* const { tree, signature, signer } = await Tree.decode(calldata);
|
|
2279
|
-
* console.log(`Tree signed by ${signer} with ${tree.offers.length} offers`);
|
|
2280
|
-
* ```
|
|
2281
|
-
*
|
|
2282
|
-
* @param encoded - Hex calldata in format `0x{vv}{gzip}{root}{signature}`
|
|
2283
|
-
* @returns Validated tree, signature, and recovered signer address
|
|
2284
|
-
* @throws {DecodeError} If version invalid, signature invalid, or root mismatch
|
|
2285
|
-
*/
|
|
2286
|
-
const decode = async (encoded) => {
|
|
2287
|
-
const bytes = hexToBytes(encoded);
|
|
2288
|
-
if (bytes.length < 98) throw new DecodeError("payload too short");
|
|
2289
|
-
const version = bytes[0];
|
|
2290
|
-
if (version !== (VERSION$1 & 255)) throw new DecodeError(`invalid version: expected ${VERSION$1}, got ${version ?? 0}`);
|
|
2291
|
-
const signature = bytesToHex(bytes.slice(-65));
|
|
2292
|
-
const root = bytesToHex(bytes.slice(-97, -65));
|
|
2293
|
-
assertHex(root, 32, "root");
|
|
2294
|
-
assertHex(signature, 65, "signature");
|
|
2295
|
-
const signer = await verifySignatureAndRecoverAddress({
|
|
2296
|
-
root,
|
|
2297
|
-
signature
|
|
2298
|
-
});
|
|
2299
|
-
const compressed = bytes.slice(1, -97);
|
|
2300
|
-
let decoded;
|
|
2301
|
-
try {
|
|
2302
|
-
decoded = ungzip(compressed, { to: "string" });
|
|
2303
|
-
} catch {
|
|
2304
|
-
throw new DecodeError("decompression failed");
|
|
2305
|
-
}
|
|
2306
|
-
let rawOffers;
|
|
2307
|
-
try {
|
|
2308
|
-
rawOffers = JSON.parse(decoded);
|
|
2309
|
-
} catch {
|
|
2310
|
-
throw new DecodeError("JSON parse failed");
|
|
2311
|
-
}
|
|
2312
|
-
const tree = from$12(rawOffers.map((o) => OfferSchema().parse(o)));
|
|
2313
|
-
if (root !== tree.root) throw new DecodeError(`root mismatch: expected ${tree.root}, got ${root}`);
|
|
2314
|
-
return {
|
|
2315
|
-
tree,
|
|
2316
|
-
signature,
|
|
2317
|
-
signer
|
|
2318
|
-
};
|
|
2319
|
-
};
|
|
2320
|
-
/**
|
|
2321
|
-
* Error thrown during tree building operations.
|
|
2322
|
-
* Indicates structural issues with the tree (missing offers, inconsistent state).
|
|
2323
|
-
*/
|
|
2324
|
-
var TreeError = class extends BaseError {
|
|
2325
|
-
name = "Tree.TreeError";
|
|
2326
|
-
constructor(reason) {
|
|
2327
|
-
super(`Tree error: ${reason}`);
|
|
2328
|
-
}
|
|
2329
|
-
};
|
|
2330
|
-
/**
|
|
2331
|
-
* Error thrown during tree encoding.
|
|
2332
|
-
* Indicates validation failures (signature, root mismatch, mixed makers).
|
|
2333
|
-
*/
|
|
2334
|
-
var EncodeError = class extends BaseError {
|
|
2335
|
-
name = "Tree.EncodeError";
|
|
2336
|
-
constructor(reason) {
|
|
2337
|
-
super(`Failed to encode tree: ${reason}`);
|
|
2338
|
-
}
|
|
2339
|
-
};
|
|
2340
|
-
/**
|
|
2341
|
-
* Error thrown during tree decoding.
|
|
2342
|
-
* Indicates payload corruption, version mismatch, or validation failures.
|
|
2343
|
-
*/
|
|
2344
|
-
var DecodeError = class extends BaseError {
|
|
2345
|
-
name = "Tree.DecodeError";
|
|
2346
|
-
constructor(reason) {
|
|
2347
|
-
super(`Failed to decode tree: ${reason}`);
|
|
2348
|
-
}
|
|
2349
|
-
};
|
|
2350
|
-
|
|
2351
2186
|
//#endregion
|
|
2352
2187
|
//#region src/core/Offer.ts
|
|
2353
2188
|
/** Internal symbol for caching the computed hash. */
|
|
@@ -2399,7 +2234,7 @@ const OfferSchema = () => {
|
|
|
2399
2234
|
* @param input - The offer to create.
|
|
2400
2235
|
* @returns The created offer.
|
|
2401
2236
|
*/
|
|
2402
|
-
function from$
|
|
2237
|
+
function from$12(input) {
|
|
2403
2238
|
try {
|
|
2404
2239
|
return OfferSchema().parse(input);
|
|
2405
2240
|
} catch (error) {
|
|
@@ -2413,7 +2248,7 @@ function from$11(input) {
|
|
|
2413
2248
|
* @returns The created offer.
|
|
2414
2249
|
*/
|
|
2415
2250
|
function fromSnakeCase(input) {
|
|
2416
|
-
return from$
|
|
2251
|
+
return from$12(fromSnakeCase$1(input));
|
|
2417
2252
|
}
|
|
2418
2253
|
/**
|
|
2419
2254
|
* Converts an offer to a snake case object.
|
|
@@ -2507,7 +2342,7 @@ function random(config) {
|
|
|
2507
2342
|
})
|
|
2508
2343
|
};
|
|
2509
2344
|
})();
|
|
2510
|
-
return from$
|
|
2345
|
+
return from$12({
|
|
2511
2346
|
maker: config?.maker ?? address(),
|
|
2512
2347
|
assets: assetsScaled,
|
|
2513
2348
|
obligationUnits: config?.obligationUnits ?? 0n,
|
|
@@ -2738,7 +2573,7 @@ var InvalidOfferError = class InvalidOfferError extends BaseError {
|
|
|
2738
2573
|
* @param data - The data to create the oracle from.
|
|
2739
2574
|
* @returns The created oracle.
|
|
2740
2575
|
*/
|
|
2741
|
-
function from$
|
|
2576
|
+
function from$11(data) {
|
|
2742
2577
|
return {
|
|
2743
2578
|
chainId: data.chainId,
|
|
2744
2579
|
address: data.address.toLowerCase(),
|
|
@@ -2772,7 +2607,7 @@ let Type = /* @__PURE__ */ function(Type) {
|
|
|
2772
2607
|
* @param parameters - {@link from.Parameters}
|
|
2773
2608
|
* @returns The created Position. {@link from.ReturnType}
|
|
2774
2609
|
*/
|
|
2775
|
-
function from$
|
|
2610
|
+
function from$10(parameters) {
|
|
2776
2611
|
return {
|
|
2777
2612
|
chainId: parameters.chainId,
|
|
2778
2613
|
contract: parameters.contract.toLowerCase(),
|
|
@@ -2803,7 +2638,7 @@ const QuoteSchema = z$2.object({
|
|
|
2803
2638
|
* const quote = Quote.from({ obligationId: "0x123", ask: { price: 100n }, bid: { price: 100n } });
|
|
2804
2639
|
* ```
|
|
2805
2640
|
*/
|
|
2806
|
-
function from$
|
|
2641
|
+
function from$9(parameters) {
|
|
2807
2642
|
try {
|
|
2808
2643
|
const parsedQuote = QuoteSchema.parse(parameters);
|
|
2809
2644
|
return {
|
|
@@ -2841,7 +2676,7 @@ const WAD = 10n ** 18n;
|
|
|
2841
2676
|
* const transfer = Transfer.from({ id: "1", chainId: 1, contract: "0x123", from: "0x456", to: "0x789", value: 100n, blockNumber: 100n });
|
|
2842
2677
|
* ```
|
|
2843
2678
|
*/
|
|
2844
|
-
function from$
|
|
2679
|
+
function from$8(parameters) {
|
|
2845
2680
|
return {
|
|
2846
2681
|
id: parameters.id,
|
|
2847
2682
|
chainId: parameters.chainId,
|
|
@@ -2854,46 +2689,293 @@ function from$7(parameters) {
|
|
|
2854
2689
|
}
|
|
2855
2690
|
|
|
2856
2691
|
//#endregion
|
|
2857
|
-
//#region src/core/
|
|
2858
|
-
const
|
|
2859
|
-
|
|
2860
|
-
//#endregion
|
|
2861
|
-
//#region src/gatekeeper/Rules.ts
|
|
2862
|
-
const chains$1 = ({ chains }) => single("chain_ids", `Validates that offer chain is one of: [${chains.map((c) => c.id).join(", ")}]`, (offer) => {
|
|
2863
|
-
const allowedChainIds = chains.map((c) => c.id);
|
|
2864
|
-
if (!allowedChainIds.some((id) => id === offer.chainId)) return { message: `Chain ID ${offer.chainId} is not in the allowed chains (${allowedChainIds.join(", ")})` };
|
|
2865
|
-
});
|
|
2866
|
-
const maturity = ({ maturities }) => single("maturity", `Validates that offer maturity is one of: [${maturities.join(", ")}]`, (offer) => {
|
|
2867
|
-
const allowedMaturities = maturities.map((m) => from$16(m));
|
|
2868
|
-
if (!allowedMaturities.includes(offer.maturity)) return { message: `Maturity must be end of current month (${allowedMaturities[0]}) or end of next month (${allowedMaturities[1]}). Got: ${offer.maturity}` };
|
|
2869
|
-
});
|
|
2870
|
-
const callback = ({ callbacks, allowedAddresses }) => single("callback", `Validates callbacks: buy empty callback is ${callbacks.includes(Type$1.BuyWithEmptyCallback) ? "allowed" : "not allowed"}; sell offers must use a non-empty callback; non-empty callbacks must target one of [${allowedAddresses.map((a) => a.toLowerCase()).join(", ")}]`, (offer) => {
|
|
2871
|
-
if (isEmptyCallback(offer) && offer.buy && !callbacks?.find((c) => c === Type$1.BuyWithEmptyCallback)) return { message: "Buy offers with empty callback not allowed." };
|
|
2872
|
-
if (isEmptyCallback(offer) && !offer.buy) return { message: "Sell offers require a non-empty callback." };
|
|
2873
|
-
if (!isEmptyCallback(offer)) {
|
|
2874
|
-
if (!allowedAddresses.includes(offer.callback.address?.toLowerCase())) return { message: `Callback address ${offer.callback.address} is not allowed.` };
|
|
2875
|
-
}
|
|
2876
|
-
});
|
|
2692
|
+
//#region src/core/Tree.ts
|
|
2693
|
+
const VERSION$1 = 1;
|
|
2877
2694
|
/**
|
|
2878
|
-
*
|
|
2879
|
-
* @param assetsByChainId - Allowed assets indexed by chain id.
|
|
2880
|
-
* @returns The issue that was found. If the offer is valid, this will be undefined.
|
|
2695
|
+
* EIP-712 types for signing the tree root (Root(bytes32 root)).
|
|
2881
2696
|
*/
|
|
2882
|
-
const
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2697
|
+
const signatureTypes = {
|
|
2698
|
+
EIP712Domain: [{
|
|
2699
|
+
name: "chainId",
|
|
2700
|
+
type: "uint256"
|
|
2701
|
+
}, {
|
|
2702
|
+
name: "verifyingContract",
|
|
2703
|
+
type: "address"
|
|
2704
|
+
}],
|
|
2705
|
+
Root: [{
|
|
2706
|
+
name: "root",
|
|
2707
|
+
type: "bytes32"
|
|
2708
|
+
}]
|
|
2709
|
+
};
|
|
2710
|
+
const normalizeHash = (hash) => hash.toLowerCase();
|
|
2888
2711
|
/**
|
|
2889
|
-
*
|
|
2890
|
-
*
|
|
2891
|
-
*
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2712
|
+
* Builds a Merkle tree from a list of offers.
|
|
2713
|
+
*
|
|
2714
|
+
* Leaves are the offer `hash` values as `bytes32` and are deterministically
|
|
2715
|
+
* ordered following the StandardMerkleTree leaf ordering so that the resulting
|
|
2716
|
+
* root is stable regardless of the input order.
|
|
2717
|
+
*
|
|
2718
|
+
* @param offers - Offers to include in the tree.
|
|
2719
|
+
* @returns A `StandardMerkleTree` of `bytes32` leaves representing the offers.
|
|
2720
|
+
* @throws {TreeError} If tree building fails due to offer inconsistencies.
|
|
2721
|
+
*/
|
|
2722
|
+
const from$7 = (offers) => {
|
|
2723
|
+
const leaves = offers.map((offer) => [hash(offer)]);
|
|
2724
|
+
const tree = StandardMerkleTree.of(leaves, ["bytes32"]);
|
|
2725
|
+
const orderedOffers = orderOffers(tree, offers);
|
|
2726
|
+
return Object.assign(tree, { offers: orderedOffers });
|
|
2727
|
+
};
|
|
2728
|
+
const orderOffers = (tree, offers) => {
|
|
2729
|
+
const offerByHash = /* @__PURE__ */ new Map();
|
|
2730
|
+
for (const offer of offers) offerByHash.set(normalizeHash(hash(offer)), offer);
|
|
2731
|
+
const entries = tree.dump().values.map((value) => {
|
|
2732
|
+
const hash = normalizeHash(value.value[0]);
|
|
2733
|
+
const offer = offerByHash.get(hash);
|
|
2734
|
+
if (!offer) throw new TreeError(`missing offer for leaf ${hash}`);
|
|
2735
|
+
return {
|
|
2736
|
+
offer,
|
|
2737
|
+
treeIndex: value.treeIndex
|
|
2738
|
+
};
|
|
2739
|
+
});
|
|
2740
|
+
entries.sort((a, b) => b.treeIndex - a.treeIndex);
|
|
2741
|
+
return entries.map((item) => item.offer);
|
|
2742
|
+
};
|
|
2743
|
+
/**
|
|
2744
|
+
* Generates merkle proofs for all offers in a tree.
|
|
2745
|
+
*
|
|
2746
|
+
* Each proof allows independent verification that an offer is included in the tree
|
|
2747
|
+
* without requiring the full tree. Proofs are ordered by StandardMerkleTree leaf ordering.
|
|
2748
|
+
*
|
|
2749
|
+
* @param tree - The {@link Tree} to generate proofs for.
|
|
2750
|
+
* @returns Array of proofs - {@link Proof}
|
|
2751
|
+
*/
|
|
2752
|
+
const proofs = (tree) => {
|
|
2753
|
+
return tree.offers.map((offer) => {
|
|
2754
|
+
return {
|
|
2755
|
+
offer,
|
|
2756
|
+
path: tree.getProof([hash(offer)])
|
|
2757
|
+
};
|
|
2758
|
+
});
|
|
2759
|
+
};
|
|
2760
|
+
const normalizeSignatureDomain = (domain, errorFactory) => {
|
|
2761
|
+
let chainId;
|
|
2762
|
+
try {
|
|
2763
|
+
chainId = typeof domain.chainId === "bigint" ? domain.chainId : BigInt(domain.chainId);
|
|
2764
|
+
} catch {
|
|
2765
|
+
throw errorFactory("invalid chainId");
|
|
2766
|
+
}
|
|
2767
|
+
if (chainId < 0n) throw errorFactory("invalid chainId");
|
|
2768
|
+
if (!isAddress(domain.verifyingContract)) throw errorFactory("invalid verifyingContract");
|
|
2769
|
+
return {
|
|
2770
|
+
chainId,
|
|
2771
|
+
verifyingContract: domain.verifyingContract.toLowerCase()
|
|
2772
|
+
};
|
|
2773
|
+
};
|
|
2774
|
+
const assertHex = (value, expectedBytes, name, errorFactory = (reason) => new DecodeError(reason)) => {
|
|
2775
|
+
if (typeof value !== "string" || !isHex(value)) throw errorFactory(`${name} is not a valid hex string`);
|
|
2776
|
+
if (hexToBytes(value).length !== expectedBytes) throw errorFactory(`${name}: expected ${expectedBytes} bytes`);
|
|
2777
|
+
};
|
|
2778
|
+
const verifySignatureAndRecoverAddress = async (params) => {
|
|
2779
|
+
const { root, signature, domain, errorFactory } = params;
|
|
2780
|
+
assertHex(root, 32, "root", errorFactory);
|
|
2781
|
+
assertHex(signature, 65, "signature", errorFactory);
|
|
2782
|
+
const hash = hashTypedData({
|
|
2783
|
+
domain,
|
|
2784
|
+
types: signatureTypes,
|
|
2785
|
+
primaryType: "Root",
|
|
2786
|
+
message: { root }
|
|
2787
|
+
});
|
|
2788
|
+
try {
|
|
2789
|
+
return await recoverAddress({
|
|
2790
|
+
hash,
|
|
2791
|
+
signature
|
|
2792
|
+
});
|
|
2793
|
+
} catch {
|
|
2794
|
+
throw errorFactory("signature recovery failed");
|
|
2795
|
+
}
|
|
2796
|
+
};
|
|
2797
|
+
/**
|
|
2798
|
+
* Encodes a merkle tree without a signature into hex payload for client-side signing.
|
|
2799
|
+
*
|
|
2800
|
+
* Layout: `0x{vv}{gzip([...offers])}{root}` where:
|
|
2801
|
+
* - `{vv}`: 1-byte version (currently 0x01)
|
|
2802
|
+
* - `{gzip([...offers])}`: gzipped JSON array of serialized offers
|
|
2803
|
+
* - `{root}`: 32-byte merkle root
|
|
2804
|
+
*
|
|
2805
|
+
* Validates root integrity before encoding.
|
|
2806
|
+
*
|
|
2807
|
+
* @param tree - Merkle tree of offers
|
|
2808
|
+
* @returns Hex-encoded unsigned payload
|
|
2809
|
+
* @throws {EncodeError} If root mismatch
|
|
2810
|
+
*/
|
|
2811
|
+
const encodeUnsigned = (tree) => {
|
|
2812
|
+
validateTreeForEncoding(tree);
|
|
2813
|
+
return bytesToHex(encodeUnsignedBytes(tree));
|
|
2814
|
+
};
|
|
2815
|
+
const validateTreeForEncoding = (tree, domain) => {
|
|
2816
|
+
if (VERSION$1 > 255) throw new EncodeError(`version overflow: ${VERSION$1} exceeds 255`);
|
|
2817
|
+
const computed = from$7(tree.offers);
|
|
2818
|
+
if (tree.root !== computed.root) throw new EncodeError(`root mismatch: expected ${computed.root}, got ${tree.root}`);
|
|
2819
|
+
if (domain) {
|
|
2820
|
+
const mismatched = tree.offers.find((offer) => BigInt(offer.chainId) !== domain.chainId);
|
|
2821
|
+
if (mismatched) throw new EncodeError(`chainId mismatch: expected ${domain.chainId}, got ${mismatched.chainId}`);
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
const encodeUnsignedBytes = (tree) => {
|
|
2825
|
+
const offersPayload = tree.offers.map(serialize);
|
|
2826
|
+
const compressed = gzip(JSON.stringify(offersPayload));
|
|
2827
|
+
const rootBytes = hexToBytes(tree.root);
|
|
2828
|
+
const encoded = new Uint8Array(1 + compressed.length + 32);
|
|
2829
|
+
encoded[0] = VERSION$1;
|
|
2830
|
+
encoded.set(compressed, 1);
|
|
2831
|
+
encoded.set(rootBytes, 1 + compressed.length);
|
|
2832
|
+
return encoded;
|
|
2833
|
+
};
|
|
2834
|
+
/**
|
|
2835
|
+
* Decodes hex calldata into a validated merkle tree.
|
|
2836
|
+
*
|
|
2837
|
+
* Validates signature before decompression for fail-fast rejection of invalid payloads.
|
|
2838
|
+
* Returns the tree with separately validated signature and recovered signer address.
|
|
2839
|
+
*
|
|
2840
|
+
* Validation order:
|
|
2841
|
+
* 1. Version check
|
|
2842
|
+
* 2. Signature verification (fail-fast, before decompression)
|
|
2843
|
+
* 3. Decompression (only if signature valid)
|
|
2844
|
+
* 4. Root verification (computed from offers vs embedded root)
|
|
2845
|
+
*
|
|
2846
|
+
* @example
|
|
2847
|
+
* ```typescript
|
|
2848
|
+
* const { tree, signature, signer } = await Tree.decode(calldata, { chainId, verifyingContract });
|
|
2849
|
+
* console.log(`Tree signed by ${signer} with ${tree.offers.length} offers`);
|
|
2850
|
+
* ```
|
|
2851
|
+
*
|
|
2852
|
+
* @param encoded - Hex calldata in format `0x{vv}{gzip}{root}{signature}`
|
|
2853
|
+
* @param domain - EIP-712 domain with chain id and verifying contract
|
|
2854
|
+
* @returns Validated tree, signature, and recovered signer address
|
|
2855
|
+
* @throws {DecodeError} If version invalid, signature invalid, or root mismatch
|
|
2856
|
+
*/
|
|
2857
|
+
const decode = async (encoded, domain) => {
|
|
2858
|
+
const errorFactory = (reason) => new DecodeError(reason);
|
|
2859
|
+
const normalizedDomain = normalizeSignatureDomain(domain, errorFactory);
|
|
2860
|
+
const bytes = hexToBytes(encoded);
|
|
2861
|
+
if (bytes.length < 98) throw new DecodeError("payload too short");
|
|
2862
|
+
const version = bytes[0];
|
|
2863
|
+
if (version !== (VERSION$1 & 255)) throw new DecodeError(`invalid version: expected ${VERSION$1}, got ${version ?? 0}`);
|
|
2864
|
+
const signature = bytesToHex(bytes.slice(-65));
|
|
2865
|
+
const root = bytesToHex(bytes.slice(-97, -65));
|
|
2866
|
+
assertHex(root, 32, "root");
|
|
2867
|
+
assertHex(signature, 65, "signature");
|
|
2868
|
+
const signer = await verifySignatureAndRecoverAddress({
|
|
2869
|
+
root,
|
|
2870
|
+
signature,
|
|
2871
|
+
domain: normalizedDomain,
|
|
2872
|
+
errorFactory
|
|
2873
|
+
});
|
|
2874
|
+
const compressed = bytes.slice(1, -97);
|
|
2875
|
+
let decoded;
|
|
2876
|
+
try {
|
|
2877
|
+
decoded = ungzip(compressed, { to: "string" });
|
|
2878
|
+
} catch {
|
|
2879
|
+
throw new DecodeError("decompression failed");
|
|
2880
|
+
}
|
|
2881
|
+
let rawOffers;
|
|
2882
|
+
try {
|
|
2883
|
+
rawOffers = JSON.parse(decoded);
|
|
2884
|
+
} catch {
|
|
2885
|
+
throw new DecodeError("JSON parse failed");
|
|
2886
|
+
}
|
|
2887
|
+
const tree = from$7(rawOffers.map((o) => OfferSchema().parse(o)));
|
|
2888
|
+
if (root !== tree.root) throw new DecodeError(`root mismatch: expected ${tree.root}, got ${root}`);
|
|
2889
|
+
const chainIdMismatch = tree.offers.find((offer) => BigInt(offer.chainId) !== normalizedDomain.chainId);
|
|
2890
|
+
if (chainIdMismatch) throw new DecodeError(`chainId mismatch: expected ${normalizedDomain.chainId}, got ${chainIdMismatch.chainId}`);
|
|
2891
|
+
return {
|
|
2892
|
+
tree,
|
|
2893
|
+
signature,
|
|
2894
|
+
signer
|
|
2895
|
+
};
|
|
2896
|
+
};
|
|
2897
|
+
/**
|
|
2898
|
+
* Error thrown during tree building operations.
|
|
2899
|
+
* Indicates structural issues with the tree (missing offers, inconsistent state).
|
|
2900
|
+
*/
|
|
2901
|
+
var TreeError = class extends BaseError {
|
|
2902
|
+
name = "Tree.TreeError";
|
|
2903
|
+
constructor(reason) {
|
|
2904
|
+
super(`Tree error: ${reason}`);
|
|
2905
|
+
}
|
|
2906
|
+
};
|
|
2907
|
+
/**
|
|
2908
|
+
* Error thrown during tree encoding.
|
|
2909
|
+
* Indicates validation failures (signature, root mismatch, mixed makers).
|
|
2910
|
+
*/
|
|
2911
|
+
var EncodeError = class extends BaseError {
|
|
2912
|
+
name = "Tree.EncodeError";
|
|
2913
|
+
constructor(reason) {
|
|
2914
|
+
super(`Failed to encode tree: ${reason}`);
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2917
|
+
/**
|
|
2918
|
+
* Error thrown during tree decoding.
|
|
2919
|
+
* Indicates payload corruption, version mismatch, or validation failures.
|
|
2920
|
+
*/
|
|
2921
|
+
var DecodeError = class extends BaseError {
|
|
2922
|
+
name = "Tree.DecodeError";
|
|
2923
|
+
constructor(reason) {
|
|
2924
|
+
super(`Failed to decode tree: ${reason}`);
|
|
2925
|
+
}
|
|
2926
|
+
};
|
|
2927
|
+
|
|
2928
|
+
//#endregion
|
|
2929
|
+
//#region src/core/types.ts
|
|
2930
|
+
const BrandTypeId = Symbol.for("mempool/Brand");
|
|
2931
|
+
|
|
2932
|
+
//#endregion
|
|
2933
|
+
//#region src/gatekeeper/Rules.ts
|
|
2934
|
+
const chains$1 = ({ chains }) => single("chain_ids", `Validates that offer chain is one of: [${chains.map((c) => c.id).join(", ")}]`, (offer) => {
|
|
2935
|
+
const allowedChainIds = chains.map((c) => c.id);
|
|
2936
|
+
if (!allowedChainIds.some((id) => id === offer.chainId)) return { message: `Chain ID ${offer.chainId} is not in the allowed chains (${allowedChainIds.join(", ")})` };
|
|
2937
|
+
});
|
|
2938
|
+
const maturity = ({ maturities }) => single("maturity", `Validates that offer maturity is one of: [${maturities.join(", ")}]`, (offer) => {
|
|
2939
|
+
const allowedMaturities = maturities.map((m) => from$16(m));
|
|
2940
|
+
if (!allowedMaturities.includes(offer.maturity)) return { message: `Maturity must be end of current month (${allowedMaturities[0]}) or end of next month (${allowedMaturities[1]}). Got: ${offer.maturity}` };
|
|
2941
|
+
});
|
|
2942
|
+
const callback = ({ callbacks, allowedAddresses }) => single("callback", `Validates callbacks: buy empty callback is ${callbacks.includes(Type$1.BuyWithEmptyCallback) ? "allowed" : "not allowed"}; sell offers must use a non-empty callback; non-empty callbacks must target one of [${allowedAddresses.map((a) => a.toLowerCase()).join(", ")}]`, (offer) => {
|
|
2943
|
+
if (isEmptyCallback(offer) && offer.buy && !callbacks?.find((c) => c === Type$1.BuyWithEmptyCallback)) return { message: "Buy offers with empty callback not allowed." };
|
|
2944
|
+
if (isEmptyCallback(offer) && !offer.buy) return { message: "Sell offers require a non-empty callback." };
|
|
2945
|
+
if (!isEmptyCallback(offer)) {
|
|
2946
|
+
if (!allowedAddresses.includes(offer.callback.address?.toLowerCase())) return { message: `Callback address ${offer.callback.address} is not allowed.` };
|
|
2947
|
+
}
|
|
2948
|
+
});
|
|
2949
|
+
/**
|
|
2950
|
+
* A validation rule that checks if the offer's tokens are allowed for its chain.
|
|
2951
|
+
* @param assetsByChainId - Allowed assets indexed by chain id.
|
|
2952
|
+
* @returns The issue that was found. If the offer is valid, this will be undefined.
|
|
2953
|
+
*/
|
|
2954
|
+
const token = ({ assetsByChainId }) => single("token", "Validates that offer loan token and collateral tokens are in the allowed assets list for the offer chain", (offer) => {
|
|
2955
|
+
const allowedAssets = assetsByChainId[offer.chainId]?.map((asset) => asset.toLowerCase());
|
|
2956
|
+
if (!allowedAssets || allowedAssets.length === 0) return { message: `No allowed assets for chain ${offer.chainId}` };
|
|
2957
|
+
if (!allowedAssets.includes(offer.loanToken.toLowerCase())) return { message: "Loan token is not allowed" };
|
|
2958
|
+
if (offer.collaterals.some((collateral) => !allowedAssets.includes(collateral.asset.toLowerCase()))) return { message: "Collateral is not allowed" };
|
|
2959
|
+
});
|
|
2960
|
+
/**
|
|
2961
|
+
* A validation rule that checks if the offer's oracle addresses are allowed for its chain.
|
|
2962
|
+
* @param oraclesByChainId - Allowed oracles indexed by chain id.
|
|
2963
|
+
* @returns The issue that was found. If the offer is valid, this will be undefined.
|
|
2964
|
+
*/
|
|
2965
|
+
const oracle = ({ oraclesByChainId }) => single("oracle", "Validates that offer collateral oracles are in the allowed oracle list for the offer chain", (offer) => {
|
|
2966
|
+
const allowedOracles = oraclesByChainId[offer.chainId]?.map((oracle) => oracle.toLowerCase());
|
|
2967
|
+
if (!allowedOracles || allowedOracles.length === 0) return { message: `No allowed oracles for chain ${offer.chainId}` };
|
|
2968
|
+
if (offer.collaterals.some((collateral) => !allowedOracles.includes(collateral.oracle.toLowerCase()))) return { message: "Oracle is not allowed" };
|
|
2969
|
+
});
|
|
2970
|
+
/**
|
|
2971
|
+
* A batch validation rule that ensures all offers in a tree have the same maker address.
|
|
2972
|
+
* Returns an issue only for the first non-conforming offer.
|
|
2973
|
+
* This rule is signing-agnostic; signer verification is handled at the collector level.
|
|
2974
|
+
*/
|
|
2975
|
+
const sameMaker = () => batch("mixed_maker", "Validates that all offers in a batch have the same maker address", (offers) => {
|
|
2976
|
+
const issues = /* @__PURE__ */ new Map();
|
|
2977
|
+
if (offers.length === 0) return issues;
|
|
2978
|
+
const firstMaker = offers[0].maker.toLowerCase();
|
|
2897
2979
|
for (let i = 1; i < offers.length; i++) {
|
|
2898
2980
|
const offer = offers[i];
|
|
2899
2981
|
if (offer.maker.toLowerCase() !== firstMaker) {
|
|
@@ -2916,7 +2998,11 @@ const amountMutualExclusivity = () => single("amount_mutual_exclusivity", "Valid
|
|
|
2916
2998
|
//#region src/gatekeeper/morphoRules.ts
|
|
2917
2999
|
const morphoRules = (chains) => {
|
|
2918
3000
|
const assetsByChainId = {};
|
|
2919
|
-
|
|
3001
|
+
const oraclesByChainId = {};
|
|
3002
|
+
for (const chain of chains) {
|
|
3003
|
+
assetsByChainId[chain.id] = assets[chain.id.toString()] ?? [];
|
|
3004
|
+
oraclesByChainId[chain.id] = oracles$1[chain.id.toString()] ?? [];
|
|
3005
|
+
}
|
|
2920
3006
|
return [
|
|
2921
3007
|
sameMaker(),
|
|
2922
3008
|
amountMutualExclusivity(),
|
|
@@ -2930,176 +3016,95 @@ const morphoRules = (chains) => {
|
|
|
2930
3016
|
],
|
|
2931
3017
|
allowedAddresses: chains.flatMap((c) => getCallbackAddresses(c.name))
|
|
2932
3018
|
}),
|
|
2933
|
-
token({ assetsByChainId })
|
|
3019
|
+
token({ assetsByChainId }),
|
|
3020
|
+
oracle({ oraclesByChainId })
|
|
2934
3021
|
];
|
|
2935
3022
|
};
|
|
2936
3023
|
|
|
2937
3024
|
//#endregion
|
|
2938
|
-
//#region src/
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
};
|
|
2966
|
-
var NotFoundError = class extends APIError {
|
|
2967
|
-
constructor(message) {
|
|
2968
|
-
super(STATUS_CODE.NOT_FOUND, message, "NOT_FOUND");
|
|
2969
|
-
}
|
|
2970
|
-
};
|
|
2971
|
-
var InternalServerError = class extends APIError {
|
|
2972
|
-
constructor(message = "Internal server error") {
|
|
2973
|
-
super(STATUS_CODE.INTERNAL_SERVER_ERROR, message, "INTERNAL_SERVER_ERROR");
|
|
2974
|
-
}
|
|
2975
|
-
};
|
|
2976
|
-
var BadRequestError = class extends APIError {
|
|
2977
|
-
constructor(message = "Invalid JSON format", details) {
|
|
2978
|
-
super(STATUS_CODE.BAD_REQUEST, message, "BAD_REQUEST", details);
|
|
2979
|
-
}
|
|
2980
|
-
};
|
|
2981
|
-
function success(args) {
|
|
2982
|
-
const { data, cursor } = args;
|
|
2983
|
-
return {
|
|
2984
|
-
statusCode: STATUS_CODE.SUCCESS,
|
|
2985
|
-
body: {
|
|
2986
|
-
meta: { timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
2987
|
-
cursor: cursor ?? null,
|
|
2988
|
-
data
|
|
3025
|
+
//#region src/gatekeeper/ConfigRules.ts
|
|
3026
|
+
/**
|
|
3027
|
+
* Build the configured rules (maturities + callback addresses + loan tokens + oracles) for the provided chains.
|
|
3028
|
+
* @param chains - Chains to include in the configured rules.
|
|
3029
|
+
* @returns Sorted list of config rules.
|
|
3030
|
+
*/
|
|
3031
|
+
function buildConfigRules(chains) {
|
|
3032
|
+
const rules = [];
|
|
3033
|
+
for (const chain of chains) {
|
|
3034
|
+
const config = configs[chain.name];
|
|
3035
|
+
const maturities = config.maturities ?? [];
|
|
3036
|
+
for (const maturityName of maturities) rules.push({
|
|
3037
|
+
type: "maturity",
|
|
3038
|
+
chain_id: chain.id,
|
|
3039
|
+
name: maturityName,
|
|
3040
|
+
timestamp: from$16(maturityName)
|
|
3041
|
+
});
|
|
3042
|
+
const callbacks = config.callbacks ?? [];
|
|
3043
|
+
for (const callback of callbacks) {
|
|
3044
|
+
if (callback.type === Type$1.BuyWithEmptyCallback) continue;
|
|
3045
|
+
if (!("addresses" in callback)) continue;
|
|
3046
|
+
for (const address of callback.addresses) rules.push({
|
|
3047
|
+
type: "callback",
|
|
3048
|
+
chain_id: chain.id,
|
|
3049
|
+
address: normalizeAddress(address),
|
|
3050
|
+
callback_type: callback.type
|
|
3051
|
+
});
|
|
2989
3052
|
}
|
|
2990
|
-
|
|
3053
|
+
const loanTokens = assets[chain.id.toString()] ?? [];
|
|
3054
|
+
for (const address of loanTokens) rules.push({
|
|
3055
|
+
type: "loan_token",
|
|
3056
|
+
chain_id: chain.id,
|
|
3057
|
+
address: normalizeAddress(address)
|
|
3058
|
+
});
|
|
3059
|
+
const oracles = oracles$1[chain.id.toString()] ?? [];
|
|
3060
|
+
for (const address of oracles) rules.push({
|
|
3061
|
+
type: "oracle",
|
|
3062
|
+
chain_id: chain.id,
|
|
3063
|
+
address: normalizeAddress(address)
|
|
3064
|
+
});
|
|
3065
|
+
}
|
|
3066
|
+
rules.sort(compareConfigRules);
|
|
3067
|
+
return rules;
|
|
2991
3068
|
}
|
|
2992
3069
|
/**
|
|
2993
|
-
*
|
|
2994
|
-
*
|
|
3070
|
+
* Compute a stable checksum for the provided configured rules.
|
|
3071
|
+
* @param rules - Configured rules to checksum.
|
|
3072
|
+
* @returns MD5 checksum.
|
|
2995
3073
|
*/
|
|
2996
|
-
function
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
|
-
return {
|
|
3004
|
-
statusCode: error.statusCode,
|
|
3005
|
-
body: {
|
|
3006
|
-
meta: { timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
3007
|
-
error: {
|
|
3008
|
-
code: error.code,
|
|
3009
|
-
message: error.message,
|
|
3010
|
-
...error.details && typeof error.details === "object" ? { details: error.details } : {}
|
|
3011
|
-
}
|
|
3074
|
+
function buildConfigRulesChecksum(rules) {
|
|
3075
|
+
const hash = createHash("md5");
|
|
3076
|
+
const orderedRules = [...rules].sort(compareConfigRules);
|
|
3077
|
+
for (const rule of orderedRules) {
|
|
3078
|
+
if (rule.type === "maturity") {
|
|
3079
|
+
hash.update(`maturity:${rule.chain_id}:${rule.name}:${rule.timestamp}\n`);
|
|
3080
|
+
continue;
|
|
3012
3081
|
}
|
|
3013
|
-
|
|
3014
|
-
}
|
|
3015
|
-
|
|
3016
|
-
return new ValidationError("Validation failed", error.issues.map((issue) => {
|
|
3017
|
-
const field = issue.path.join(".");
|
|
3018
|
-
let msg = issue.message;
|
|
3019
|
-
switch (issue.code) {
|
|
3020
|
-
case "invalid_type":
|
|
3021
|
-
if (issue.message.includes("received undefined")) msg = `${field} is required`;
|
|
3022
|
-
break;
|
|
3023
|
-
case "invalid_format":
|
|
3024
|
-
msg = issue.format === "regex" ? issue.message : `${field} has an invalid format`;
|
|
3025
|
-
break;
|
|
3026
|
-
default: break;
|
|
3082
|
+
if (rule.type === "callback") {
|
|
3083
|
+
hash.update(`callback:${rule.chain_id}:${rule.callback_type}:${rule.address}\n`);
|
|
3084
|
+
continue;
|
|
3027
3085
|
}
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
};
|
|
3032
|
-
}));
|
|
3033
|
-
}
|
|
3034
|
-
|
|
3035
|
-
//#endregion
|
|
3036
|
-
//#region src/logger/Logger.ts
|
|
3037
|
-
const LogLevelValues = [
|
|
3038
|
-
"trace",
|
|
3039
|
-
"debug",
|
|
3040
|
-
"info",
|
|
3041
|
-
"warn",
|
|
3042
|
-
"error",
|
|
3043
|
-
"fatal",
|
|
3044
|
-
"silent"
|
|
3045
|
-
];
|
|
3046
|
-
function defaultLogger(minLevel, pretty) {
|
|
3047
|
-
const threshold = minLevel ?? process.env.ROUTER_LOG_LEVEL ?? "info";
|
|
3048
|
-
const prettyEnabled = typeof pretty === "boolean" ? pretty : String(process.env.ROUTER_LOG_PRETTY ?? "false").toLowerCase() === "true";
|
|
3049
|
-
const levelIndexByName = LogLevelValues.reduce((acc, lvl, idx) => {
|
|
3050
|
-
acc[lvl] = idx;
|
|
3051
|
-
return acc;
|
|
3052
|
-
}, {});
|
|
3053
|
-
const isEnabled = (methodLevel) => levelIndexByName[methodLevel] >= levelIndexByName[threshold];
|
|
3054
|
-
const wrap = (consoleMethod, methodLevel) => isEnabled(methodLevel) ? (entry) => {
|
|
3055
|
-
if (!prettyEnabled) {
|
|
3056
|
-
console[consoleMethod](stringify({
|
|
3057
|
-
level: methodLevel,
|
|
3058
|
-
...entry
|
|
3059
|
-
}));
|
|
3060
|
-
return;
|
|
3086
|
+
if (rule.type === "oracle") {
|
|
3087
|
+
hash.update(`oracle:${rule.chain_id}:${rule.address}\n`);
|
|
3088
|
+
continue;
|
|
3061
3089
|
}
|
|
3062
|
-
|
|
3063
|
-
const stack = typeof rest.stack === "string" ? rest.stack : void 0;
|
|
3064
|
-
if (stack) delete rest.stack;
|
|
3065
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3066
|
-
const level = methodLevel.toUpperCase();
|
|
3067
|
-
const extras = Object.entries(rest).map(([k, v]) => `${k}=${formatValue(v)}`).join(" ");
|
|
3068
|
-
const line = extras.length > 0 ? `${timestamp} [${level}] ${msg} ${extras}` : `${timestamp} [${level}] ${msg}`;
|
|
3069
|
-
console[consoleMethod](line);
|
|
3070
|
-
if (stack) console[consoleMethod](stack);
|
|
3071
|
-
} : () => {};
|
|
3072
|
-
return {
|
|
3073
|
-
trace: wrap("trace", "trace"),
|
|
3074
|
-
debug: wrap("debug", "debug"),
|
|
3075
|
-
info: wrap("info", "info"),
|
|
3076
|
-
warn: wrap("warn", "warn"),
|
|
3077
|
-
error: wrap("error", "error"),
|
|
3078
|
-
fatal: wrap("error", "fatal")
|
|
3079
|
-
};
|
|
3080
|
-
}
|
|
3081
|
-
const loggerContext = new AsyncLocalStorage();
|
|
3082
|
-
function runWithLogger(logger, fn) {
|
|
3083
|
-
return loggerContext.run(logger, fn);
|
|
3084
|
-
}
|
|
3085
|
-
function getLogger() {
|
|
3086
|
-
return loggerContext.getStore() ?? defaultLogger();
|
|
3087
|
-
}
|
|
3088
|
-
function formatValue(value) {
|
|
3089
|
-
if (value === null || value === void 0 || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") return String(value);
|
|
3090
|
-
if (typeof value === "string") {
|
|
3091
|
-
if (value.includes(" ")) return JSON.stringify(value);
|
|
3092
|
-
return value;
|
|
3090
|
+
hash.update(`loan_token:${rule.chain_id}:${rule.address}\n`);
|
|
3093
3091
|
}
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3092
|
+
return hash.digest("hex");
|
|
3093
|
+
}
|
|
3094
|
+
function normalizeAddress(address) {
|
|
3095
|
+
return address.toLowerCase();
|
|
3096
|
+
}
|
|
3097
|
+
function compareConfigRules(left, right) {
|
|
3098
|
+
if (left.chain_id !== right.chain_id) return left.chain_id - right.chain_id;
|
|
3099
|
+
if (left.type !== right.type) return left.type.localeCompare(right.type);
|
|
3100
|
+
if (left.type === "maturity" && right.type === "maturity") return left.timestamp - right.timestamp;
|
|
3101
|
+
if (left.type === "callback" && right.type === "callback") {
|
|
3102
|
+
if (left.callback_type !== right.callback_type) return left.callback_type.localeCompare(right.callback_type);
|
|
3103
|
+
return left.address.localeCompare(right.address);
|
|
3102
3104
|
}
|
|
3105
|
+
if (left.type === "loan_token" && right.type === "loan_token") return left.address.localeCompare(right.address);
|
|
3106
|
+
if (left.type === "oracle" && right.type === "oracle") return left.address.localeCompare(right.address);
|
|
3107
|
+
return 0;
|
|
3103
3108
|
}
|
|
3104
3109
|
|
|
3105
3110
|
//#endregion
|
|
@@ -3169,65 +3174,163 @@ function from$5(obligation, quote) {
|
|
|
3169
3174
|
bid: { price: quote.bid.price.toString() }
|
|
3170
3175
|
};
|
|
3171
3176
|
}
|
|
3172
|
-
|
|
3173
|
-
//#endregion
|
|
3174
|
-
//#region src/api/Schema/OfferResponse.ts
|
|
3177
|
+
|
|
3178
|
+
//#endregion
|
|
3179
|
+
//#region src/api/Schema/OfferResponse.ts
|
|
3180
|
+
/**
|
|
3181
|
+
* Creates an `OfferResponse` matching the Solidity Offer struct layout.
|
|
3182
|
+
* @constructor
|
|
3183
|
+
* @param input - {@link Input}
|
|
3184
|
+
* @returns The created `OfferResponse`. {@link OfferResponse}
|
|
3185
|
+
*/
|
|
3186
|
+
function from$4(input) {
|
|
3187
|
+
const base = {
|
|
3188
|
+
offer: {
|
|
3189
|
+
obligation: {
|
|
3190
|
+
loan_token: input.loanToken,
|
|
3191
|
+
collaterals: input.collaterals.map((c) => ({
|
|
3192
|
+
token: c.asset,
|
|
3193
|
+
lltv: c.lltv.toString(),
|
|
3194
|
+
oracle: c.oracle
|
|
3195
|
+
})),
|
|
3196
|
+
maturity: input.maturity
|
|
3197
|
+
},
|
|
3198
|
+
buy: input.buy,
|
|
3199
|
+
maker: input.maker,
|
|
3200
|
+
assets: input.assets.toString(),
|
|
3201
|
+
obligation_units: input.obligationUnits.toString(),
|
|
3202
|
+
obligation_shares: input.obligationShares.toString(),
|
|
3203
|
+
start: input.start,
|
|
3204
|
+
expiry: input.expiry,
|
|
3205
|
+
price: input.price.toString(),
|
|
3206
|
+
group: input.group,
|
|
3207
|
+
session: input.session,
|
|
3208
|
+
callback: input.callback.address,
|
|
3209
|
+
callback_data: input.callback.data
|
|
3210
|
+
},
|
|
3211
|
+
offer_hash: input.hash,
|
|
3212
|
+
obligation_id: id({
|
|
3213
|
+
chainId: input.chainId,
|
|
3214
|
+
loanToken: input.loanToken,
|
|
3215
|
+
collaterals: [...input.collaterals],
|
|
3216
|
+
maturity: input.maturity
|
|
3217
|
+
}),
|
|
3218
|
+
chain_id: input.chainId,
|
|
3219
|
+
consumed: input.consumed.toString(),
|
|
3220
|
+
takeable: input.takeable.toString(),
|
|
3221
|
+
block_number: input.blockNumber
|
|
3222
|
+
};
|
|
3223
|
+
if (!input.proof || !input.root || !input.signature) return {
|
|
3224
|
+
...base,
|
|
3225
|
+
root: null,
|
|
3226
|
+
proof: null,
|
|
3227
|
+
signature: null
|
|
3228
|
+
};
|
|
3229
|
+
return {
|
|
3230
|
+
...base,
|
|
3231
|
+
root: input.root.toLowerCase(),
|
|
3232
|
+
proof: input.proof.map((p) => p.toLowerCase()),
|
|
3233
|
+
signature: input.signature.toLowerCase()
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
//#endregion
|
|
3238
|
+
//#region src/api/Controllers/Payload.ts
|
|
3239
|
+
const API_ERROR_CODES = [
|
|
3240
|
+
"VALIDATION_ERROR",
|
|
3241
|
+
"NOT_FOUND",
|
|
3242
|
+
"INTERNAL_SERVER_ERROR",
|
|
3243
|
+
"BAD_REQUEST"
|
|
3244
|
+
];
|
|
3245
|
+
let STATUS_CODE = /* @__PURE__ */ function(STATUS_CODE) {
|
|
3246
|
+
STATUS_CODE[STATUS_CODE["SUCCESS"] = 200] = "SUCCESS";
|
|
3247
|
+
STATUS_CODE[STATUS_CODE["BAD_REQUEST"] = 400] = "BAD_REQUEST";
|
|
3248
|
+
STATUS_CODE[STATUS_CODE["NOT_FOUND"] = 404] = "NOT_FOUND";
|
|
3249
|
+
STATUS_CODE[STATUS_CODE["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
|
|
3250
|
+
return STATUS_CODE;
|
|
3251
|
+
}({});
|
|
3252
|
+
var APIError = class extends Error {
|
|
3253
|
+
constructor(statusCode, message, code, details) {
|
|
3254
|
+
super(message);
|
|
3255
|
+
this.statusCode = statusCode;
|
|
3256
|
+
this.code = code;
|
|
3257
|
+
this.details = details;
|
|
3258
|
+
this.name = "APIError";
|
|
3259
|
+
}
|
|
3260
|
+
};
|
|
3261
|
+
var ValidationError = class extends APIError {
|
|
3262
|
+
constructor(message, details) {
|
|
3263
|
+
super(STATUS_CODE.BAD_REQUEST, message, "VALIDATION_ERROR", details);
|
|
3264
|
+
}
|
|
3265
|
+
};
|
|
3266
|
+
var NotFoundError = class extends APIError {
|
|
3267
|
+
constructor(message) {
|
|
3268
|
+
super(STATUS_CODE.NOT_FOUND, message, "NOT_FOUND");
|
|
3269
|
+
}
|
|
3270
|
+
};
|
|
3271
|
+
var InternalServerError = class extends APIError {
|
|
3272
|
+
constructor(message = "Internal server error") {
|
|
3273
|
+
super(STATUS_CODE.INTERNAL_SERVER_ERROR, message, "INTERNAL_SERVER_ERROR");
|
|
3274
|
+
}
|
|
3275
|
+
};
|
|
3276
|
+
var BadRequestError = class extends APIError {
|
|
3277
|
+
constructor(message = "Invalid JSON format", details) {
|
|
3278
|
+
super(STATUS_CODE.BAD_REQUEST, message, "BAD_REQUEST", details);
|
|
3279
|
+
}
|
|
3280
|
+
};
|
|
3281
|
+
function success(args) {
|
|
3282
|
+
const { data, cursor } = args;
|
|
3283
|
+
return {
|
|
3284
|
+
statusCode: STATUS_CODE.SUCCESS,
|
|
3285
|
+
body: {
|
|
3286
|
+
meta: { timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
3287
|
+
cursor: cursor ?? null,
|
|
3288
|
+
data
|
|
3289
|
+
}
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3175
3292
|
/**
|
|
3176
|
-
*
|
|
3177
|
-
*
|
|
3178
|
-
* @param input - {@link Input}
|
|
3179
|
-
* @returns The created `OfferResponse`. {@link OfferResponse}
|
|
3293
|
+
* Generic failure builder. Preserves the concrete status code when the input is an APIError.
|
|
3294
|
+
* If not an APIError, maps to INTERNAL_SERVER_ERROR. Zod & SyntaxError are mapped to BAD_REQUEST.
|
|
3180
3295
|
*/
|
|
3181
|
-
function
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
lltv: c.lltv.toString(),
|
|
3189
|
-
oracle: c.oracle
|
|
3190
|
-
})),
|
|
3191
|
-
maturity: input.maturity
|
|
3192
|
-
},
|
|
3193
|
-
buy: input.buy,
|
|
3194
|
-
maker: input.maker,
|
|
3195
|
-
assets: input.assets.toString(),
|
|
3196
|
-
obligation_units: input.obligationUnits.toString(),
|
|
3197
|
-
obligation_shares: input.obligationShares.toString(),
|
|
3198
|
-
start: input.start,
|
|
3199
|
-
expiry: input.expiry,
|
|
3200
|
-
price: input.price.toString(),
|
|
3201
|
-
group: input.group,
|
|
3202
|
-
session: input.session,
|
|
3203
|
-
callback: input.callback.address,
|
|
3204
|
-
callback_data: input.callback.data
|
|
3205
|
-
},
|
|
3206
|
-
offer_hash: input.hash,
|
|
3207
|
-
obligation_id: id({
|
|
3208
|
-
chainId: input.chainId,
|
|
3209
|
-
loanToken: input.loanToken,
|
|
3210
|
-
collaterals: [...input.collaterals],
|
|
3211
|
-
maturity: input.maturity
|
|
3212
|
-
}),
|
|
3213
|
-
chain_id: input.chainId,
|
|
3214
|
-
consumed: input.consumed.toString(),
|
|
3215
|
-
takeable: input.takeable.toString(),
|
|
3216
|
-
block_number: input.blockNumber
|
|
3217
|
-
};
|
|
3218
|
-
if (!input.proof || !input.root || !input.signature) return {
|
|
3219
|
-
...base,
|
|
3220
|
-
root: null,
|
|
3221
|
-
proof: null,
|
|
3222
|
-
signature: null
|
|
3223
|
-
};
|
|
3296
|
+
function failure(err) {
|
|
3297
|
+
if (err instanceof APIError) return handleAPIError(err);
|
|
3298
|
+
if (err instanceof SyntaxError) return handleAPIError(new BadRequestError(err.message));
|
|
3299
|
+
if (err instanceof z$2.ZodError) return handleAPIError(handleZodError(err));
|
|
3300
|
+
return handleAPIError(new InternalServerError());
|
|
3301
|
+
}
|
|
3302
|
+
function handleAPIError(error) {
|
|
3224
3303
|
return {
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3304
|
+
statusCode: error.statusCode,
|
|
3305
|
+
body: {
|
|
3306
|
+
meta: { timestamp: (/* @__PURE__ */ new Date()).toISOString() },
|
|
3307
|
+
error: {
|
|
3308
|
+
code: error.code,
|
|
3309
|
+
message: error.message,
|
|
3310
|
+
...error.details && typeof error.details === "object" ? { details: error.details } : {}
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3229
3313
|
};
|
|
3230
3314
|
}
|
|
3315
|
+
function handleZodError(error) {
|
|
3316
|
+
return new ValidationError("Validation failed", error.issues.map((issue) => {
|
|
3317
|
+
const field = issue.path.join(".");
|
|
3318
|
+
let msg = issue.message;
|
|
3319
|
+
switch (issue.code) {
|
|
3320
|
+
case "invalid_type":
|
|
3321
|
+
if (issue.message.includes("received undefined")) msg = `${field} is required`;
|
|
3322
|
+
break;
|
|
3323
|
+
case "invalid_format":
|
|
3324
|
+
msg = issue.format === "regex" ? issue.message : `${field} has an invalid format`;
|
|
3325
|
+
break;
|
|
3326
|
+
default: break;
|
|
3327
|
+
}
|
|
3328
|
+
return {
|
|
3329
|
+
field,
|
|
3330
|
+
issue: msg
|
|
3331
|
+
};
|
|
3332
|
+
}));
|
|
3333
|
+
}
|
|
3231
3334
|
|
|
3232
3335
|
//#endregion
|
|
3233
3336
|
//#region \0@oxc-project+runtime@0.110.0/helpers/decorate.js
|
|
@@ -3321,6 +3424,21 @@ const validateOfferExample = {
|
|
|
3321
3424
|
data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000100000000000000000000000034cf890db685fc536e05652fb41f02090c3fb751000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000108e644e3ab01184155270aa92a00000000000"
|
|
3322
3425
|
}
|
|
3323
3426
|
};
|
|
3427
|
+
const callbackTypesRequestExample = { callbacks: [{
|
|
3428
|
+
chain_id: 1,
|
|
3429
|
+
addresses: [
|
|
3430
|
+
"0x1111111111111111111111111111111111111111",
|
|
3431
|
+
"0x3333333333333333333333333333333333333333",
|
|
3432
|
+
"0x9999999999999999999999999999999999999999"
|
|
3433
|
+
]
|
|
3434
|
+
}] };
|
|
3435
|
+
const callbackTypesResponseExample = [{
|
|
3436
|
+
chain_id: 1,
|
|
3437
|
+
sell_erc20_callback: ["0x1111111111111111111111111111111111111111"],
|
|
3438
|
+
buy_erc20: ["0x5555555555555555555555555555555555555555"],
|
|
3439
|
+
buy_vault_v1_callback: ["0x3333333333333333333333333333333333333333"],
|
|
3440
|
+
not_supported: ["0x9999999999999999999999999999999999999999"]
|
|
3441
|
+
}];
|
|
3324
3442
|
const routerStatusExample = {
|
|
3325
3443
|
status: "live",
|
|
3326
3444
|
initialized: true,
|
|
@@ -3389,6 +3507,55 @@ __decorate([ApiProperty({
|
|
|
3389
3507
|
type: "string",
|
|
3390
3508
|
example: validateOfferExample.callback.data
|
|
3391
3509
|
})], ValidateCallbackRequest.prototype, "data", void 0);
|
|
3510
|
+
var CallbackTypesChainRequest = class {};
|
|
3511
|
+
__decorate([ApiProperty({
|
|
3512
|
+
type: "number",
|
|
3513
|
+
example: callbackTypesRequestExample.callbacks[0].chain_id
|
|
3514
|
+
})], CallbackTypesChainRequest.prototype, "chain_id", void 0);
|
|
3515
|
+
__decorate([ApiProperty({
|
|
3516
|
+
type: () => [String],
|
|
3517
|
+
example: callbackTypesRequestExample.callbacks[0].addresses
|
|
3518
|
+
})], CallbackTypesChainRequest.prototype, "addresses", void 0);
|
|
3519
|
+
var CallbackTypesRequest = class {};
|
|
3520
|
+
__decorate([ApiProperty({
|
|
3521
|
+
type: () => [CallbackTypesChainRequest],
|
|
3522
|
+
example: callbackTypesRequestExample.callbacks
|
|
3523
|
+
})], CallbackTypesRequest.prototype, "callbacks", void 0);
|
|
3524
|
+
var CallbackTypesChainResponse = class {};
|
|
3525
|
+
__decorate([ApiProperty({
|
|
3526
|
+
type: "number",
|
|
3527
|
+
example: callbackTypesResponseExample[0].chain_id
|
|
3528
|
+
})], CallbackTypesChainResponse.prototype, "chain_id", void 0);
|
|
3529
|
+
__decorate([ApiProperty({
|
|
3530
|
+
type: () => [String],
|
|
3531
|
+
required: false,
|
|
3532
|
+
example: callbackTypesResponseExample[0].buy_vault_v1_callback
|
|
3533
|
+
})], CallbackTypesChainResponse.prototype, "buy_vault_v1_callback", void 0);
|
|
3534
|
+
__decorate([ApiProperty({
|
|
3535
|
+
type: () => [String],
|
|
3536
|
+
required: false,
|
|
3537
|
+
example: callbackTypesResponseExample[0].sell_erc20_callback
|
|
3538
|
+
})], CallbackTypesChainResponse.prototype, "sell_erc20_callback", void 0);
|
|
3539
|
+
__decorate([ApiProperty({
|
|
3540
|
+
type: () => [String],
|
|
3541
|
+
required: false,
|
|
3542
|
+
example: callbackTypesResponseExample[0].buy_erc20
|
|
3543
|
+
})], CallbackTypesChainResponse.prototype, "buy_erc20", void 0);
|
|
3544
|
+
__decorate([ApiProperty({
|
|
3545
|
+
type: () => [String],
|
|
3546
|
+
example: callbackTypesResponseExample[0].not_supported
|
|
3547
|
+
})], CallbackTypesChainResponse.prototype, "not_supported", void 0);
|
|
3548
|
+
var CallbackTypesSuccessResponse = class extends SuccessResponse {};
|
|
3549
|
+
__decorate([ApiProperty({
|
|
3550
|
+
type: "string",
|
|
3551
|
+
nullable: true,
|
|
3552
|
+
example: "maturity:1:1730415600:end_of_next_month"
|
|
3553
|
+
})], CallbackTypesSuccessResponse.prototype, "cursor", void 0);
|
|
3554
|
+
__decorate([ApiProperty({
|
|
3555
|
+
type: () => [CallbackTypesChainResponse],
|
|
3556
|
+
description: "Callback types grouped by chain.",
|
|
3557
|
+
example: callbackTypesResponseExample
|
|
3558
|
+
})], CallbackTypesSuccessResponse.prototype, "data", void 0);
|
|
3392
3559
|
var AskResponse = class {};
|
|
3393
3560
|
__decorate([ApiProperty({
|
|
3394
3561
|
type: "string",
|
|
@@ -3553,7 +3720,8 @@ var OfferListResponse = class extends SuccessResponse {};
|
|
|
3553
3720
|
__decorate([ApiProperty({
|
|
3554
3721
|
type: "string",
|
|
3555
3722
|
nullable: true,
|
|
3556
|
-
example: offerCursorExample
|
|
3723
|
+
example: offerCursorExample,
|
|
3724
|
+
description: "Pagination cursor. Offer hash (0x...) for maker queries; base64url-encoded cursor for obligation queries."
|
|
3557
3725
|
})], OfferListResponse.prototype, "cursor", void 0);
|
|
3558
3726
|
__decorate([ApiProperty({
|
|
3559
3727
|
type: () => [OfferListItemResponse],
|
|
@@ -3924,6 +4092,28 @@ ValidateController = __decorate([ApiTags("Make"), ApiResponse({
|
|
|
3924
4092
|
description: "Bad Request",
|
|
3925
4093
|
type: BadRequestResponse
|
|
3926
4094
|
})], ValidateController);
|
|
4095
|
+
let CallbacksController = class CallbacksController {
|
|
4096
|
+
async resolveCallbackTypes() {}
|
|
4097
|
+
};
|
|
4098
|
+
__decorate([
|
|
4099
|
+
ApiOperation({
|
|
4100
|
+
methods: ["post"],
|
|
4101
|
+
path: "/v1/callbacks",
|
|
4102
|
+
summary: "Resolve callback types",
|
|
4103
|
+
description: "Returns callback types for callback addresses grouped by chain."
|
|
4104
|
+
}),
|
|
4105
|
+
ApiBody({ type: CallbackTypesRequest }),
|
|
4106
|
+
ApiResponse({
|
|
4107
|
+
status: 200,
|
|
4108
|
+
description: "Success",
|
|
4109
|
+
type: CallbackTypesSuccessResponse
|
|
4110
|
+
})
|
|
4111
|
+
], CallbacksController.prototype, "resolveCallbackTypes", null);
|
|
4112
|
+
CallbacksController = __decorate([ApiTags("Make"), ApiResponse({
|
|
4113
|
+
status: 400,
|
|
4114
|
+
description: "Bad Request",
|
|
4115
|
+
type: BadRequestResponse
|
|
4116
|
+
})], CallbacksController);
|
|
3927
4117
|
let OffersController = class OffersController {
|
|
3928
4118
|
async getOffers() {}
|
|
3929
4119
|
};
|
|
@@ -3995,104 +4185,277 @@ __decorate([
|
|
|
3995
4185
|
name: "strict",
|
|
3996
4186
|
type: "boolean",
|
|
3997
4187
|
required: false,
|
|
3998
|
-
example: true,
|
|
3999
|
-
description: "Fail the request if initialization is incomplete."
|
|
4188
|
+
example: true,
|
|
4189
|
+
description: "Fail the request if initialization is incomplete."
|
|
4190
|
+
}),
|
|
4191
|
+
ApiResponse({
|
|
4192
|
+
status: 200,
|
|
4193
|
+
description: "Success",
|
|
4194
|
+
type: RouterStatusSuccessResponse
|
|
4195
|
+
})
|
|
4196
|
+
], HealthController.prototype, "getRouterStatus", null);
|
|
4197
|
+
__decorate([
|
|
4198
|
+
ApiOperation({
|
|
4199
|
+
methods: ["get"],
|
|
4200
|
+
path: "/v1/health/collectors",
|
|
4201
|
+
summary: "Retrieve collectors health",
|
|
4202
|
+
description: "Returns the latest block numbers processed by collectors and their sync status."
|
|
4203
|
+
}),
|
|
4204
|
+
ApiQuery({
|
|
4205
|
+
name: "strict",
|
|
4206
|
+
type: "boolean",
|
|
4207
|
+
required: false,
|
|
4208
|
+
example: true,
|
|
4209
|
+
description: "Fail the request if initialization is incomplete."
|
|
4210
|
+
}),
|
|
4211
|
+
ApiResponse({
|
|
4212
|
+
status: 200,
|
|
4213
|
+
description: "Success",
|
|
4214
|
+
type: CollectorsHealthSuccessResponse
|
|
4215
|
+
})
|
|
4216
|
+
], HealthController.prototype, "getCollectorsHealth", null);
|
|
4217
|
+
__decorate([
|
|
4218
|
+
ApiOperation({
|
|
4219
|
+
methods: ["get"],
|
|
4220
|
+
path: "/v1/health/chains",
|
|
4221
|
+
summary: "Retrieve chains health",
|
|
4222
|
+
description: "Returns the latest block that can be processed by collectors for each chain."
|
|
4223
|
+
}),
|
|
4224
|
+
ApiQuery({
|
|
4225
|
+
name: "strict",
|
|
4226
|
+
type: "boolean",
|
|
4227
|
+
required: false,
|
|
4228
|
+
example: true,
|
|
4229
|
+
description: "Fail the request if initialization is incomplete."
|
|
4230
|
+
}),
|
|
4231
|
+
ApiResponse({
|
|
4232
|
+
status: 200,
|
|
4233
|
+
description: "Success",
|
|
4234
|
+
type: ChainsHealthSuccessResponse
|
|
4235
|
+
})
|
|
4236
|
+
], HealthController.prototype, "getChainsHealth", null);
|
|
4237
|
+
HealthController = __decorate([ApiTags("System")], HealthController);
|
|
4238
|
+
const configContractsExample = {
|
|
4239
|
+
chain_id: 505050505,
|
|
4240
|
+
address: "0xD946246695A9259F3B33a78629026F61B3Ab40aF",
|
|
4241
|
+
name: "mempool"
|
|
4242
|
+
};
|
|
4243
|
+
const configContractsPayloadExample = [
|
|
4244
|
+
{
|
|
4245
|
+
chain_id: 505050505,
|
|
4246
|
+
address: "0xD946246695A9259F3B33a78629026F61B3Ab40aF",
|
|
4247
|
+
name: "mempool"
|
|
4248
|
+
},
|
|
4249
|
+
{
|
|
4250
|
+
chain_id: 505050505,
|
|
4251
|
+
address: "0x8A409D5D6394fC197c596d4E6E2c35e5d13f8a4d",
|
|
4252
|
+
name: "multicall"
|
|
4253
|
+
},
|
|
4254
|
+
{
|
|
4255
|
+
chain_id: 505050505,
|
|
4256
|
+
address: "0x23DFBc4B8B80C14CC5e25011B8491f268395BAd6",
|
|
4257
|
+
name: "v2"
|
|
4258
|
+
}
|
|
4259
|
+
];
|
|
4260
|
+
const configRulesMaturityExample = {
|
|
4261
|
+
type: "maturity",
|
|
4262
|
+
chain_id: 1,
|
|
4263
|
+
name: "end_of_next_month",
|
|
4264
|
+
timestamp: 1730415600
|
|
4265
|
+
};
|
|
4266
|
+
const configRulesCallbackExample = {
|
|
4267
|
+
type: "callback",
|
|
4268
|
+
chain_id: 1,
|
|
4269
|
+
address: "0x1111111111111111111111111111111111111111",
|
|
4270
|
+
callback_type: "sell_erc20_callback"
|
|
4271
|
+
};
|
|
4272
|
+
const configRulesLoanTokenExample = {
|
|
4273
|
+
type: "loan_token",
|
|
4274
|
+
chain_id: 1,
|
|
4275
|
+
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
4276
|
+
};
|
|
4277
|
+
const configRulesOracleExample = {
|
|
4278
|
+
type: "oracle",
|
|
4279
|
+
chain_id: 1,
|
|
4280
|
+
address: "0xDddd770BADd886dF3864029e4B377B5F6a2B6b83"
|
|
4281
|
+
};
|
|
4282
|
+
const configRulesChecksumExample = "f1d2d2f924e986ac86fdf7b36c94bcdf";
|
|
4283
|
+
const configRulesPayloadExample = [
|
|
4284
|
+
configRulesMaturityExample,
|
|
4285
|
+
configRulesCallbackExample,
|
|
4286
|
+
configRulesLoanTokenExample,
|
|
4287
|
+
configRulesOracleExample
|
|
4288
|
+
];
|
|
4289
|
+
const configContractNames = [
|
|
4290
|
+
"mempool",
|
|
4291
|
+
"multicall",
|
|
4292
|
+
"v2"
|
|
4293
|
+
];
|
|
4294
|
+
const configContractsCursorExample = "505050505:0xd946246695a9259f3b33a78629026f61b3ab40af";
|
|
4295
|
+
var ConfigContractResponse = class {};
|
|
4296
|
+
__decorate([ApiProperty({
|
|
4297
|
+
type: "number",
|
|
4298
|
+
example: configContractsExample.chain_id
|
|
4299
|
+
})], ConfigContractResponse.prototype, "chain_id", void 0);
|
|
4300
|
+
__decorate([ApiProperty({
|
|
4301
|
+
type: "string",
|
|
4302
|
+
example: configContractsExample.address
|
|
4303
|
+
})], ConfigContractResponse.prototype, "address", void 0);
|
|
4304
|
+
__decorate([ApiProperty({
|
|
4305
|
+
type: "string",
|
|
4306
|
+
enum: configContractNames,
|
|
4307
|
+
example: configContractsExample.name
|
|
4308
|
+
})], ConfigContractResponse.prototype, "name", void 0);
|
|
4309
|
+
var ConfigContractsSuccessResponse = class extends SuccessResponse {};
|
|
4310
|
+
__decorate([ApiProperty({
|
|
4311
|
+
type: "string",
|
|
4312
|
+
nullable: true,
|
|
4313
|
+
example: null
|
|
4314
|
+
})], ConfigContractsSuccessResponse.prototype, "cursor", void 0);
|
|
4315
|
+
__decorate([ApiProperty({
|
|
4316
|
+
type: () => [ConfigContractResponse],
|
|
4317
|
+
description: "Indexer contract configuration for all indexed chains.",
|
|
4318
|
+
example: configContractsPayloadExample
|
|
4319
|
+
})], ConfigContractsSuccessResponse.prototype, "data", void 0);
|
|
4320
|
+
var ConfigRulesMeta = class {};
|
|
4321
|
+
__decorate([ApiProperty({
|
|
4322
|
+
type: "string",
|
|
4323
|
+
example: timestampExample
|
|
4324
|
+
})], ConfigRulesMeta.prototype, "timestamp", void 0);
|
|
4325
|
+
__decorate([ApiProperty({
|
|
4326
|
+
type: "string",
|
|
4327
|
+
example: configRulesChecksumExample
|
|
4328
|
+
})], ConfigRulesMeta.prototype, "checksum", void 0);
|
|
4329
|
+
var ConfigRulesRuleResponse = class {};
|
|
4330
|
+
__decorate([ApiProperty({
|
|
4331
|
+
type: "string",
|
|
4332
|
+
example: configRulesMaturityExample.type
|
|
4333
|
+
})], ConfigRulesRuleResponse.prototype, "type", void 0);
|
|
4334
|
+
__decorate([ApiProperty({
|
|
4335
|
+
type: "number",
|
|
4336
|
+
example: configRulesMaturityExample.chain_id
|
|
4337
|
+
})], ConfigRulesRuleResponse.prototype, "chain_id", void 0);
|
|
4338
|
+
__decorate([ApiProperty({
|
|
4339
|
+
type: "string",
|
|
4340
|
+
example: configRulesMaturityExample.name,
|
|
4341
|
+
required: false
|
|
4342
|
+
})], ConfigRulesRuleResponse.prototype, "name", void 0);
|
|
4343
|
+
__decorate([ApiProperty({
|
|
4344
|
+
type: "number",
|
|
4345
|
+
example: configRulesMaturityExample.timestamp,
|
|
4346
|
+
required: false
|
|
4347
|
+
})], ConfigRulesRuleResponse.prototype, "timestamp", void 0);
|
|
4348
|
+
__decorate([ApiProperty({
|
|
4349
|
+
type: "string",
|
|
4350
|
+
example: configRulesCallbackExample.address,
|
|
4351
|
+
required: false
|
|
4352
|
+
})], ConfigRulesRuleResponse.prototype, "address", void 0);
|
|
4353
|
+
__decorate([ApiProperty({
|
|
4354
|
+
type: "string",
|
|
4355
|
+
example: configRulesCallbackExample.callback_type,
|
|
4356
|
+
required: false
|
|
4357
|
+
})], ConfigRulesRuleResponse.prototype, "callback_type", void 0);
|
|
4358
|
+
var ConfigRulesSuccessResponse = class {};
|
|
4359
|
+
__decorate([ApiProperty({ type: () => ConfigRulesMeta })], ConfigRulesSuccessResponse.prototype, "meta", void 0);
|
|
4360
|
+
__decorate([ApiProperty({
|
|
4361
|
+
type: "string",
|
|
4362
|
+
nullable: true,
|
|
4363
|
+
example: null
|
|
4364
|
+
})], ConfigRulesSuccessResponse.prototype, "cursor", void 0);
|
|
4365
|
+
__decorate([ApiProperty({
|
|
4366
|
+
type: () => [ConfigRulesRuleResponse],
|
|
4367
|
+
description: "Configured rules returned by the router API.",
|
|
4368
|
+
example: configRulesPayloadExample
|
|
4369
|
+
})], ConfigRulesSuccessResponse.prototype, "data", void 0);
|
|
4370
|
+
let ConfigContractsController = class ConfigContractsController {
|
|
4371
|
+
async getConfigContracts() {}
|
|
4372
|
+
};
|
|
4373
|
+
__decorate([
|
|
4374
|
+
ApiOperation({
|
|
4375
|
+
methods: ["get"],
|
|
4376
|
+
path: "/v1/config/contracts",
|
|
4377
|
+
summary: "Get indexer contract configuration",
|
|
4378
|
+
description: "Returns contract addresses used by indexers (mempool, v2) and multicall for indexed chains."
|
|
4379
|
+
}),
|
|
4380
|
+
ApiQuery({
|
|
4381
|
+
name: "cursor",
|
|
4382
|
+
type: "string",
|
|
4383
|
+
required: false,
|
|
4384
|
+
example: configContractsCursorExample,
|
|
4385
|
+
description: "Pagination cursor in chain_id:address format (lowercase address)."
|
|
4386
|
+
}),
|
|
4387
|
+
ApiQuery({
|
|
4388
|
+
name: "limit",
|
|
4389
|
+
type: "number",
|
|
4390
|
+
required: false,
|
|
4391
|
+
example: 1e3,
|
|
4392
|
+
description: "Maximum number of contracts to return (max 1000)."
|
|
4393
|
+
}),
|
|
4394
|
+
ApiQuery({
|
|
4395
|
+
name: "chains",
|
|
4396
|
+
type: ["number"],
|
|
4397
|
+
required: false,
|
|
4398
|
+
example: "1,8453",
|
|
4399
|
+
description: "Filter by chain IDs (comma-separated).",
|
|
4400
|
+
style: "form",
|
|
4401
|
+
explode: false
|
|
4000
4402
|
}),
|
|
4001
4403
|
ApiResponse({
|
|
4002
4404
|
status: 200,
|
|
4003
4405
|
description: "Success",
|
|
4004
|
-
type:
|
|
4406
|
+
type: ConfigContractsSuccessResponse
|
|
4005
4407
|
})
|
|
4006
|
-
],
|
|
4408
|
+
], ConfigContractsController.prototype, "getConfigContracts", null);
|
|
4409
|
+
ConfigContractsController = __decorate([ApiTags("System")], ConfigContractsController);
|
|
4410
|
+
let ConfigRulesController = class ConfigRulesController {
|
|
4411
|
+
async getConfigRules() {}
|
|
4412
|
+
};
|
|
4007
4413
|
__decorate([
|
|
4008
4414
|
ApiOperation({
|
|
4009
4415
|
methods: ["get"],
|
|
4010
|
-
path: "/v1/
|
|
4011
|
-
summary: "
|
|
4012
|
-
description: "Returns
|
|
4416
|
+
path: "/v1/config/rules",
|
|
4417
|
+
summary: "Get config rules",
|
|
4418
|
+
description: "Returns configured rules for supported chains."
|
|
4013
4419
|
}),
|
|
4014
4420
|
ApiQuery({
|
|
4015
|
-
name: "
|
|
4016
|
-
type: "
|
|
4421
|
+
name: "cursor",
|
|
4422
|
+
type: "string",
|
|
4017
4423
|
required: false,
|
|
4018
|
-
example:
|
|
4019
|
-
description: "
|
|
4424
|
+
example: "maturity:1:1730415600:end_of_next_month",
|
|
4425
|
+
description: "Pagination cursor in type:chain_id:<value> format."
|
|
4020
4426
|
}),
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
__decorate([
|
|
4028
|
-
ApiOperation({
|
|
4029
|
-
methods: ["get"],
|
|
4030
|
-
path: "/v1/health/chains",
|
|
4031
|
-
summary: "Retrieve chains health",
|
|
4032
|
-
description: "Returns the latest block that can be processed by collectors for each chain."
|
|
4427
|
+
ApiQuery({
|
|
4428
|
+
name: "limit",
|
|
4429
|
+
type: "number",
|
|
4430
|
+
required: false,
|
|
4431
|
+
example: 100,
|
|
4432
|
+
description: "Maximum number of rules to return (max 1000)."
|
|
4033
4433
|
}),
|
|
4034
4434
|
ApiQuery({
|
|
4035
|
-
name: "
|
|
4036
|
-
type: "
|
|
4435
|
+
name: "types",
|
|
4436
|
+
type: ["string"],
|
|
4037
4437
|
required: false,
|
|
4038
|
-
example:
|
|
4039
|
-
description: "
|
|
4438
|
+
example: "maturity,loan_token,oracle",
|
|
4439
|
+
description: "Filter by rule types (comma-separated).",
|
|
4440
|
+
style: "form",
|
|
4441
|
+
explode: false
|
|
4442
|
+
}),
|
|
4443
|
+
ApiQuery({
|
|
4444
|
+
name: "chains",
|
|
4445
|
+
type: ["number"],
|
|
4446
|
+
required: false,
|
|
4447
|
+
example: "1,8453",
|
|
4448
|
+
description: "Filter by chain IDs (comma-separated).",
|
|
4449
|
+
style: "form",
|
|
4450
|
+
explode: false
|
|
4040
4451
|
}),
|
|
4041
4452
|
ApiResponse({
|
|
4042
4453
|
status: 200,
|
|
4043
4454
|
description: "Success",
|
|
4044
|
-
type:
|
|
4455
|
+
type: ConfigRulesSuccessResponse
|
|
4045
4456
|
})
|
|
4046
|
-
],
|
|
4047
|
-
|
|
4048
|
-
const callbacksExample = [Type$1.BuyWithEmptyCallback];
|
|
4049
|
-
const chainConfigExample = {
|
|
4050
|
-
chain_id: 505050505,
|
|
4051
|
-
contracts: { mempool: "0xD946246695A9259F3B33a78629026F61B3Ab40aF" },
|
|
4052
|
-
callbacks: callbacksExample
|
|
4053
|
-
};
|
|
4054
|
-
var ConfigContractsResponse = class {};
|
|
4055
|
-
__decorate([ApiProperty({
|
|
4056
|
-
type: "string",
|
|
4057
|
-
example: chainConfigExample.contracts.mempool
|
|
4058
|
-
})], ConfigContractsResponse.prototype, "mempool", void 0);
|
|
4059
|
-
var ConfigDataResponse = class {};
|
|
4060
|
-
__decorate([ApiProperty({
|
|
4061
|
-
type: "number",
|
|
4062
|
-
example: chainConfigExample.chain_id
|
|
4063
|
-
})], ConfigDataResponse.prototype, "chain_id", void 0);
|
|
4064
|
-
__decorate([ApiProperty({ type: () => ConfigContractsResponse })], ConfigDataResponse.prototype, "contracts", void 0);
|
|
4065
|
-
__decorate([ApiProperty({
|
|
4066
|
-
type: () => [String],
|
|
4067
|
-
enum: Object.values(Type$1),
|
|
4068
|
-
description: "Supported callback types for this chain.",
|
|
4069
|
-
example: callbacksExample
|
|
4070
|
-
})], ConfigDataResponse.prototype, "callbacks", void 0);
|
|
4071
|
-
var ConfigSuccessResponse = class extends SuccessResponse {};
|
|
4072
|
-
__decorate([ApiProperty({
|
|
4073
|
-
type: "string",
|
|
4074
|
-
nullable: true,
|
|
4075
|
-
example: null
|
|
4076
|
-
})], ConfigSuccessResponse.prototype, "cursor", void 0);
|
|
4077
|
-
__decorate([ApiProperty({
|
|
4078
|
-
type: () => [ConfigDataResponse],
|
|
4079
|
-
description: "Array of chain configurations for all indexed chains.",
|
|
4080
|
-
example: [chainConfigExample]
|
|
4081
|
-
})], ConfigSuccessResponse.prototype, "data", void 0);
|
|
4082
|
-
let ConfigController = class ConfigController {
|
|
4083
|
-
async getConfig() {}
|
|
4084
|
-
};
|
|
4085
|
-
__decorate([ApiOperation({
|
|
4086
|
-
methods: ["get"],
|
|
4087
|
-
path: "/v1/config",
|
|
4088
|
-
summary: "Get router configuration",
|
|
4089
|
-
description: "Returns chain configurations including contract addresses and supported callback types."
|
|
4090
|
-
}), ApiResponse({
|
|
4091
|
-
status: 200,
|
|
4092
|
-
description: "Success",
|
|
4093
|
-
type: ConfigSuccessResponse
|
|
4094
|
-
})], ConfigController.prototype, "getConfig", null);
|
|
4095
|
-
ConfigController = __decorate([ApiTags("System")], ConfigController);
|
|
4457
|
+
], ConfigRulesController.prototype, "getConfigRules", null);
|
|
4458
|
+
ConfigRulesController = __decorate([ApiTags("System")], ConfigRulesController);
|
|
4096
4459
|
let ObligationsController = class ObligationsController {
|
|
4097
4460
|
async getObligations() {}
|
|
4098
4461
|
async getObligation() {}
|
|
@@ -4221,16 +4584,18 @@ UsersController = __decorate([ApiTags("Make"), ApiResponse({
|
|
|
4221
4584
|
description: "Bad Request",
|
|
4222
4585
|
type: BadRequestResponse
|
|
4223
4586
|
})], UsersController);
|
|
4224
|
-
const OpenApi = async (
|
|
4225
|
-
|
|
4587
|
+
const OpenApi = async () => {
|
|
4588
|
+
return await generateDocument({
|
|
4226
4589
|
controllers: [
|
|
4227
4590
|
BooksController,
|
|
4228
|
-
|
|
4591
|
+
ConfigContractsController,
|
|
4592
|
+
ConfigRulesController,
|
|
4229
4593
|
OffersController,
|
|
4230
4594
|
ObligationsController,
|
|
4231
4595
|
HealthController,
|
|
4232
4596
|
UsersController,
|
|
4233
|
-
ValidateController
|
|
4597
|
+
ValidateController,
|
|
4598
|
+
CallbacksController
|
|
4234
4599
|
],
|
|
4235
4600
|
document: {
|
|
4236
4601
|
openapi: "3.1.0",
|
|
@@ -4262,12 +4627,6 @@ const OpenApi = async (options = {}) => {
|
|
|
4262
4627
|
]
|
|
4263
4628
|
}
|
|
4264
4629
|
});
|
|
4265
|
-
if (options.rules && options.rules.length > 0) {
|
|
4266
|
-
const rulesDescription = options.rules.map((rule) => `- **${rule.name}**: ${rule.description}`).join("\n");
|
|
4267
|
-
const validatePath = document.paths?.["/v1/validate"];
|
|
4268
|
-
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}`;
|
|
4269
|
-
}
|
|
4270
|
-
return document;
|
|
4271
4630
|
};
|
|
4272
4631
|
|
|
4273
4632
|
//#endregion
|
|
@@ -4291,6 +4650,10 @@ function from$3(position) {
|
|
|
4291
4650
|
//#region src/api/Schema/requests.ts
|
|
4292
4651
|
const MAX_LIMIT = 100;
|
|
4293
4652
|
const DEFAULT_LIMIT$4 = 20;
|
|
4653
|
+
const CONFIG_RULES_MAX_LIMIT = 1e3;
|
|
4654
|
+
const CONFIG_RULES_DEFAULT_LIMIT = 100;
|
|
4655
|
+
const CONFIG_CONTRACTS_MAX_LIMIT = 1e3;
|
|
4656
|
+
const CONFIG_CONTRACTS_DEFAULT_LIMIT = 1e3;
|
|
4294
4657
|
/** Validate cursor is a valid base64url-encoded JSON object.
|
|
4295
4658
|
* Domain layer handles semantic validation of cursor fields. */
|
|
4296
4659
|
function isValidBase64urlJson(val) {
|
|
@@ -4302,6 +4665,9 @@ function isValidBase64urlJson(val) {
|
|
|
4302
4665
|
return false;
|
|
4303
4666
|
}
|
|
4304
4667
|
}
|
|
4668
|
+
function isValidOfferHashCursor(val) {
|
|
4669
|
+
return /^0x[a-f0-9]{64}$/i.test(val);
|
|
4670
|
+
}
|
|
4305
4671
|
const csvArray = (schema) => z$2.preprocess((value) => {
|
|
4306
4672
|
if (value === void 0) return void 0;
|
|
4307
4673
|
if (Array.isArray(value)) {
|
|
@@ -4324,8 +4690,49 @@ const PaginationQueryParams = z$2.object({
|
|
|
4324
4690
|
example: 10
|
|
4325
4691
|
})
|
|
4326
4692
|
});
|
|
4327
|
-
const
|
|
4328
|
-
|
|
4693
|
+
const ConfigRuleTypes = z$2.enum([
|
|
4694
|
+
"maturity",
|
|
4695
|
+
"callback",
|
|
4696
|
+
"loan_token",
|
|
4697
|
+
"oracle"
|
|
4698
|
+
]);
|
|
4699
|
+
const GetConfigRulesQueryParams = z$2.object({
|
|
4700
|
+
cursor: z$2.string().regex(/^(maturity|callback|loan_token|oracle):[1-9]\d*:.+$/, { message: "Cursor must be in the format type:chain_id:<value>" }).optional().meta({
|
|
4701
|
+
description: "Pagination cursor in type:chain_id:<value> format",
|
|
4702
|
+
example: "maturity:1:1730415600:end_of_next_month"
|
|
4703
|
+
}),
|
|
4704
|
+
limit: z$2.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$2.number().max(CONFIG_RULES_MAX_LIMIT, { message: `Limit cannot exceed ${CONFIG_RULES_MAX_LIMIT}` })).optional().default(CONFIG_RULES_DEFAULT_LIMIT).meta({
|
|
4705
|
+
description: `Limit maximum: ${CONFIG_RULES_MAX_LIMIT}. Default: ${CONFIG_RULES_DEFAULT_LIMIT}`,
|
|
4706
|
+
example: 100
|
|
4707
|
+
}),
|
|
4708
|
+
types: csvArray(ConfigRuleTypes).meta({
|
|
4709
|
+
description: "Filter by rule types (comma-separated).",
|
|
4710
|
+
example: "maturity,loan_token,oracle"
|
|
4711
|
+
}),
|
|
4712
|
+
chains: csvArray(z$2.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
|
|
4713
|
+
description: "Filter by chain IDs (comma-separated).",
|
|
4714
|
+
example: "1,8453"
|
|
4715
|
+
})
|
|
4716
|
+
});
|
|
4717
|
+
const GetConfigContractsQueryParams = z$2.object({
|
|
4718
|
+
cursor: z$2.string().regex(/^[1-9]\d*:0x[a-fA-F0-9]{40}$/, { message: "Cursor must be in the format chain_id:0x..." }).optional().meta({
|
|
4719
|
+
description: "Pagination cursor in chain_id:address format (lowercase address).",
|
|
4720
|
+
example: "1:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
|
|
4721
|
+
}),
|
|
4722
|
+
limit: z$2.string().regex(/^[1-9]\d*$/, { message: "Limit must be a positive integer" }).transform((val) => Number.parseInt(val, 10)).pipe(z$2.number().max(CONFIG_CONTRACTS_MAX_LIMIT, { message: `Limit cannot exceed ${CONFIG_CONTRACTS_MAX_LIMIT}` })).optional().default(CONFIG_CONTRACTS_DEFAULT_LIMIT).meta({
|
|
4723
|
+
description: `Limit maximum: ${CONFIG_CONTRACTS_MAX_LIMIT}. Default: ${CONFIG_CONTRACTS_DEFAULT_LIMIT}`,
|
|
4724
|
+
example: 1e3
|
|
4725
|
+
}),
|
|
4726
|
+
chains: csvArray(z$2.string().regex(/^[1-9]\d*$/, { message: "Chain must be a positive integer" }).transform((val) => Number.parseInt(val, 10))).meta({
|
|
4727
|
+
description: "Filter by chain IDs (comma-separated).",
|
|
4728
|
+
example: "1,8453"
|
|
4729
|
+
})
|
|
4730
|
+
});
|
|
4731
|
+
const GetOffersQueryParams = PaginationQueryParams.omit({ cursor: true }).extend({
|
|
4732
|
+
cursor: z$2.string().optional().meta({
|
|
4733
|
+
description: "Pagination cursor. Use offer hash (0x...) for maker queries, base64url for obligation queries.",
|
|
4734
|
+
example: "eyJzaWRlIjoic2VsbCIsImN1cnJlbnRQcmljZSI6IjEwMDAwMDAwMDAwMDAwMDAwMDAiLCJibG9ja051bWJlciI6MSwiYXNzZXRzIjoiMTAwMDAwMDAwMDAwMDAwMDAwMCIsImhhc2giOiIweGRmZDY4NTllM2UwODJkMTkzODlhMWFlYzFiZGFkN2U4ZDkyZDk2YjFhYTc5NDBkYTkxYTMxMjVkMzFlM2JlNWIiLCJ0b3RhbFJldHVybmVkIjoxMCwibm93IjoxNjAwMDAwMDAwfQ"
|
|
4735
|
+
}),
|
|
4329
4736
|
side: z$2.enum(["buy", "sell"]).optional().meta({
|
|
4330
4737
|
description: "Side of the offer. Required when using obligation_id.",
|
|
4331
4738
|
example: "buy"
|
|
@@ -4349,11 +4756,29 @@ const GetOffersQueryParams = z$2.object({
|
|
|
4349
4756
|
});
|
|
4350
4757
|
return;
|
|
4351
4758
|
}
|
|
4352
|
-
if (hasMaker)
|
|
4759
|
+
if (hasMaker) {
|
|
4760
|
+
if (val.cursor !== void 0 && !isValidOfferHashCursor(val.cursor)) ctx.addIssue({
|
|
4761
|
+
code: "custom",
|
|
4762
|
+
path: ["cursor"],
|
|
4763
|
+
message: "Cursor must be a 32-byte hex offer hash when filtering by maker"
|
|
4764
|
+
});
|
|
4765
|
+
return;
|
|
4766
|
+
}
|
|
4353
4767
|
if (!hasObligation || !hasSide) ctx.addIssue({
|
|
4354
4768
|
code: "custom",
|
|
4355
4769
|
message: "Must provide either maker or both obligation_id and side"
|
|
4356
4770
|
});
|
|
4771
|
+
if (val.cursor !== void 0 && !isValidBase64urlJson(val.cursor)) ctx.addIssue({
|
|
4772
|
+
code: "custom",
|
|
4773
|
+
path: ["cursor"],
|
|
4774
|
+
message: "Invalid cursor format. Must be a valid base64url-encoded cursor object"
|
|
4775
|
+
});
|
|
4776
|
+
}).transform((val) => {
|
|
4777
|
+
if (val.maker && val.cursor) return {
|
|
4778
|
+
...val,
|
|
4779
|
+
cursor: val.cursor.toLowerCase()
|
|
4780
|
+
};
|
|
4781
|
+
return val;
|
|
4357
4782
|
});
|
|
4358
4783
|
const GetObligationsQueryParams = z$2.object({
|
|
4359
4784
|
...PaginationQueryParams.shape,
|
|
@@ -4426,6 +4851,16 @@ const GetBookParams = z$2.object({
|
|
|
4426
4851
|
})
|
|
4427
4852
|
});
|
|
4428
4853
|
const ValidateOffersBody = z$2.object({ offers: z$2.array(z$2.unknown()).min(1, { message: "'offers' must contain at least 1 offer" }) }).strict();
|
|
4854
|
+
const CallbackTypesBody = z$2.object({ callbacks: z$2.array(z$2.object({
|
|
4855
|
+
chain_id: z$2.number().int().positive().meta({
|
|
4856
|
+
description: "Chain id.",
|
|
4857
|
+
example: 1
|
|
4858
|
+
}),
|
|
4859
|
+
addresses: z$2.array(z$2.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "Callback address must be a valid 20-byte address" }).transform((val) => val.toLowerCase())).meta({
|
|
4860
|
+
description: "Callback contract addresses.",
|
|
4861
|
+
example: ["0x1111111111111111111111111111111111111111", "0x3333333333333333333333333333333333333333"]
|
|
4862
|
+
})
|
|
4863
|
+
}).strict()) }).strict();
|
|
4429
4864
|
const GetUserPositionsParams = z$2.object({
|
|
4430
4865
|
...PaginationQueryParams.shape,
|
|
4431
4866
|
user_address: z$2.string().regex(/^0x[a-fA-F0-9]{40}$/, { error: "User address must be a valid 20-byte address" }).transform((val) => val.toLowerCase()).meta({
|
|
@@ -4437,17 +4872,208 @@ const schemas = {
|
|
|
4437
4872
|
get_health: HealthQueryParams,
|
|
4438
4873
|
get_health_collectors: HealthQueryParams,
|
|
4439
4874
|
get_health_chains: HealthQueryParams,
|
|
4875
|
+
get_config_contracts: GetConfigContractsQueryParams,
|
|
4876
|
+
get_config_rules: GetConfigRulesQueryParams,
|
|
4440
4877
|
get_offers: GetOffersQueryParams,
|
|
4441
4878
|
get_obligations: GetObligationsQueryParams,
|
|
4442
4879
|
get_obligation: GetObligationParams,
|
|
4443
4880
|
get_book: GetBookParams,
|
|
4444
4881
|
validate_offers: ValidateOffersBody,
|
|
4882
|
+
callback_types: CallbackTypesBody,
|
|
4445
4883
|
get_user_positions: GetUserPositionsParams
|
|
4446
4884
|
};
|
|
4447
4885
|
function safeParse(action, query, error) {
|
|
4448
4886
|
return schemas[action].safeParse(query, { error });
|
|
4449
4887
|
}
|
|
4450
4888
|
|
|
4889
|
+
//#endregion
|
|
4890
|
+
//#region src/api/Controllers/getConfigRules.ts
|
|
4891
|
+
/**
|
|
4892
|
+
* Returns configured rules for the configured chains.
|
|
4893
|
+
* @param query - Raw query parameters containing filters/cursor/limit.
|
|
4894
|
+
* @param chains - Chains to include in the configured rules.
|
|
4895
|
+
* @returns Config rules response payload. {@link ApiPayload.Payload}
|
|
4896
|
+
*/
|
|
4897
|
+
async function getConfigRules(query, chains) {
|
|
4898
|
+
const parsed = safeParse("get_config_rules", query ?? {});
|
|
4899
|
+
if (!parsed.success) return failure(parsed.error);
|
|
4900
|
+
const { cursor, limit, types, chains: chainIds } = parsed.data;
|
|
4901
|
+
const typeFilter = types?.length ? new Set(types) : null;
|
|
4902
|
+
const chainFilter = chainIds?.length ? new Set(chainIds) : null;
|
|
4903
|
+
const filteredRules = buildConfigRules(chains).filter((rule) => {
|
|
4904
|
+
if (chainFilter && !chainFilter.has(rule.chain_id)) return false;
|
|
4905
|
+
if (typeFilter && !typeFilter.has(rule.type)) return false;
|
|
4906
|
+
return true;
|
|
4907
|
+
});
|
|
4908
|
+
const checksum = buildConfigRulesChecksum(filteredRules);
|
|
4909
|
+
let cursorRule = null;
|
|
4910
|
+
if (cursor) try {
|
|
4911
|
+
cursorRule = parseCursor$1(cursor);
|
|
4912
|
+
} catch (err) {
|
|
4913
|
+
return failure(err);
|
|
4914
|
+
}
|
|
4915
|
+
if (cursorRule && typeFilter && !typeFilter.has(cursorRule.type)) return failure(new BadRequestError("Cursor type must match requested rule types"));
|
|
4916
|
+
if (cursorRule && chainFilter && !chainFilter.has(cursorRule.chain_id)) return failure(new BadRequestError("Cursor chain_id must match requested chains"));
|
|
4917
|
+
const startIndex = cursorRule ? findStartIndex$1(filteredRules, cursorRule) : 0;
|
|
4918
|
+
const page = filteredRules.slice(startIndex, startIndex + limit);
|
|
4919
|
+
const nextCursor = startIndex + limit < filteredRules.length && page.length > 0 ? formatCursor$1(page.at(-1)) : null;
|
|
4920
|
+
const response = success({
|
|
4921
|
+
data: page,
|
|
4922
|
+
cursor: nextCursor
|
|
4923
|
+
});
|
|
4924
|
+
response.body.meta.checksum = checksum;
|
|
4925
|
+
return response;
|
|
4926
|
+
}
|
|
4927
|
+
function formatCursor$1(rule) {
|
|
4928
|
+
if (rule.type === "maturity") return `maturity:${rule.chain_id}:${rule.timestamp}:${rule.name}`;
|
|
4929
|
+
if (rule.type === "callback") return `callback:${rule.chain_id}:${rule.callback_type}:${rule.address.toLowerCase()}`;
|
|
4930
|
+
if (rule.type === "oracle") return `oracle:${rule.chain_id}:${rule.address.toLowerCase()}`;
|
|
4931
|
+
return `loan_token:${rule.chain_id}:${rule.address.toLowerCase()}`;
|
|
4932
|
+
}
|
|
4933
|
+
function parseCursor$1(cursor) {
|
|
4934
|
+
const [type, chain, ...rest] = cursor.split(":");
|
|
4935
|
+
if (!type || !chain || rest.length === 0) throw new BadRequestError("Cursor must be in the format type:chain_id:<value>");
|
|
4936
|
+
if (!isConfigRuleType(type)) throw new BadRequestError("Cursor has an invalid rule type");
|
|
4937
|
+
const chain_id = Number.parseInt(chain, 10);
|
|
4938
|
+
if (!Number.isFinite(chain_id)) throw new BadRequestError("Cursor has an invalid chain_id");
|
|
4939
|
+
if (type === "maturity") {
|
|
4940
|
+
const timestampValue = Number.parseInt(rest[0] ?? "", 10);
|
|
4941
|
+
const nameValue = rest.slice(1).join(":");
|
|
4942
|
+
if (!Number.isFinite(timestampValue) || nameValue.length === 0) throw new BadRequestError("Cursor must be in the format maturity:chain_id:timestamp:name");
|
|
4943
|
+
if (!isMaturityType(nameValue)) throw new BadRequestError("Cursor has an invalid maturity name");
|
|
4944
|
+
return {
|
|
4945
|
+
type,
|
|
4946
|
+
chain_id,
|
|
4947
|
+
timestamp: parseMaturity(timestampValue),
|
|
4948
|
+
name: nameValue
|
|
4949
|
+
};
|
|
4950
|
+
}
|
|
4951
|
+
if (type === "callback") {
|
|
4952
|
+
const callbackTypeValue = rest[0] ?? "";
|
|
4953
|
+
const addressValue = rest.slice(1).join(":");
|
|
4954
|
+
if (!callbackTypeValue || !addressValue) throw new BadRequestError("Cursor must be in the format callback:chain_id:callback_type:address");
|
|
4955
|
+
if (!isCallbackType(callbackTypeValue)) throw new BadRequestError("Cursor has an invalid callback type");
|
|
4956
|
+
return {
|
|
4957
|
+
type,
|
|
4958
|
+
chain_id,
|
|
4959
|
+
callback_type: callbackTypeValue,
|
|
4960
|
+
address: parseAddress(addressValue, "Cursor address")
|
|
4961
|
+
};
|
|
4962
|
+
}
|
|
4963
|
+
if (type === "loan_token" || type === "oracle") {
|
|
4964
|
+
const addressValue = rest.join(":");
|
|
4965
|
+
if (!addressValue) throw new BadRequestError(`Cursor must be in the format ${type}:chain_id:address`);
|
|
4966
|
+
return {
|
|
4967
|
+
type,
|
|
4968
|
+
chain_id,
|
|
4969
|
+
address: parseAddress(addressValue, "Cursor address")
|
|
4970
|
+
};
|
|
4971
|
+
}
|
|
4972
|
+
throw new BadRequestError("Cursor has an invalid rule type");
|
|
4973
|
+
}
|
|
4974
|
+
function findStartIndex$1(rules, cursor) {
|
|
4975
|
+
let low = 0;
|
|
4976
|
+
let high = rules.length;
|
|
4977
|
+
while (low < high) {
|
|
4978
|
+
const mid = Math.floor((low + high) / 2);
|
|
4979
|
+
const current = rules[mid];
|
|
4980
|
+
if (compareConfigRules(current, cursor) <= 0) low = mid + 1;
|
|
4981
|
+
else high = mid;
|
|
4982
|
+
}
|
|
4983
|
+
return low;
|
|
4984
|
+
}
|
|
4985
|
+
function parseAddress(address, label) {
|
|
4986
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(address)) throw new BadRequestError(`${label} must be a valid 20-byte address`);
|
|
4987
|
+
return address.toLowerCase();
|
|
4988
|
+
}
|
|
4989
|
+
function isConfigRuleType(value) {
|
|
4990
|
+
return value === "maturity" || value === "callback" || value === "loan_token" || value === "oracle";
|
|
4991
|
+
}
|
|
4992
|
+
function isMaturityType(value) {
|
|
4993
|
+
return Object.values(MaturityType).includes(value);
|
|
4994
|
+
}
|
|
4995
|
+
function parseMaturity(value) {
|
|
4996
|
+
try {
|
|
4997
|
+
return from$16(value);
|
|
4998
|
+
} catch (err) {
|
|
4999
|
+
throw new BadRequestError(err instanceof Error ? err.message : "Invalid maturity timestamp");
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
function isCallbackType(value) {
|
|
5003
|
+
if (value === Type$1.BuyWithEmptyCallback) return false;
|
|
5004
|
+
return Object.values(Type$1).includes(value);
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
//#endregion
|
|
5008
|
+
//#region src/logger/Logger.ts
|
|
5009
|
+
const LogLevelValues = [
|
|
5010
|
+
"trace",
|
|
5011
|
+
"debug",
|
|
5012
|
+
"info",
|
|
5013
|
+
"warn",
|
|
5014
|
+
"error",
|
|
5015
|
+
"fatal",
|
|
5016
|
+
"silent"
|
|
5017
|
+
];
|
|
5018
|
+
function defaultLogger(minLevel, pretty) {
|
|
5019
|
+
const threshold = minLevel ?? process.env.ROUTER_LOG_LEVEL ?? "info";
|
|
5020
|
+
const prettyEnabled = typeof pretty === "boolean" ? pretty : String(process.env.ROUTER_LOG_PRETTY ?? "false").toLowerCase() === "true";
|
|
5021
|
+
const levelIndexByName = LogLevelValues.reduce((acc, lvl, idx) => {
|
|
5022
|
+
acc[lvl] = idx;
|
|
5023
|
+
return acc;
|
|
5024
|
+
}, {});
|
|
5025
|
+
const isEnabled = (methodLevel) => levelIndexByName[methodLevel] >= levelIndexByName[threshold];
|
|
5026
|
+
const wrap = (consoleMethod, methodLevel) => isEnabled(methodLevel) ? (entry) => {
|
|
5027
|
+
if (!prettyEnabled) {
|
|
5028
|
+
console[consoleMethod](stringify({
|
|
5029
|
+
level: methodLevel,
|
|
5030
|
+
...entry
|
|
5031
|
+
}));
|
|
5032
|
+
return;
|
|
5033
|
+
}
|
|
5034
|
+
const { msg, ...rest } = entry;
|
|
5035
|
+
const stack = typeof rest.stack === "string" ? rest.stack : void 0;
|
|
5036
|
+
if (stack) delete rest.stack;
|
|
5037
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
5038
|
+
const level = methodLevel.toUpperCase();
|
|
5039
|
+
const extras = Object.entries(rest).map(([k, v]) => `${k}=${formatValue(v)}`).join(" ");
|
|
5040
|
+
const line = extras.length > 0 ? `${timestamp} [${level}] ${msg} ${extras}` : `${timestamp} [${level}] ${msg}`;
|
|
5041
|
+
console[consoleMethod](line);
|
|
5042
|
+
if (stack) console[consoleMethod](stack);
|
|
5043
|
+
} : () => {};
|
|
5044
|
+
return {
|
|
5045
|
+
trace: wrap("trace", "trace"),
|
|
5046
|
+
debug: wrap("debug", "debug"),
|
|
5047
|
+
info: wrap("info", "info"),
|
|
5048
|
+
warn: wrap("warn", "warn"),
|
|
5049
|
+
error: wrap("error", "error"),
|
|
5050
|
+
fatal: wrap("error", "fatal")
|
|
5051
|
+
};
|
|
5052
|
+
}
|
|
5053
|
+
const loggerContext = new AsyncLocalStorage();
|
|
5054
|
+
function runWithLogger(logger, fn) {
|
|
5055
|
+
return loggerContext.run(logger, fn);
|
|
5056
|
+
}
|
|
5057
|
+
function getLogger() {
|
|
5058
|
+
return loggerContext.getStore() ?? defaultLogger();
|
|
5059
|
+
}
|
|
5060
|
+
function formatValue(value) {
|
|
5061
|
+
if (value === null || value === void 0 || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") return String(value);
|
|
5062
|
+
if (typeof value === "string") {
|
|
5063
|
+
if (value.includes(" ")) return JSON.stringify(value);
|
|
5064
|
+
return value;
|
|
5065
|
+
}
|
|
5066
|
+
try {
|
|
5067
|
+
return stringify(value);
|
|
5068
|
+
} catch {
|
|
5069
|
+
try {
|
|
5070
|
+
return JSON.stringify(value);
|
|
5071
|
+
} catch {
|
|
5072
|
+
return String(value);
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
}
|
|
5076
|
+
|
|
4451
5077
|
//#endregion
|
|
4452
5078
|
//#region src/api/Controllers/validateOffers.ts
|
|
4453
5079
|
async function validateOffers(body, gatekeeper) {
|
|
@@ -4461,9 +5087,9 @@ async function validateOffers(body, gatekeeper) {
|
|
|
4461
5087
|
const rawOffer = rawOffers[i];
|
|
4462
5088
|
try {
|
|
4463
5089
|
const offer = fromSnakeCase(rawOffer);
|
|
4464
|
-
const hash$
|
|
4465
|
-
if (!offerIndexByHash.has(hash$
|
|
4466
|
-
offerIndexByHash.set(hash$
|
|
5090
|
+
const hash$3 = hash(offer);
|
|
5091
|
+
if (!offerIndexByHash.has(hash$3)) {
|
|
5092
|
+
offerIndexByHash.set(hash$3, i);
|
|
4467
5093
|
parsedOffers.push(offer);
|
|
4468
5094
|
}
|
|
4469
5095
|
} catch (err) {
|
|
@@ -4489,7 +5115,7 @@ async function validateOffers(body, gatekeeper) {
|
|
|
4489
5115
|
cursor: null
|
|
4490
5116
|
});
|
|
4491
5117
|
}
|
|
4492
|
-
const tree = from$
|
|
5118
|
+
const tree = from$7(parsedOffers);
|
|
4493
5119
|
const payload = encodeUnsigned(tree);
|
|
4494
5120
|
return success({
|
|
4495
5121
|
data: {
|
|
@@ -4509,6 +5135,35 @@ async function validateOffers(body, gatekeeper) {
|
|
|
4509
5135
|
}
|
|
4510
5136
|
}
|
|
4511
5137
|
|
|
5138
|
+
//#endregion
|
|
5139
|
+
//#region src/gatekeeper/CallbackTypes.ts
|
|
5140
|
+
/**
|
|
5141
|
+
* Resolve callback types for a list of callback addresses grouped by chain.
|
|
5142
|
+
* @param parameters - Resolve parameters. {@link resolveCallbackTypes.Parameters}
|
|
5143
|
+
* @returns Callback types grouped by chain. {@link resolveCallbackTypes.ReturnType}
|
|
5144
|
+
* @throws If a chain id is unknown.
|
|
5145
|
+
*/
|
|
5146
|
+
function resolveCallbackTypes$2(parameters) {
|
|
5147
|
+
const { chains, request } = parameters;
|
|
5148
|
+
const chainsById = new Map(chains.map((chain) => [chain.id, chain]));
|
|
5149
|
+
return request.callbacks.map(({ chain_id, addresses }) => {
|
|
5150
|
+
const chain = chainsById.get(chain_id);
|
|
5151
|
+
if (!chain) throw new Error(`Unknown chain id ${chain_id}`);
|
|
5152
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
5153
|
+
const uniqueAddresses = new Set(addresses.map((address) => address.toLowerCase()));
|
|
5154
|
+
for (const address of uniqueAddresses) {
|
|
5155
|
+
const bucketKey = getCallbackType(chain.name, address) ?? "not_supported";
|
|
5156
|
+
const list = buckets.get(bucketKey) ?? [];
|
|
5157
|
+
list.push(address);
|
|
5158
|
+
buckets.set(bucketKey, list);
|
|
5159
|
+
}
|
|
5160
|
+
const response = { chain_id };
|
|
5161
|
+
for (const [type, list] of buckets.entries()) response[type] = list;
|
|
5162
|
+
if (!response.not_supported) response.not_supported = [];
|
|
5163
|
+
return response;
|
|
5164
|
+
});
|
|
5165
|
+
}
|
|
5166
|
+
|
|
4512
5167
|
//#endregion
|
|
4513
5168
|
//#region src/gatekeeper/Service.ts
|
|
4514
5169
|
/**
|
|
@@ -4516,28 +5171,59 @@ async function validateOffers(body, gatekeeper) {
|
|
|
4516
5171
|
* @param parameters - App parameters including the {@link Gatekeeper} instance.
|
|
4517
5172
|
* @returns Hono app exposing gatekeeper endpoints.
|
|
4518
5173
|
*/
|
|
5174
|
+
const CallbackTypesRequestSchema = z$2.object({ callbacks: z$2.array(z$2.object({
|
|
5175
|
+
chain_id: z$2.number(),
|
|
5176
|
+
addresses: z$2.array(z$2.string().regex(/^0x[a-fA-F0-9]{40}$/))
|
|
5177
|
+
})) });
|
|
4519
5178
|
function createApp(parameters) {
|
|
4520
|
-
const { gatekeeper } = parameters;
|
|
5179
|
+
const { gatekeeper, chainRegistry } = parameters;
|
|
4521
5180
|
const app = new Hono();
|
|
4522
5181
|
app.post("/v1/validate", async (c) => {
|
|
4523
5182
|
let body;
|
|
4524
5183
|
try {
|
|
4525
5184
|
body = await c.req.json();
|
|
4526
5185
|
} catch (err) {
|
|
4527
|
-
const failure$
|
|
4528
|
-
return c.json(failure$
|
|
5186
|
+
const failure$5 = failure(err);
|
|
5187
|
+
return c.json(failure$5.body, failure$5.statusCode);
|
|
4529
5188
|
}
|
|
4530
5189
|
if (body === null || typeof body !== "object") {
|
|
4531
|
-
const failure$
|
|
4532
|
-
return c.json(failure$
|
|
5190
|
+
const failure$9 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
5191
|
+
return c.json(failure$9.body, failure$9.statusCode);
|
|
4533
5192
|
}
|
|
4534
5193
|
const { statusCode, body: payload } = await validateOffers(body, gatekeeper);
|
|
4535
5194
|
return c.json(payload, statusCode);
|
|
4536
5195
|
});
|
|
4537
|
-
app.get("/v1/rules", async (c) => {
|
|
4538
|
-
const
|
|
4539
|
-
|
|
4540
|
-
|
|
5196
|
+
app.get("/v1/config/rules", async (c) => {
|
|
5197
|
+
const { statusCode, body } = await getConfigRules(c.req.query(), chainRegistry.list());
|
|
5198
|
+
return c.json(body, statusCode);
|
|
5199
|
+
});
|
|
5200
|
+
app.post("/v1/callbacks", async (c) => {
|
|
5201
|
+
let body;
|
|
5202
|
+
try {
|
|
5203
|
+
body = await c.req.json();
|
|
5204
|
+
} catch (err) {
|
|
5205
|
+
const failure$8 = failure(err);
|
|
5206
|
+
return c.json(failure$8.body, failure$8.statusCode);
|
|
5207
|
+
}
|
|
5208
|
+
if (body === null || typeof body !== "object") {
|
|
5209
|
+
const failure$6 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
5210
|
+
return c.json(failure$6.body, failure$6.statusCode);
|
|
5211
|
+
}
|
|
5212
|
+
try {
|
|
5213
|
+
const request = CallbackTypesRequestSchema.parse(body);
|
|
5214
|
+
const chainIds = new Set(chainRegistry.list().map((chain) => chain.id));
|
|
5215
|
+
const unknown = request.callbacks.find((entry) => !chainIds.has(entry.chain_id));
|
|
5216
|
+
if (unknown) throw new BadRequestError(`Unknown chain id ${unknown.chain_id}`);
|
|
5217
|
+
const data = resolveCallbackTypes$2({
|
|
5218
|
+
chains: chainRegistry.list(),
|
|
5219
|
+
request
|
|
5220
|
+
});
|
|
5221
|
+
const response = success({ data });
|
|
5222
|
+
return c.json(response.body, response.statusCode);
|
|
5223
|
+
} catch (err) {
|
|
5224
|
+
const failure$7 = failure(err);
|
|
5225
|
+
return c.json(failure$7.body, failure$7.statusCode);
|
|
5226
|
+
}
|
|
4541
5227
|
});
|
|
4542
5228
|
return app;
|
|
4543
5229
|
}
|
|
@@ -4547,8 +5233,11 @@ function createApp(parameters) {
|
|
|
4547
5233
|
* @returns Service handle including base URL and shutdown method. {@link ServiceHandle}
|
|
4548
5234
|
*/
|
|
4549
5235
|
async function start$1(config) {
|
|
4550
|
-
const { gatekeeper, port, hostname } = config;
|
|
4551
|
-
const app = createApp({
|
|
5236
|
+
const { gatekeeper, chainRegistry, port, hostname } = config;
|
|
5237
|
+
const app = createApp({
|
|
5238
|
+
gatekeeper,
|
|
5239
|
+
chainRegistry
|
|
5240
|
+
});
|
|
4552
5241
|
let address = null;
|
|
4553
5242
|
let server;
|
|
4554
5243
|
await new Promise((resolve) => {
|
|
@@ -4748,6 +5437,18 @@ async function* collectOffersV2(parameters) {
|
|
|
4748
5437
|
const logger = getLogger();
|
|
4749
5438
|
let startBlock = blockNumber;
|
|
4750
5439
|
let reorgDetected = false;
|
|
5440
|
+
if (client.chain.custom.morpho.address.toLowerCase() === zeroAddress) {
|
|
5441
|
+
const msg = "Morpho V2 address is zero, signature verification will fail. Please set the Morpho V2 address in the chain configuration.";
|
|
5442
|
+
logger.error({
|
|
5443
|
+
msg,
|
|
5444
|
+
chain_id: client.chain.id
|
|
5445
|
+
});
|
|
5446
|
+
throw new Error(msg);
|
|
5447
|
+
}
|
|
5448
|
+
const signatureDomain = {
|
|
5449
|
+
chainId: client.chain.id,
|
|
5450
|
+
verifyingContract: client.chain.custom.morpho.address
|
|
5451
|
+
};
|
|
4751
5452
|
const { blockNumber: latestBlockNumberChain } = await db.blocks.getChain(client.chain.id);
|
|
4752
5453
|
const stream = streamLogs({
|
|
4753
5454
|
client,
|
|
@@ -4778,7 +5479,7 @@ async function* collectOffersV2(parameters) {
|
|
|
4778
5479
|
if (!log) continue;
|
|
4779
5480
|
const [payload] = decodeAbiParameters([{ type: "bytes" }], log.data);
|
|
4780
5481
|
try {
|
|
4781
|
-
const { tree, signature, signer } = await decode(payload);
|
|
5482
|
+
const { tree, signature, signer } = await decode(payload, signatureDomain);
|
|
4782
5483
|
const signerMismatch = tree.offers.find((offer) => offer.maker.toLowerCase() !== signer.toLowerCase());
|
|
4783
5484
|
if (signerMismatch) {
|
|
4784
5485
|
logger.debug({
|
|
@@ -4810,6 +5511,7 @@ async function* collectOffersV2(parameters) {
|
|
|
4810
5511
|
const { epoch, blockNumber: latestBlockNumber } = await dbTx.blocks.getChain(client.chain.id);
|
|
4811
5512
|
const treesToInsert = [];
|
|
4812
5513
|
let totalValidOffers = 0;
|
|
5514
|
+
const offersWithBlock = [];
|
|
4813
5515
|
for (const { tree, signature, blockNumber: treeBlockNumber } of decodedTrees) try {
|
|
4814
5516
|
const allowedResults = await gatekeeper.isAllowed(tree.offers);
|
|
4815
5517
|
const hasBlockWindowViolation = treeBlockNumber > latestBlockNumber;
|
|
@@ -4831,10 +5533,13 @@ async function* collectOffersV2(parameters) {
|
|
|
4831
5533
|
}
|
|
4832
5534
|
treesToInsert.push({
|
|
4833
5535
|
tree,
|
|
4834
|
-
signature
|
|
4835
|
-
blockNumber: treeBlockNumber
|
|
5536
|
+
signature
|
|
4836
5537
|
});
|
|
4837
5538
|
totalValidOffers += tree.offers.length;
|
|
5539
|
+
offersWithBlock.push(...tree.offers.map((offer) => ({
|
|
5540
|
+
offer,
|
|
5541
|
+
blockNumber: treeBlockNumber
|
|
5542
|
+
})));
|
|
4838
5543
|
} catch (err) {
|
|
4839
5544
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
4840
5545
|
logger.error({
|
|
@@ -4844,7 +5549,24 @@ async function* collectOffersV2(parameters) {
|
|
|
4844
5549
|
});
|
|
4845
5550
|
throw new Error("Gatekeeper validation failed", { cause: error });
|
|
4846
5551
|
}
|
|
5552
|
+
const dependencies = buildOfferDependencies$1(offersWithBlock);
|
|
5553
|
+
await dbTx.oracles.upsert(dependencies.oracles);
|
|
5554
|
+
await dbTx.obligations.create(dependencies.obligations);
|
|
5555
|
+
await dbTx.groups.create(dependencies.groups);
|
|
5556
|
+
const insertedHashes = await dbTx.offers.create(dependencies.offerBatches);
|
|
4847
5557
|
if (treesToInsert.length > 0) await dbTx.trees.create(treesToInsert);
|
|
5558
|
+
const insertedOffers = filterInsertedOffers({
|
|
5559
|
+
offers: offersWithBlock,
|
|
5560
|
+
hashes: insertedHashes
|
|
5561
|
+
});
|
|
5562
|
+
const { callbacks, positions, lots } = await decodeCallbacks({
|
|
5563
|
+
chainId: client.chain.id,
|
|
5564
|
+
gatekeeper,
|
|
5565
|
+
offers: insertedOffers
|
|
5566
|
+
});
|
|
5567
|
+
if (positions.length > 0) await dbTx.positions.upsert(positions);
|
|
5568
|
+
if (callbacks.length > 0) await dbTx.callbacks.upsert(callbacks);
|
|
5569
|
+
if (lots.length > 0) await dbTx.lots.create(lots);
|
|
4848
5570
|
try {
|
|
4849
5571
|
await dbTx.blocks.advanceCollector({
|
|
4850
5572
|
collectorName: collector,
|
|
@@ -4897,10 +5619,151 @@ async function* collectOffersV2(parameters) {
|
|
|
4897
5619
|
}
|
|
4898
5620
|
}
|
|
4899
5621
|
});
|
|
4900
|
-
if (reorgDetected) return;
|
|
4901
|
-
yield blockNumber;
|
|
4902
|
-
startBlock = blockNumber;
|
|
5622
|
+
if (reorgDetected) return;
|
|
5623
|
+
yield blockNumber;
|
|
5624
|
+
startBlock = blockNumber;
|
|
5625
|
+
}
|
|
5626
|
+
}
|
|
5627
|
+
async function decodeCallbacks(parameters) {
|
|
5628
|
+
const { chainId, gatekeeper, offers } = parameters;
|
|
5629
|
+
if (offers.length === 0) return {
|
|
5630
|
+
callbacks: [],
|
|
5631
|
+
positions: [],
|
|
5632
|
+
lots: []
|
|
5633
|
+
};
|
|
5634
|
+
const addresses = offers.filter((entry) => entry.offer.callback.data !== "0x").map((entry) => entry.offer.callback.address);
|
|
5635
|
+
if (addresses.length === 0) return {
|
|
5636
|
+
callbacks: [],
|
|
5637
|
+
positions: [],
|
|
5638
|
+
lots: []
|
|
5639
|
+
};
|
|
5640
|
+
let response;
|
|
5641
|
+
try {
|
|
5642
|
+
response = await gatekeeper.getCallbackTypes({ callbacks: [{
|
|
5643
|
+
chain_id: chainId,
|
|
5644
|
+
addresses
|
|
5645
|
+
}] });
|
|
5646
|
+
} catch (err) {
|
|
5647
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
5648
|
+
throw new Error("Failed to resolve callback types", { cause: error });
|
|
5649
|
+
}
|
|
5650
|
+
const entry = response.find((item) => item.chain_id === chainId);
|
|
5651
|
+
const typeByAddress = /* @__PURE__ */ new Map();
|
|
5652
|
+
if (entry) for (const [key, list] of Object.entries(entry)) {
|
|
5653
|
+
if (key === "chain_id" || key === "not_supported") continue;
|
|
5654
|
+
if (!Array.isArray(list)) continue;
|
|
5655
|
+
for (const address of list) typeByAddress.set(address.toLowerCase(), key);
|
|
5656
|
+
}
|
|
5657
|
+
const callbacks = [];
|
|
5658
|
+
const positions = [];
|
|
5659
|
+
const lots = [];
|
|
5660
|
+
for (const { offer, blockNumber: offerBlockNumber } of offers) {
|
|
5661
|
+
if (offer.callback.data === "0x") continue;
|
|
5662
|
+
const callbackType = typeByAddress.get(offer.callback.address.toLowerCase());
|
|
5663
|
+
if (!callbackType) continue;
|
|
5664
|
+
let decoded;
|
|
5665
|
+
try {
|
|
5666
|
+
decoded = decode$1(callbackType, offer.callback.data);
|
|
5667
|
+
} catch (err) {
|
|
5668
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
5669
|
+
throw new Error("Failed to decode callback data", { cause: error });
|
|
5670
|
+
}
|
|
5671
|
+
if (decoded.length === 0) continue;
|
|
5672
|
+
const offerHash = hash(offer);
|
|
5673
|
+
const callbackInputs = decoded.map((callback) => ({
|
|
5674
|
+
chainId: offer.chainId,
|
|
5675
|
+
contract: callback.contract,
|
|
5676
|
+
user: offer.maker,
|
|
5677
|
+
amount: callback.amount
|
|
5678
|
+
}));
|
|
5679
|
+
callbacks.push({
|
|
5680
|
+
offerHash,
|
|
5681
|
+
callbacks: callbackInputs
|
|
5682
|
+
});
|
|
5683
|
+
for (const callback of decoded) {
|
|
5684
|
+
const contract = callback.contract;
|
|
5685
|
+
const positionType = callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20;
|
|
5686
|
+
const asset = callbackType === Type$1.BuyVaultV1Callback ? void 0 : contract;
|
|
5687
|
+
positions.push(from$10({
|
|
5688
|
+
chainId: offer.chainId,
|
|
5689
|
+
contract,
|
|
5690
|
+
user: offer.maker,
|
|
5691
|
+
type: positionType,
|
|
5692
|
+
asset,
|
|
5693
|
+
blockNumber: offerBlockNumber
|
|
5694
|
+
}));
|
|
5695
|
+
const isLoanPosition = offer.loanToken.toLowerCase() === asset?.toLowerCase();
|
|
5696
|
+
lots.push({
|
|
5697
|
+
positionChainId: offer.chainId,
|
|
5698
|
+
positionContract: contract,
|
|
5699
|
+
positionUser: offer.maker,
|
|
5700
|
+
group: offer.group,
|
|
5701
|
+
size: isLoanPosition ? offer.assets : callback.amount
|
|
5702
|
+
});
|
|
5703
|
+
}
|
|
5704
|
+
}
|
|
5705
|
+
return {
|
|
5706
|
+
callbacks,
|
|
5707
|
+
positions,
|
|
5708
|
+
lots
|
|
5709
|
+
};
|
|
5710
|
+
}
|
|
5711
|
+
function buildOfferDependencies$1(offers) {
|
|
5712
|
+
const obligationsById = /* @__PURE__ */ new Map();
|
|
5713
|
+
const oraclesByKey = /* @__PURE__ */ new Map();
|
|
5714
|
+
const groupsByKey = /* @__PURE__ */ new Map();
|
|
5715
|
+
const offersByBlock = /* @__PURE__ */ new Map();
|
|
5716
|
+
for (const { offer, blockNumber } of offers) {
|
|
5717
|
+
const list = offersByBlock.get(blockNumber) ?? [];
|
|
5718
|
+
list.push(offer);
|
|
5719
|
+
offersByBlock.set(blockNumber, list);
|
|
5720
|
+
const obligationId$2 = obligationId(offer);
|
|
5721
|
+
if (!obligationsById.get(obligationId$2)) obligationsById.set(obligationId$2, from$13({
|
|
5722
|
+
chainId: offer.chainId,
|
|
5723
|
+
loanToken: offer.loanToken,
|
|
5724
|
+
maturity: offer.maturity,
|
|
5725
|
+
collaterals: offer.collaterals
|
|
5726
|
+
}));
|
|
5727
|
+
for (const collateral of offer.collaterals) {
|
|
5728
|
+
const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
|
|
5729
|
+
if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$11({
|
|
5730
|
+
chainId: offer.chainId,
|
|
5731
|
+
address: collateral.oracle,
|
|
5732
|
+
price: null,
|
|
5733
|
+
blockNumber
|
|
5734
|
+
}));
|
|
5735
|
+
}
|
|
5736
|
+
const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
|
|
5737
|
+
if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
|
|
5738
|
+
chainId: offer.chainId,
|
|
5739
|
+
maker: offer.maker,
|
|
5740
|
+
group: offer.group,
|
|
5741
|
+
blockNumber
|
|
5742
|
+
});
|
|
5743
|
+
}
|
|
5744
|
+
return {
|
|
5745
|
+
obligations: Array.from(obligationsById.values()),
|
|
5746
|
+
oracles: Array.from(oraclesByKey.values()),
|
|
5747
|
+
groups: Array.from(groupsByKey.values()),
|
|
5748
|
+
offerBatches: Array.from(offersByBlock.entries()).map(([blockNumber, items]) => ({
|
|
5749
|
+
blockNumber,
|
|
5750
|
+
offers: items
|
|
5751
|
+
}))
|
|
5752
|
+
};
|
|
5753
|
+
}
|
|
5754
|
+
function filterInsertedOffers(parameters) {
|
|
5755
|
+
if (parameters.hashes.length === 0) return [];
|
|
5756
|
+
const inserted = new Set(parameters.hashes.map((hash) => hash.toLowerCase()));
|
|
5757
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5758
|
+
const filtered = [];
|
|
5759
|
+
for (const entry of parameters.offers) {
|
|
5760
|
+
const hash$2 = hash(entry.offer).toLowerCase();
|
|
5761
|
+
if (!inserted.has(hash$2)) continue;
|
|
5762
|
+
if (seen.has(hash$2)) continue;
|
|
5763
|
+
seen.add(hash$2);
|
|
5764
|
+
filtered.push(entry);
|
|
4903
5765
|
}
|
|
5766
|
+
return filtered;
|
|
4904
5767
|
}
|
|
4905
5768
|
|
|
4906
5769
|
//#endregion
|
|
@@ -5160,7 +6023,7 @@ async function* collectPositions(parameters) {
|
|
|
5160
6023
|
});
|
|
5161
6024
|
continue;
|
|
5162
6025
|
}
|
|
5163
|
-
transfers.push(from$
|
|
6026
|
+
transfers.push(from$8({
|
|
5164
6027
|
id: `${client.chain.id}-${log.blockNumber.toString()}-${log.transactionHash}-${log.logIndex.toString()}`,
|
|
5165
6028
|
chainId: client.chain.id,
|
|
5166
6029
|
contract: log.address,
|
|
@@ -5499,7 +6362,7 @@ async function* collectPrices(parameters) {
|
|
|
5499
6362
|
//#region src/indexer/collectors/CollectorBuilder.ts
|
|
5500
6363
|
function createBuilder(parameters) {
|
|
5501
6364
|
const { client, db, gatekeeper, options: { maxBlockNumber, blockWindow, interval } = {} } = parameters;
|
|
5502
|
-
const createCollector = (name, collect) => create$
|
|
6365
|
+
const createCollector = (name, collect) => create$16({
|
|
5503
6366
|
name,
|
|
5504
6367
|
collect,
|
|
5505
6368
|
client,
|
|
@@ -5603,7 +6466,7 @@ function from$1(config) {
|
|
|
5603
6466
|
retryAttempts,
|
|
5604
6467
|
retryDelayMs
|
|
5605
6468
|
});
|
|
5606
|
-
return create$
|
|
6469
|
+
return create$18({
|
|
5607
6470
|
client,
|
|
5608
6471
|
collectors: [
|
|
5609
6472
|
offersCollector,
|
|
@@ -5613,7 +6476,7 @@ function from$1(config) {
|
|
|
5613
6476
|
]
|
|
5614
6477
|
});
|
|
5615
6478
|
}
|
|
5616
|
-
function create$
|
|
6479
|
+
function create$18(params) {
|
|
5617
6480
|
const { collectors, client } = params;
|
|
5618
6481
|
const indexerId = `${client.chain.id.toString()}.indexer`;
|
|
5619
6482
|
const tracer = getTracer(`router.${indexerId}`);
|
|
@@ -5642,7 +6505,7 @@ function create$15(params) {
|
|
|
5642
6505
|
|
|
5643
6506
|
//#endregion
|
|
5644
6507
|
//#region src/indexer/collectors/Admin.ts
|
|
5645
|
-
function create$
|
|
6508
|
+
function create$17(parameters) {
|
|
5646
6509
|
const collector = "admin";
|
|
5647
6510
|
const { client, db, options: { maxBatchSize = 25, maxBlockNumber } = {} } = parameters;
|
|
5648
6511
|
const maxBlockNumberBI = maxBlockNumber !== void 0 ? BigInt(maxBlockNumber) : void 0;
|
|
@@ -5882,8 +6745,8 @@ const names = [
|
|
|
5882
6745
|
"positions",
|
|
5883
6746
|
"prices"
|
|
5884
6747
|
];
|
|
5885
|
-
function create$
|
|
5886
|
-
const admin = create$
|
|
6748
|
+
function create$16({ name, collect, client, db, options }) {
|
|
6749
|
+
const admin = create$17({
|
|
5887
6750
|
client,
|
|
5888
6751
|
db,
|
|
5889
6752
|
options
|
|
@@ -6119,7 +6982,6 @@ const obligationCollateralsV2 = s.table(EnumTableName.OBLIGATION_COLLATERALS_V2,
|
|
|
6119
6982
|
oracleChainId: bigint("oracle_chain_id", { mode: "number" }).$type().notNull(),
|
|
6120
6983
|
oracleAddress: varchar("oracle_address", { length: 42 }).notNull(),
|
|
6121
6984
|
lltv: bigint("lltv", { mode: "bigint" }).notNull(),
|
|
6122
|
-
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
6123
6985
|
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
6124
6986
|
}, (table) => [
|
|
6125
6987
|
primaryKey({
|
|
@@ -6415,7 +7277,7 @@ const merklePaths = s.table(EnumTableName.MERKLE_PATHS, {
|
|
|
6415
7277
|
//#endregion
|
|
6416
7278
|
//#region src/database/domains/Blocks.ts
|
|
6417
7279
|
/** Postgres implementation. */
|
|
6418
|
-
const create$
|
|
7280
|
+
const create$15 = (config) => {
|
|
6419
7281
|
const { db, chainRegistry } = config;
|
|
6420
7282
|
const getChain = async (chainId) => {
|
|
6421
7283
|
const rows = await db.select({
|
|
@@ -6596,7 +7458,7 @@ const create$12 = (config) => {
|
|
|
6596
7458
|
//#region src/database/domains/Book.ts
|
|
6597
7459
|
const DEFAULT_LIMIT$3 = 100;
|
|
6598
7460
|
const MAX_TOTAL_OFFERS = 500;
|
|
6599
|
-
function create$
|
|
7461
|
+
function create$14(config) {
|
|
6600
7462
|
const db = config.db;
|
|
6601
7463
|
const logger = getLogger();
|
|
6602
7464
|
const getOffers = async (parameters) => {
|
|
@@ -7060,16 +7922,76 @@ let LevelCursor;
|
|
|
7060
7922
|
*/
|
|
7061
7923
|
const DEFAULT_BATCH_SIZE = 4e3;
|
|
7062
7924
|
|
|
7925
|
+
//#endregion
|
|
7926
|
+
//#region src/database/domains/Callbacks.ts
|
|
7927
|
+
/**
|
|
7928
|
+
* Create a callbacks domain instance.
|
|
7929
|
+
* @param db - Database core instance.
|
|
7930
|
+
* @returns Callbacks domain. {@link CallbacksDomain}
|
|
7931
|
+
*/
|
|
7932
|
+
function create$13(db) {
|
|
7933
|
+
return {
|
|
7934
|
+
upsert: async (inputs) => {
|
|
7935
|
+
if (inputs.length === 0) return;
|
|
7936
|
+
const idCache = /* @__PURE__ */ new Map();
|
|
7937
|
+
const seenCallbackIds = /* @__PURE__ */ new Set();
|
|
7938
|
+
const callbacksRows = [];
|
|
7939
|
+
const offersCallbacksRows = [];
|
|
7940
|
+
const callbackId = (input) => {
|
|
7941
|
+
const preimage = `0x${input.chainId}${input.contract}${input.user}${input.amount.toString()}`.toLowerCase();
|
|
7942
|
+
const id = idCache.get(preimage) ?? keccak256(preimage);
|
|
7943
|
+
idCache.set(preimage, id);
|
|
7944
|
+
return id;
|
|
7945
|
+
};
|
|
7946
|
+
for (const { offerHash, callbacks } of inputs) {
|
|
7947
|
+
const normalizedOfferHash = offerHash.toLowerCase();
|
|
7948
|
+
for (const callback of callbacks) {
|
|
7949
|
+
const normalized = {
|
|
7950
|
+
chainId: callback.chainId,
|
|
7951
|
+
contract: callback.contract.toLowerCase(),
|
|
7952
|
+
user: callback.user.toLowerCase(),
|
|
7953
|
+
amount: callback.amount
|
|
7954
|
+
};
|
|
7955
|
+
const id = callbackId(normalized);
|
|
7956
|
+
offersCallbacksRows.push({
|
|
7957
|
+
offerHash: normalizedOfferHash,
|
|
7958
|
+
callbackId: id
|
|
7959
|
+
});
|
|
7960
|
+
if (seenCallbackIds.has(id)) continue;
|
|
7961
|
+
seenCallbackIds.add(id);
|
|
7962
|
+
callbacksRows.push({
|
|
7963
|
+
id,
|
|
7964
|
+
positionChainId: normalized.chainId,
|
|
7965
|
+
positionContract: normalized.contract,
|
|
7966
|
+
positionUser: normalized.user,
|
|
7967
|
+
amount: normalized.amount.toString()
|
|
7968
|
+
});
|
|
7969
|
+
}
|
|
7970
|
+
}
|
|
7971
|
+
if (offersCallbacksRows.length === 0) return;
|
|
7972
|
+
await db.transaction(async (dbTx) => {
|
|
7973
|
+
for (const batch of batch$1(callbacksRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(callbacks).values(batch).onConflictDoNothing();
|
|
7974
|
+
for (const batch of batch$1(offersCallbacksRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(offersCallbacks).values(batch).onConflictDoNothing();
|
|
7975
|
+
});
|
|
7976
|
+
},
|
|
7977
|
+
delete: async ({ offers }) => {
|
|
7978
|
+
if (offers.length === 0) return 0;
|
|
7979
|
+
const normalized = offers.map((offer) => offer.toLowerCase());
|
|
7980
|
+
return (await db.delete(offersCallbacks).where(inArray(offersCallbacks.offerHash, normalized))).affectedRows;
|
|
7981
|
+
}
|
|
7982
|
+
};
|
|
7983
|
+
}
|
|
7984
|
+
|
|
7063
7985
|
//#endregion
|
|
7064
7986
|
//#region src/database/domains/Consumed.ts
|
|
7065
|
-
function create$
|
|
7987
|
+
function create$12(db) {
|
|
7066
7988
|
return {
|
|
7067
7989
|
create: async (events) => {
|
|
7068
7990
|
if (events.length === 0) return;
|
|
7069
|
-
const groups$
|
|
7991
|
+
const groups$2 = /* @__PURE__ */ new Map();
|
|
7070
7992
|
for (const event of events) {
|
|
7071
7993
|
const groupId = `${event.chainId}-${event.maker}-${event.group}`.toLowerCase();
|
|
7072
|
-
groups$
|
|
7994
|
+
groups$2.set(groupId, {
|
|
7073
7995
|
chainId: event.chainId,
|
|
7074
7996
|
maker: event.maker,
|
|
7075
7997
|
group: event.group,
|
|
@@ -7077,7 +7999,7 @@ function create$10(db) {
|
|
|
7077
7999
|
});
|
|
7078
8000
|
}
|
|
7079
8001
|
await db.transaction(async (dbTx) => {
|
|
7080
|
-
const groupsRows = Array.from(groups$
|
|
8002
|
+
const groupsRows = Array.from(groups$2.values()).map((group) => ({
|
|
7081
8003
|
chainId: group.chainId,
|
|
7082
8004
|
maker: group.maker.toLowerCase(),
|
|
7083
8005
|
group: group.group.toLowerCase(),
|
|
@@ -7103,9 +8025,30 @@ function create$10(db) {
|
|
|
7103
8025
|
};
|
|
7104
8026
|
}
|
|
7105
8027
|
|
|
8028
|
+
//#endregion
|
|
8029
|
+
//#region src/database/domains/Groups.ts
|
|
8030
|
+
/**
|
|
8031
|
+
* Create a groups domain instance.
|
|
8032
|
+
* @param db - Database core instance.
|
|
8033
|
+
* @returns Groups domain. {@link GroupsDomain}
|
|
8034
|
+
*/
|
|
8035
|
+
function create$11(db) {
|
|
8036
|
+
return { create: async (groups$1) => {
|
|
8037
|
+
if (groups$1.length === 0) return;
|
|
8038
|
+
const rows = groups$1.map((group) => ({
|
|
8039
|
+
chainId: group.chainId,
|
|
8040
|
+
maker: group.maker.toLowerCase(),
|
|
8041
|
+
group: group.group.toLowerCase(),
|
|
8042
|
+
consumed: (group.consumed ?? 0n).toString(),
|
|
8043
|
+
blockNumber: group.blockNumber
|
|
8044
|
+
}));
|
|
8045
|
+
for (const batch of batch$1(rows, DEFAULT_BATCH_SIZE)) await db.insert(groups).values(batch).onConflictDoNothing();
|
|
8046
|
+
} };
|
|
8047
|
+
}
|
|
8048
|
+
|
|
7106
8049
|
//#endregion
|
|
7107
8050
|
//#region src/database/domains/Lots.ts
|
|
7108
|
-
function create$
|
|
8051
|
+
function create$10(db) {
|
|
7109
8052
|
return {
|
|
7110
8053
|
get: async (parameters) => {
|
|
7111
8054
|
const { chainId, user, contract, group } = parameters ?? {};
|
|
@@ -7149,268 +8092,88 @@ function create$9(db) {
|
|
|
7149
8092
|
}
|
|
7150
8093
|
|
|
7151
8094
|
//#endregion
|
|
7152
|
-
//#region src/
|
|
7153
|
-
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
8095
|
+
//#region src/database/domains/Obligations.ts
|
|
7154
8096
|
/**
|
|
7155
|
-
* Create an
|
|
7156
|
-
* @param
|
|
7157
|
-
* @returns
|
|
8097
|
+
* Create an obligations domain instance.
|
|
8098
|
+
* @param db - Database core instance.
|
|
8099
|
+
* @returns Obligations domain. {@link ObligationsDomain}
|
|
7158
8100
|
*/
|
|
7159
|
-
function
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
8101
|
+
function create$9(db) {
|
|
8102
|
+
return { create: async (obligations$1) => {
|
|
8103
|
+
if (obligations$1.length === 0) return;
|
|
8104
|
+
const obligationsById = /* @__PURE__ */ new Map();
|
|
8105
|
+
for (const obligation of obligations$1) {
|
|
8106
|
+
const id$1 = id(obligation).toLowerCase();
|
|
8107
|
+
if (!obligationsById.get(id$1)) obligationsById.set(id$1, obligation);
|
|
8108
|
+
}
|
|
7166
8109
|
try {
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
8110
|
+
await db.transaction(async (dbTx) => {
|
|
8111
|
+
const obligationRows = obligations$1.map((obligation) => ({
|
|
8112
|
+
obligationId: id(obligation),
|
|
8113
|
+
chainId: obligation.chainId,
|
|
8114
|
+
loanToken: obligation.loanToken.toLowerCase(),
|
|
8115
|
+
maturity: obligation.maturity
|
|
8116
|
+
}));
|
|
8117
|
+
for (const batch of batch$1(obligationRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(obligations).values(batch).onConflictDoNothing();
|
|
8118
|
+
const collateralRows = obligations$1.flatMap((obligation) => {
|
|
8119
|
+
return obligation.collaterals.map((collateral) => ({
|
|
8120
|
+
obligationId: id(obligation),
|
|
8121
|
+
asset: collateral.asset.toLowerCase(),
|
|
8122
|
+
oracleChainId: obligation.chainId,
|
|
8123
|
+
oracleAddress: collateral.oracle.toLowerCase(),
|
|
8124
|
+
lltv: collateral.lltv
|
|
8125
|
+
}));
|
|
8126
|
+
});
|
|
8127
|
+
for (const batch of batch$1(collateralRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(obligationCollateralsV2).values(batch).onConflictDoNothing();
|
|
7170
8128
|
});
|
|
7171
|
-
}
|
|
7172
|
-
|
|
7173
|
-
|
|
7174
|
-
};
|
|
7175
|
-
const validate = async (body) => {
|
|
7176
|
-
const response = await request("/v1/validate", {
|
|
7177
|
-
method: "POST",
|
|
7178
|
-
headers: { "content-type": "application/json" },
|
|
7179
|
-
body: JSON.stringify(body)
|
|
7180
|
-
});
|
|
7181
|
-
const json = await response.json();
|
|
7182
|
-
return {
|
|
7183
|
-
statusCode: response.status,
|
|
7184
|
-
body: json
|
|
7185
|
-
};
|
|
7186
|
-
};
|
|
7187
|
-
const getRules = async () => {
|
|
7188
|
-
const response = await request("/v1/rules", { method: "GET" });
|
|
7189
|
-
const json = await response.json();
|
|
7190
|
-
if (!response.ok) throw new Error(`Gatekeeper rules request failed: ${extractErrorMessage(json) ?? response.statusText}`);
|
|
7191
|
-
if (!("data" in json) || !Array.isArray(json.data)) throw new Error("Gatekeeper rules response is invalid.");
|
|
7192
|
-
return json.data;
|
|
7193
|
-
};
|
|
7194
|
-
const isAllowed = async (offers) => {
|
|
7195
|
-
const { statusCode, body } = await validate({ offers: offers.map((offer) => toSnakeCase(offer)) });
|
|
7196
|
-
if (statusCode !== 200) {
|
|
7197
|
-
const errorMessage = extractErrorMessage(body);
|
|
7198
|
-
throw new Error(`Gatekeeper validation failed: ${errorMessage ?? `status ${statusCode}`}`);
|
|
7199
|
-
}
|
|
7200
|
-
const data = body.data;
|
|
7201
|
-
if (!data || typeof data !== "object") throw new Error("Gatekeeper validation response is invalid.");
|
|
7202
|
-
if ("issues" in data) {
|
|
7203
|
-
const issues = data.issues.map((issue) => ({
|
|
7204
|
-
ruleName: issue.rule,
|
|
7205
|
-
message: issue.message,
|
|
7206
|
-
item: offers[issue.index]
|
|
7207
|
-
}));
|
|
7208
|
-
const invalidIndices = new Set(data.issues.map((issue) => issue.index));
|
|
7209
|
-
return {
|
|
7210
|
-
valid: offers.filter((_, index) => !invalidIndices.has(index)),
|
|
7211
|
-
issues
|
|
7212
|
-
};
|
|
8129
|
+
} catch (err) {
|
|
8130
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
8131
|
+
throw new Error("Obligations.create failed. Ensure oracles exist before inserting obligations.", { cause: error });
|
|
7213
8132
|
}
|
|
7214
|
-
|
|
7215
|
-
return {
|
|
7216
|
-
valid: offers.slice(),
|
|
7217
|
-
issues: []
|
|
7218
|
-
};
|
|
7219
|
-
};
|
|
7220
|
-
return {
|
|
7221
|
-
baseUrl,
|
|
7222
|
-
validate,
|
|
7223
|
-
isAllowed,
|
|
7224
|
-
getRules
|
|
7225
|
-
};
|
|
7226
|
-
}
|
|
7227
|
-
function normalizeBaseUrl(url) {
|
|
7228
|
-
return url.trim().replace(/\/+$/, "");
|
|
7229
|
-
}
|
|
7230
|
-
function extractErrorMessage(payload) {
|
|
7231
|
-
if (!payload || typeof payload !== "object") return void 0;
|
|
7232
|
-
const error = payload.error;
|
|
7233
|
-
if (!error || typeof error !== "object") return void 0;
|
|
7234
|
-
return typeof error.message === "string" ? error.message : void 0;
|
|
8133
|
+
} };
|
|
7235
8134
|
}
|
|
7236
8135
|
|
|
7237
8136
|
//#endregion
|
|
7238
8137
|
//#region src/database/domains/Offers.ts
|
|
7239
8138
|
const DEFAULT_LIMIT$2 = 100;
|
|
7240
8139
|
function create$8(config) {
|
|
7241
|
-
const { db
|
|
8140
|
+
const { db } = config;
|
|
7242
8141
|
return {
|
|
7243
8142
|
create: async (batches) => {
|
|
7244
8143
|
if (batches.length === 0) return [];
|
|
7245
|
-
const
|
|
7246
|
-
offer,
|
|
8144
|
+
const offersRows = batches.flatMap(({ blockNumber, offers }) => offers.map((offer) => ({
|
|
8145
|
+
...serialize(offer),
|
|
8146
|
+
obligationId: obligationId(offer),
|
|
8147
|
+
groupChainId: offer.chainId,
|
|
8148
|
+
groupMaker: offer.maker.toLowerCase(),
|
|
8149
|
+
callbackAddress: offer.callback.address.toLowerCase(),
|
|
8150
|
+
callbackData: offer.callback.data,
|
|
7247
8151
|
blockNumber
|
|
7248
8152
|
})));
|
|
7249
|
-
if (
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
|
|
7256
|
-
|
|
7257
|
-
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
|
|
7261
|
-
|
|
7262
|
-
|
|
7263
|
-
|
|
7264
|
-
|
|
7265
|
-
blockNumber
|
|
7266
|
-
});
|
|
7267
|
-
for (const collateral of offer.collaterals) {
|
|
7268
|
-
const oracleId = `${offer.chainId}-${collateral.oracle.toLowerCase()}`.toLowerCase();
|
|
7269
|
-
if (!oraclesMap.has(oracleId)) oraclesMap.set(oracleId, {
|
|
7270
|
-
chainId: offer.chainId,
|
|
7271
|
-
address: collateral.oracle,
|
|
7272
|
-
blockNumber
|
|
7273
|
-
});
|
|
8153
|
+
if (offersRows.length === 0) return [];
|
|
8154
|
+
try {
|
|
8155
|
+
return await db.transaction(async (dbTx) => {
|
|
8156
|
+
const selectExisting = async (hashes) => {
|
|
8157
|
+
if (hashes.length === 0) return /* @__PURE__ */ new Set();
|
|
8158
|
+
const existing = /* @__PURE__ */ new Set();
|
|
8159
|
+
for (const batch of batch$1(hashes, DEFAULT_BATCH_SIZE)) {
|
|
8160
|
+
const rows = await dbTx.select({ hash: offers.hash }).from(offers).where(inArray(offers.hash, batch));
|
|
8161
|
+
for (const row of rows) existing.add(String(row.hash).toLowerCase());
|
|
8162
|
+
}
|
|
8163
|
+
return existing;
|
|
8164
|
+
};
|
|
8165
|
+
const inserted = [];
|
|
8166
|
+
for (const batch of batch$1(offersRows, DEFAULT_BATCH_SIZE)) {
|
|
8167
|
+
const rows = await dbTx.insert(offers).values(batch).onConflictDoNothing().returning();
|
|
8168
|
+
inserted.push(...rows.map((row) => row.hash));
|
|
7274
8169
|
}
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
if (!groupsMap.has(groupId)) groupsMap.set(groupId, {
|
|
7278
|
-
chainId: offer.chainId,
|
|
7279
|
-
maker: offer.maker,
|
|
7280
|
-
group: offer.group,
|
|
7281
|
-
blockNumber
|
|
8170
|
+
const existing = await selectExisting(inserted);
|
|
8171
|
+
return inserted.filter((hash) => existing.has(hash));
|
|
7282
8172
|
});
|
|
8173
|
+
} catch (err) {
|
|
8174
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
8175
|
+
throw new Error("Offers.create failed. Ensure obligations and groups exist before inserting offers.", { cause: error });
|
|
7283
8176
|
}
|
|
7284
|
-
return await db.transaction(async (dbTx) => {
|
|
7285
|
-
const obligationsRows = Array.from(obligationsMap.entries()).map(([obligationId, obligation]) => ({
|
|
7286
|
-
obligationId,
|
|
7287
|
-
chainId: obligation.chainId,
|
|
7288
|
-
loanToken: obligation.loanToken.toLowerCase(),
|
|
7289
|
-
maturity: obligation.maturity
|
|
7290
|
-
}));
|
|
7291
|
-
for (const batch of batch$1(obligationsRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(obligations).values(batch).onConflictDoNothing();
|
|
7292
|
-
const oraclesRows = Array.from(oraclesMap.values()).map((oracle) => ({
|
|
7293
|
-
chainId: oracle.chainId,
|
|
7294
|
-
address: oracle.address.toLowerCase(),
|
|
7295
|
-
blockNumber: oracle.blockNumber
|
|
7296
|
-
}));
|
|
7297
|
-
for (const batch of batch$1(oraclesRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(oracles).values(batch).onConflictDoNothing();
|
|
7298
|
-
const collateralsRows = Array.from(collateralsMap.entries()).flatMap(([obligationId, items]) => items.collaterals.map((collateral) => ({
|
|
7299
|
-
obligationId,
|
|
7300
|
-
asset: collateral.asset.toLowerCase(),
|
|
7301
|
-
oracleChainId: obligationsMap.get(obligationId).chainId,
|
|
7302
|
-
oracleAddress: collateral.oracle.toLowerCase(),
|
|
7303
|
-
lltv: collateral.lltv,
|
|
7304
|
-
blockNumber: items.blockNumber
|
|
7305
|
-
})));
|
|
7306
|
-
for (const batch of batch$1(collateralsRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(obligationCollateralsV2).values(batch).onConflictDoNothing();
|
|
7307
|
-
const groupsRows = Array.from(groupsMap.values()).map((group) => ({
|
|
7308
|
-
chainId: group.chainId,
|
|
7309
|
-
maker: group.maker.toLowerCase(),
|
|
7310
|
-
group: group.group.toLowerCase(),
|
|
7311
|
-
consumed: "0",
|
|
7312
|
-
blockNumber: group.blockNumber
|
|
7313
|
-
}));
|
|
7314
|
-
for (const batch of batch$1(groupsRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(groups).values(batch).onConflictDoNothing();
|
|
7315
|
-
const offersRows = offersWithBlock.map(({ offer, blockNumber }) => ({
|
|
7316
|
-
...serialize(offer),
|
|
7317
|
-
obligationId: obligationId(offer),
|
|
7318
|
-
groupChainId: offer.chainId,
|
|
7319
|
-
groupMaker: offer.maker.toLowerCase(),
|
|
7320
|
-
callbackAddress: offer.callback.address.toLowerCase(),
|
|
7321
|
-
callbackData: offer.callback.data,
|
|
7322
|
-
blockNumber
|
|
7323
|
-
}));
|
|
7324
|
-
const inserted = [];
|
|
7325
|
-
for (const batch of batch$1(offersRows, DEFAULT_BATCH_SIZE)) {
|
|
7326
|
-
const result = await dbTx.insert(offers).values(batch).onConflictDoNothing().returning();
|
|
7327
|
-
inserted.push(...result);
|
|
7328
|
-
}
|
|
7329
|
-
if (inserted.length === 0) return [];
|
|
7330
|
-
const idCached = /* @__PURE__ */ new Map();
|
|
7331
|
-
const id = (params) => {
|
|
7332
|
-
const preimage = `0x${params.chainId}${params.contract}${params.user}${params.amount}`.toLowerCase();
|
|
7333
|
-
const id = idCached.get(preimage) ?? keccak256(preimage);
|
|
7334
|
-
idCached.set(preimage, id);
|
|
7335
|
-
return id;
|
|
7336
|
-
};
|
|
7337
|
-
const offersCallbacksMap = /* @__PURE__ */ new Map();
|
|
7338
|
-
for (const offer of inserted) {
|
|
7339
|
-
if (offer.callbackData === "0x") continue;
|
|
7340
|
-
const user = offer.groupMaker.toLowerCase();
|
|
7341
|
-
if (!offersCallbacksMap.has(offer.hash)) offersCallbacksMap.set(offer.hash, []);
|
|
7342
|
-
const chain = chainRegistry.getById(offer.groupChainId);
|
|
7343
|
-
if (!chain) continue;
|
|
7344
|
-
const callbackType = getCallbackType(chain.name, offer.callbackAddress);
|
|
7345
|
-
if (!callbackType) continue;
|
|
7346
|
-
const callbacks = decode$1(callbackType, offer.callbackData).map((callback) => ({
|
|
7347
|
-
chainId: offer.groupChainId,
|
|
7348
|
-
contract: callback.contract.toLowerCase(),
|
|
7349
|
-
user: user.toLowerCase(),
|
|
7350
|
-
amount: callback.amount.toString(),
|
|
7351
|
-
type: callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20,
|
|
7352
|
-
asset: callbackType === Type$1.BuyVaultV1Callback ? void 0 : callback.contract.toLowerCase(),
|
|
7353
|
-
blockNumber: offer.blockNumber
|
|
7354
|
-
}));
|
|
7355
|
-
try {
|
|
7356
|
-
await dbTx.insert(offersCallbacks).values(callbacks.map((callback) => ({
|
|
7357
|
-
offerHash: offer.hash,
|
|
7358
|
-
callbackId: id(callback)
|
|
7359
|
-
}))).onConflictDoNothing();
|
|
7360
|
-
offersCallbacksMap.get(offer.hash).push(...callbacks);
|
|
7361
|
-
} catch (_) {
|
|
7362
|
-
offersCallbacksMap.delete(offer.hash);
|
|
7363
|
-
}
|
|
7364
|
-
}
|
|
7365
|
-
if (offersCallbacksMap.size === 0) {
|
|
7366
|
-
obligationsMap.clear();
|
|
7367
|
-
collateralsMap.clear();
|
|
7368
|
-
oraclesMap.clear();
|
|
7369
|
-
groupsMap.clear();
|
|
7370
|
-
offersCallbacksMap.clear();
|
|
7371
|
-
idCached.clear();
|
|
7372
|
-
return inserted.map((offer) => offer.hash);
|
|
7373
|
-
}
|
|
7374
|
-
await dbTx.positions.upsert(Array.from(offersCallbacksMap.values()).flatMap((callbacks) => callbacks.map((callback) => ({
|
|
7375
|
-
chainId: callback.chainId,
|
|
7376
|
-
contract: callback.contract,
|
|
7377
|
-
user: callback.user,
|
|
7378
|
-
type: callback.type,
|
|
7379
|
-
asset: callback.asset,
|
|
7380
|
-
blockNumber: callback.blockNumber
|
|
7381
|
-
}))));
|
|
7382
|
-
const callbacksRows = Array.from(offersCallbacksMap.values()).flatMap((callbacks) => callbacks.map((callback) => ({
|
|
7383
|
-
id: id(callback),
|
|
7384
|
-
positionChainId: callback.chainId,
|
|
7385
|
-
positionContract: callback.contract,
|
|
7386
|
-
positionUser: callback.user,
|
|
7387
|
-
amount: callback.amount
|
|
7388
|
-
})));
|
|
7389
|
-
for (const batch of batch$1(callbacksRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(callbacks).values(batch).onConflictDoNothing();
|
|
7390
|
-
const lotInfos = [];
|
|
7391
|
-
for (const [offerHash, callbacks] of offersCallbacksMap.entries()) {
|
|
7392
|
-
const offer = inserted.find((o) => o.hash === offerHash);
|
|
7393
|
-
if (!offer) continue;
|
|
7394
|
-
for (const callback of callbacks) {
|
|
7395
|
-
const isLoanPosition = obligationsMap.get(offer.obligationId)?.loanToken.toLowerCase() === callback.asset?.toLowerCase();
|
|
7396
|
-
lotInfos.push({
|
|
7397
|
-
positionChainId: callback.chainId,
|
|
7398
|
-
positionContract: callback.contract,
|
|
7399
|
-
positionUser: callback.user,
|
|
7400
|
-
group: offer.group,
|
|
7401
|
-
size: isLoanPosition ? BigInt(offer.assets) : BigInt(callback.amount)
|
|
7402
|
-
});
|
|
7403
|
-
}
|
|
7404
|
-
}
|
|
7405
|
-
if (lotInfos.length > 0) await dbTx.lots.create(lotInfos);
|
|
7406
|
-
obligationsMap.clear();
|
|
7407
|
-
collateralsMap.clear();
|
|
7408
|
-
oraclesMap.clear();
|
|
7409
|
-
groupsMap.clear();
|
|
7410
|
-
offersCallbacksMap.clear();
|
|
7411
|
-
idCached.clear();
|
|
7412
|
-
return inserted.map((offer) => offer.hash);
|
|
7413
|
-
});
|
|
7414
8177
|
},
|
|
7415
8178
|
get: async (parameters) => {
|
|
7416
8179
|
const limit = parameters?.limit ?? DEFAULT_LIMIT$2;
|
|
@@ -7430,36 +8193,12 @@ function create$8(config) {
|
|
|
7430
8193
|
'[]'::jsonb
|
|
7431
8194
|
)`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles, sql`${obligationCollateralsV2.oracleChainId} = ${oracles.chainId}
|
|
7432
8195
|
AND ${obligationCollateralsV2.oracleAddress} = ${oracles.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
|
|
7433
|
-
const availableLateral = db.select({ available: sql`COALESCE(SUM(
|
|
7434
|
-
CASE
|
|
7435
|
-
-- If asset is null, position available is 0
|
|
7436
|
-
WHEN ${positions.asset} IS NULL THEN 0
|
|
7437
|
-
|
|
7438
|
-
-- Position asset matches loan token: no conversion needed
|
|
7439
|
-
WHEN ${positions.asset} = ${obligations.loanToken} THEN
|
|
7440
|
-
CASE
|
|
7441
|
-
WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
|
|
7442
|
-
ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
|
|
7443
|
-
END
|
|
7444
|
-
|
|
7445
|
-
-- Position asset is collateral: apply oracle price * lltv
|
|
7446
|
-
-- Formula: balance * price / 1e36 * lltv / 1e18
|
|
7447
|
-
ELSE
|
|
7448
|
-
(CASE
|
|
7449
|
-
WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
|
|
7450
|
-
ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
|
|
7451
|
-
END)
|
|
7452
|
-
* COALESCE(${oracles.price}, 0)::numeric / 1e36
|
|
7453
|
-
* COALESCE(${obligationCollateralsV2.lltv}, 0)::numeric / 1e18
|
|
7454
|
-
END
|
|
7455
|
-
), 0)`.as("available") }).from(offersCallbacks).innerJoin(callbacks, eq(offersCallbacks.callbackId, callbacks.id)).innerJoin(positions, and(eq(callbacks.positionChainId, positions.chainId), eq(callbacks.positionContract, positions.contract), eq(callbacks.positionUser, positions.user))).leftJoin(obligationCollateralsV2, and(eq(obligationCollateralsV2.obligationId, offers.obligationId), eq(obligationCollateralsV2.asset, positions.asset))).leftJoin(oracles, and(eq(oracles.chainId, obligationCollateralsV2.oracleChainId), eq(oracles.address, obligationCollateralsV2.oracleAddress))).where(eq(offersCallbacks.offerHash, offers.hash)).as("available_lateral");
|
|
7456
8196
|
const rows = (await db.select({
|
|
7457
8197
|
hash: offers.hash,
|
|
7458
8198
|
maker: offers.groupMaker,
|
|
7459
8199
|
assets: offers.assets,
|
|
7460
8200
|
obligationUnits: offers.obligationUnits,
|
|
7461
8201
|
obligationShares: offers.obligationShares,
|
|
7462
|
-
consumed: groups.consumed,
|
|
7463
8202
|
price: offers.price,
|
|
7464
8203
|
maturity: offers.maturity,
|
|
7465
8204
|
expiry: offers.expiry,
|
|
@@ -7472,19 +8211,8 @@ function create$8(config) {
|
|
|
7472
8211
|
callbackAddress: offers.callbackAddress,
|
|
7473
8212
|
callbackData: offers.callbackData,
|
|
7474
8213
|
collaterals: collateralsLateral.collaterals,
|
|
7475
|
-
blockNumber: offers.blockNumber
|
|
7476
|
-
|
|
7477
|
-
takeable: sql`FLOOR(GREATEST(
|
|
7478
|
-
0,
|
|
7479
|
-
LEAST(
|
|
7480
|
-
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
7481
|
-
COALESCE(${availableLateral.available}::numeric, 0)
|
|
7482
|
-
)
|
|
7483
|
-
))`.as("takeable")
|
|
7484
|
-
}).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).leftJoinLateral(availableLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0, maker === void 0 ? sql`GREATEST(0, LEAST(
|
|
7485
|
-
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
7486
|
-
COALESCE(${availableLateral.available}::numeric, 0)
|
|
7487
|
-
)) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
|
|
8214
|
+
blockNumber: offers.blockNumber
|
|
8215
|
+
}).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoinLateral(collateralsLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
|
|
7488
8216
|
return {
|
|
7489
8217
|
hash: row.hash,
|
|
7490
8218
|
maker: row.maker,
|
|
@@ -7509,13 +8237,9 @@ function create$8(config) {
|
|
|
7509
8237
|
address: row.callbackAddress,
|
|
7510
8238
|
data: row.callbackData
|
|
7511
8239
|
},
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
},
|
|
7516
|
-
consumed: BigInt(row.consumed),
|
|
7517
|
-
available: BigInt(String(row.available ?? "0").split(".")[0] ?? "0"),
|
|
7518
|
-
takeable: BigInt(String(row.takeable ?? "0").split(".")[0] ?? "0"),
|
|
8240
|
+
consumed: 0n,
|
|
8241
|
+
available: 0n,
|
|
8242
|
+
takeable: 0n,
|
|
7519
8243
|
blockNumber: row.blockNumber
|
|
7520
8244
|
};
|
|
7521
8245
|
});
|
|
@@ -7597,7 +8321,7 @@ function create$8(config) {
|
|
|
7597
8321
|
quote.bid = { price: BigInt(row.price) };
|
|
7598
8322
|
}
|
|
7599
8323
|
return Array.from(quotes.entries()).map(([id, quote]) => {
|
|
7600
|
-
return from$
|
|
8324
|
+
return from$9({
|
|
7601
8325
|
obligationId: id,
|
|
7602
8326
|
ask: quote.ask,
|
|
7603
8327
|
bid: quote.bid
|
|
@@ -7639,27 +8363,30 @@ function create$6(db) {
|
|
|
7639
8363
|
price: oracles.price,
|
|
7640
8364
|
blockNumber: oracles.blockNumber,
|
|
7641
8365
|
chainId: oracles.chainId
|
|
7642
|
-
}).from(oracles).where(eq(oracles.chainId, chainId))).map((r) => from$
|
|
8366
|
+
}).from(oracles).where(eq(oracles.chainId, chainId))).map((r) => from$11({
|
|
7643
8367
|
chainId: r.chainId,
|
|
7644
8368
|
address: r.address,
|
|
7645
8369
|
price: r.price,
|
|
7646
8370
|
blockNumber: r.blockNumber
|
|
7647
8371
|
}));
|
|
7648
8372
|
},
|
|
7649
|
-
upsert: async (oracles$
|
|
7650
|
-
if (oracles$
|
|
7651
|
-
const rows = oracles$
|
|
8373
|
+
upsert: async (oracles$2) => {
|
|
8374
|
+
if (oracles$2.length === 0) return;
|
|
8375
|
+
const rows = oracles$2.map((o) => ({
|
|
7652
8376
|
chainId: o.chainId,
|
|
7653
8377
|
address: o.address.toLowerCase(),
|
|
7654
8378
|
price: o.price !== null ? o.price.toString() : null,
|
|
7655
8379
|
blockNumber: o.blockNumber
|
|
7656
8380
|
}));
|
|
7657
|
-
db.transaction(async (dbTx) => {
|
|
8381
|
+
await db.transaction(async (dbTx) => {
|
|
7658
8382
|
for (const batch of batch$1(rows, DEFAULT_BATCH_SIZE)) await dbTx.insert(oracles).values(batch).onConflictDoUpdate({
|
|
7659
8383
|
target: [oracles.chainId, oracles.address],
|
|
7660
8384
|
set: {
|
|
7661
|
-
price: sql`EXCLUDED.price`,
|
|
7662
|
-
blockNumber: sql`
|
|
8385
|
+
price: sql`COALESCE(EXCLUDED.price, ${oracles.price})`,
|
|
8386
|
+
blockNumber: sql`CASE
|
|
8387
|
+
WHEN EXCLUDED.price IS NULL THEN ${oracles.blockNumber}
|
|
8388
|
+
ELSE EXCLUDED.block_number
|
|
8389
|
+
END`,
|
|
7663
8390
|
updatedAt: sql`NOW()`
|
|
7664
8391
|
}
|
|
7665
8392
|
});
|
|
@@ -8005,41 +8732,42 @@ function create$3(config) {
|
|
|
8005
8732
|
return {
|
|
8006
8733
|
create: async (trees$1) => {
|
|
8007
8734
|
if (trees$1.length === 0) return [];
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
const
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8735
|
+
try {
|
|
8736
|
+
return await db.transaction(async (dbTx) => {
|
|
8737
|
+
const roots = [];
|
|
8738
|
+
for (const { tree, signature } of trees$1) {
|
|
8739
|
+
const root = tree.root.toLowerCase();
|
|
8740
|
+
roots.push(root);
|
|
8741
|
+
await dbTx.insert(trees).values({
|
|
8742
|
+
root,
|
|
8743
|
+
rootSignature: signature.toLowerCase()
|
|
8744
|
+
}).onConflictDoUpdate({
|
|
8745
|
+
target: [trees.root],
|
|
8746
|
+
set: {
|
|
8747
|
+
rootSignature: signature.toLowerCase(),
|
|
8748
|
+
createdAt: sql`NOW()`
|
|
8749
|
+
}
|
|
8750
|
+
});
|
|
8751
|
+
const pathRows = proofs(tree).map((proof) => ({
|
|
8752
|
+
offerHash: hash(proof.offer).toLowerCase(),
|
|
8753
|
+
treeRoot: root,
|
|
8754
|
+
proofNodes: concatenateProofs(proof.path)
|
|
8755
|
+
}));
|
|
8756
|
+
for (const batch of batch$1(pathRows, DEFAULT_BATCH_SIZE)) await dbTx.insert(merklePaths).values(batch).onConflictDoUpdate({
|
|
8757
|
+
target: [merklePaths.offerHash],
|
|
8758
|
+
set: {
|
|
8759
|
+
treeRoot: sql`excluded.tree_root`,
|
|
8760
|
+
proofNodes: sql`excluded.proof_nodes`,
|
|
8761
|
+
createdAt: sql`NOW()`
|
|
8762
|
+
}
|
|
8763
|
+
});
|
|
8764
|
+
}
|
|
8765
|
+
return roots;
|
|
8766
|
+
});
|
|
8767
|
+
} catch (err) {
|
|
8768
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
8769
|
+
throw new Error("Trees.create failed. Ensure offers exist before inserting merkle paths.", { cause: error });
|
|
8770
|
+
}
|
|
8043
8771
|
},
|
|
8044
8772
|
getAttestations: async (hashes) => {
|
|
8045
8773
|
if (hashes.length === 0) return /* @__PURE__ */ new Map();
|
|
@@ -8144,17 +8872,17 @@ function create$2(db) {
|
|
|
8144
8872
|
//#region src/database/Database.ts
|
|
8145
8873
|
function createDomains(core, chainRegistry) {
|
|
8146
8874
|
return {
|
|
8147
|
-
book: create$
|
|
8148
|
-
blocks: create$
|
|
8149
|
-
db: core,
|
|
8150
|
-
chainRegistry
|
|
8151
|
-
}),
|
|
8152
|
-
offers: create$8({
|
|
8875
|
+
book: create$14({ db: core }),
|
|
8876
|
+
blocks: create$15({
|
|
8153
8877
|
db: core,
|
|
8154
8878
|
chainRegistry
|
|
8155
8879
|
}),
|
|
8156
|
-
|
|
8157
|
-
|
|
8880
|
+
callbacks: create$13(core),
|
|
8881
|
+
offers: create$8({ db: core }),
|
|
8882
|
+
consumed: create$12(core),
|
|
8883
|
+
groups: create$11(core),
|
|
8884
|
+
lots: create$10(core),
|
|
8885
|
+
obligations: create$9(core),
|
|
8158
8886
|
offsets: create$7(core),
|
|
8159
8887
|
oracles: create$6(core),
|
|
8160
8888
|
trees: create$3({ db: core }),
|
|
@@ -8183,6 +8911,10 @@ function augmentWithDomains(base, chainRegistry) {
|
|
|
8183
8911
|
value: dms.blocks,
|
|
8184
8912
|
enumerable: true
|
|
8185
8913
|
},
|
|
8914
|
+
callbacks: {
|
|
8915
|
+
value: dms.callbacks,
|
|
8916
|
+
enumerable: true
|
|
8917
|
+
},
|
|
8186
8918
|
offers: {
|
|
8187
8919
|
value: dms.offers,
|
|
8188
8920
|
enumerable: true
|
|
@@ -8191,10 +8923,18 @@ function augmentWithDomains(base, chainRegistry) {
|
|
|
8191
8923
|
value: dms.consumed,
|
|
8192
8924
|
enumerable: true
|
|
8193
8925
|
},
|
|
8926
|
+
groups: {
|
|
8927
|
+
value: dms.groups,
|
|
8928
|
+
enumerable: true
|
|
8929
|
+
},
|
|
8194
8930
|
lots: {
|
|
8195
8931
|
value: dms.lots,
|
|
8196
8932
|
enumerable: true
|
|
8197
8933
|
},
|
|
8934
|
+
obligations: {
|
|
8935
|
+
value: dms.obligations,
|
|
8936
|
+
enumerable: true
|
|
8937
|
+
},
|
|
8198
8938
|
offsets: {
|
|
8199
8939
|
value: dms.offsets,
|
|
8200
8940
|
enumerable: true
|
|
@@ -8636,6 +9376,8 @@ const ApiSchema = z.object({ port: z.number().int().positive() }).strict();
|
|
|
8636
9376
|
const GatekeeperSchema = z.object({
|
|
8637
9377
|
url_env: z.string().min(1).optional(),
|
|
8638
9378
|
url: z.string().min(1).optional(),
|
|
9379
|
+
origin_secret_env: z.string().min(1).optional(),
|
|
9380
|
+
origin_secret: z.string().min(1).optional(),
|
|
8639
9381
|
timeout_ms: z.number().int().positive().optional(),
|
|
8640
9382
|
port: z.number().int().positive().optional()
|
|
8641
9383
|
}).strict().optional();
|
|
@@ -8701,6 +9443,7 @@ function resolveRouterConfig(config, env) {
|
|
|
8701
9443
|
api: config.api ? { port: config.api.port } : void 0,
|
|
8702
9444
|
gatekeeper: {
|
|
8703
9445
|
url: resolveGatekeeperUrl(config.gatekeeper, env),
|
|
9446
|
+
originSecret: resolveGatekeeperOriginSecret(config.gatekeeper, env),
|
|
8704
9447
|
timeoutMs: config.gatekeeper?.timeout_ms ?? 1e4,
|
|
8705
9448
|
port: config.gatekeeper?.port ?? 8082
|
|
8706
9449
|
},
|
|
@@ -8811,6 +9554,7 @@ function createDefaultConfig() {
|
|
|
8811
9554
|
api: { port: 8081 },
|
|
8812
9555
|
gatekeeper: {
|
|
8813
9556
|
url: void 0,
|
|
9557
|
+
originSecret: void 0,
|
|
8814
9558
|
timeoutMs: 1e4,
|
|
8815
9559
|
port: 8082
|
|
8816
9560
|
},
|
|
@@ -8829,6 +9573,11 @@ function resolveGatekeeperUrl(gatekeeper, env) {
|
|
|
8829
9573
|
if (gatekeeper.url_env) return readEnvVar(env, gatekeeper.url_env, "Gatekeeper URL");
|
|
8830
9574
|
return gatekeeper.url;
|
|
8831
9575
|
}
|
|
9576
|
+
function resolveGatekeeperOriginSecret(gatekeeper, env) {
|
|
9577
|
+
if (!gatekeeper) return void 0;
|
|
9578
|
+
if (gatekeeper.origin_secret_env) return readEnvVar(env, gatekeeper.origin_secret_env, "Gatekeeper origin secret");
|
|
9579
|
+
return gatekeeper.origin_secret;
|
|
9580
|
+
}
|
|
8832
9581
|
|
|
8833
9582
|
//#endregion
|
|
8834
9583
|
//#region src/cli/commands/RouterCmd.ts
|
|
@@ -8851,7 +9600,7 @@ var RouterCmd = class RouterCmd extends Command {
|
|
|
8851
9600
|
const configPath = resolveConfigPath(options.configFile);
|
|
8852
9601
|
const config = configPath !== null ? loadRouterConfig(configPath) : createDefaultConfig();
|
|
8853
9602
|
const logger = defaultLogger(config.logging.level, config.logging.pretty);
|
|
8854
|
-
const chainRegistry = create$
|
|
9603
|
+
const chainRegistry = create$19(Object.values(config.chains).map((entry) => entry.chain));
|
|
8855
9604
|
const clients = (config.indexer?.chains ?? []).map((name) => {
|
|
8856
9605
|
const chainConfig = config.chains[name];
|
|
8857
9606
|
if (!chainConfig) throw new Error(`Indexer chain ${name} is not defined under [chains].`);
|
|
@@ -8899,9 +9648,10 @@ const gatekeeperCmd = new RouterCmd("gatekeeper");
|
|
|
8899
9648
|
gatekeeperCmd.description("Start Gatekeeper validation service.").action(async (opts) => {
|
|
8900
9649
|
const { gatekeeper: gatekeeperConfig, chainRegistry, logger } = opts;
|
|
8901
9650
|
await runWithLogger(logger, async () => {
|
|
8902
|
-
const gatekeeperCore = create$
|
|
9651
|
+
const gatekeeperCore = create$20({ rules: morphoRules(chainRegistry.list()) });
|
|
8903
9652
|
const handle = await start$1({
|
|
8904
9653
|
gatekeeper: gatekeeperCore,
|
|
9654
|
+
chainRegistry,
|
|
8905
9655
|
port: gatekeeperConfig?.port ?? 8082
|
|
8906
9656
|
});
|
|
8907
9657
|
logger.info({
|
|
@@ -8954,21 +9704,97 @@ async function getBook(params, db) {
|
|
|
8954
9704
|
}
|
|
8955
9705
|
|
|
8956
9706
|
//#endregion
|
|
8957
|
-
//#region src/api/Controllers/
|
|
9707
|
+
//#region src/api/Controllers/getConfigContracts.ts
|
|
8958
9708
|
/**
|
|
8959
|
-
* Returns
|
|
9709
|
+
* Returns contract addresses used by indexers (mempool, v2) plus multicall per chain.
|
|
9710
|
+
* @param query - Raw query parameters containing optional chain filters.
|
|
8960
9711
|
* @param chainRegistry - The chain registry instance. {@link ChainRegistry.ChainRegistry}
|
|
8961
|
-
* @returns The
|
|
9712
|
+
* @returns The indexer contract configuration. {@link ApiPayload.Payload<ConfigContract[]>}
|
|
8962
9713
|
*/
|
|
8963
|
-
async function
|
|
8964
|
-
const
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
8968
|
-
|
|
9714
|
+
async function getConfigContracts(query, chainRegistry) {
|
|
9715
|
+
const parsed = safeParse("get_config_contracts", query ?? {});
|
|
9716
|
+
if (!parsed.success) return failure(parsed.error);
|
|
9717
|
+
const { chains: chainsFilter, cursor, limit } = parsed.data;
|
|
9718
|
+
const chainFilter = chainsFilter?.length ? new Set(chainsFilter) : null;
|
|
9719
|
+
const contracts = [];
|
|
9720
|
+
const seenAddresses = /* @__PURE__ */ new Set();
|
|
9721
|
+
for (const chain of chainRegistry.list()) {
|
|
9722
|
+
if (chainFilter && !chainFilter.has(chain.id)) continue;
|
|
9723
|
+
const mempool = chain.custom?.mempool?.address;
|
|
9724
|
+
if (!mempool) return failure(new InternalServerError(`Missing mempool address for chain ${chain.id}.`));
|
|
9725
|
+
const multicall = chain.contracts?.multicall3?.address;
|
|
9726
|
+
if (!multicall) return failure(new InternalServerError(`Missing multicall3 address for chain ${chain.id}.`));
|
|
9727
|
+
const v2 = chain.custom?.morpho?.address;
|
|
9728
|
+
if (!v2) return failure(new InternalServerError(`Missing morpho address for chain ${chain.id}.`));
|
|
9729
|
+
const chainContracts = [
|
|
9730
|
+
{
|
|
9731
|
+
chain_id: chain.id,
|
|
9732
|
+
name: "mempool",
|
|
9733
|
+
address: mempool
|
|
9734
|
+
},
|
|
9735
|
+
{
|
|
9736
|
+
chain_id: chain.id,
|
|
9737
|
+
name: "multicall",
|
|
9738
|
+
address: multicall
|
|
9739
|
+
},
|
|
9740
|
+
{
|
|
9741
|
+
chain_id: chain.id,
|
|
9742
|
+
name: "v2",
|
|
9743
|
+
address: v2
|
|
9744
|
+
}
|
|
9745
|
+
];
|
|
9746
|
+
for (const contract of chainContracts) {
|
|
9747
|
+
const cursorKey = `${contract.chain_id}:${contract.address.toLowerCase()}`;
|
|
9748
|
+
if (seenAddresses.has(cursorKey)) return failure(new InternalServerError(`Duplicate contract address ${contract.address} for chain ${chain.id}.`));
|
|
9749
|
+
seenAddresses.add(cursorKey);
|
|
9750
|
+
contracts.push(contract);
|
|
9751
|
+
}
|
|
9752
|
+
}
|
|
9753
|
+
contracts.sort((a, b) => {
|
|
9754
|
+
if (a.chain_id !== b.chain_id) return a.chain_id - b.chain_id;
|
|
9755
|
+
const addressCompare = a.address.toLowerCase().localeCompare(b.address.toLowerCase());
|
|
9756
|
+
if (addressCompare !== 0) return addressCompare;
|
|
9757
|
+
return a.name.localeCompare(b.name);
|
|
8969
9758
|
});
|
|
8970
|
-
|
|
8971
|
-
|
|
9759
|
+
let cursorContract = null;
|
|
9760
|
+
if (cursor) try {
|
|
9761
|
+
cursorContract = parseCursor(cursor);
|
|
9762
|
+
} catch (err) {
|
|
9763
|
+
return failure(err);
|
|
9764
|
+
}
|
|
9765
|
+
const startIndex = cursorContract ? findStartIndex(contracts, cursorContract) : 0;
|
|
9766
|
+
const page = contracts.slice(startIndex, startIndex + limit);
|
|
9767
|
+
const nextCursor = startIndex + limit < contracts.length && page.length > 0 ? formatCursor(page.at(-1)) : null;
|
|
9768
|
+
return success({
|
|
9769
|
+
data: page,
|
|
9770
|
+
cursor: nextCursor
|
|
9771
|
+
});
|
|
9772
|
+
}
|
|
9773
|
+
function parseCursor(cursor) {
|
|
9774
|
+
const [chain, address] = cursor.split(":", 2);
|
|
9775
|
+
if (!chain || !address) throw new BadRequestError("Cursor must be in the format chain_id:0x...");
|
|
9776
|
+
return {
|
|
9777
|
+
chain_id: Number.parseInt(chain, 10),
|
|
9778
|
+
address: address.toLowerCase()
|
|
9779
|
+
};
|
|
9780
|
+
}
|
|
9781
|
+
function formatCursor(contract) {
|
|
9782
|
+
return `${contract.chain_id}:${contract.address.toLowerCase()}`;
|
|
9783
|
+
}
|
|
9784
|
+
function findStartIndex(contracts, cursor) {
|
|
9785
|
+
let low = 0;
|
|
9786
|
+
let high = contracts.length;
|
|
9787
|
+
while (low < high) {
|
|
9788
|
+
const mid = Math.floor((low + high) / 2);
|
|
9789
|
+
const current = contracts[mid];
|
|
9790
|
+
if (compareContract(current, cursor) <= 0) low = mid + 1;
|
|
9791
|
+
else high = mid;
|
|
9792
|
+
}
|
|
9793
|
+
return low;
|
|
9794
|
+
}
|
|
9795
|
+
function compareContract(contract, cursor) {
|
|
9796
|
+
if (contract.chain_id !== cursor.chain_id) return contract.chain_id - cursor.chain_id;
|
|
9797
|
+
return contract.address.toLowerCase().localeCompare(cursor.address.toLowerCase());
|
|
8972
9798
|
}
|
|
8973
9799
|
|
|
8974
9800
|
//#endregion
|
|
@@ -8980,28 +9806,19 @@ const __dirname = (() => {
|
|
|
8980
9806
|
return process.cwd();
|
|
8981
9807
|
}
|
|
8982
9808
|
})();
|
|
8983
|
-
const parse_error_Description = {
|
|
8984
|
-
name: "parse_error",
|
|
8985
|
-
description: "Returns when an offer fails to parse due to invalid format or missing required fields"
|
|
8986
|
-
};
|
|
8987
|
-
const getGatekeeperRules = async (gatekeeper) => {
|
|
8988
|
-
return [parse_error_Description, ...await gatekeeper.getRules()];
|
|
8989
|
-
};
|
|
8990
9809
|
/**
|
|
8991
9810
|
* Build the OpenAPI document for the router.
|
|
8992
|
-
* @param parameters - Includes a {@link RulesProvider} to fetch gatekeeper rules.
|
|
8993
9811
|
* @returns OpenAPI document. {@link OpenAPIDocument}
|
|
8994
9812
|
*/
|
|
8995
|
-
async function getSwaggerJson(
|
|
8996
|
-
return OpenApi(
|
|
9813
|
+
async function getSwaggerJson() {
|
|
9814
|
+
return OpenApi();
|
|
8997
9815
|
}
|
|
8998
9816
|
/**
|
|
8999
9817
|
* Render the API documentation HTML page.
|
|
9000
|
-
* @param parameters - Includes a {@link RulesProvider} to fetch gatekeeper rules.
|
|
9001
9818
|
* @returns HTML page as string.
|
|
9002
9819
|
*/
|
|
9003
|
-
async function getDocsHtml(
|
|
9004
|
-
const spec = await OpenApi(
|
|
9820
|
+
async function getDocsHtml() {
|
|
9821
|
+
const spec = await OpenApi();
|
|
9005
9822
|
return `<!DOCTYPE html>
|
|
9006
9823
|
<html>
|
|
9007
9824
|
<head>
|
|
@@ -9347,13 +10164,127 @@ async function getObligations(queryParameters, db) {
|
|
|
9347
10164
|
|
|
9348
10165
|
//#endregion
|
|
9349
10166
|
//#region src/api/Controllers/getOffers.ts
|
|
10167
|
+
/**
|
|
10168
|
+
* Query offers with computed consumed/available/takeable values.
|
|
10169
|
+
* @param db - The database client. {@link Database.Core}
|
|
10170
|
+
* @param parameters - {@link GetOffersQueryParams}
|
|
10171
|
+
* @returns The offers with pagination cursor.
|
|
10172
|
+
*/
|
|
10173
|
+
async function getOffersQuery(db, parameters) {
|
|
10174
|
+
const limit = parameters?.limit ?? DEFAULT_LIMIT$2;
|
|
10175
|
+
const cursor = parameters?.cursor;
|
|
10176
|
+
const maker = parameters?.maker;
|
|
10177
|
+
if (cursor !== null && cursor !== void 0) {
|
|
10178
|
+
if (!cursor.startsWith("0x") || cursor.length !== 66) throw new Error("Invalid cursor format");
|
|
10179
|
+
}
|
|
10180
|
+
const collateralsLateral = db.select({ collaterals: sql`COALESCE(
|
|
10181
|
+
jsonb_agg(
|
|
10182
|
+
jsonb_build_object(
|
|
10183
|
+
'asset', ${obligationCollateralsV2.asset},
|
|
10184
|
+
'oracle', ${oracles.address},
|
|
10185
|
+
'lltv', ${obligationCollateralsV2.lltv}
|
|
10186
|
+
)
|
|
10187
|
+
),
|
|
10188
|
+
'[]'::jsonb
|
|
10189
|
+
)`.as("collaterals") }).from(obligationCollateralsV2).innerJoin(oracles, sql`${obligationCollateralsV2.oracleChainId} = ${oracles.chainId}
|
|
10190
|
+
AND ${obligationCollateralsV2.oracleAddress} = ${oracles.address}`).where(eq(obligationCollateralsV2.obligationId, offers.obligationId)).as("collaterals_lateral");
|
|
10191
|
+
const availableLateral = db.select({ available: sql`COALESCE(SUM(
|
|
10192
|
+
CASE
|
|
10193
|
+
-- If asset is null, position available is 0
|
|
10194
|
+
WHEN ${positions.asset} IS NULL THEN 0
|
|
10195
|
+
|
|
10196
|
+
-- Position asset matches loan token: no conversion needed
|
|
10197
|
+
WHEN ${positions.asset} = ${obligations.loanToken} THEN
|
|
10198
|
+
CASE
|
|
10199
|
+
WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
|
|
10200
|
+
ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
|
|
10201
|
+
END
|
|
10202
|
+
|
|
10203
|
+
-- Position asset is collateral: apply oracle price * lltv
|
|
10204
|
+
-- Formula: balance * price / 1e36 * lltv / 1e18
|
|
10205
|
+
ELSE
|
|
10206
|
+
(CASE
|
|
10207
|
+
WHEN ${callbacks.amount} IS NULL THEN COALESCE(${positions.balance}, 0)::numeric
|
|
10208
|
+
ELSE LEAST(${callbacks.amount}::numeric, COALESCE(${positions.balance}, 0)::numeric)
|
|
10209
|
+
END)
|
|
10210
|
+
* COALESCE(${oracles.price}, 0)::numeric / 1e36
|
|
10211
|
+
* COALESCE(${obligationCollateralsV2.lltv}, 0)::numeric / 1e18
|
|
10212
|
+
END
|
|
10213
|
+
), 0)`.as("available") }).from(offersCallbacks).innerJoin(callbacks, eq(offersCallbacks.callbackId, callbacks.id)).innerJoin(positions, and(eq(callbacks.positionChainId, positions.chainId), eq(callbacks.positionContract, positions.contract), eq(callbacks.positionUser, positions.user))).leftJoin(obligationCollateralsV2, and(eq(obligationCollateralsV2.obligationId, offers.obligationId), eq(obligationCollateralsV2.asset, positions.asset))).leftJoin(oracles, and(eq(oracles.chainId, obligationCollateralsV2.oracleChainId), eq(oracles.address, obligationCollateralsV2.oracleAddress))).where(eq(offersCallbacks.offerHash, offers.hash)).as("available_lateral");
|
|
10214
|
+
const rows = (await db.select({
|
|
10215
|
+
hash: offers.hash,
|
|
10216
|
+
maker: offers.groupMaker,
|
|
10217
|
+
assets: offers.assets,
|
|
10218
|
+
obligationUnits: offers.obligationUnits,
|
|
10219
|
+
obligationShares: offers.obligationShares,
|
|
10220
|
+
consumed: groups.consumed,
|
|
10221
|
+
price: offers.price,
|
|
10222
|
+
maturity: offers.maturity,
|
|
10223
|
+
expiry: offers.expiry,
|
|
10224
|
+
start: offers.start,
|
|
10225
|
+
group: offers.group,
|
|
10226
|
+
session: offers.session,
|
|
10227
|
+
buy: offers.buy,
|
|
10228
|
+
chainId: obligations.chainId,
|
|
10229
|
+
loanToken: obligations.loanToken,
|
|
10230
|
+
callbackAddress: offers.callbackAddress,
|
|
10231
|
+
callbackData: offers.callbackData,
|
|
10232
|
+
collaterals: collateralsLateral.collaterals,
|
|
10233
|
+
blockNumber: offers.blockNumber,
|
|
10234
|
+
available: sql`COALESCE(${availableLateral.available}::numeric, 0)`.as("available"),
|
|
10235
|
+
takeable: sql`FLOOR(GREATEST(
|
|
10236
|
+
0,
|
|
10237
|
+
LEAST(
|
|
10238
|
+
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
10239
|
+
COALESCE(${availableLateral.available}::numeric, 0)
|
|
10240
|
+
)
|
|
10241
|
+
))`.as("takeable")
|
|
10242
|
+
}).from(offers).innerJoin(obligations, eq(offers.obligationId, obligations.obligationId)).innerJoin(groups, and(eq(offers.groupChainId, groups.chainId), eq(offers.groupMaker, groups.maker), eq(offers.group, groups.group))).innerJoinLateral(collateralsLateral, sql`true`).leftJoinLateral(availableLateral, sql`true`).where(and(cursor !== null && cursor !== void 0 ? gt(offers.hash, cursor) : void 0, maker !== void 0 ? eq(offers.groupMaker, maker.toLowerCase()) : void 0, maker === void 0 ? sql`GREATEST(0, LEAST(
|
|
10243
|
+
${offers.assets}::numeric - ${groups.consumed}::numeric,
|
|
10244
|
+
COALESCE(${availableLateral.available}::numeric, 0)
|
|
10245
|
+
)) > 0` : void 0)).orderBy(asc(offers.hash)).limit(limit)).map((row) => {
|
|
10246
|
+
return {
|
|
10247
|
+
hash: row.hash,
|
|
10248
|
+
maker: row.maker,
|
|
10249
|
+
assets: BigInt(row.assets),
|
|
10250
|
+
obligationUnits: BigInt(row.obligationUnits),
|
|
10251
|
+
obligationShares: BigInt(row.obligationShares),
|
|
10252
|
+
price: BigInt(row.price),
|
|
10253
|
+
maturity: from$16(row.maturity),
|
|
10254
|
+
expiry: row.expiry,
|
|
10255
|
+
start: row.start,
|
|
10256
|
+
group: row.group,
|
|
10257
|
+
session: row.session,
|
|
10258
|
+
buy: row.buy,
|
|
10259
|
+
chainId: row.chainId,
|
|
10260
|
+
loanToken: row.loanToken,
|
|
10261
|
+
collaterals: row.collaterals.map((c) => ({
|
|
10262
|
+
asset: c.asset,
|
|
10263
|
+
oracle: c.oracle,
|
|
10264
|
+
lltv: BigInt(c.lltv)
|
|
10265
|
+
})).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
|
|
10266
|
+
callback: {
|
|
10267
|
+
address: row.callbackAddress,
|
|
10268
|
+
data: row.callbackData
|
|
10269
|
+
},
|
|
10270
|
+
consumed: BigInt(row.consumed),
|
|
10271
|
+
available: BigInt(String(row.available ?? "0").split(".")[0] ?? "0"),
|
|
10272
|
+
takeable: BigInt(String(row.takeable ?? "0").split(".")[0] ?? "0"),
|
|
10273
|
+
blockNumber: row.blockNumber
|
|
10274
|
+
};
|
|
10275
|
+
});
|
|
10276
|
+
return {
|
|
10277
|
+
rows,
|
|
10278
|
+
nextCursor: rows.length === limit ? rows[rows.length - 1].hash : null
|
|
10279
|
+
};
|
|
10280
|
+
}
|
|
9350
10281
|
async function getOffers(queryParameters, db) {
|
|
9351
10282
|
const logger = getLogger();
|
|
9352
10283
|
const result = safeParse("get_offers", queryParameters, (issue) => issue.message);
|
|
9353
10284
|
if (!result.success) return failure(result.error);
|
|
9354
10285
|
const query = result.data;
|
|
9355
10286
|
try {
|
|
9356
|
-
const { rows, nextCursor } = query.maker ? await db
|
|
10287
|
+
const { rows, nextCursor } = query.maker ? await getOffersQuery(db, {
|
|
9357
10288
|
maker: query.maker,
|
|
9358
10289
|
cursor: query.cursor,
|
|
9359
10290
|
limit: query.limit
|
|
@@ -9421,6 +10352,35 @@ async function getUserPositions(queryParameters, db) {
|
|
|
9421
10352
|
}
|
|
9422
10353
|
}
|
|
9423
10354
|
|
|
10355
|
+
//#endregion
|
|
10356
|
+
//#region src/api/Controllers/resolveCallbackTypes.ts
|
|
10357
|
+
/**
|
|
10358
|
+
* Resolve callback types for a list of callback addresses grouped by chain.
|
|
10359
|
+
* @param body - Request body with callback addresses. {@link CallbackTypesRequest}
|
|
10360
|
+
* @param chains - Chains to resolve callback types against. {@link Chain.Chain}
|
|
10361
|
+
* @returns Callback types grouped by chain. {@link CallbackTypesPayload}
|
|
10362
|
+
*/
|
|
10363
|
+
async function resolveCallbackTypes$1(body, chains) {
|
|
10364
|
+
const result = safeParse("callback_types", body, (issue) => issue.message);
|
|
10365
|
+
if (!result.success) return failure(result.error);
|
|
10366
|
+
const request = result.data;
|
|
10367
|
+
const chainIds = new Set(chains.map((chain) => chain.id));
|
|
10368
|
+
const unknown = request.callbacks.find((entry) => !chainIds.has(entry.chain_id));
|
|
10369
|
+
if (unknown) return failure(new BadRequestError(`Unknown chain id ${unknown.chain_id}`));
|
|
10370
|
+
try {
|
|
10371
|
+
const data = resolveCallbackTypes$2({
|
|
10372
|
+
chains,
|
|
10373
|
+
request
|
|
10374
|
+
});
|
|
10375
|
+
return success({
|
|
10376
|
+
data,
|
|
10377
|
+
cursor: null
|
|
10378
|
+
});
|
|
10379
|
+
} catch (err) {
|
|
10380
|
+
return failure(err);
|
|
10381
|
+
}
|
|
10382
|
+
}
|
|
10383
|
+
|
|
9424
10384
|
//#endregion
|
|
9425
10385
|
//#region src/api/Api.ts
|
|
9426
10386
|
function from(config) {
|
|
@@ -9494,6 +10454,21 @@ function serve$1(parameters) {
|
|
|
9494
10454
|
return c.json(failure$1.body, failure$1.statusCode);
|
|
9495
10455
|
}
|
|
9496
10456
|
});
|
|
10457
|
+
app.post("/v1/callbacks", async (c) => {
|
|
10458
|
+
let body;
|
|
10459
|
+
try {
|
|
10460
|
+
body = await c.req.json();
|
|
10461
|
+
} catch (err) {
|
|
10462
|
+
const failure$3 = failure(err);
|
|
10463
|
+
return c.json(failure$3.body, failure$3.statusCode);
|
|
10464
|
+
}
|
|
10465
|
+
if (body === null || typeof body !== "object") {
|
|
10466
|
+
const failure$2 = failure(new BadRequestError("Request body must be a JSON object"));
|
|
10467
|
+
return c.json(failure$2.body, failure$2.statusCode);
|
|
10468
|
+
}
|
|
10469
|
+
const { statusCode, body: responseBody } = await resolveCallbackTypes$1(body, chainRegistry.list());
|
|
10470
|
+
return c.json(responseBody, statusCode);
|
|
10471
|
+
});
|
|
9497
10472
|
app.get("/v1/users/:userAddress/positions", async (c) => {
|
|
9498
10473
|
const query = c.req.query();
|
|
9499
10474
|
const { statusCode, body } = await getUserPositions({
|
|
@@ -9515,12 +10490,21 @@ function serve$1(parameters) {
|
|
|
9515
10490
|
const { statusCode, body } = await getHealthChains(c.req.query(), db, void 0, chainRegistry);
|
|
9516
10491
|
return c.json(body, statusCode);
|
|
9517
10492
|
});
|
|
9518
|
-
app.get("/v1/config", async (c) => {
|
|
9519
|
-
const { statusCode, body } = await
|
|
10493
|
+
app.get("/v1/config/contracts", async (c) => {
|
|
10494
|
+
const { statusCode, body } = await getConfigContracts(c.req.query(), chainRegistry);
|
|
9520
10495
|
return c.json(body, statusCode);
|
|
9521
10496
|
});
|
|
9522
|
-
app.get("/
|
|
9523
|
-
|
|
10497
|
+
app.get("/v1/config/rules", async (c) => {
|
|
10498
|
+
try {
|
|
10499
|
+
const { statusCode, body } = await gatekeeper.getConfigRules(c.req.query());
|
|
10500
|
+
return c.json(body, statusCode);
|
|
10501
|
+
} catch (err) {
|
|
10502
|
+
const failure$4 = failure(err);
|
|
10503
|
+
return c.json(failure$4.body, failure$4.statusCode);
|
|
10504
|
+
}
|
|
10505
|
+
});
|
|
10506
|
+
app.get("/docs/openapi", async (c) => c.text(JSON.stringify(await getSwaggerJson()), 200, { "Content-Type": "application/json; charset=utf-8" }));
|
|
10507
|
+
app.get("/docs/api", async (c) => c.html(await getDocsHtml(), 200));
|
|
9524
10508
|
app.get("/docs", async (c) => c.html(await getIntegratorDocsHtml(), 200));
|
|
9525
10509
|
serve({
|
|
9526
10510
|
fetch: app.fetch,
|
|
@@ -9528,6 +10512,120 @@ function serve$1(parameters) {
|
|
|
9528
10512
|
});
|
|
9529
10513
|
}
|
|
9530
10514
|
|
|
10515
|
+
//#endregion
|
|
10516
|
+
//#region src/gatekeeper/Client.ts
|
|
10517
|
+
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
10518
|
+
/**
|
|
10519
|
+
* Create an HTTP client for a gatekeeper service.
|
|
10520
|
+
* @param config - Gatekeeper client configuration. {@link ClientConfig}
|
|
10521
|
+
* @returns An HTTP-backed gatekeeper client. {@link GatekeeperClient}
|
|
10522
|
+
*/
|
|
10523
|
+
function createHttpClient(config) {
|
|
10524
|
+
const fetchFn = config.fetchFn ?? fetch;
|
|
10525
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
10526
|
+
const baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
10527
|
+
const baseHeaders = config.originSecret ? { "x-origin-verify": config.originSecret } : void 0;
|
|
10528
|
+
const request = async (path, init) => {
|
|
10529
|
+
const controller = new AbortController();
|
|
10530
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
10531
|
+
try {
|
|
10532
|
+
return await fetchFn(`${baseUrl}${path}`, {
|
|
10533
|
+
...init,
|
|
10534
|
+
headers: mergeHeaders(baseHeaders, init.headers),
|
|
10535
|
+
signal: controller.signal
|
|
10536
|
+
});
|
|
10537
|
+
} finally {
|
|
10538
|
+
clearTimeout(timeout);
|
|
10539
|
+
}
|
|
10540
|
+
};
|
|
10541
|
+
const validate = async (body) => {
|
|
10542
|
+
const response = await request("/v1/validate", {
|
|
10543
|
+
method: "POST",
|
|
10544
|
+
headers: { "content-type": "application/json" },
|
|
10545
|
+
body: JSON.stringify(body)
|
|
10546
|
+
});
|
|
10547
|
+
const json = await response.json();
|
|
10548
|
+
return {
|
|
10549
|
+
statusCode: response.status,
|
|
10550
|
+
body: json
|
|
10551
|
+
};
|
|
10552
|
+
};
|
|
10553
|
+
const getConfigRules = async (query) => {
|
|
10554
|
+
const params = new URLSearchParams();
|
|
10555
|
+
if (query?.cursor) params.set("cursor", query.cursor);
|
|
10556
|
+
if (query?.limit !== void 0) params.set("limit", query.limit.toString());
|
|
10557
|
+
if (query?.types !== void 0) {
|
|
10558
|
+
const typesValue = Array.isArray(query.types) ? query.types.join(",") : query.types;
|
|
10559
|
+
if (typesValue.length > 0) params.set("types", typesValue);
|
|
10560
|
+
}
|
|
10561
|
+
const response = await request(params.size > 0 ? `/v1/config/rules?${params.toString()}` : "/v1/config/rules", { method: "GET" });
|
|
10562
|
+
const json = await response.json();
|
|
10563
|
+
return {
|
|
10564
|
+
statusCode: response.status,
|
|
10565
|
+
body: json
|
|
10566
|
+
};
|
|
10567
|
+
};
|
|
10568
|
+
const isAllowed = async (offers) => {
|
|
10569
|
+
const { statusCode, body } = await validate({ offers: offers.map((offer) => toSnakeCase(offer)) });
|
|
10570
|
+
if (statusCode !== 200) {
|
|
10571
|
+
const errorMessage = extractErrorMessage(body);
|
|
10572
|
+
throw new Error(`Gatekeeper validation failed: ${errorMessage ?? `status ${statusCode}`}`);
|
|
10573
|
+
}
|
|
10574
|
+
const data = body.data;
|
|
10575
|
+
if (!data || typeof data !== "object") throw new Error("Gatekeeper validation response is invalid.");
|
|
10576
|
+
if ("issues" in data) {
|
|
10577
|
+
const issues = data.issues.map((issue) => ({
|
|
10578
|
+
ruleName: issue.rule,
|
|
10579
|
+
message: issue.message,
|
|
10580
|
+
item: offers[issue.index]
|
|
10581
|
+
}));
|
|
10582
|
+
const invalidIndices = new Set(data.issues.map((issue) => issue.index));
|
|
10583
|
+
return {
|
|
10584
|
+
valid: offers.filter((_, index) => !invalidIndices.has(index)),
|
|
10585
|
+
issues
|
|
10586
|
+
};
|
|
10587
|
+
}
|
|
10588
|
+
if (!("payload" in data) || !("root" in data)) throw new Error("Gatekeeper validation response is missing payload data.");
|
|
10589
|
+
return {
|
|
10590
|
+
valid: offers.slice(),
|
|
10591
|
+
issues: []
|
|
10592
|
+
};
|
|
10593
|
+
};
|
|
10594
|
+
const getCallbackTypes = async (requestPayload) => {
|
|
10595
|
+
const response = await request("/v1/callbacks", {
|
|
10596
|
+
method: "POST",
|
|
10597
|
+
headers: { "content-type": "application/json" },
|
|
10598
|
+
body: JSON.stringify(requestPayload)
|
|
10599
|
+
});
|
|
10600
|
+
const json = await response.json();
|
|
10601
|
+
if (!response.ok) throw new Error(`Gatekeeper callbacks request failed: ${extractErrorMessage(json) ?? response.statusText}`);
|
|
10602
|
+
if (!("data" in json) || !Array.isArray(json.data)) throw new Error("Gatekeeper callbacks response is invalid.");
|
|
10603
|
+
return json.data;
|
|
10604
|
+
};
|
|
10605
|
+
return {
|
|
10606
|
+
baseUrl,
|
|
10607
|
+
validate,
|
|
10608
|
+
getConfigRules,
|
|
10609
|
+
isAllowed,
|
|
10610
|
+
getCallbackTypes
|
|
10611
|
+
};
|
|
10612
|
+
}
|
|
10613
|
+
function mergeHeaders(base, extra) {
|
|
10614
|
+
if (!base && !extra) return void 0;
|
|
10615
|
+
const merged = new Headers(base ?? void 0);
|
|
10616
|
+
if (extra) for (const [key, value] of new Headers(extra).entries()) merged.set(key, value);
|
|
10617
|
+
return merged;
|
|
10618
|
+
}
|
|
10619
|
+
function normalizeBaseUrl(url) {
|
|
10620
|
+
return url.trim().replace(/\/+$/, "");
|
|
10621
|
+
}
|
|
10622
|
+
function extractErrorMessage(payload) {
|
|
10623
|
+
if (!payload || typeof payload !== "object") return void 0;
|
|
10624
|
+
const error = payload.error;
|
|
10625
|
+
if (!error || typeof error !== "object") return void 0;
|
|
10626
|
+
return typeof error.message === "string" ? error.message : void 0;
|
|
10627
|
+
}
|
|
10628
|
+
|
|
9531
10629
|
//#endregion
|
|
9532
10630
|
//#region src/cli/commands/start.ts
|
|
9533
10631
|
const startCmd = new RouterCmd("start");
|
|
@@ -9538,9 +10636,10 @@ startCmd.description("Start Router services.").addOption(new Option("--mock <n>"
|
|
|
9538
10636
|
let gatekeeperUrl = gatekeeperConfig?.url;
|
|
9539
10637
|
let gatekeeperHandle = null;
|
|
9540
10638
|
if (!gatekeeperUrl) {
|
|
9541
|
-
const gatekeeperCore = create$
|
|
10639
|
+
const gatekeeperCore = create$20({ rules: morphoRules(chainRegistry.list()) });
|
|
9542
10640
|
gatekeeperHandle = await start$1({
|
|
9543
10641
|
gatekeeper: gatekeeperCore,
|
|
10642
|
+
chainRegistry,
|
|
9544
10643
|
port: gatekeeperConfig?.port ?? 8082
|
|
9545
10644
|
});
|
|
9546
10645
|
gatekeeperUrl = gatekeeperHandle.url;
|
|
@@ -9552,72 +10651,36 @@ startCmd.description("Start Router services.").addOption(new Option("--mock <n>"
|
|
|
9552
10651
|
}
|
|
9553
10652
|
const gatekeeper = createHttpClient({
|
|
9554
10653
|
baseUrl: gatekeeperUrl ?? "http://localhost:8082",
|
|
9555
|
-
timeoutMs: gatekeeperConfig?.timeoutMs
|
|
10654
|
+
timeoutMs: gatekeeperConfig?.timeoutMs,
|
|
10655
|
+
originSecret: gatekeeperConfig?.originSecret
|
|
9556
10656
|
});
|
|
9557
10657
|
if (mock !== void 0 && mock > 0) {
|
|
9558
|
-
const
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
"0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599": 8,
|
|
9564
|
-
"0x4200000000000000000000000000000000000006": 18,
|
|
9565
|
-
"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913": 6,
|
|
9566
|
-
"0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb": 18
|
|
9567
|
-
};
|
|
9568
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
9569
|
-
const offerBlockNumber = 1;
|
|
9570
|
-
const positionBlockNumber = offerBlockNumber + 1;
|
|
9571
|
-
const start = Math.max(0, now - 60);
|
|
9572
|
-
const expiry = now + 3600;
|
|
9573
|
-
const maturity = from$16("end_of_next_month");
|
|
9574
|
-
const oraclePrice = 10n ** 36n;
|
|
9575
|
-
const offers = createMockOffers({
|
|
9576
|
-
count: mock,
|
|
9577
|
-
chains: configuredChains,
|
|
9578
|
-
loanTokens: Object.keys(tokens).map((key) => key),
|
|
9579
|
-
assetsDecimals: tokens,
|
|
9580
|
-
start,
|
|
9581
|
-
expiry,
|
|
9582
|
-
maturity,
|
|
9583
|
-
chainRegistry
|
|
10658
|
+
const offers = await seedMockOffers({
|
|
10659
|
+
db,
|
|
10660
|
+
gatekeeper,
|
|
10661
|
+
chainRegistry,
|
|
10662
|
+
count: mock
|
|
9584
10663
|
});
|
|
9585
|
-
await db.offers.create([{
|
|
9586
|
-
blockNumber: offerBlockNumber,
|
|
9587
|
-
offers
|
|
9588
|
-
}]);
|
|
9589
|
-
await db.oracles.upsert(seedMockOracles(offers, oraclePrice, positionBlockNumber));
|
|
9590
|
-
for (const offer of offers) {
|
|
9591
|
-
if (offer.callback.data === "0x") continue;
|
|
9592
|
-
const chain = chainRegistry.getById(offer.chainId);
|
|
9593
|
-
if (!chain) continue;
|
|
9594
|
-
const callbackType = getCallbackType(chain.name, offer.callback.address);
|
|
9595
|
-
if (!callbackType) continue;
|
|
9596
|
-
const callbacks = decode$1(callbackType, offer.callback.data);
|
|
9597
|
-
for (const cb of callbacks) {
|
|
9598
|
-
const positionAsset = callbackType === Type$1.SellERC20Callback ? cb.contract : offer.loanToken;
|
|
9599
|
-
await db.positions.upsert([from$9({
|
|
9600
|
-
chainId: offer.chainId,
|
|
9601
|
-
contract: cb.contract,
|
|
9602
|
-
user: offer.maker,
|
|
9603
|
-
type: callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20,
|
|
9604
|
-
balance: cb.amount * 2n,
|
|
9605
|
-
asset: positionAsset,
|
|
9606
|
-
blockNumber: positionBlockNumber
|
|
9607
|
-
})]);
|
|
9608
|
-
}
|
|
9609
|
-
}
|
|
9610
10664
|
logger.info({
|
|
9611
10665
|
msg: `Offers seeded`,
|
|
9612
|
-
count:
|
|
10666
|
+
count: offers.length
|
|
9613
10667
|
});
|
|
9614
10668
|
}
|
|
9615
10669
|
if (file) {
|
|
9616
10670
|
const offers = await getOffersFromFile(file);
|
|
9617
|
-
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
10671
|
+
const offerBlockNumber = 1;
|
|
10672
|
+
const positionBlockNumber = offerBlockNumber + 1;
|
|
10673
|
+
await seedOffers({
|
|
10674
|
+
db,
|
|
10675
|
+
offers,
|
|
10676
|
+
blockNumber: offerBlockNumber
|
|
10677
|
+
});
|
|
10678
|
+
await seedOfferAssociations({
|
|
10679
|
+
db,
|
|
10680
|
+
gatekeeper,
|
|
10681
|
+
offers,
|
|
10682
|
+
blockNumber: positionBlockNumber
|
|
10683
|
+
});
|
|
9621
10684
|
logger.info({
|
|
9622
10685
|
msg: `Offers seeded from file`,
|
|
9623
10686
|
count: offers.length,
|
|
@@ -9669,32 +10732,127 @@ startCmd.description("Start Router services.").addOption(new Option("--mock <n>"
|
|
|
9669
10732
|
process.stdin.resume();
|
|
9670
10733
|
});
|
|
9671
10734
|
});
|
|
10735
|
+
/**
|
|
10736
|
+
* Seed mock offers and their dependencies for the router API.
|
|
10737
|
+
* @param parameters - Seed parameters. {@link seedMockOffers.Parameters}
|
|
10738
|
+
* @returns The generated mock offers. {@link seedMockOffers.ReturnType}
|
|
10739
|
+
*/
|
|
10740
|
+
async function seedMockOffers(parameters) {
|
|
10741
|
+
const { db, gatekeeper, chainRegistry, count } = parameters;
|
|
10742
|
+
if (count <= 0) return [];
|
|
10743
|
+
const configuredChains = chainRegistry.list();
|
|
10744
|
+
const assetsDecimals = {
|
|
10745
|
+
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": 6,
|
|
10746
|
+
"0x6b175474e89094c44da98b954eedeac495271d0f": 18,
|
|
10747
|
+
"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": 18,
|
|
10748
|
+
"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599": 8,
|
|
10749
|
+
"0x1abaea1f7c830bd89acc67ec4af516284b1bc33c": 6,
|
|
10750
|
+
"0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0": 18,
|
|
10751
|
+
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": 6,
|
|
10752
|
+
"0x50c5725949a6f0c72e6c4a641f24049a917db0cb": 18,
|
|
10753
|
+
"0x4200000000000000000000000000000000000006": 18,
|
|
10754
|
+
"0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf": 8,
|
|
10755
|
+
"0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452": 18,
|
|
10756
|
+
"0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42": 6,
|
|
10757
|
+
"0xce79ddb3152d52ff8fe65a4c7e058b035fcb560a": 18
|
|
10758
|
+
};
|
|
10759
|
+
const assetsByChainId = {};
|
|
10760
|
+
const oraclesByChainId = {};
|
|
10761
|
+
for (const chain of configuredChains) {
|
|
10762
|
+
const assets$1 = assets[chain.id.toString()]?.map((asset) => asset.toLowerCase()) ?? [];
|
|
10763
|
+
if (assets$1.length === 0) throw new Error(`No gatekeeper assets configured for chain ${chain.id}`);
|
|
10764
|
+
const missingDecimals = assets$1.filter((asset) => assetsDecimals[asset] === void 0);
|
|
10765
|
+
if (missingDecimals.length > 0) throw new Error(`Missing decimals for assets on chain ${chain.id}: ${missingDecimals.join(", ")}`);
|
|
10766
|
+
assetsByChainId[chain.id] = assets$1;
|
|
10767
|
+
const oracles = oracles$1[chain.id.toString()]?.map((oracle) => oracle.toLowerCase()) ?? [];
|
|
10768
|
+
if (oracles.length === 0) throw new Error(`No gatekeeper oracles configured for chain ${chain.id}`);
|
|
10769
|
+
oraclesByChainId[chain.id] = oracles;
|
|
10770
|
+
}
|
|
10771
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
10772
|
+
const offerBlockNumber = 1;
|
|
10773
|
+
const positionBlockNumber = offerBlockNumber + 1;
|
|
10774
|
+
const start = Math.max(0, now - 60);
|
|
10775
|
+
const expiry = now + 3600;
|
|
10776
|
+
const maturity = from$16("end_of_next_month");
|
|
10777
|
+
const oraclePrice = 10n ** 36n;
|
|
10778
|
+
const offers = createMockOffers({
|
|
10779
|
+
count,
|
|
10780
|
+
chains: configuredChains,
|
|
10781
|
+
assetsByChainId,
|
|
10782
|
+
oraclesByChainId,
|
|
10783
|
+
assetsDecimals,
|
|
10784
|
+
start,
|
|
10785
|
+
expiry,
|
|
10786
|
+
maturity,
|
|
10787
|
+
chainRegistry
|
|
10788
|
+
});
|
|
10789
|
+
await seedOffers({
|
|
10790
|
+
db,
|
|
10791
|
+
offers,
|
|
10792
|
+
blockNumber: offerBlockNumber
|
|
10793
|
+
});
|
|
10794
|
+
await db.oracles.upsert(seedMockOracles(offers, oraclePrice, positionBlockNumber));
|
|
10795
|
+
await seedOfferAssociations({
|
|
10796
|
+
db,
|
|
10797
|
+
gatekeeper,
|
|
10798
|
+
offers,
|
|
10799
|
+
blockNumber: positionBlockNumber
|
|
10800
|
+
});
|
|
10801
|
+
return offers;
|
|
10802
|
+
}
|
|
9672
10803
|
async function getOffersFromFile(filePath) {
|
|
9673
10804
|
const content = await readFile(filePath, "utf-8");
|
|
9674
10805
|
const data = JSON.parse(content);
|
|
9675
|
-
return Array.isArray(data) ? data.map(from$
|
|
10806
|
+
return Array.isArray(data) ? data.map(from$12) : [from$12(data)];
|
|
9676
10807
|
}
|
|
9677
10808
|
function createMockOffers(parameters) {
|
|
9678
|
-
const { count, chains,
|
|
10809
|
+
const { count, chains, assetsByChainId, oraclesByChainId, assetsDecimals, start, expiry, maturity, chainRegistry } = parameters;
|
|
10810
|
+
if (chains.length === 0) throw new Error("No chains configured for mock offers");
|
|
9679
10811
|
return Array.from({ length: count }, () => {
|
|
9680
10812
|
const buy = Math.random() < .5;
|
|
10813
|
+
const chain = chains[Math.floor(Math.random() * chains.length)];
|
|
10814
|
+
const allowedAssets = assetsByChainId[chain.id] ?? [];
|
|
10815
|
+
if (allowedAssets.length === 0) throw new Error(`No allowed assets configured for chain ${chain.id}`);
|
|
10816
|
+
const loanToken = allowedAssets[Math.floor(Math.random() * allowedAssets.length)];
|
|
9681
10817
|
const offer = random({
|
|
9682
|
-
chains,
|
|
9683
|
-
loanTokens,
|
|
10818
|
+
chains: [chain],
|
|
10819
|
+
loanTokens: [loanToken],
|
|
9684
10820
|
assetsDecimals,
|
|
9685
10821
|
start,
|
|
9686
10822
|
expiry,
|
|
9687
10823
|
maturity,
|
|
9688
10824
|
buy
|
|
9689
10825
|
});
|
|
9690
|
-
const
|
|
9691
|
-
if (
|
|
10826
|
+
const allowedOracles = oraclesByChainId[chain.id] ?? [];
|
|
10827
|
+
if (allowedOracles.length === 0) throw new Error(`No allowed oracles configured for chain ${chain.id}`);
|
|
10828
|
+
const collateralAssets = allowedAssets.filter((asset) => asset !== loanToken);
|
|
10829
|
+
if (collateralAssets.length === 0) throw new Error(`No collateral assets available for chain ${chain.id}`);
|
|
10830
|
+
const collateralCount = Math.min(collateralAssets.length, 1 + Math.floor(Math.random() * 3));
|
|
10831
|
+
const collateralPool = [...collateralAssets];
|
|
10832
|
+
const collateralSelections = [];
|
|
10833
|
+
while (collateralSelections.length < collateralCount && collateralPool.length > 0) {
|
|
10834
|
+
const index = Math.floor(Math.random() * collateralPool.length);
|
|
10835
|
+
const [value] = collateralPool.splice(index, 1);
|
|
10836
|
+
if (value !== void 0) collateralSelections.push(value);
|
|
10837
|
+
}
|
|
10838
|
+
collateralSelections.sort();
|
|
10839
|
+
const lltv = offer.collaterals[0]?.lltv ?? .86;
|
|
10840
|
+
const collaterals = collateralSelections.map((asset) => from$14({
|
|
10841
|
+
asset,
|
|
10842
|
+
oracle: allowedOracles[Math.floor(Math.random() * allowedOracles.length)],
|
|
10843
|
+
lltv
|
|
10844
|
+
}));
|
|
10845
|
+
if (!chainRegistry.getById(offer.chainId)) throw new Error(`Missing chain config for id ${offer.chainId}`);
|
|
9692
10846
|
const callbackType = buy ? Type$1.BuyVaultV1Callback : Type$1.SellERC20Callback;
|
|
9693
|
-
const callbackAddress =
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
10847
|
+
const callbackAddress = buy ? BUY_CALLBACK_ADDRESS : SELL_CALLBACK_ADDRESS;
|
|
10848
|
+
const callbackData = buildMockCallbackData(callbackType, {
|
|
10849
|
+
...offer,
|
|
10850
|
+
collaterals
|
|
10851
|
+
});
|
|
10852
|
+
return from$12({
|
|
9697
10853
|
...offer,
|
|
10854
|
+
loanToken,
|
|
10855
|
+
collaterals,
|
|
9698
10856
|
callback: {
|
|
9699
10857
|
address: callbackAddress,
|
|
9700
10858
|
data: callbackData
|
|
@@ -9719,7 +10877,7 @@ function seedMockOracles(offers, price, blockNumber) {
|
|
|
9719
10877
|
for (const offer of offers) for (const collateral of offer.collaterals) {
|
|
9720
10878
|
const key = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
|
|
9721
10879
|
if (oracleMap.has(key)) continue;
|
|
9722
|
-
oracleMap.set(key, from$
|
|
10880
|
+
oracleMap.set(key, from$11({
|
|
9723
10881
|
chainId: offer.chainId,
|
|
9724
10882
|
address: collateral.oracle,
|
|
9725
10883
|
price: price.toString(),
|
|
@@ -9728,6 +10886,158 @@ function seedMockOracles(offers, price, blockNumber) {
|
|
|
9728
10886
|
}
|
|
9729
10887
|
return Array.from(oracleMap.values());
|
|
9730
10888
|
}
|
|
10889
|
+
const BUY_CALLBACK_ADDRESS = "0x3333333333333333333333333333333333333333";
|
|
10890
|
+
const SELL_CALLBACK_ADDRESS = "0x1111111111111111111111111111111111111111";
|
|
10891
|
+
async function seedOffers(parameters) {
|
|
10892
|
+
const { db, offers, blockNumber } = parameters;
|
|
10893
|
+
if (offers.length === 0) return;
|
|
10894
|
+
const dependencies = buildOfferDependencies({
|
|
10895
|
+
offers,
|
|
10896
|
+
blockNumber
|
|
10897
|
+
});
|
|
10898
|
+
await db.oracles.upsert(dependencies.oracles);
|
|
10899
|
+
await db.obligations.create(dependencies.obligations);
|
|
10900
|
+
await db.groups.create(dependencies.groups);
|
|
10901
|
+
await db.offers.create([{
|
|
10902
|
+
blockNumber,
|
|
10903
|
+
offers
|
|
10904
|
+
}]);
|
|
10905
|
+
}
|
|
10906
|
+
async function seedOfferAssociations(parameters) {
|
|
10907
|
+
const { db, gatekeeper, offers, blockNumber } = parameters;
|
|
10908
|
+
if (offers.length === 0) return;
|
|
10909
|
+
const { callbacks, positions, lots } = buildOfferAssociationsFromOffers({
|
|
10910
|
+
offers,
|
|
10911
|
+
callbackTypes: await resolveCallbackTypes({
|
|
10912
|
+
gatekeeper,
|
|
10913
|
+
offers
|
|
10914
|
+
}),
|
|
10915
|
+
blockNumber
|
|
10916
|
+
});
|
|
10917
|
+
if (positions.length > 0) await db.positions.upsert(positions);
|
|
10918
|
+
if (callbacks.length > 0) await db.callbacks.upsert(callbacks);
|
|
10919
|
+
if (lots.length > 0) await db.lots.create(lots);
|
|
10920
|
+
}
|
|
10921
|
+
function buildOfferDependencies(parameters) {
|
|
10922
|
+
const { offers, blockNumber } = parameters;
|
|
10923
|
+
const obligationsById = /* @__PURE__ */ new Map();
|
|
10924
|
+
const oraclesByKey = /* @__PURE__ */ new Map();
|
|
10925
|
+
const groupsByKey = /* @__PURE__ */ new Map();
|
|
10926
|
+
for (const offer of offers) {
|
|
10927
|
+
const obligationId$1 = obligationId(offer);
|
|
10928
|
+
if (!obligationsById.get(obligationId$1)) obligationsById.set(obligationId$1, from$13({
|
|
10929
|
+
chainId: offer.chainId,
|
|
10930
|
+
loanToken: offer.loanToken,
|
|
10931
|
+
maturity: offer.maturity,
|
|
10932
|
+
collaterals: offer.collaterals
|
|
10933
|
+
}));
|
|
10934
|
+
for (const collateral of offer.collaterals) {
|
|
10935
|
+
const oracleKey = `${offer.chainId}-${collateral.oracle}`.toLowerCase();
|
|
10936
|
+
if (!oraclesByKey.has(oracleKey)) oraclesByKey.set(oracleKey, from$11({
|
|
10937
|
+
chainId: offer.chainId,
|
|
10938
|
+
address: collateral.oracle,
|
|
10939
|
+
price: null,
|
|
10940
|
+
blockNumber
|
|
10941
|
+
}));
|
|
10942
|
+
}
|
|
10943
|
+
const groupKey = `${offer.chainId}-${offer.maker}-${offer.group}`.toLowerCase();
|
|
10944
|
+
if (!groupsByKey.has(groupKey)) groupsByKey.set(groupKey, {
|
|
10945
|
+
chainId: offer.chainId,
|
|
10946
|
+
maker: offer.maker,
|
|
10947
|
+
group: offer.group,
|
|
10948
|
+
blockNumber
|
|
10949
|
+
});
|
|
10950
|
+
}
|
|
10951
|
+
return {
|
|
10952
|
+
obligations: Array.from(obligationsById.values()),
|
|
10953
|
+
oracles: Array.from(oraclesByKey.values()),
|
|
10954
|
+
groups: Array.from(groupsByKey.values())
|
|
10955
|
+
};
|
|
10956
|
+
}
|
|
10957
|
+
async function resolveCallbackTypes(parameters) {
|
|
10958
|
+
const { gatekeeper, offers } = parameters;
|
|
10959
|
+
const addressesByChain = /* @__PURE__ */ new Map();
|
|
10960
|
+
for (const offer of offers) {
|
|
10961
|
+
if (offer.callback.data === "0x") continue;
|
|
10962
|
+
const set = addressesByChain.get(offer.chainId) ?? /* @__PURE__ */ new Set();
|
|
10963
|
+
set.add(offer.callback.address.toLowerCase());
|
|
10964
|
+
addressesByChain.set(offer.chainId, set);
|
|
10965
|
+
}
|
|
10966
|
+
if (addressesByChain.size === 0) return /* @__PURE__ */ new Map();
|
|
10967
|
+
const request = { callbacks: Array.from(addressesByChain.entries()).map(([chainId, addresses]) => ({
|
|
10968
|
+
chain_id: chainId,
|
|
10969
|
+
addresses: Array.from(addresses)
|
|
10970
|
+
})) };
|
|
10971
|
+
const response = await gatekeeper.getCallbackTypes(request);
|
|
10972
|
+
const typeByAddress = /* @__PURE__ */ new Map();
|
|
10973
|
+
for (const entry of response) {
|
|
10974
|
+
const chainId = entry.chain_id;
|
|
10975
|
+
for (const [key, list] of Object.entries(entry)) {
|
|
10976
|
+
if (key === "chain_id" || key === "not_supported") continue;
|
|
10977
|
+
if (!Array.isArray(list)) continue;
|
|
10978
|
+
for (const address of list) {
|
|
10979
|
+
const mapKey = `${chainId}-${address.toLowerCase()}`;
|
|
10980
|
+
typeByAddress.set(mapKey, key);
|
|
10981
|
+
}
|
|
10982
|
+
}
|
|
10983
|
+
}
|
|
10984
|
+
return typeByAddress;
|
|
10985
|
+
}
|
|
10986
|
+
function buildOfferAssociationsFromOffers(parameters) {
|
|
10987
|
+
const { offers, callbackTypes, blockNumber } = parameters;
|
|
10988
|
+
const callbacks = [];
|
|
10989
|
+
const positions = [];
|
|
10990
|
+
const lots = [];
|
|
10991
|
+
for (const offer of offers) {
|
|
10992
|
+
if (offer.callback.data === "0x") continue;
|
|
10993
|
+
const key = `${offer.chainId}-${offer.callback.address.toLowerCase()}`;
|
|
10994
|
+
const callbackType = callbackTypes.get(key);
|
|
10995
|
+
if (!callbackType) continue;
|
|
10996
|
+
let decoded;
|
|
10997
|
+
try {
|
|
10998
|
+
decoded = decode$1(callbackType, offer.callback.data);
|
|
10999
|
+
} catch (err) {
|
|
11000
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
11001
|
+
throw new Error("Failed to decode callback data", { cause: error });
|
|
11002
|
+
}
|
|
11003
|
+
if (decoded.length === 0) continue;
|
|
11004
|
+
callbacks.push({
|
|
11005
|
+
offerHash: hash(offer),
|
|
11006
|
+
callbacks: decoded.map((callback) => ({
|
|
11007
|
+
chainId: offer.chainId,
|
|
11008
|
+
contract: callback.contract,
|
|
11009
|
+
user: offer.maker,
|
|
11010
|
+
amount: callback.amount
|
|
11011
|
+
}))
|
|
11012
|
+
});
|
|
11013
|
+
for (const callback of decoded) {
|
|
11014
|
+
const positionType = callbackType === Type$1.BuyVaultV1Callback ? Type.VAULT_V1 : Type.ERC20;
|
|
11015
|
+
const positionAsset = callbackType === Type$1.BuyVaultV1Callback ? offer.loanToken : callback.contract;
|
|
11016
|
+
positions.push(from$10({
|
|
11017
|
+
chainId: offer.chainId,
|
|
11018
|
+
contract: callback.contract,
|
|
11019
|
+
user: offer.maker,
|
|
11020
|
+
type: positionType,
|
|
11021
|
+
balance: callback.amount * 2n,
|
|
11022
|
+
asset: positionAsset,
|
|
11023
|
+
blockNumber
|
|
11024
|
+
}));
|
|
11025
|
+
const isLoanPosition = positionAsset.toLowerCase() === offer.loanToken.toLowerCase();
|
|
11026
|
+
lots.push({
|
|
11027
|
+
positionChainId: offer.chainId,
|
|
11028
|
+
positionContract: callback.contract,
|
|
11029
|
+
positionUser: offer.maker,
|
|
11030
|
+
group: offer.group,
|
|
11031
|
+
size: isLoanPosition ? offer.assets : callback.amount
|
|
11032
|
+
});
|
|
11033
|
+
}
|
|
11034
|
+
}
|
|
11035
|
+
return {
|
|
11036
|
+
callbacks,
|
|
11037
|
+
positions,
|
|
11038
|
+
lots
|
|
11039
|
+
};
|
|
11040
|
+
}
|
|
9731
11041
|
|
|
9732
11042
|
//#endregion
|
|
9733
11043
|
//#region src/cli/cli.ts
|