@morpho-dev/router 0.1.6 → 0.1.7
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/drizzle/offers_v1.1/0000_init.sql +95 -0
- package/dist/drizzle/offers_v1.1/0001_new_table_for_collectors_block_numbers.sql +5 -0
- package/dist/drizzle/offers_v1.1/0002_update-liquidity-tables.sql +8 -0
- package/dist/drizzle/offers_v1.1/0003_add-not-null-for-queue-id.sql +1 -0
- package/dist/drizzle/offers_v1.1/0004_add-callback-id-to-offer.sql +1 -0
- package/dist/drizzle/offers_v1.1/0005_add-missing-indices-to-liquidity-tables.sql +2 -0
- package/dist/drizzle/offers_v1.1/meta/0000_snapshot.json +827 -0
- package/dist/drizzle/offers_v1.1/meta/0001_snapshot.json +827 -0
- package/dist/drizzle/offers_v1.1/meta/0002_snapshot.json +833 -0
- package/dist/drizzle/offers_v1.1/meta/0003_snapshot.json +833 -0
- package/dist/drizzle/offers_v1.1/meta/0004_snapshot.json +839 -0
- package/dist/drizzle/offers_v1.1/meta/0005_snapshot.json +877 -0
- package/dist/drizzle/offers_v1.1/meta/_journal.json +48 -0
- package/dist/index.browser.d.cts +1 -114
- package/dist/index.browser.d.ts +1 -114
- package/dist/index.browser.js +0 -410
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.mjs +2 -411
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.node.d.cts +1352 -212
- package/dist/index.node.d.ts +1352 -212
- package/dist/index.node.js +2208 -1002
- package/dist/index.node.js.map +1 -1
- package/dist/index.node.mjs +2204 -1008
- package/dist/index.node.mjs.map +1 -1
- package/package.json +9 -1
package/dist/index.node.mjs
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import { Errors, LLTV, Offer, Format,
|
|
1
|
+
import { Errors, LLTV, Utils, Offer, Format, Chain, Maturity, Time, Mempool } from '@morpho-dev/mempool';
|
|
2
2
|
export * from '@morpho-dev/mempool';
|
|
3
|
-
import {
|
|
3
|
+
import { parseUnits, publicActions, maxUint256, parseEventLogs, formatUnits, createWalletClient, http, erc20Abi } from 'viem';
|
|
4
|
+
import { getBlockNumber } from 'viem/actions';
|
|
5
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
4
6
|
import { z } from 'zod/v4';
|
|
7
|
+
import { desc, and, eq, sql, gte, lte, asc, inArray } from 'drizzle-orm';
|
|
8
|
+
import { pgSchema, timestamp, varchar, bigint, text, boolean, integer, numeric, index, serial, jsonb, uniqueIndex, primaryKey } from 'drizzle-orm/pg-core';
|
|
9
|
+
import { Base64 } from 'js-base64';
|
|
5
10
|
import { createDocument } from 'zod-openapi';
|
|
6
|
-
import { parseUnits, maxUint256, formatUnits, erc20Abi } from 'viem';
|
|
7
11
|
import { serve as serve$1 } from '@hono/node-server';
|
|
8
12
|
import { Hono } from 'hono';
|
|
9
|
-
import
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { PGlite } from '@electric-sql/pglite';
|
|
15
|
+
import { drizzle } from 'drizzle-orm/node-postgres';
|
|
16
|
+
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
|
17
|
+
import { drizzle as drizzle$1 } from 'drizzle-orm/pglite';
|
|
18
|
+
import { migrate as migrate$1 } from 'drizzle-orm/pglite/migrator';
|
|
19
|
+
import { Pool } from 'pg';
|
|
10
20
|
|
|
11
21
|
var __defProp = Object.defineProperty;
|
|
12
22
|
var __export = (target, all) => {
|
|
@@ -26,7 +36,7 @@ var CallbackType = /* @__PURE__ */ ((CallbackType2) => {
|
|
|
26
36
|
return CallbackType2;
|
|
27
37
|
})(CallbackType || {});
|
|
28
38
|
function buildLiquidity(parameters) {
|
|
29
|
-
const { type, user, contract, chainId, amount, index = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
|
|
39
|
+
const { type, user, contract, chainId, amount, index: index2 = 0, updatedAt = /* @__PURE__ */ new Date() } = parameters;
|
|
30
40
|
if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
|
|
31
41
|
throw new Error(`CallbackType not implemented: ${type}`);
|
|
32
42
|
const amountStr = amount.toString();
|
|
@@ -45,7 +55,7 @@ function buildLiquidity(parameters) {
|
|
|
45
55
|
queue: {
|
|
46
56
|
queueId: id,
|
|
47
57
|
availableLiquidityPoolId: id,
|
|
48
|
-
index,
|
|
58
|
+
index: index2,
|
|
49
59
|
updatedAt
|
|
50
60
|
},
|
|
51
61
|
pool: {
|
|
@@ -67,101 +77,436 @@ function getCallbackIdForOffer(offer) {
|
|
|
67
77
|
return null;
|
|
68
78
|
}
|
|
69
79
|
|
|
70
|
-
// src/
|
|
71
|
-
var
|
|
72
|
-
__export(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
// src/Collector/index.ts
|
|
81
|
+
var Collector_exports = {};
|
|
82
|
+
__export(Collector_exports, {
|
|
83
|
+
createBuyWithEmptyCallbackLiquidityCollector: () => createBuyWithEmptyCallbackLiquidityCollector,
|
|
84
|
+
createChainReorgsCollector: () => createChainReorgsCollector,
|
|
85
|
+
createConsumedEventsCollector: () => createConsumedEventsCollector,
|
|
86
|
+
createMempoolCollector: () => createMempoolCollector,
|
|
87
|
+
start: () => start
|
|
76
88
|
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
+
|
|
90
|
+
// src/Liquidity.ts
|
|
91
|
+
var Liquidity_exports = {};
|
|
92
|
+
__export(Liquidity_exports, {
|
|
93
|
+
fetch: () => fetch2,
|
|
94
|
+
fetchBalancesAndAllowances: () => fetchBalancesAndAllowances,
|
|
95
|
+
serialize: () => serialize
|
|
96
|
+
});
|
|
97
|
+
async function fetchBalancesAndAllowances(parameters) {
|
|
98
|
+
const { client, spender, pairs, options } = parameters;
|
|
99
|
+
if (pairs.length === 0) return /* @__PURE__ */ new Map();
|
|
100
|
+
const batchSize = Math.max(1, options?.batchSize ?? 5e3);
|
|
101
|
+
const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
|
|
102
|
+
const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
|
|
103
|
+
const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
|
|
104
|
+
const out = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const pairsBatch of Utils.batch(pairs, batchSize)) {
|
|
106
|
+
const balanceContracts = [];
|
|
107
|
+
const allowanceContracts = [];
|
|
108
|
+
for (const { user, token } of pairsBatch) {
|
|
109
|
+
balanceContracts.push({
|
|
110
|
+
address: token,
|
|
111
|
+
abi: erc20Abi,
|
|
112
|
+
functionName: "balanceOf",
|
|
113
|
+
args: [user]
|
|
114
|
+
});
|
|
115
|
+
allowanceContracts.push({
|
|
116
|
+
address: token,
|
|
117
|
+
abi: erc20Abi,
|
|
118
|
+
functionName: "allowance",
|
|
119
|
+
args: [user, spender]
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const [balances, allowances] = await Promise.all([
|
|
123
|
+
Utils.retry(
|
|
124
|
+
() => client.multicall({
|
|
125
|
+
allowFailure: false,
|
|
126
|
+
contracts: balanceContracts,
|
|
127
|
+
...blockNumber ? { blockNumber } : {}
|
|
128
|
+
}),
|
|
129
|
+
retryAttempts,
|
|
130
|
+
retryDelayMs
|
|
131
|
+
),
|
|
132
|
+
Utils.retry(
|
|
133
|
+
() => client.multicall({
|
|
134
|
+
allowFailure: false,
|
|
135
|
+
contracts: allowanceContracts,
|
|
136
|
+
...blockNumber ? { blockNumber } : {}
|
|
137
|
+
}),
|
|
138
|
+
retryAttempts,
|
|
139
|
+
retryDelayMs
|
|
140
|
+
)
|
|
141
|
+
]);
|
|
142
|
+
for (let i = 0; i < pairsBatch.length; i++) {
|
|
143
|
+
const { user, token } = pairsBatch[i];
|
|
144
|
+
const balance = balances[i];
|
|
145
|
+
const allowance = allowances[i];
|
|
146
|
+
let perUser = out.get(user);
|
|
147
|
+
if (!perUser) {
|
|
148
|
+
perUser = /* @__PURE__ */ new Map();
|
|
149
|
+
out.set(user, perUser);
|
|
150
|
+
}
|
|
151
|
+
perUser.set(token, { balance, allowance });
|
|
152
|
+
}
|
|
89
153
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
async function fetch2(parameters) {
|
|
157
|
+
const { client, chainId, spender, type, pairs, options } = parameters;
|
|
158
|
+
if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
|
|
159
|
+
throw new Error(`CallbackType not implemented: ${type}`);
|
|
160
|
+
const map = await fetchBalancesAndAllowances({
|
|
161
|
+
client,
|
|
162
|
+
spender,
|
|
163
|
+
pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
|
|
164
|
+
options
|
|
165
|
+
});
|
|
166
|
+
const out = [];
|
|
167
|
+
for (const [user, perContract] of map) {
|
|
168
|
+
for (const [contract, { balance, allowance }] of perContract) {
|
|
169
|
+
const amount = balance < allowance ? balance : allowance;
|
|
170
|
+
out.push(
|
|
171
|
+
buildLiquidity({
|
|
172
|
+
type,
|
|
173
|
+
user,
|
|
174
|
+
contract,
|
|
175
|
+
chainId,
|
|
176
|
+
amount: amount.toString(),
|
|
177
|
+
index: 0
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
}
|
|
94
181
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
pattern: /^\d+$/,
|
|
106
|
-
error: "numeric string"
|
|
107
|
-
},
|
|
108
|
-
maturity: {
|
|
109
|
-
field: "maturity",
|
|
110
|
-
type: "number",
|
|
111
|
-
validator: (val) => val > 0,
|
|
112
|
-
error: "positive number"
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
function serialize(liquidity) {
|
|
185
|
+
const normalized = {
|
|
186
|
+
userPosition: {
|
|
187
|
+
id: liquidity.userPosition.id,
|
|
188
|
+
availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
|
|
189
|
+
user: liquidity.userPosition.user,
|
|
190
|
+
chainId: String(liquidity.userPosition.chainId),
|
|
191
|
+
amount: String(liquidity.userPosition.amount)
|
|
113
192
|
},
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
193
|
+
queues: liquidity.queues.map((queueWithPool) => ({
|
|
194
|
+
queue: {
|
|
195
|
+
queueId: queueWithPool.queue.queueId,
|
|
196
|
+
availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
|
|
197
|
+
index: queueWithPool.queue.index
|
|
198
|
+
},
|
|
199
|
+
pool: {
|
|
200
|
+
id: queueWithPool.pool.id,
|
|
201
|
+
amount: String(queueWithPool.pool.amount)
|
|
202
|
+
}
|
|
203
|
+
})).sort(
|
|
204
|
+
(left, right) => {
|
|
205
|
+
const leftQueueId = left.queue.queueId || "";
|
|
206
|
+
const rightQueueId = right.queue.queueId || "";
|
|
207
|
+
if (leftQueueId < rightQueueId) return -1;
|
|
208
|
+
if (leftQueueId > rightQueueId) return 1;
|
|
209
|
+
const leftPoolId = left.pool.id;
|
|
210
|
+
const rightPoolId = right.pool.id;
|
|
211
|
+
if (leftPoolId < rightPoolId) return -1;
|
|
212
|
+
if (leftPoolId > rightPoolId) return 1;
|
|
213
|
+
const leftIndex = left.queue.index;
|
|
214
|
+
const rightIndex = right.queue.index;
|
|
215
|
+
if (leftIndex < rightIndex) return -1;
|
|
216
|
+
if (leftIndex > rightIndex) return 1;
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
)
|
|
120
220
|
};
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
221
|
+
return JSON.stringify(normalized);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/Collector/BuyWithEmptyCallbackLiquidityCollector.ts
|
|
225
|
+
function createBuyWithEmptyCallbackLiquidityCollector(params) {
|
|
226
|
+
const {
|
|
227
|
+
client,
|
|
228
|
+
collectorBlockStore,
|
|
229
|
+
liquidityStore,
|
|
230
|
+
offerStore,
|
|
231
|
+
chain,
|
|
232
|
+
options: { maxBatchSize, interval } = {}
|
|
233
|
+
} = params;
|
|
234
|
+
const collectorName = "buy_with_empty_callback_liquidity";
|
|
235
|
+
async function getUniqueUserTokenPairs() {
|
|
236
|
+
const unique = /* @__PURE__ */ new Set();
|
|
237
|
+
const pairs = [];
|
|
238
|
+
let cursor;
|
|
239
|
+
do {
|
|
240
|
+
const { offers: offers2, nextCursor } = await offerStore.getAll({
|
|
241
|
+
query: {
|
|
242
|
+
side: "buy",
|
|
243
|
+
chains: [Number(chain.id)],
|
|
244
|
+
cursor,
|
|
245
|
+
limit: 1e3
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
cursor = nextCursor ?? void 0;
|
|
249
|
+
for (const offer of offers2) {
|
|
250
|
+
const creator = offer.offering;
|
|
251
|
+
const loanToken = offer.loanToken;
|
|
252
|
+
const key = `${offer.offering}-${offer.loanToken}`.toLowerCase();
|
|
253
|
+
if (unique.has(key)) continue;
|
|
254
|
+
unique.add(key);
|
|
255
|
+
pairs.push({ user: creator, token: loanToken });
|
|
256
|
+
}
|
|
257
|
+
} while (cursor);
|
|
258
|
+
return pairs;
|
|
259
|
+
}
|
|
260
|
+
const collect = Utils.lazy((emit) => {
|
|
261
|
+
return Utils.poll(
|
|
262
|
+
async () => {
|
|
263
|
+
const publicClient = client.extend(publicActions);
|
|
264
|
+
const pairs = await getUniqueUserTokenPairs();
|
|
265
|
+
const latestBlockNumber = await getBlockNumber(publicClient);
|
|
266
|
+
const liquidityEntries = await fetch2({
|
|
267
|
+
client: publicClient,
|
|
268
|
+
chainId: chain.id,
|
|
269
|
+
spender: chain.morpho,
|
|
270
|
+
type: "buy_with_empty_callback" /* BuyWithEmptyCallback */,
|
|
271
|
+
pairs: pairs.map(({ user, token }) => ({ user, contract: token })),
|
|
272
|
+
options: {
|
|
273
|
+
blockNumber: Number(latestBlockNumber),
|
|
274
|
+
batchSize: maxBatchSize
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
const existingLiquidityEntries = await liquidityStore.getAll();
|
|
278
|
+
const existingById = /* @__PURE__ */ new Map();
|
|
279
|
+
for (const liquidityEntry of existingLiquidityEntries) {
|
|
280
|
+
const liquidityId = liquidityEntry.userPosition.id;
|
|
281
|
+
const serializedEntry = serialize(liquidityEntry);
|
|
282
|
+
existingById.set(liquidityId, serializedEntry);
|
|
283
|
+
}
|
|
284
|
+
for (const liquidity of liquidityEntries) {
|
|
285
|
+
const serialized = serialize(liquidity);
|
|
286
|
+
const prevSerialized = existingById.get(liquidity.userPosition.id);
|
|
287
|
+
if (prevSerialized === serialized) continue;
|
|
288
|
+
await liquidityStore.save({ liquidity });
|
|
289
|
+
}
|
|
290
|
+
await collectorBlockStore.saveBlockNumber({
|
|
291
|
+
collectorName,
|
|
292
|
+
chainId: chain.id,
|
|
293
|
+
blockNumber: Number(latestBlockNumber)
|
|
294
|
+
});
|
|
295
|
+
emit(Number(latestBlockNumber));
|
|
296
|
+
},
|
|
297
|
+
{ interval: interval ?? 3e4 }
|
|
142
298
|
);
|
|
143
|
-
}
|
|
144
|
-
|
|
299
|
+
});
|
|
300
|
+
const onReorg = (_lastFinalizedBlockNumber) => {
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
name: collectorName,
|
|
304
|
+
lastSyncedBlock: async () => await collectorBlockStore.getBlockNumber({ collectorName, chainId: chain.id }),
|
|
305
|
+
collect,
|
|
306
|
+
onReorg
|
|
307
|
+
};
|
|
145
308
|
}
|
|
146
|
-
function
|
|
147
|
-
|
|
309
|
+
function createChainReorgsCollector(parameters) {
|
|
310
|
+
const collectorName = "chain_reorgs";
|
|
311
|
+
const {
|
|
312
|
+
client,
|
|
313
|
+
subscribers,
|
|
314
|
+
collectorStore,
|
|
315
|
+
chain,
|
|
316
|
+
options: { maxBatchSize = 25, interval } = {}
|
|
317
|
+
} = parameters;
|
|
318
|
+
let finalizedBlock = null;
|
|
319
|
+
let unfinalizedBlocks = [];
|
|
320
|
+
const commonAncestor = (block) => {
|
|
321
|
+
const parent = unfinalizedBlocks.find((b) => b.hash === block.parentHash);
|
|
322
|
+
if (parent) return parent;
|
|
323
|
+
if (finalizedBlock == null) throw new Error("Failed to get common ancestor");
|
|
324
|
+
return finalizedBlock;
|
|
325
|
+
};
|
|
326
|
+
const reconcile = async (block) => {
|
|
327
|
+
if (block.hash === null || block.number === null || block.parentHash === null)
|
|
328
|
+
throw new Error("Failed to get block");
|
|
329
|
+
const latestBlock = unfinalizedBlocks[unfinalizedBlocks.length - 1];
|
|
330
|
+
if (latestBlock === void 0) {
|
|
331
|
+
unfinalizedBlocks.push({
|
|
332
|
+
hash: block.hash,
|
|
333
|
+
number: block.number,
|
|
334
|
+
parentHash: block.parentHash
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (latestBlock.hash === block.hash) return;
|
|
339
|
+
if (latestBlock.number >= block.number) {
|
|
340
|
+
const ancestor = commonAncestor(block);
|
|
341
|
+
console.log(
|
|
342
|
+
`reorg detected, latestBlock.number: ${latestBlock.number} > block.number: ${block.number} on chain ${chain.id}. Ancestor: ${ancestor.number}`
|
|
343
|
+
);
|
|
344
|
+
subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
|
|
345
|
+
unfinalizedBlocks = [];
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (latestBlock.number + 1n < block.number) {
|
|
349
|
+
console.log(
|
|
350
|
+
`missing blocks between block ${latestBlock.number} and block ${block.number} on chain ${chain.id}`
|
|
351
|
+
);
|
|
352
|
+
const missingBlockNumbers = (() => {
|
|
353
|
+
const missingBlockNumbers2 = [];
|
|
354
|
+
let start2 = latestBlock.number + 1n;
|
|
355
|
+
const threshold = latestBlock.number + BigInt(maxBatchSize) > block.number ? block.number : latestBlock.number + BigInt(maxBatchSize);
|
|
356
|
+
while (start2 < threshold) {
|
|
357
|
+
missingBlockNumbers2.push(start2);
|
|
358
|
+
start2 = start2 + 1n;
|
|
359
|
+
}
|
|
360
|
+
return missingBlockNumbers2;
|
|
361
|
+
})();
|
|
362
|
+
const missingBlocks = await Promise.all(
|
|
363
|
+
missingBlockNumbers.map(
|
|
364
|
+
(blockNumber) => client.getBlock({ blockNumber, includeTransactions: false })
|
|
365
|
+
)
|
|
366
|
+
);
|
|
367
|
+
for (const missingBlock of missingBlocks) await reconcile(missingBlock);
|
|
368
|
+
await reconcile(block);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (block.parentHash !== latestBlock.hash) {
|
|
372
|
+
const ancestor = commonAncestor(block);
|
|
373
|
+
console.log(
|
|
374
|
+
`reorg detected, block.parentHash: ${block.parentHash} !== latestBlock.hash: ${latestBlock.hash} on chain ${chain.id}. Ancestor: ${ancestor.number}`
|
|
375
|
+
);
|
|
376
|
+
subscribers.forEach((subscriber) => subscriber.onReorg(Number(ancestor.number)));
|
|
377
|
+
unfinalizedBlocks = [];
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
unfinalizedBlocks.push({
|
|
381
|
+
hash: block.hash,
|
|
382
|
+
number: block.number,
|
|
383
|
+
parentHash: block.parentHash
|
|
384
|
+
});
|
|
385
|
+
};
|
|
386
|
+
const collect = Utils.lazy((emit) => {
|
|
387
|
+
let tick = 0;
|
|
388
|
+
const fetchFinalizedBlock = async () => {
|
|
389
|
+
if (tick % 20 === 0 || finalizedBlock === null) {
|
|
390
|
+
finalizedBlock = await client.getBlock({
|
|
391
|
+
blockTag: "finalized",
|
|
392
|
+
includeTransactions: false
|
|
393
|
+
});
|
|
394
|
+
if (finalizedBlock === null) throw new Error("Failed to get finalized block");
|
|
395
|
+
}
|
|
396
|
+
tick++;
|
|
397
|
+
};
|
|
398
|
+
return Utils.poll(
|
|
399
|
+
async () => {
|
|
400
|
+
await fetchFinalizedBlock();
|
|
401
|
+
const latestBlock = await client.getBlock({
|
|
402
|
+
blockTag: "latest",
|
|
403
|
+
includeTransactions: false
|
|
404
|
+
});
|
|
405
|
+
await reconcile(latestBlock);
|
|
406
|
+
const lastBlockNumber = Number(latestBlock.number);
|
|
407
|
+
await collectorStore.saveBlockNumber({
|
|
408
|
+
collectorName: "chain_reorgs",
|
|
409
|
+
chainId: chain.id,
|
|
410
|
+
blockNumber: lastBlockNumber
|
|
411
|
+
});
|
|
412
|
+
emit(lastBlockNumber);
|
|
413
|
+
},
|
|
414
|
+
{ interval: interval ?? 3e4 }
|
|
415
|
+
);
|
|
416
|
+
});
|
|
417
|
+
return {
|
|
418
|
+
name: collectorName,
|
|
419
|
+
lastSyncedBlock: async () => await collectorStore.getBlockNumber({ collectorName, chainId: chain.id }),
|
|
420
|
+
collect,
|
|
421
|
+
onReorg: (_) => {
|
|
422
|
+
}
|
|
423
|
+
};
|
|
148
424
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
425
|
+
|
|
426
|
+
// src/Collector/Collector.ts
|
|
427
|
+
function start(collector) {
|
|
428
|
+
let stopped = false;
|
|
429
|
+
const it = collector.collect();
|
|
430
|
+
(async () => {
|
|
431
|
+
while (!stopped) await it.next();
|
|
432
|
+
await it.return();
|
|
433
|
+
})();
|
|
434
|
+
return () => {
|
|
435
|
+
stopped = true;
|
|
436
|
+
};
|
|
154
437
|
}
|
|
155
438
|
|
|
156
|
-
// src/
|
|
157
|
-
var
|
|
158
|
-
__export(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
439
|
+
// src/Logger.ts
|
|
440
|
+
var Logger_exports = {};
|
|
441
|
+
__export(Logger_exports, {
|
|
442
|
+
LogLevelValues: () => LogLevelValues,
|
|
443
|
+
defaultLogger: () => defaultLogger,
|
|
444
|
+
getLogger: () => getLogger,
|
|
445
|
+
runWithLogger: () => runWithLogger,
|
|
446
|
+
silentLogger: () => silentLogger
|
|
164
447
|
});
|
|
448
|
+
var LogLevelValues = [
|
|
449
|
+
"silent",
|
|
450
|
+
"trace",
|
|
451
|
+
"debug",
|
|
452
|
+
"info",
|
|
453
|
+
"warn",
|
|
454
|
+
"error",
|
|
455
|
+
"fatal"
|
|
456
|
+
];
|
|
457
|
+
function defaultLogger(minLevel) {
|
|
458
|
+
const threshold = minLevel ?? "trace";
|
|
459
|
+
const levelIndexByName = LogLevelValues.reduce(
|
|
460
|
+
(acc, lvl, idx) => {
|
|
461
|
+
acc[lvl] = idx;
|
|
462
|
+
return acc;
|
|
463
|
+
},
|
|
464
|
+
{}
|
|
465
|
+
);
|
|
466
|
+
const isEnabled = (methodLevel) => levelIndexByName[methodLevel] >= levelIndexByName[threshold];
|
|
467
|
+
return {
|
|
468
|
+
// biome-ignore lint/suspicious/noConsole: console is used for logging
|
|
469
|
+
trace: isEnabled("trace") ? console.trace.bind(console) : () => {
|
|
470
|
+
},
|
|
471
|
+
// biome-ignore lint/suspicious/noConsole: console is used for logging
|
|
472
|
+
debug: isEnabled("debug") ? console.debug.bind(console) : () => {
|
|
473
|
+
},
|
|
474
|
+
// biome-ignore lint/suspicious/noConsole: console is used for logging
|
|
475
|
+
info: isEnabled("info") ? console.info.bind(console) : () => {
|
|
476
|
+
},
|
|
477
|
+
// biome-ignore lint/suspicious/noConsole: console is used for logging
|
|
478
|
+
warn: isEnabled("warn") ? console.warn.bind(console) : () => {
|
|
479
|
+
},
|
|
480
|
+
// biome-ignore lint/suspicious/noConsole: console is used for logging
|
|
481
|
+
error: isEnabled("error") ? console.error.bind(console) : () => {
|
|
482
|
+
},
|
|
483
|
+
fatal: isEnabled("fatal") ? (...args) => console.error("[fatal]", ...args) : () => {
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function silentLogger() {
|
|
488
|
+
return {
|
|
489
|
+
trace: () => {
|
|
490
|
+
},
|
|
491
|
+
debug: () => {
|
|
492
|
+
},
|
|
493
|
+
info: () => {
|
|
494
|
+
},
|
|
495
|
+
warn: () => {
|
|
496
|
+
},
|
|
497
|
+
error: () => {
|
|
498
|
+
},
|
|
499
|
+
fatal: () => {
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
var loggerContext = new AsyncLocalStorage();
|
|
504
|
+
function runWithLogger(logger, fn) {
|
|
505
|
+
return loggerContext.run(logger, fn);
|
|
506
|
+
}
|
|
507
|
+
function getLogger() {
|
|
508
|
+
return loggerContext.getStore() ?? defaultLogger();
|
|
509
|
+
}
|
|
165
510
|
|
|
166
511
|
// src/RouterOffer.ts
|
|
167
512
|
var RouterOffer_exports = {};
|
|
@@ -243,158 +588,715 @@ var InvalidRouterOfferError = class extends Errors.BaseError {
|
|
|
243
588
|
}
|
|
244
589
|
};
|
|
245
590
|
|
|
246
|
-
// src/
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
message: "Chains must be comma-separated chain IDs"
|
|
265
|
-
}).transform((val) => val.split(",").map(Number)).optional().meta({
|
|
266
|
-
description: "Filter by multiple blockchain networks (comma-separated chain IDs)",
|
|
267
|
-
example: "1,137,10"
|
|
268
|
-
}),
|
|
269
|
-
loan_tokens: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
|
|
270
|
-
message: "Loan assets must be comma-separated Ethereum addresses"
|
|
271
|
-
}).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
|
|
272
|
-
description: "Filter by multiple loan assets (comma-separated)",
|
|
273
|
-
example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
274
|
-
}),
|
|
275
|
-
status: z.string().regex(/^[a-zA-Z_]+(,[a-zA-Z_]+)*$/, {
|
|
276
|
-
message: "Status must be comma-separated status values"
|
|
277
|
-
}).transform((val) => val.split(",")).refine((statuses) => statuses.every((status) => OfferStatusValues.includes(status)), {
|
|
278
|
-
message: `Invalid status value. Must be one of: ${OfferStatusValues.join(", ")}`
|
|
279
|
-
}).optional().meta({
|
|
280
|
-
description: `Filter by multiple statuses (comma-separated). Valid values: ${OfferStatusValues.join(", ")}. By default, only offers with 'valid' status are returned.`,
|
|
281
|
-
example: "valid,callback_error"
|
|
282
|
-
}),
|
|
283
|
-
callback_addresses: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
|
|
284
|
-
message: "Callback addresses must be comma-separated Ethereum addresses"
|
|
285
|
-
}).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
|
|
286
|
-
description: "Filter by multiple callback addresses (comma-separated)",
|
|
287
|
-
example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
288
|
-
}),
|
|
289
|
-
// Asset range
|
|
290
|
-
min_amount: z.bigint({ coerce: true }).positive({
|
|
291
|
-
message: "Min amount must be a positive number"
|
|
292
|
-
}).optional().meta({
|
|
293
|
-
description: "Minimum amount of assets in the offer",
|
|
294
|
-
example: "1000"
|
|
295
|
-
}),
|
|
296
|
-
max_amount: z.bigint({ coerce: true }).positive({
|
|
297
|
-
message: "Max amount must be a positive number"
|
|
298
|
-
}).optional().meta({
|
|
299
|
-
description: "Maximum amount of assets in the offer",
|
|
300
|
-
example: "10000"
|
|
301
|
-
}),
|
|
302
|
-
// Rate range
|
|
303
|
-
min_rate: z.bigint({ coerce: true }).positive({
|
|
304
|
-
message: "Min rate must be a positive number"
|
|
305
|
-
}).optional().meta({
|
|
306
|
-
description: "Minimum rate per asset (in wei)",
|
|
307
|
-
example: "500000000000000000"
|
|
308
|
-
}),
|
|
309
|
-
max_rate: z.bigint({ coerce: true }).positive({
|
|
310
|
-
message: "Max rate must be a positive number"
|
|
311
|
-
}).optional().meta({
|
|
312
|
-
description: "Maximum rate per asset (in wei)",
|
|
313
|
-
example: "1500000000000000000"
|
|
314
|
-
}),
|
|
315
|
-
// Time range
|
|
316
|
-
min_maturity: z.coerce.number().int().min(0).optional().meta({
|
|
317
|
-
description: "Minimum maturity timestamp (Unix timestamp in seconds)",
|
|
318
|
-
example: "1700000000"
|
|
319
|
-
}),
|
|
320
|
-
max_maturity: z.coerce.number().int().min(0).optional().meta({
|
|
321
|
-
description: "Maximum maturity timestamp (Unix timestamp in seconds)",
|
|
322
|
-
example: "1800000000"
|
|
323
|
-
}),
|
|
324
|
-
min_expiry: z.coerce.number().int().optional().meta({
|
|
325
|
-
description: "Minimum expiry timestamp (Unix timestamp in seconds)",
|
|
326
|
-
example: "1700000000"
|
|
327
|
-
}),
|
|
328
|
-
max_expiry: z.coerce.number().int().optional().meta({
|
|
329
|
-
description: "Maximum expiry timestamp (Unix timestamp in seconds)",
|
|
330
|
-
example: "1800000000"
|
|
331
|
-
}),
|
|
332
|
-
// Collateral filtering
|
|
333
|
-
collateral_assets: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
|
|
334
|
-
message: "Collateral assets must be comma-separated Ethereum addresses"
|
|
335
|
-
}).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
|
|
336
|
-
description: "Filter by multiple collateral assets (comma-separated)",
|
|
337
|
-
example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
338
|
-
}),
|
|
339
|
-
collateral_oracles: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
|
|
340
|
-
message: "Collateral oracles must be comma-separated Ethereum addresses"
|
|
341
|
-
}).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
|
|
342
|
-
description: "Filter by multiple rate oracles (comma-separated)",
|
|
343
|
-
example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
344
|
-
}),
|
|
345
|
-
collateral_tuple: z.string().transform((val, ctx) => {
|
|
346
|
-
const pattern = /^(0x[a-fA-F0-9]{40}(:0x[a-fA-F0-9]{40})?(:[0-9]+(\.[0-9]+)?)?)(#0x[a-fA-F0-9]{40}(:0x[a-fA-F0-9]{40})?(:[0-9]+(\.[0-9]+)?)?)*$/;
|
|
347
|
-
if (!pattern.test(val)) {
|
|
348
|
-
ctx.addIssue({
|
|
349
|
-
code: "custom",
|
|
350
|
-
message: "collateral_tuple has an invalid format",
|
|
351
|
-
input: val
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
return val.split("#").map((tuple) => {
|
|
355
|
-
const parts = tuple.split(":");
|
|
356
|
-
if (parts.length === 0 || !parts[0]) {
|
|
357
|
-
ctx.addIssue({
|
|
358
|
-
code: "custom",
|
|
359
|
-
message: "Asset address is required for each collateral tuple",
|
|
360
|
-
path: ["asset"],
|
|
361
|
-
input: val
|
|
591
|
+
// src/Collector/ConsumedEventsCollector.ts
|
|
592
|
+
function createConsumedEventsCollector(parameters) {
|
|
593
|
+
const {
|
|
594
|
+
client,
|
|
595
|
+
contractAddress,
|
|
596
|
+
offerStore,
|
|
597
|
+
collectorBlockStore,
|
|
598
|
+
options: { maxBatchSize, interval } = {},
|
|
599
|
+
chainId
|
|
600
|
+
} = parameters;
|
|
601
|
+
const logger = getLogger();
|
|
602
|
+
const collectorName = "consumed_events";
|
|
603
|
+
const collect = Utils.lazy((emit) => {
|
|
604
|
+
return Utils.poll(
|
|
605
|
+
async () => {
|
|
606
|
+
const lastSyncedBlock = await collectorBlockStore.getBlockNumber({
|
|
607
|
+
collectorName,
|
|
608
|
+
chainId
|
|
362
609
|
});
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
code: "custom",
|
|
371
|
-
message: `LLTV must be between ${MIN_LLTV} and ${MAX_LLTV} (0-100%)`,
|
|
372
|
-
path: ["lltv"],
|
|
373
|
-
input: val
|
|
610
|
+
const stream = Chain.streamLogs({
|
|
611
|
+
client,
|
|
612
|
+
contractAddress,
|
|
613
|
+
event: consumedEvent,
|
|
614
|
+
blockNumberGte: lastSyncedBlock,
|
|
615
|
+
order: "asc",
|
|
616
|
+
options: { maxBatchSize }
|
|
374
617
|
});
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
618
|
+
for await (const logs of stream) {
|
|
619
|
+
if (logs.length === 0) break;
|
|
620
|
+
const parsedLogs = parseEventLogs({ abi: [consumedEvent], logs });
|
|
621
|
+
const offersConsumed = [];
|
|
622
|
+
for (const log of parsedLogs) {
|
|
623
|
+
offersConsumed.push(
|
|
624
|
+
fromConsumedLog({
|
|
625
|
+
blockNumber: log.blockNumber,
|
|
626
|
+
logIndex: log.logIndex,
|
|
627
|
+
chainId: Number(chainId),
|
|
628
|
+
transactionHash: log.transactionHash,
|
|
629
|
+
user: log.args.user,
|
|
630
|
+
nonce: log.args.nonce,
|
|
631
|
+
amount: log.args.amount
|
|
632
|
+
})
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
try {
|
|
636
|
+
for (const offerConsumed of offersConsumed) {
|
|
637
|
+
logger.debug?.("updating consumed amount", { offerConsumed });
|
|
638
|
+
await offerStore.updateConsumedAmount({
|
|
639
|
+
id: offerConsumed.id,
|
|
640
|
+
chainId: offerConsumed.chainId,
|
|
641
|
+
offering: offerConsumed.offering,
|
|
642
|
+
nonce: offerConsumed.nonce,
|
|
643
|
+
consumed: offerConsumed.amount
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
const lastBlockNumber = Number(logs[logs.length - 1]?.blockNumber);
|
|
647
|
+
await collectorBlockStore.saveBlockNumber({
|
|
648
|
+
collectorName: "consumed_events",
|
|
649
|
+
chainId,
|
|
650
|
+
blockNumber: lastBlockNumber
|
|
651
|
+
});
|
|
652
|
+
emit(lastBlockNumber);
|
|
653
|
+
} catch (err) {
|
|
654
|
+
logger.error({ err }, "Failed to process offer_consumed events");
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
{ interval: interval ?? 3e4 }
|
|
659
|
+
);
|
|
660
|
+
});
|
|
661
|
+
const onReorg = (_lastFinalizedBlockNumber) => {
|
|
662
|
+
};
|
|
663
|
+
return {
|
|
664
|
+
name: collectorName,
|
|
665
|
+
lastSyncedBlock: async () => await collectorBlockStore.getBlockNumber({ collectorName, chainId }),
|
|
666
|
+
onReorg,
|
|
667
|
+
collect
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/Validation.ts
|
|
672
|
+
var Validation_exports = {};
|
|
673
|
+
__export(Validation_exports, {
|
|
674
|
+
run: () => run
|
|
675
|
+
});
|
|
676
|
+
async function run(parameters) {
|
|
677
|
+
const { items, rules, ctx = {}, chunkSize } = parameters;
|
|
678
|
+
const issues = [];
|
|
679
|
+
let validItems = items.slice();
|
|
680
|
+
for (const rule of rules) {
|
|
681
|
+
if (validItems.length === 0) return { valid: [], issues };
|
|
682
|
+
const indicesToRemove = /* @__PURE__ */ new Set();
|
|
683
|
+
if (rule.kind === "single") {
|
|
684
|
+
for (let i = 0; i < validItems.length; i++) {
|
|
685
|
+
const item = validItems[i];
|
|
686
|
+
const issue = await rule.run(item, ctx);
|
|
687
|
+
if (issue) {
|
|
688
|
+
issues.push({ ...issue, ruleName: rule.name, item });
|
|
689
|
+
indicesToRemove.add(i);
|
|
389
690
|
}
|
|
390
691
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
692
|
+
} else if (rule.kind === "batch") {
|
|
693
|
+
const exec = async (slice, offset) => {
|
|
694
|
+
const map = await rule.run(slice, ctx);
|
|
695
|
+
for (let i = 0; i < slice.length; i++) {
|
|
696
|
+
const issue = map.get(i);
|
|
697
|
+
if (issue !== void 0) {
|
|
698
|
+
issues.push({ ...issue, ruleName: rule.name, item: slice[i] });
|
|
699
|
+
indicesToRemove.add(offset + i);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
if (!chunkSize) await exec(validItems, 0);
|
|
704
|
+
else {
|
|
705
|
+
for (let i = 0; i < validItems.length; i += chunkSize) {
|
|
706
|
+
await exec(validItems.slice(i, i + chunkSize), i);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
validItems = validItems.filter((_, i) => !indicesToRemove.has(i));
|
|
711
|
+
}
|
|
712
|
+
return {
|
|
713
|
+
valid: validItems,
|
|
714
|
+
issues
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/ValidationRule.ts
|
|
719
|
+
var ValidationRule_exports = {};
|
|
720
|
+
__export(ValidationRule_exports, {
|
|
721
|
+
batch: () => batch,
|
|
722
|
+
morpho: () => morpho,
|
|
723
|
+
single: () => single
|
|
724
|
+
});
|
|
725
|
+
function single(name, run2) {
|
|
726
|
+
return { kind: "single", name, run: run2 };
|
|
727
|
+
}
|
|
728
|
+
function batch(name, run2) {
|
|
729
|
+
return { kind: "batch", name, run: run2 };
|
|
730
|
+
}
|
|
731
|
+
function morpho() {
|
|
732
|
+
const chainId = single("chain_id", (offer, { chain }) => {
|
|
733
|
+
if (chain.id !== offer.chainId) {
|
|
734
|
+
return {
|
|
735
|
+
message: `Chain ID ${offer.chainId} is not the same as the chain ID in the context (${chain.id})`
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
const loanToken = single("loan_token", (offer, { chain }) => {
|
|
740
|
+
const tokens = new Set(
|
|
741
|
+
Array.from(chain.whitelistedAssets.values()).map((a) => a.toLowerCase())
|
|
742
|
+
);
|
|
743
|
+
if (!tokens.has(offer.loanToken.toLowerCase())) {
|
|
744
|
+
return {
|
|
745
|
+
message: `Loan token ${offer.loanToken} is not whitelisted on chain ${offer.chainId}`
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
const expiry = single("expiry", (offer, _) => {
|
|
750
|
+
if (offer.expiry < Math.floor(Date.now() / 1e3)) {
|
|
751
|
+
return { message: "Expiry mismatch" };
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
const callback = single("empty_callback", (offer, _) => {
|
|
755
|
+
if (!offer.buy || offer.callback.data !== "0x") {
|
|
756
|
+
return { message: "Callback not supported yet." };
|
|
757
|
+
}
|
|
758
|
+
});
|
|
759
|
+
return [
|
|
760
|
+
chainId,
|
|
761
|
+
loanToken,
|
|
762
|
+
expiry,
|
|
763
|
+
// note: callback rule should be the last one, since it does not mean that the offer is forever invalid
|
|
764
|
+
// integrators should be able to choose if they want to keep the offer or not
|
|
765
|
+
callback
|
|
766
|
+
];
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/Collector/MempoolCollector.ts
|
|
770
|
+
function createMempoolCollector(parameters) {
|
|
771
|
+
const logger = getLogger();
|
|
772
|
+
const {
|
|
773
|
+
mempool,
|
|
774
|
+
offerStore,
|
|
775
|
+
collectorBlockStore,
|
|
776
|
+
chain,
|
|
777
|
+
options: { maxBatchSize, interval } = {}
|
|
778
|
+
} = parameters;
|
|
779
|
+
const collectorName = "mempool_offers";
|
|
780
|
+
const collect = Utils.lazy((emit) => {
|
|
781
|
+
return mempool.watch({
|
|
782
|
+
lastSyncedBlock: async () => await collectorBlockStore.getBlockNumber({ collectorName, chainId: chain.id }),
|
|
783
|
+
polling: { maxBatchSize, interval },
|
|
784
|
+
onOffers: async (offers2, blockNumber) => {
|
|
785
|
+
try {
|
|
786
|
+
const { valid: validOffers, issues } = await run({
|
|
787
|
+
items: offers2,
|
|
788
|
+
rules: morpho(),
|
|
789
|
+
ctx: { chain }
|
|
790
|
+
});
|
|
791
|
+
const invalidOffersToSave = [];
|
|
792
|
+
const issueToStatus = {
|
|
793
|
+
empty_callback: "callback_not_supported",
|
|
794
|
+
sell_offers_empty_callback: "callback_not_supported",
|
|
795
|
+
buy_offers_empty_callback: "callback_error"
|
|
796
|
+
};
|
|
797
|
+
for (const issue of issues) {
|
|
798
|
+
const status = issueToStatus[issue.ruleName];
|
|
799
|
+
if (status) {
|
|
800
|
+
invalidOffersToSave.push({
|
|
801
|
+
offer: issue.item,
|
|
802
|
+
status,
|
|
803
|
+
metadata: { issue: issue.ruleName }
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
await offerStore.createMany([
|
|
808
|
+
...validOffers.map((offer) => ({
|
|
809
|
+
offer,
|
|
810
|
+
status: "valid"
|
|
811
|
+
})),
|
|
812
|
+
...invalidOffersToSave
|
|
813
|
+
]);
|
|
814
|
+
} catch (err) {
|
|
815
|
+
logger.error(
|
|
816
|
+
{ err },
|
|
817
|
+
"Failed to process offer_created events, falling back to unverified status"
|
|
818
|
+
);
|
|
819
|
+
await offerStore.createMany(
|
|
820
|
+
offers2.map((offer) => ({
|
|
821
|
+
offer,
|
|
822
|
+
status: "unverified",
|
|
823
|
+
metadata: { issue: "Offer processing failed" }
|
|
824
|
+
}))
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
await collectorBlockStore.saveBlockNumber({
|
|
828
|
+
collectorName,
|
|
829
|
+
chainId: chain.id,
|
|
830
|
+
blockNumber
|
|
831
|
+
});
|
|
832
|
+
emit(blockNumber);
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
const onReorg = (_lastFinalizedBlockNumber) => {
|
|
837
|
+
};
|
|
838
|
+
return {
|
|
839
|
+
name: collectorName,
|
|
840
|
+
lastSyncedBlock: async () => await collectorBlockStore.getBlockNumber({ collectorName, chainId: chain.id }),
|
|
841
|
+
collect,
|
|
842
|
+
onReorg
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// src/CollectorBlockStore.ts
|
|
847
|
+
var CollectorBlockStore_exports = {};
|
|
848
|
+
__export(CollectorBlockStore_exports, {
|
|
849
|
+
create: () => create,
|
|
850
|
+
memory: () => memory
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
// src/OfferStore/drizzle/schema.ts
|
|
854
|
+
var schema_exports = {};
|
|
855
|
+
__export(schema_exports, {
|
|
856
|
+
VERSION: () => VERSION,
|
|
857
|
+
availableLiquidityPools: () => availableLiquidityPools,
|
|
858
|
+
availableLiquidityQueues: () => availableLiquidityQueues,
|
|
859
|
+
collectorBlockNumbers: () => collectorBlockNumbers,
|
|
860
|
+
consumed: () => consumed,
|
|
861
|
+
offerCollaterals: () => offerCollaterals,
|
|
862
|
+
offerStatus: () => offerStatus,
|
|
863
|
+
offers: () => offers,
|
|
864
|
+
userPositions: () => userPositions
|
|
865
|
+
});
|
|
866
|
+
var VERSION = "offers_v1.1";
|
|
867
|
+
var s = pgSchema(VERSION);
|
|
868
|
+
var offers = s.table(
|
|
869
|
+
"offers",
|
|
870
|
+
{
|
|
871
|
+
hash: varchar("hash", { length: 66 }).primaryKey(),
|
|
872
|
+
offering: varchar("offering", { length: 42 }).notNull(),
|
|
873
|
+
assets: numeric("assets", { precision: 78, scale: 0 }).notNull(),
|
|
874
|
+
rate: bigint("rate", { mode: "bigint" }).notNull(),
|
|
875
|
+
maturity: integer("maturity").notNull(),
|
|
876
|
+
expiry: integer("expiry").notNull(),
|
|
877
|
+
start: integer("start").notNull(),
|
|
878
|
+
nonce: bigint("nonce", { mode: "bigint" }).notNull(),
|
|
879
|
+
buy: boolean("buy").notNull(),
|
|
880
|
+
chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
|
|
881
|
+
loanToken: varchar("loan_token", { length: 42 }).notNull(),
|
|
882
|
+
callbackAddress: varchar("callback_address", { length: 42 }).notNull(),
|
|
883
|
+
callbackData: text("callback_data").notNull(),
|
|
884
|
+
callbackGasLimit: bigint("callback_gas_limit", { mode: "bigint" }).notNull(),
|
|
885
|
+
signature: varchar("signature", { length: 132 }),
|
|
886
|
+
callbackId: varchar("callback_id", { length: 256 }),
|
|
887
|
+
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
888
|
+
},
|
|
889
|
+
(table) => [
|
|
890
|
+
index("offers_offering_idx").on(table.offering),
|
|
891
|
+
index("offers_buy_idx").on(table.buy),
|
|
892
|
+
index("offers_chain_id_idx").on(table.chainId),
|
|
893
|
+
index("offers_loan_token_idx").on(table.loanToken),
|
|
894
|
+
index("offers_maturity_idx").on(table.maturity),
|
|
895
|
+
index("offers_expiry_idx").on(table.expiry),
|
|
896
|
+
index("offers_rate_idx").on(table.rate),
|
|
897
|
+
index("offers_assets_idx").on(table.assets),
|
|
898
|
+
// Compound indices for cursor pagination with hash
|
|
899
|
+
index("offers_rate_hash_idx").on(table.rate, table.hash),
|
|
900
|
+
index("offers_maturity_hash_idx").on(table.maturity, table.hash),
|
|
901
|
+
index("offers_expiry_hash_idx").on(table.expiry, table.hash),
|
|
902
|
+
index("offers_assets_hash_idx").on(table.assets, table.hash)
|
|
903
|
+
]
|
|
904
|
+
);
|
|
905
|
+
var offerCollaterals = s.table(
|
|
906
|
+
"offer_collaterals",
|
|
907
|
+
{
|
|
908
|
+
id: serial("id").primaryKey(),
|
|
909
|
+
offerHash: varchar("offer_hash", { length: 66 }).notNull().references(() => offers.hash, { onDelete: "cascade" }),
|
|
910
|
+
asset: varchar("asset", { length: 42 }).notNull(),
|
|
911
|
+
oracle: varchar("oracle", { length: 42 }).notNull(),
|
|
912
|
+
lltv: bigint("lltv", { mode: "bigint" }).notNull()
|
|
913
|
+
},
|
|
914
|
+
(table) => [
|
|
915
|
+
index("offer_collaterals_offer_hash_idx").on(table.offerHash),
|
|
916
|
+
index("offer_collaterals_asset_idx").on(table.asset),
|
|
917
|
+
index("offer_collaterals_oracle_idx").on(table.oracle),
|
|
918
|
+
// Composite index
|
|
919
|
+
index("offer_collaterals_tuple_idx").on(table.asset, table.oracle, table.lltv)
|
|
920
|
+
]
|
|
921
|
+
);
|
|
922
|
+
var offerStatus = s.table(
|
|
923
|
+
"offer_status",
|
|
924
|
+
{
|
|
925
|
+
id: serial("id").primaryKey(),
|
|
926
|
+
offerHash: varchar("offer_hash", { length: 66 }).notNull().references(() => offers.hash, { onDelete: "cascade" }),
|
|
927
|
+
status: text("status").$type().notNull(),
|
|
928
|
+
metadata: jsonb("metadata"),
|
|
929
|
+
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
930
|
+
},
|
|
931
|
+
(table) => [
|
|
932
|
+
index("offer_status_offer_hash_created_at_idx").on(table.offerHash, desc(table.createdAt)),
|
|
933
|
+
index("offer_status_status_idx").on(table.status)
|
|
934
|
+
]
|
|
935
|
+
);
|
|
936
|
+
var consumed = s.table(
|
|
937
|
+
"consumed_per_user_and_nonce",
|
|
938
|
+
{
|
|
939
|
+
id: varchar("id", { length: 255 }).primaryKey(),
|
|
940
|
+
chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
|
|
941
|
+
offering: varchar("offering", { length: 42 }).notNull(),
|
|
942
|
+
nonce: bigint("nonce", { mode: "bigint" }).notNull(),
|
|
943
|
+
consumed: numeric("consumed", { precision: 78, scale: 0 }).notNull(),
|
|
944
|
+
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
945
|
+
},
|
|
946
|
+
(table) => [
|
|
947
|
+
index("consumed_per_user_and_nonce_chain_id_offering_nonce_created_at_idx").on(
|
|
948
|
+
table.chainId,
|
|
949
|
+
table.offering,
|
|
950
|
+
table.nonce,
|
|
951
|
+
desc(table.createdAt)
|
|
952
|
+
)
|
|
953
|
+
]
|
|
954
|
+
);
|
|
955
|
+
var collectorBlockNumbers = s.table(
|
|
956
|
+
"collector_block_numbers",
|
|
957
|
+
{
|
|
958
|
+
chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
|
|
959
|
+
name: text("name").notNull(),
|
|
960
|
+
blockNumber: bigint("block_number", { mode: "number" }).notNull(),
|
|
961
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
962
|
+
},
|
|
963
|
+
(table) => [uniqueIndex("collector_block_numbers_chain_name_idx").on(table.chainId, table.name)]
|
|
964
|
+
);
|
|
965
|
+
var availableLiquidityPools = s.table("available_liquidity_pools", {
|
|
966
|
+
id: varchar("id", { length: 255 }).primaryKey(),
|
|
967
|
+
amount: numeric("amount", { precision: 78, scale: 0 }).notNull(),
|
|
968
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
969
|
+
});
|
|
970
|
+
var availableLiquidityQueues = s.table(
|
|
971
|
+
"available_liquidity_queues",
|
|
972
|
+
{
|
|
973
|
+
queueId: varchar("queue_id", { length: 255 }).notNull(),
|
|
974
|
+
availableLiquidityPoolId: varchar("available_liquidity_pool_id", { length: 255 }).notNull().references(() => availableLiquidityPools.id, { onDelete: "cascade" }),
|
|
975
|
+
index: integer("index").notNull(),
|
|
976
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
977
|
+
},
|
|
978
|
+
(table) => [
|
|
979
|
+
primaryKey({
|
|
980
|
+
columns: [table.queueId, table.availableLiquidityPoolId],
|
|
981
|
+
name: "available_liquidity_queues_pk"
|
|
982
|
+
}),
|
|
983
|
+
index("available_liquidity_queues_queue_index_idx").on(table.queueId, table.index)
|
|
984
|
+
]
|
|
985
|
+
);
|
|
986
|
+
var userPositions = s.table(
|
|
987
|
+
"user_positions",
|
|
988
|
+
{
|
|
989
|
+
id: varchar("id", { length: 255 }).primaryKey(),
|
|
990
|
+
availableLiquidityQueueId: varchar("available_liquidity_queue_id", { length: 255 }).notNull(),
|
|
991
|
+
user: varchar("user", { length: 255 }).notNull(),
|
|
992
|
+
chainId: bigint("chain_id", { mode: "bigint" }).notNull(),
|
|
993
|
+
amount: numeric("amount", { precision: 78, scale: 0 }).notNull(),
|
|
994
|
+
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
995
|
+
},
|
|
996
|
+
(table) => [
|
|
997
|
+
index("user_positions_available_liquidity_queue_id_idx").on(table.availableLiquidityQueueId)
|
|
998
|
+
]
|
|
999
|
+
);
|
|
1000
|
+
|
|
1001
|
+
// src/CollectorBlockStore.ts
|
|
1002
|
+
var create = (config) => {
|
|
1003
|
+
const db = config.db;
|
|
1004
|
+
return {
|
|
1005
|
+
getBlockNumber: async (parameters) => {
|
|
1006
|
+
const name = parameters.collectorName.toLowerCase();
|
|
1007
|
+
const result = await db.select({ blockNumber: collectorBlockNumbers.blockNumber }).from(collectorBlockNumbers).where(
|
|
1008
|
+
and(
|
|
1009
|
+
eq(collectorBlockNumbers.name, name),
|
|
1010
|
+
eq(collectorBlockNumbers.chainId, parameters.chainId)
|
|
1011
|
+
)
|
|
1012
|
+
).limit(1);
|
|
1013
|
+
if (result.length === 0)
|
|
1014
|
+
return Chain.getChain(parameters.chainId)?.mempool.deploymentBlock || 0;
|
|
1015
|
+
return Number(result[0].blockNumber);
|
|
1016
|
+
},
|
|
1017
|
+
saveBlockNumber: async (parameters) => {
|
|
1018
|
+
const name = parameters.collectorName.toLowerCase();
|
|
1019
|
+
await db.insert(collectorBlockNumbers).values({
|
|
1020
|
+
chainId: parameters.chainId,
|
|
1021
|
+
name,
|
|
1022
|
+
blockNumber: parameters.blockNumber,
|
|
1023
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1024
|
+
}).onConflictDoUpdate({
|
|
1025
|
+
target: [collectorBlockNumbers.chainId, collectorBlockNumbers.name],
|
|
1026
|
+
set: {
|
|
1027
|
+
blockNumber: parameters.blockNumber,
|
|
1028
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
};
|
|
1034
|
+
function memory() {
|
|
1035
|
+
const blockNumbers = /* @__PURE__ */ new Map();
|
|
1036
|
+
return {
|
|
1037
|
+
getBlockNumber: async (parameters) => {
|
|
1038
|
+
const name = parameters.collectorName.toLowerCase();
|
|
1039
|
+
return blockNumbers.get(name)?.get(parameters.chainId) || Chain.getChain(parameters.chainId)?.mempool.deploymentBlock || 0;
|
|
1040
|
+
},
|
|
1041
|
+
saveBlockNumber: async (parameters) => {
|
|
1042
|
+
const name = parameters.collectorName.toLowerCase();
|
|
1043
|
+
if (!blockNumbers.has(name)) {
|
|
1044
|
+
blockNumbers.set(name, /* @__PURE__ */ new Map());
|
|
1045
|
+
}
|
|
1046
|
+
if (!blockNumbers.get(name)?.has(parameters.chainId)) {
|
|
1047
|
+
blockNumbers.get(name).set(parameters.chainId, parameters.blockNumber);
|
|
1048
|
+
}
|
|
1049
|
+
blockNumbers.get(name).set(parameters.chainId, parameters.blockNumber);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// src/Cursor.ts
|
|
1055
|
+
var Cursor_exports = {};
|
|
1056
|
+
__export(Cursor_exports, {
|
|
1057
|
+
decode: () => decode,
|
|
1058
|
+
encode: () => encode,
|
|
1059
|
+
validate: () => validate
|
|
1060
|
+
});
|
|
1061
|
+
function validate(cursor) {
|
|
1062
|
+
if (!cursor || typeof cursor !== "object") {
|
|
1063
|
+
throw new Error("Cursor must be an object");
|
|
1064
|
+
}
|
|
1065
|
+
const c = cursor;
|
|
1066
|
+
if (!["rate", "maturity", "expiry", "amount"].includes(c.sort)) {
|
|
1067
|
+
throw new Error(
|
|
1068
|
+
`Invalid sort field: ${c.sort}. Must be one of: rate, maturity, expiry, amount`
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
if (!["asc", "desc"].includes(c.dir)) {
|
|
1072
|
+
throw new Error(`Invalid direction: ${c.dir}. Must be one of: asc, desc`);
|
|
1073
|
+
}
|
|
1074
|
+
if (!/^0x[a-fA-F0-9]{64}$/.test(c.hash)) {
|
|
1075
|
+
throw new Error(
|
|
1076
|
+
`Invalid hash format: ${c.hash}. Must be a 64-character hex string starting with 0x`
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
const validations = {
|
|
1080
|
+
rate: {
|
|
1081
|
+
field: "rate",
|
|
1082
|
+
type: "string",
|
|
1083
|
+
pattern: /^\d+$/,
|
|
1084
|
+
error: "numeric string"
|
|
1085
|
+
},
|
|
1086
|
+
amount: {
|
|
1087
|
+
field: "assets",
|
|
1088
|
+
type: "string",
|
|
1089
|
+
pattern: /^\d+$/,
|
|
1090
|
+
error: "numeric string"
|
|
1091
|
+
},
|
|
1092
|
+
maturity: {
|
|
1093
|
+
field: "maturity",
|
|
1094
|
+
type: "number",
|
|
1095
|
+
validator: (val) => val > 0,
|
|
1096
|
+
error: "positive number"
|
|
1097
|
+
},
|
|
1098
|
+
expiry: {
|
|
1099
|
+
field: "expiry",
|
|
1100
|
+
type: "number",
|
|
1101
|
+
validator: (val) => val > 0,
|
|
1102
|
+
error: "positive number"
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
const validation = validations[c.sort];
|
|
1106
|
+
if (!validation) {
|
|
1107
|
+
throw new Error(`Invalid sort field: ${c.sort}`);
|
|
1108
|
+
}
|
|
1109
|
+
const fieldValue = c[validation.field];
|
|
1110
|
+
if (!fieldValue) {
|
|
1111
|
+
throw new Error(`${c.sort} sort requires '${validation.field}' field to be present`);
|
|
1112
|
+
}
|
|
1113
|
+
if (typeof fieldValue !== validation.type) {
|
|
1114
|
+
throw new Error(
|
|
1115
|
+
`${c.sort} sort requires '${validation.field}' field of type ${validation.type}`
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
if (validation.pattern && !validation.pattern.test(fieldValue)) {
|
|
1119
|
+
throw new Error(
|
|
1120
|
+
`Invalid ${validation.field} format: ${fieldValue}. Must be a ${validation.error}`
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
if (validation.validator && !validation.validator(fieldValue)) {
|
|
1124
|
+
throw new Error(
|
|
1125
|
+
`Invalid ${validation.field} value: ${fieldValue}. Must be a ${validation.error}`
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
return true;
|
|
1129
|
+
}
|
|
1130
|
+
function encode(c) {
|
|
1131
|
+
return Base64.encodeURL(JSON.stringify(c));
|
|
1132
|
+
}
|
|
1133
|
+
function decode(token) {
|
|
1134
|
+
if (!token) return null;
|
|
1135
|
+
const decoded = JSON.parse(Base64.decode(token));
|
|
1136
|
+
validate(decoded);
|
|
1137
|
+
return decoded;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// src/core/apiSchema/index.ts
|
|
1141
|
+
var apiSchema_exports = {};
|
|
1142
|
+
__export(apiSchema_exports, {
|
|
1143
|
+
OpenApi: () => OpenApi,
|
|
1144
|
+
fromResponse: () => fromResponse,
|
|
1145
|
+
parse: () => parse,
|
|
1146
|
+
safeParse: () => safeParse,
|
|
1147
|
+
toResponse: () => toResponse
|
|
1148
|
+
});
|
|
1149
|
+
var MAX_LIMIT = 100;
|
|
1150
|
+
var DEFAULT_LIMIT = 20;
|
|
1151
|
+
var MAX_LLTV = 100;
|
|
1152
|
+
var MIN_LLTV = 0;
|
|
1153
|
+
var GetOffersQueryParams = z.object({
|
|
1154
|
+
// Core filtering parameters
|
|
1155
|
+
creators: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
|
|
1156
|
+
message: "Creators must be comma-separated Ethereum addresses"
|
|
1157
|
+
}).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
|
|
1158
|
+
description: "Filter by multiple creator addresses (comma-separated)",
|
|
1159
|
+
example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
1160
|
+
}),
|
|
1161
|
+
side: z.enum(["buy", "sell"]).optional().meta({
|
|
1162
|
+
description: "Filter by offer type: buy offers or sell offers",
|
|
1163
|
+
example: "buy"
|
|
1164
|
+
}),
|
|
1165
|
+
chains: z.string().regex(/^\d+(,\d+)*$/, {
|
|
1166
|
+
message: "Chains must be comma-separated chain IDs"
|
|
1167
|
+
}).transform((val) => val.split(",").map(Number)).optional().meta({
|
|
1168
|
+
description: "Filter by multiple blockchain networks (comma-separated chain IDs)",
|
|
1169
|
+
example: "1,137,10"
|
|
1170
|
+
}),
|
|
1171
|
+
loan_tokens: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
|
|
1172
|
+
message: "Loan assets must be comma-separated Ethereum addresses"
|
|
1173
|
+
}).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
|
|
1174
|
+
description: "Filter by multiple loan assets (comma-separated)",
|
|
1175
|
+
example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
1176
|
+
}),
|
|
1177
|
+
status: z.string().regex(/^[a-zA-Z_]+(,[a-zA-Z_]+)*$/, {
|
|
1178
|
+
message: "Status must be comma-separated status values"
|
|
1179
|
+
}).transform((val) => val.split(",")).refine((statuses) => statuses.every((status) => OfferStatusValues.includes(status)), {
|
|
1180
|
+
message: `Invalid status value. Must be one of: ${OfferStatusValues.join(", ")}`
|
|
1181
|
+
}).optional().meta({
|
|
1182
|
+
description: `Filter by multiple statuses (comma-separated). Valid values: ${OfferStatusValues.join(", ")}. By default, only offers with 'valid' status are returned.`,
|
|
1183
|
+
example: "valid,callback_error"
|
|
1184
|
+
}),
|
|
1185
|
+
callback_addresses: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
|
|
1186
|
+
message: "Callback addresses must be comma-separated Ethereum addresses"
|
|
1187
|
+
}).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
|
|
1188
|
+
description: "Filter by multiple callback addresses (comma-separated)",
|
|
1189
|
+
example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
1190
|
+
}),
|
|
1191
|
+
// Asset range
|
|
1192
|
+
min_amount: z.bigint({ coerce: true }).positive({
|
|
1193
|
+
message: "Min amount must be a positive number"
|
|
1194
|
+
}).optional().meta({
|
|
1195
|
+
description: "Minimum amount of assets in the offer",
|
|
1196
|
+
example: "1000"
|
|
1197
|
+
}),
|
|
1198
|
+
max_amount: z.bigint({ coerce: true }).positive({
|
|
1199
|
+
message: "Max amount must be a positive number"
|
|
1200
|
+
}).optional().meta({
|
|
1201
|
+
description: "Maximum amount of assets in the offer",
|
|
1202
|
+
example: "10000"
|
|
1203
|
+
}),
|
|
1204
|
+
// Rate range
|
|
1205
|
+
min_rate: z.bigint({ coerce: true }).positive({
|
|
1206
|
+
message: "Min rate must be a positive number"
|
|
1207
|
+
}).optional().meta({
|
|
1208
|
+
description: "Minimum rate per asset (in wei)",
|
|
1209
|
+
example: "500000000000000000"
|
|
1210
|
+
}),
|
|
1211
|
+
max_rate: z.bigint({ coerce: true }).positive({
|
|
1212
|
+
message: "Max rate must be a positive number"
|
|
1213
|
+
}).optional().meta({
|
|
1214
|
+
description: "Maximum rate per asset (in wei)",
|
|
1215
|
+
example: "1500000000000000000"
|
|
1216
|
+
}),
|
|
1217
|
+
// Time range
|
|
1218
|
+
min_maturity: z.coerce.number().int().min(0).optional().meta({
|
|
1219
|
+
description: "Minimum maturity timestamp (Unix timestamp in seconds)",
|
|
1220
|
+
example: "1700000000"
|
|
1221
|
+
}),
|
|
1222
|
+
max_maturity: z.coerce.number().int().min(0).optional().meta({
|
|
1223
|
+
description: "Maximum maturity timestamp (Unix timestamp in seconds)",
|
|
1224
|
+
example: "1800000000"
|
|
1225
|
+
}),
|
|
1226
|
+
min_expiry: z.coerce.number().int().optional().meta({
|
|
1227
|
+
description: "Minimum expiry timestamp (Unix timestamp in seconds)",
|
|
1228
|
+
example: "1700000000"
|
|
1229
|
+
}),
|
|
1230
|
+
max_expiry: z.coerce.number().int().optional().meta({
|
|
1231
|
+
description: "Maximum expiry timestamp (Unix timestamp in seconds)",
|
|
1232
|
+
example: "1800000000"
|
|
1233
|
+
}),
|
|
1234
|
+
// Collateral filtering
|
|
1235
|
+
collateral_assets: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
|
|
1236
|
+
message: "Collateral assets must be comma-separated Ethereum addresses"
|
|
1237
|
+
}).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
|
|
1238
|
+
description: "Filter by multiple collateral assets (comma-separated)",
|
|
1239
|
+
example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
1240
|
+
}),
|
|
1241
|
+
collateral_oracles: z.string().regex(/^0x[a-fA-F0-9]{40}(,0x[a-fA-F0-9]{40})*$/, {
|
|
1242
|
+
message: "Collateral oracles must be comma-separated Ethereum addresses"
|
|
1243
|
+
}).transform((val) => val.split(",").map((addr) => addr.toLowerCase())).optional().meta({
|
|
1244
|
+
description: "Filter by multiple rate oracles (comma-separated)",
|
|
1245
|
+
example: "0x1234567890123456789012345678901234567890,0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"
|
|
1246
|
+
}),
|
|
1247
|
+
collateral_tuple: z.string().transform((val, ctx) => {
|
|
1248
|
+
const pattern = /^(0x[a-fA-F0-9]{40}(:0x[a-fA-F0-9]{40})?(:[0-9]+(\.[0-9]+)?)?)(#0x[a-fA-F0-9]{40}(:0x[a-fA-F0-9]{40})?(:[0-9]+(\.[0-9]+)?)?)*$/;
|
|
1249
|
+
if (!pattern.test(val)) {
|
|
1250
|
+
ctx.addIssue({
|
|
1251
|
+
code: "custom",
|
|
1252
|
+
message: "collateral_tuple has an invalid format",
|
|
1253
|
+
input: val
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
return val.split("#").map((tuple) => {
|
|
1257
|
+
const parts = tuple.split(":");
|
|
1258
|
+
if (parts.length === 0 || !parts[0]) {
|
|
1259
|
+
ctx.addIssue({
|
|
1260
|
+
code: "custom",
|
|
1261
|
+
message: "Asset address is required for each collateral tuple",
|
|
1262
|
+
path: ["asset"],
|
|
1263
|
+
input: val
|
|
1264
|
+
});
|
|
1265
|
+
return z.NEVER;
|
|
1266
|
+
}
|
|
1267
|
+
const asset = parts[0]?.toLowerCase();
|
|
1268
|
+
const oracle = parts[1]?.toLowerCase();
|
|
1269
|
+
const lltv = parts[2] ? parseFloat(parts[2]) : void 0;
|
|
1270
|
+
if (lltv !== void 0 && (lltv < MIN_LLTV || lltv > MAX_LLTV)) {
|
|
1271
|
+
ctx.addIssue({
|
|
1272
|
+
code: "custom",
|
|
1273
|
+
message: `LLTV must be between ${MIN_LLTV} and ${MAX_LLTV} (0-100%)`,
|
|
1274
|
+
path: ["lltv"],
|
|
1275
|
+
input: val
|
|
1276
|
+
});
|
|
1277
|
+
return z.NEVER;
|
|
1278
|
+
}
|
|
1279
|
+
let lltvValue;
|
|
1280
|
+
if (lltv !== void 0) {
|
|
1281
|
+
try {
|
|
1282
|
+
lltvValue = LLTV.from(parseUnits(lltv.toString(), 16));
|
|
1283
|
+
} catch (e) {
|
|
1284
|
+
ctx.issues.push({
|
|
1285
|
+
code: "custom",
|
|
1286
|
+
message: e instanceof LLTV.InvalidLLTVError || e instanceof LLTV.InvalidOptionError ? e.message : "Invalid LLTV.",
|
|
1287
|
+
input: lltv,
|
|
1288
|
+
path: ["lltv"]
|
|
1289
|
+
});
|
|
1290
|
+
return z.NEVER;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
asset,
|
|
1295
|
+
oracle,
|
|
1296
|
+
lltv: lltvValue
|
|
1297
|
+
};
|
|
1298
|
+
});
|
|
1299
|
+
}).optional().meta({
|
|
398
1300
|
description: "Filter by collateral combinations in format: asset:oracle:lltv#asset2:oracle2:lltv2. Oracle and lltv are optional. Use # to separate multiple combinations.",
|
|
399
1301
|
example: "0x1234567890123456789012345678901234567890:0xabcdefabcdefabcdefabcdefabcdefabcdefabcd:86#0x9876543210987654321098765432109876543210:94.5"
|
|
400
1302
|
}),
|
|
@@ -740,19 +1642,19 @@ var OpenApi = createDocument({
|
|
|
740
1642
|
|
|
741
1643
|
// src/core/apiSchema/utils.ts
|
|
742
1644
|
function toResponse(routerOffer) {
|
|
743
|
-
const { consumed, status, metadata, ...offer } = routerOffer;
|
|
1645
|
+
const { consumed: consumed2, status, metadata, ...offer } = routerOffer;
|
|
744
1646
|
return {
|
|
745
1647
|
offer,
|
|
746
|
-
consumed,
|
|
1648
|
+
consumed: consumed2,
|
|
747
1649
|
status,
|
|
748
1650
|
metadata
|
|
749
1651
|
};
|
|
750
1652
|
}
|
|
751
1653
|
function fromResponse(offerResponse) {
|
|
752
|
-
const { offer, consumed, status, metadata } = offerResponse;
|
|
1654
|
+
const { offer, consumed: consumed2, status, metadata } = offerResponse;
|
|
753
1655
|
return {
|
|
754
1656
|
...offer,
|
|
755
|
-
consumed,
|
|
1657
|
+
consumed: consumed2,
|
|
756
1658
|
status,
|
|
757
1659
|
metadata
|
|
758
1660
|
};
|
|
@@ -878,8 +1780,8 @@ async function get(config, parameters) {
|
|
|
878
1780
|
if (parameters.limit !== void 0) {
|
|
879
1781
|
url.searchParams.set("limit", parameters.limit.toString());
|
|
880
1782
|
}
|
|
881
|
-
const { cursor: returnedCursor, data:
|
|
882
|
-
const routerOffers =
|
|
1783
|
+
const { cursor: returnedCursor, data: offers2 } = await getApi(config, url);
|
|
1784
|
+
const routerOffers = offers2.map(Format.fromSnakeCase).map(fromResponse);
|
|
883
1785
|
return {
|
|
884
1786
|
cursor: returnedCursor,
|
|
885
1787
|
offers: routerOffers.map(from).map(toResponse)
|
|
@@ -920,8 +1822,8 @@ async function match(config, parameters) {
|
|
|
920
1822
|
if (parameters.limit !== void 0) {
|
|
921
1823
|
url.searchParams.set("limit", parameters.limit.toString());
|
|
922
1824
|
}
|
|
923
|
-
const { cursor: returnedCursor, data:
|
|
924
|
-
const routerOffers =
|
|
1825
|
+
const { cursor: returnedCursor, data: offers2 } = await getApi(config, url);
|
|
1826
|
+
const routerOffers = offers2.map(Format.fromSnakeCase).map(fromResponse);
|
|
925
1827
|
return {
|
|
926
1828
|
cursor: returnedCursor,
|
|
927
1829
|
offers: routerOffers.map(from).map(toResponse)
|
|
@@ -933,501 +1835,87 @@ async function getApi(config, url) {
|
|
|
933
1835
|
switch (true) {
|
|
934
1836
|
case pathname.includes("/v1/offers/match"):
|
|
935
1837
|
action = "match_offers";
|
|
936
|
-
break;
|
|
937
|
-
case pathname.includes("/v1/offers"):
|
|
938
|
-
action = "get_offers";
|
|
939
|
-
break;
|
|
940
|
-
default:
|
|
941
|
-
throw new HttpGetOffersFailedError("Unknown endpoint", {
|
|
942
|
-
details: `Unsupported path: ${pathname}`
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
const schemaParseResult = safeParse(action, Object.fromEntries(url.searchParams));
|
|
946
|
-
if (!schemaParseResult.success) {
|
|
947
|
-
throw new HttpGetOffersFailedError(`Invalid URL parameters`, {
|
|
948
|
-
details: schemaParseResult.error.issues[0]?.message
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
|
-
const response = await fetch(url.toString(), {
|
|
952
|
-
method: "GET",
|
|
953
|
-
headers: config.headers
|
|
954
|
-
});
|
|
955
|
-
if (!response.ok) {
|
|
956
|
-
switch (response.status) {
|
|
957
|
-
case 401:
|
|
958
|
-
throw new HttpUnauthorizedError();
|
|
959
|
-
case 403:
|
|
960
|
-
throw new HttpForbiddenError();
|
|
961
|
-
case 429:
|
|
962
|
-
throw new HttpRateLimitError();
|
|
963
|
-
}
|
|
964
|
-
throw new HttpGetOffersFailedError(`GET request returned ${response.status}`, {
|
|
965
|
-
details: await response.text()
|
|
966
|
-
});
|
|
967
|
-
}
|
|
968
|
-
return response.json();
|
|
969
|
-
}
|
|
970
|
-
var InvalidUrlError = class extends Errors.BaseError {
|
|
971
|
-
name = "Router.InvalidUrlError";
|
|
972
|
-
constructor(url) {
|
|
973
|
-
super(`URL "${url}" is not http/https.`);
|
|
974
|
-
}
|
|
975
|
-
};
|
|
976
|
-
var HttpUnauthorizedError = class extends Errors.BaseError {
|
|
977
|
-
name = "Router.HttpUnauthorizedError";
|
|
978
|
-
constructor() {
|
|
979
|
-
super("Unauthorized.", {
|
|
980
|
-
metaMessages: ["Ensure that an API key is provided."]
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
};
|
|
984
|
-
var HttpForbiddenError = class extends Errors.BaseError {
|
|
985
|
-
name = "Router.HttpForbiddenError";
|
|
986
|
-
constructor() {
|
|
987
|
-
super("Forbidden.", {
|
|
988
|
-
metaMessages: ["Ensure that the API key is valid."]
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
};
|
|
992
|
-
var HttpRateLimitError = class extends Errors.BaseError {
|
|
993
|
-
name = "Router.HttpRateLimitError";
|
|
994
|
-
constructor() {
|
|
995
|
-
super("Rate limit exceeded.", {
|
|
996
|
-
metaMessages: [
|
|
997
|
-
"The number of allowed requests has been exceeded. You must wait for the rate limit to reset."
|
|
998
|
-
]
|
|
999
|
-
});
|
|
1000
|
-
}
|
|
1001
|
-
};
|
|
1002
|
-
var HttpGetOffersFailedError = class extends Errors.BaseError {
|
|
1003
|
-
name = "Router.HttpGetOffersFailedError";
|
|
1004
|
-
constructor(message, { details } = {}) {
|
|
1005
|
-
super(message, {
|
|
1006
|
-
metaMessages: [details]
|
|
1007
|
-
});
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
|
|
1011
|
-
// src/OfferStore/index.ts
|
|
1012
|
-
var OfferStore_exports = {};
|
|
1013
|
-
__export(OfferStore_exports, {
|
|
1014
|
-
memory: () => memory
|
|
1015
|
-
});
|
|
1016
|
-
function memory(parameters) {
|
|
1017
|
-
const map = parameters.offers;
|
|
1018
|
-
const filled = parameters.filled;
|
|
1019
|
-
const consumedIds = /* @__PURE__ */ new Set();
|
|
1020
|
-
const create = async (parameters2) => {
|
|
1021
|
-
if (map.has(parameters2.offer.hash.toLowerCase())) return parameters2.offer.hash;
|
|
1022
|
-
const callbackId = getCallbackIdForOffer(parameters2.offer);
|
|
1023
|
-
map.set(parameters2.offer.hash.toLowerCase(), {
|
|
1024
|
-
...parameters2.offer,
|
|
1025
|
-
...callbackId ? { callbackId } : {},
|
|
1026
|
-
status: parameters2.status,
|
|
1027
|
-
metadata: parameters2.metadata
|
|
1028
|
-
});
|
|
1029
|
-
const chainId = parameters2.offer.chainId;
|
|
1030
|
-
const address = parameters2.offer.offering.toLowerCase();
|
|
1031
|
-
const nonce = parameters2.offer.nonce;
|
|
1032
|
-
const filledForChain = filled.get(chainId) || /* @__PURE__ */ new Map();
|
|
1033
|
-
const filledForOffering = filledForChain.get(address) || /* @__PURE__ */ new Map();
|
|
1034
|
-
if (!filledForOffering.has(nonce)) filledForOffering.set(nonce, 0n);
|
|
1035
|
-
filledForChain.set(address, filledForOffering);
|
|
1036
|
-
filled.set(chainId, filledForChain);
|
|
1037
|
-
return Promise.resolve(parameters2.offer.hash);
|
|
1038
|
-
};
|
|
1039
|
-
const sort = (sortBy, sortOrder, a, b) => {
|
|
1040
|
-
sortBy = sortBy || "expiry";
|
|
1041
|
-
sortOrder = sortOrder || "desc";
|
|
1042
|
-
const sortKey = sortBy === "amount" ? "assets" : sortBy;
|
|
1043
|
-
if (a[sortKey] === b[sortKey]) {
|
|
1044
|
-
if (a.hash === b.hash) return 0;
|
|
1045
|
-
return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : b.hash > a.hash ? 1 : -1;
|
|
1046
|
-
}
|
|
1047
|
-
switch (sortBy) {
|
|
1048
|
-
case "rate":
|
|
1049
|
-
if (a.rate === b.rate) {
|
|
1050
|
-
if (a.hash === b.hash) return 0;
|
|
1051
|
-
return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
|
|
1052
|
-
}
|
|
1053
|
-
return sortOrder === "asc" ? a.rate > b.rate ? 1 : -1 : b.rate > a.rate ? 1 : -1;
|
|
1054
|
-
case "maturity":
|
|
1055
|
-
if (a.maturity === b.maturity) {
|
|
1056
|
-
if (a.hash === b.hash) return 0;
|
|
1057
|
-
return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
|
|
1058
|
-
}
|
|
1059
|
-
return sortOrder === "asc" ? a.maturity > b.maturity ? 1 : -1 : b.maturity > a.maturity ? 1 : -1;
|
|
1060
|
-
case "expiry":
|
|
1061
|
-
if (a.expiry === b.expiry) {
|
|
1062
|
-
if (a.hash === b.hash) return 0;
|
|
1063
|
-
return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
|
|
1064
|
-
}
|
|
1065
|
-
return sortOrder === "asc" ? a.expiry > b.expiry ? 1 : -1 : b.expiry > a.expiry ? 1 : -1;
|
|
1066
|
-
case "amount":
|
|
1067
|
-
if (a.assets === b.assets) {
|
|
1068
|
-
if (a.hash === b.hash) return 0;
|
|
1069
|
-
return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
|
|
1070
|
-
}
|
|
1071
|
-
return sortOrder === "asc" ? a.assets > b.assets ? 1 : -1 : b.assets > a.assets ? 1 : -1;
|
|
1072
|
-
default:
|
|
1073
|
-
if (a.expiry === b.expiry) {
|
|
1074
|
-
if (a.hash === b.hash) return 0;
|
|
1075
|
-
return sortOrder === "asc" ? a.hash > b.hash ? 1 : -1 : a.hash > b.hash ? -1 : 1;
|
|
1076
|
-
}
|
|
1077
|
-
return sortOrder === "asc" ? a.expiry > b.expiry ? 1 : -1 : b.expiry > a.expiry ? 1 : -1;
|
|
1078
|
-
}
|
|
1079
|
-
};
|
|
1080
|
-
return {
|
|
1081
|
-
create,
|
|
1082
|
-
createMany: async (parameters2) => {
|
|
1083
|
-
return Promise.all(
|
|
1084
|
-
parameters2.map((p) => create({ offer: p.offer, status: p.status, metadata: p.metadata }))
|
|
1085
|
-
);
|
|
1086
|
-
},
|
|
1087
|
-
getAll: async (params) => {
|
|
1088
|
-
const { query } = params || {};
|
|
1089
|
-
let {
|
|
1090
|
-
creators,
|
|
1091
|
-
side,
|
|
1092
|
-
chains,
|
|
1093
|
-
loanTokens,
|
|
1094
|
-
status = ["valid"],
|
|
1095
|
-
callbackAddresses,
|
|
1096
|
-
minAmount,
|
|
1097
|
-
maxAmount,
|
|
1098
|
-
minRate,
|
|
1099
|
-
maxRate,
|
|
1100
|
-
minMaturity,
|
|
1101
|
-
maxMaturity,
|
|
1102
|
-
minExpiry,
|
|
1103
|
-
maxExpiry,
|
|
1104
|
-
collateralAssets,
|
|
1105
|
-
collateralOracles,
|
|
1106
|
-
collateralTuple,
|
|
1107
|
-
minLltv,
|
|
1108
|
-
maxLltv,
|
|
1109
|
-
sortBy = "expiry",
|
|
1110
|
-
sortOrder = "desc",
|
|
1111
|
-
cursor: queryCursor,
|
|
1112
|
-
limit = 20
|
|
1113
|
-
} = query || {};
|
|
1114
|
-
const now = Time.now();
|
|
1115
|
-
const buy = side === "buy";
|
|
1116
|
-
let offers = Array.from(map.values()).map((o) => ({
|
|
1117
|
-
...o,
|
|
1118
|
-
consumed: filled.get(o.chainId)?.get(o.offering.toLowerCase())?.get(o.nonce) || 0n
|
|
1119
|
-
})).filter((o) => o.consumed < o.assets);
|
|
1120
|
-
const cursor = decode(queryCursor);
|
|
1121
|
-
if (cursor) {
|
|
1122
|
-
if (cursor.sort !== sortBy || cursor.dir !== sortOrder) {
|
|
1123
|
-
throw new Error("Cursor does not match the current sort parameters");
|
|
1124
|
-
}
|
|
1125
|
-
switch (cursor.sort) {
|
|
1126
|
-
case "rate":
|
|
1127
|
-
offers = offers.filter(
|
|
1128
|
-
(o) => (sortOrder === "asc" ? o.rate >= BigInt(cursor.rate) : o.rate <= BigInt(cursor.rate)) && (o.rate !== BigInt(cursor.rate) || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
|
|
1129
|
-
);
|
|
1130
|
-
break;
|
|
1131
|
-
case "maturity":
|
|
1132
|
-
offers = offers.filter(
|
|
1133
|
-
(o) => (sortOrder === "asc" ? o.maturity >= BigInt(cursor.maturity) : o.maturity <= BigInt(cursor.maturity)) && (o.maturity !== Maturity.from(cursor.maturity) || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
|
|
1134
|
-
);
|
|
1135
|
-
break;
|
|
1136
|
-
case "expiry":
|
|
1137
|
-
offers = offers.filter(
|
|
1138
|
-
(o) => (sortOrder === "asc" ? o.expiry >= cursor.expiry : o.expiry <= cursor.expiry) && (o.expiry !== cursor.expiry || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
|
|
1139
|
-
);
|
|
1140
|
-
break;
|
|
1141
|
-
case "amount":
|
|
1142
|
-
offers = offers.filter(
|
|
1143
|
-
(o) => (sortOrder === "asc" ? o.assets >= BigInt(cursor.assets) : o.assets <= BigInt(cursor.assets)) && (o.assets !== BigInt(cursor.assets) || (sortOrder === "asc" ? o.hash > cursor.hash : o.hash < cursor.hash))
|
|
1144
|
-
);
|
|
1145
|
-
break;
|
|
1146
|
-
default:
|
|
1147
|
-
throw new Error("Invalid sort parameter");
|
|
1148
|
-
}
|
|
1149
|
-
offers = offers.filter((o) => o.hash !== cursor.hash);
|
|
1150
|
-
}
|
|
1151
|
-
creators && (creators = creators.map((c) => c.toLowerCase()));
|
|
1152
|
-
loanTokens && (loanTokens = loanTokens.map((lt) => lt.toLowerCase()));
|
|
1153
|
-
callbackAddresses && (callbackAddresses = callbackAddresses.map((ca) => ca.toLowerCase()));
|
|
1154
|
-
collateralAssets && (collateralAssets = collateralAssets.map((ca) => ca.toLowerCase()));
|
|
1155
|
-
collateralOracles && (collateralOracles = collateralOracles.map((co) => co.toLowerCase()));
|
|
1156
|
-
collateralTuple && (collateralTuple = collateralTuple.map((ct) => ({
|
|
1157
|
-
asset: ct.asset.toLowerCase(),
|
|
1158
|
-
oracle: ct.oracle?.toLowerCase()
|
|
1159
|
-
})));
|
|
1160
|
-
offers = offers.filter((o) => o.expiry >= now);
|
|
1161
|
-
creators && (offers = offers.filter((o) => creators.includes(o.offering.toLowerCase())));
|
|
1162
|
-
side && (offers = offers.filter((o) => o.buy === buy));
|
|
1163
|
-
chains && (offers = offers.filter((o) => chains.includes(Number(o.chainId))));
|
|
1164
|
-
loanTokens && (offers = offers.filter((o) => loanTokens.includes(o.loanToken.toLowerCase())));
|
|
1165
|
-
status && (offers = offers.filter((o) => status.includes(o.status)));
|
|
1166
|
-
callbackAddresses && (offers = offers.filter(
|
|
1167
|
-
(o) => callbackAddresses.includes(o.callback.address.toLowerCase())
|
|
1168
|
-
));
|
|
1169
|
-
minAmount && (offers = offers.filter((o) => o.assets >= minAmount));
|
|
1170
|
-
maxAmount && (offers = offers.filter((o) => o.assets <= maxAmount));
|
|
1171
|
-
minRate && (offers = offers.filter((o) => o.rate >= minRate));
|
|
1172
|
-
maxRate && (offers = offers.filter((o) => o.rate <= maxRate));
|
|
1173
|
-
minMaturity && (offers = offers.filter((o) => o.maturity >= minMaturity));
|
|
1174
|
-
maxMaturity && (offers = offers.filter((o) => o.maturity <= maxMaturity));
|
|
1175
|
-
minExpiry && (offers = offers.filter((o) => o.expiry >= minExpiry));
|
|
1176
|
-
maxExpiry && (offers = offers.filter((o) => o.expiry <= maxExpiry));
|
|
1177
|
-
collateralAssets && (offers = offers.filter(
|
|
1178
|
-
(o) => o.collaterals.some((c) => collateralAssets.includes(c.asset.toLowerCase()))
|
|
1179
|
-
));
|
|
1180
|
-
collateralOracles && (offers = offers.filter(
|
|
1181
|
-
(o) => o.collaterals.some((c) => collateralOracles.includes(c.oracle.toLowerCase()))
|
|
1182
|
-
));
|
|
1183
|
-
collateralTuple && (offers = offers.filter(
|
|
1184
|
-
(o) => o.collaterals.some(
|
|
1185
|
-
(c) => collateralTuple.some(
|
|
1186
|
-
(ct) => c.asset.toLowerCase() === ct.asset.toLowerCase() && (ct.oracle ? c.oracle.toLowerCase() === ct.oracle.toLowerCase() : true) && (ct.lltv ? c.lltv === LLTV.from(BigInt(ct.lltv)) : true)
|
|
1187
|
-
)
|
|
1188
|
-
)
|
|
1189
|
-
));
|
|
1190
|
-
minLltv && (offers = offers.filter(
|
|
1191
|
-
(o) => o.collaterals.every((c) => c.lltv >= parseUnits(minLltv.toString(), 16))
|
|
1192
|
-
));
|
|
1193
|
-
maxLltv && (offers = offers.filter(
|
|
1194
|
-
(o) => o.collaterals.every((c) => c.lltv <= parseUnits(maxLltv.toString(), 16))
|
|
1195
|
-
));
|
|
1196
|
-
offers = offers.sort((a, b) => sort(sortBy, sortOrder, a, b));
|
|
1197
|
-
let nextCursor = null;
|
|
1198
|
-
if (offers.length > limit) {
|
|
1199
|
-
const last = offers[limit - 1];
|
|
1200
|
-
const base = {
|
|
1201
|
-
sort: sortBy,
|
|
1202
|
-
dir: sortOrder,
|
|
1203
|
-
hash: last.hash
|
|
1204
|
-
};
|
|
1205
|
-
switch (sortBy) {
|
|
1206
|
-
case "rate":
|
|
1207
|
-
base.rate = last.rate.toString();
|
|
1208
|
-
break;
|
|
1209
|
-
case "amount":
|
|
1210
|
-
base.assets = last.assets.toString();
|
|
1211
|
-
break;
|
|
1212
|
-
case "maturity":
|
|
1213
|
-
base.maturity = last.maturity;
|
|
1214
|
-
break;
|
|
1215
|
-
default:
|
|
1216
|
-
base.expiry = last.expiry;
|
|
1217
|
-
}
|
|
1218
|
-
nextCursor = encode(base);
|
|
1219
|
-
}
|
|
1220
|
-
offers = offers.slice(0, limit);
|
|
1221
|
-
const data = offers.map((o) => ({
|
|
1222
|
-
...Offer.from({
|
|
1223
|
-
offering: o.offering,
|
|
1224
|
-
assets: o.assets,
|
|
1225
|
-
rate: o.rate,
|
|
1226
|
-
maturity: Maturity.from(o.maturity),
|
|
1227
|
-
expiry: o.expiry,
|
|
1228
|
-
start: o.start,
|
|
1229
|
-
nonce: o.nonce,
|
|
1230
|
-
buy: o.buy,
|
|
1231
|
-
chainId: o.chainId,
|
|
1232
|
-
loanToken: o.loanToken,
|
|
1233
|
-
collaterals: o.collaterals.map((c) => ({ asset: c.asset, oracle: c.oracle, lltv: c.lltv })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
|
|
1234
|
-
callback: {
|
|
1235
|
-
address: o.callback.address,
|
|
1236
|
-
data: o.callback.data,
|
|
1237
|
-
gasLimit: o.callback.gasLimit
|
|
1238
|
-
},
|
|
1239
|
-
...o.signature !== null && o.signature !== void 0 ? { signature: o.signature } : {}
|
|
1240
|
-
}),
|
|
1241
|
-
consumed: o.consumed,
|
|
1242
|
-
status: o.status,
|
|
1243
|
-
...o.metadata ? { metadata: o.metadata } : {}
|
|
1244
|
-
}));
|
|
1245
|
-
return {
|
|
1246
|
-
offers: data,
|
|
1247
|
-
nextCursor
|
|
1248
|
-
};
|
|
1249
|
-
},
|
|
1250
|
-
findMatchingOffers: async (params) => {
|
|
1251
|
-
const {
|
|
1252
|
-
side,
|
|
1253
|
-
chainId,
|
|
1254
|
-
rate,
|
|
1255
|
-
collaterals = [],
|
|
1256
|
-
maturity,
|
|
1257
|
-
minMaturity,
|
|
1258
|
-
maxMaturity,
|
|
1259
|
-
loanToken,
|
|
1260
|
-
creator,
|
|
1261
|
-
cursor: queryCursor,
|
|
1262
|
-
limit = 20
|
|
1263
|
-
} = params;
|
|
1264
|
-
const now = Time.now();
|
|
1265
|
-
const isBuying = side === "buy";
|
|
1266
|
-
const sortOrder = isBuying ? "desc" : "asc";
|
|
1267
|
-
let offers = Array.from(map.values()).map((o) => ({
|
|
1268
|
-
...o,
|
|
1269
|
-
consumed: filled.get(o.chainId)?.get(o.offering.toLowerCase())?.get(o.nonce) || 0n
|
|
1270
|
-
})).filter((o) => o.consumed < o.assets);
|
|
1271
|
-
const cursor = decode(queryCursor);
|
|
1272
|
-
if (cursor) {
|
|
1273
|
-
if (cursor.sort !== "rate" || cursor.dir !== sortOrder) {
|
|
1274
|
-
throw new Error("Cursor does not match the current sort parameters");
|
|
1275
|
-
}
|
|
1276
|
-
offers = offers.filter(
|
|
1277
|
-
(o) => sortOrder === "asc" ? o.rate >= BigInt(cursor.rate) : o.rate <= BigInt(cursor.rate)
|
|
1278
|
-
);
|
|
1279
|
-
}
|
|
1280
|
-
offers = offers.filter((o) => o.buy === !isBuying);
|
|
1281
|
-
offers = offers.filter((o) => o.chainId === BigInt(chainId));
|
|
1282
|
-
offers = offers.filter((o) => o.expiry >= now);
|
|
1283
|
-
rate && (offers = offers.filter((o) => isBuying ? o.rate >= rate : o.rate <= rate));
|
|
1284
|
-
collaterals.length > 0 && (offers = offers.filter(
|
|
1285
|
-
(o) => isBuying ? (
|
|
1286
|
-
// when wanting to buy, sell offer collaterals ⊆ user buy collaterals
|
|
1287
|
-
o.collaterals.every((oc) => {
|
|
1288
|
-
return collaterals.some(
|
|
1289
|
-
(c) => oc.asset.toLowerCase() === c.asset.toLowerCase() && oc.oracle.toLowerCase() === c.oracle.toLowerCase() && oc.lltv === c.lltv
|
|
1290
|
-
);
|
|
1291
|
-
})
|
|
1292
|
-
) : (
|
|
1293
|
-
// when wanting to sell, user sell collaterals ⊆ buy offer collaterals
|
|
1294
|
-
collaterals.every((c) => {
|
|
1295
|
-
return o.collaterals.some(
|
|
1296
|
-
(oc) => oc.asset.toLowerCase() === c.asset.toLowerCase() && oc.oracle.toLowerCase() === c.oracle.toLowerCase() && oc.lltv === c.lltv
|
|
1297
|
-
);
|
|
1298
|
-
})
|
|
1299
|
-
)
|
|
1300
|
-
));
|
|
1301
|
-
maturity && (offers = offers.filter((o) => o.maturity === maturity));
|
|
1302
|
-
minMaturity && (offers = offers.filter((o) => o.maturity >= minMaturity));
|
|
1303
|
-
maxMaturity && (offers = offers.filter((o) => o.maturity <= maxMaturity));
|
|
1304
|
-
loanToken && (offers = offers.filter((o) => o.loanToken.toLowerCase() === loanToken.toLowerCase()));
|
|
1305
|
-
creator && (offers = offers.filter((o) => o.offering.toLowerCase() === creator.toLowerCase()));
|
|
1306
|
-
offers = offers.filter((o) => ["valid"].includes(o.status));
|
|
1307
|
-
const byGroup = /* @__PURE__ */ new Map();
|
|
1308
|
-
for (const offer of offers) {
|
|
1309
|
-
const groupKey = `${offer.chainId}-${offer.offering.toLowerCase()}-${offer.nonce}-${offer.buy}`;
|
|
1310
|
-
const current = byGroup.get(groupKey);
|
|
1311
|
-
if (!current) {
|
|
1312
|
-
byGroup.set(groupKey, offer);
|
|
1313
|
-
continue;
|
|
1314
|
-
}
|
|
1315
|
-
const remainingCandidate = offer.assets - offer.consumed;
|
|
1316
|
-
const remainingCurrent = current.assets - current.consumed;
|
|
1317
|
-
let candidateIsBetter = false;
|
|
1318
|
-
if (offer.buy) {
|
|
1319
|
-
if (offer.rate !== current.rate) candidateIsBetter = offer.rate < current.rate;
|
|
1320
|
-
else if (remainingCandidate !== remainingCurrent)
|
|
1321
|
-
candidateIsBetter = remainingCandidate > remainingCurrent;
|
|
1322
|
-
else if (offer.maturity !== current.maturity)
|
|
1323
|
-
candidateIsBetter = offer.maturity > current.maturity;
|
|
1324
|
-
else candidateIsBetter = offer.hash < current.hash;
|
|
1325
|
-
} else {
|
|
1326
|
-
if (offer.rate !== current.rate) candidateIsBetter = offer.rate > current.rate;
|
|
1327
|
-
else if (remainingCandidate !== remainingCurrent)
|
|
1328
|
-
candidateIsBetter = remainingCandidate > remainingCurrent;
|
|
1329
|
-
else if (offer.maturity !== current.maturity)
|
|
1330
|
-
candidateIsBetter = offer.maturity > current.maturity;
|
|
1331
|
-
else candidateIsBetter = offer.hash < current.hash;
|
|
1332
|
-
}
|
|
1333
|
-
if (candidateIsBetter) byGroup.set(groupKey, offer);
|
|
1334
|
-
}
|
|
1335
|
-
offers = Array.from(byGroup.values());
|
|
1336
|
-
offers = offers.sort((a, b) => sort("rate", sortOrder, a, b));
|
|
1337
|
-
cursor && (offers = offers.filter((o) => o.hash !== cursor.hash));
|
|
1338
|
-
let nextCursor = null;
|
|
1339
|
-
if (offers.length > limit) {
|
|
1340
|
-
const last = offers[limit - 1];
|
|
1341
|
-
nextCursor = encode({
|
|
1342
|
-
sort: "rate",
|
|
1343
|
-
dir: sortOrder,
|
|
1344
|
-
hash: last.hash,
|
|
1345
|
-
rate: last.rate.toString()
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
|
-
offers = offers.slice(0, limit);
|
|
1349
|
-
const data = offers.map((o) => ({
|
|
1350
|
-
...Offer.from({
|
|
1351
|
-
offering: o.offering,
|
|
1352
|
-
assets: o.assets,
|
|
1353
|
-
rate: o.rate,
|
|
1354
|
-
maturity: Maturity.from(o.maturity),
|
|
1355
|
-
expiry: o.expiry,
|
|
1356
|
-
start: o.start,
|
|
1357
|
-
nonce: o.nonce,
|
|
1358
|
-
buy: o.buy,
|
|
1359
|
-
chainId: o.chainId,
|
|
1360
|
-
loanToken: o.loanToken,
|
|
1361
|
-
collaterals: o.collaterals.map((c) => ({ asset: c.asset, oracle: c.oracle, lltv: c.lltv })).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
|
|
1362
|
-
callback: {
|
|
1363
|
-
address: o.callback.address,
|
|
1364
|
-
data: o.callback.data,
|
|
1365
|
-
gasLimit: o.callback.gasLimit
|
|
1366
|
-
},
|
|
1367
|
-
...o.signature !== null && o.signature !== void 0 ? { signature: o.signature } : {}
|
|
1368
|
-
}),
|
|
1369
|
-
consumed: o.consumed,
|
|
1370
|
-
status: o.status,
|
|
1371
|
-
...o.metadata ? { metadata: o.metadata } : {}
|
|
1372
|
-
}));
|
|
1373
|
-
return {
|
|
1374
|
-
offers: data,
|
|
1375
|
-
nextCursor
|
|
1376
|
-
};
|
|
1377
|
-
},
|
|
1378
|
-
delete: async (hash) => {
|
|
1379
|
-
if (!map.has(hash.toLowerCase())) return false;
|
|
1380
|
-
map.delete(hash.toLowerCase());
|
|
1381
|
-
return true;
|
|
1382
|
-
},
|
|
1383
|
-
deleteMany: async (hashes) => {
|
|
1384
|
-
let deleted = 0;
|
|
1385
|
-
for (const hash of hashes) {
|
|
1386
|
-
if (map.has(hash.toLowerCase())) {
|
|
1387
|
-
map.delete(hash.toLowerCase());
|
|
1388
|
-
deleted++;
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
return deleted;
|
|
1392
|
-
},
|
|
1393
|
-
updateStatus: async (parameters2) => {
|
|
1394
|
-
const key = parameters2.offerHash.toLowerCase();
|
|
1395
|
-
const existing = map.get(key);
|
|
1396
|
-
if (!existing) return;
|
|
1397
|
-
if (existing.status === parameters2.status) return;
|
|
1398
|
-
map.set(key, {
|
|
1399
|
-
...existing,
|
|
1400
|
-
status: parameters2.status,
|
|
1401
|
-
metadata: parameters2.metadata
|
|
1838
|
+
break;
|
|
1839
|
+
case pathname.includes("/v1/offers"):
|
|
1840
|
+
action = "get_offers";
|
|
1841
|
+
break;
|
|
1842
|
+
default:
|
|
1843
|
+
throw new HttpGetOffersFailedError("Unknown endpoint", {
|
|
1844
|
+
details: `Unsupported path: ${pathname}`
|
|
1402
1845
|
});
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1846
|
+
}
|
|
1847
|
+
const schemaParseResult = safeParse(action, Object.fromEntries(url.searchParams));
|
|
1848
|
+
if (!schemaParseResult.success) {
|
|
1849
|
+
throw new HttpGetOffersFailedError(`Invalid URL parameters`, {
|
|
1850
|
+
details: schemaParseResult.error.issues[0]?.message
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
const response = await fetch(url.toString(), {
|
|
1854
|
+
method: "GET",
|
|
1855
|
+
headers: config.headers
|
|
1856
|
+
});
|
|
1857
|
+
if (!response.ok) {
|
|
1858
|
+
switch (response.status) {
|
|
1859
|
+
case 401:
|
|
1860
|
+
throw new HttpUnauthorizedError();
|
|
1861
|
+
case 403:
|
|
1862
|
+
throw new HttpForbiddenError();
|
|
1863
|
+
case 429:
|
|
1864
|
+
throw new HttpRateLimitError();
|
|
1416
1865
|
}
|
|
1417
|
-
|
|
1866
|
+
throw new HttpGetOffersFailedError(`GET request returned ${response.status}`, {
|
|
1867
|
+
details: await response.text()
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
return response.json();
|
|
1418
1871
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1872
|
+
var InvalidUrlError = class extends Errors.BaseError {
|
|
1873
|
+
name = "Router.InvalidUrlError";
|
|
1874
|
+
constructor(url) {
|
|
1875
|
+
super(`URL "${url}" is not http/https.`);
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
var HttpUnauthorizedError = class extends Errors.BaseError {
|
|
1879
|
+
name = "Router.HttpUnauthorizedError";
|
|
1880
|
+
constructor() {
|
|
1881
|
+
super("Unauthorized.", {
|
|
1882
|
+
metaMessages: ["Ensure that an API key is provided."]
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
var HttpForbiddenError = class extends Errors.BaseError {
|
|
1887
|
+
name = "Router.HttpForbiddenError";
|
|
1888
|
+
constructor() {
|
|
1889
|
+
super("Forbidden.", {
|
|
1890
|
+
metaMessages: ["Ensure that the API key is valid."]
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
};
|
|
1894
|
+
var HttpRateLimitError = class extends Errors.BaseError {
|
|
1895
|
+
name = "Router.HttpRateLimitError";
|
|
1896
|
+
constructor() {
|
|
1897
|
+
super("Rate limit exceeded.", {
|
|
1898
|
+
metaMessages: [
|
|
1899
|
+
"The number of allowed requests has been exceeded. You must wait for the rate limit to reset."
|
|
1900
|
+
]
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
var HttpGetOffersFailedError = class extends Errors.BaseError {
|
|
1905
|
+
name = "Router.HttpGetOffersFailedError";
|
|
1906
|
+
constructor(message, { details } = {}) {
|
|
1907
|
+
super(message, {
|
|
1908
|
+
metaMessages: [details]
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1421
1912
|
async function serve(parameters) {
|
|
1422
1913
|
const app = new Hono();
|
|
1423
|
-
const store = parameters.store
|
|
1424
|
-
offers: /* @__PURE__ */ new Map(),
|
|
1425
|
-
filled: /* @__PURE__ */ new Map()
|
|
1426
|
-
});
|
|
1914
|
+
const store = parameters.store;
|
|
1427
1915
|
app.get("/v1/offers", async (c) => {
|
|
1428
1916
|
try {
|
|
1429
1917
|
const params = parse("get_offers", c.req.query());
|
|
1430
|
-
const
|
|
1918
|
+
const offers2 = await store.getAll({
|
|
1431
1919
|
query: {
|
|
1432
1920
|
creators: params.creators,
|
|
1433
1921
|
side: params.side,
|
|
@@ -1455,10 +1943,10 @@ async function serve(parameters) {
|
|
|
1455
1943
|
}
|
|
1456
1944
|
});
|
|
1457
1945
|
return success(c, {
|
|
1458
|
-
data:
|
|
1946
|
+
data: offers2.offers.map(
|
|
1459
1947
|
(offer) => Format.stringifyBigint(Format.toSnakeCase(toResponse(offer)))
|
|
1460
1948
|
),
|
|
1461
|
-
cursor:
|
|
1949
|
+
cursor: offers2.nextCursor ?? null
|
|
1462
1950
|
});
|
|
1463
1951
|
} catch (err) {
|
|
1464
1952
|
console.error(err);
|
|
@@ -1468,7 +1956,7 @@ async function serve(parameters) {
|
|
|
1468
1956
|
app.get("/v1/offers/match", async (c) => {
|
|
1469
1957
|
try {
|
|
1470
1958
|
const params = parse("match_offers", c.req.query());
|
|
1471
|
-
const
|
|
1959
|
+
const offers2 = await store.findMatchingOffers({
|
|
1472
1960
|
side: params.side,
|
|
1473
1961
|
chainId: params.chain_id,
|
|
1474
1962
|
rate: params.rate,
|
|
@@ -1482,10 +1970,10 @@ async function serve(parameters) {
|
|
|
1482
1970
|
limit: params.limit
|
|
1483
1971
|
});
|
|
1484
1972
|
return success(c, {
|
|
1485
|
-
data:
|
|
1973
|
+
data: offers2.offers.map(
|
|
1486
1974
|
(offer) => Format.stringifyBigint(Format.toSnakeCase(toResponse(offer)))
|
|
1487
1975
|
),
|
|
1488
|
-
cursor:
|
|
1976
|
+
cursor: offers2.nextCursor ?? null
|
|
1489
1977
|
});
|
|
1490
1978
|
} catch (err) {
|
|
1491
1979
|
console.error(err);
|
|
@@ -1593,310 +2081,1018 @@ function handleAPIError(error2, c) {
|
|
|
1593
2081
|
});
|
|
1594
2082
|
}
|
|
1595
2083
|
|
|
1596
|
-
// src/
|
|
1597
|
-
var
|
|
1598
|
-
__export(
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
serialize: () => serialize
|
|
1602
|
-
});
|
|
1603
|
-
async function fetchBalancesAndAllowances(parameters) {
|
|
1604
|
-
const { client, spender, pairs, options } = parameters;
|
|
1605
|
-
if (pairs.length === 0) return /* @__PURE__ */ new Map();
|
|
1606
|
-
const batchSize = Math.max(1, options?.batchSize ?? 5e3);
|
|
1607
|
-
const retryAttempts = Math.max(1, options?.retryAttempts ?? 3);
|
|
1608
|
-
const retryDelayMs = Math.max(0, options?.retryDelayMs ?? 50);
|
|
1609
|
-
const blockNumber = options?.blockNumber ? BigInt(options.blockNumber) : void 0;
|
|
1610
|
-
const out = /* @__PURE__ */ new Map();
|
|
1611
|
-
for (const pairsBatch of Utils.batch(pairs, batchSize)) {
|
|
1612
|
-
const balanceContracts = [];
|
|
1613
|
-
const allowanceContracts = [];
|
|
1614
|
-
for (const { user, token } of pairsBatch) {
|
|
1615
|
-
balanceContracts.push({
|
|
1616
|
-
address: token,
|
|
1617
|
-
abi: erc20Abi,
|
|
1618
|
-
functionName: "balanceOf",
|
|
1619
|
-
args: [user]
|
|
1620
|
-
});
|
|
1621
|
-
allowanceContracts.push({
|
|
1622
|
-
address: token,
|
|
1623
|
-
abi: erc20Abi,
|
|
1624
|
-
functionName: "allowance",
|
|
1625
|
-
args: [user, spender]
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
const [balances, allowances] = await Promise.all([
|
|
1629
|
-
Utils.retry(
|
|
1630
|
-
() => client.multicall({
|
|
1631
|
-
allowFailure: false,
|
|
1632
|
-
contracts: balanceContracts,
|
|
1633
|
-
...blockNumber ? { blockNumber } : {}
|
|
1634
|
-
}),
|
|
1635
|
-
retryAttempts,
|
|
1636
|
-
retryDelayMs
|
|
1637
|
-
),
|
|
1638
|
-
Utils.retry(
|
|
1639
|
-
() => client.multicall({
|
|
1640
|
-
allowFailure: false,
|
|
1641
|
-
contracts: allowanceContracts,
|
|
1642
|
-
...blockNumber ? { blockNumber } : {}
|
|
1643
|
-
}),
|
|
1644
|
-
retryAttempts,
|
|
1645
|
-
retryDelayMs
|
|
1646
|
-
)
|
|
1647
|
-
]);
|
|
1648
|
-
for (let i = 0; i < pairsBatch.length; i++) {
|
|
1649
|
-
const { user, token } = pairsBatch[i];
|
|
1650
|
-
const balance = balances[i];
|
|
1651
|
-
const allowance = allowances[i];
|
|
1652
|
-
let perUser = out.get(user);
|
|
1653
|
-
if (!perUser) {
|
|
1654
|
-
perUser = /* @__PURE__ */ new Map();
|
|
1655
|
-
out.set(user, perUser);
|
|
1656
|
-
}
|
|
1657
|
-
perUser.set(token, { balance, allowance });
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
return out;
|
|
1661
|
-
}
|
|
1662
|
-
async function fetch2(parameters) {
|
|
1663
|
-
const { client, chainId, spender, type, pairs, options } = parameters;
|
|
1664
|
-
if (type !== "buy_with_empty_callback" /* BuyWithEmptyCallback */)
|
|
1665
|
-
throw new Error(`CallbackType not implemented: ${type}`);
|
|
1666
|
-
const map = await fetchBalancesAndAllowances({
|
|
1667
|
-
client,
|
|
1668
|
-
spender,
|
|
1669
|
-
pairs: pairs.map(({ user, contract }) => ({ user, token: contract })),
|
|
1670
|
-
options
|
|
1671
|
-
});
|
|
1672
|
-
const out = [];
|
|
1673
|
-
for (const [user, perContract] of map) {
|
|
1674
|
-
for (const [contract, { balance, allowance }] of perContract) {
|
|
1675
|
-
const amount = balance < allowance ? balance : allowance;
|
|
1676
|
-
out.push(
|
|
1677
|
-
buildLiquidity({
|
|
1678
|
-
type,
|
|
1679
|
-
user,
|
|
1680
|
-
contract,
|
|
1681
|
-
chainId,
|
|
1682
|
-
amount: amount.toString(),
|
|
1683
|
-
index: 0
|
|
1684
|
-
})
|
|
1685
|
-
);
|
|
1686
|
-
}
|
|
1687
|
-
}
|
|
1688
|
-
return out;
|
|
1689
|
-
}
|
|
1690
|
-
function serialize(liquidity) {
|
|
1691
|
-
const normalized = {
|
|
1692
|
-
userPosition: {
|
|
1693
|
-
id: liquidity.userPosition.id,
|
|
1694
|
-
availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
|
|
1695
|
-
user: liquidity.userPosition.user,
|
|
1696
|
-
chainId: String(liquidity.userPosition.chainId),
|
|
1697
|
-
amount: String(liquidity.userPosition.amount)
|
|
1698
|
-
},
|
|
1699
|
-
queues: liquidity.queues.map((queueWithPool) => ({
|
|
1700
|
-
queue: {
|
|
1701
|
-
queueId: queueWithPool.queue.queueId,
|
|
1702
|
-
availableLiquidityPoolId: queueWithPool.queue.availableLiquidityPoolId,
|
|
1703
|
-
index: queueWithPool.queue.index
|
|
1704
|
-
},
|
|
1705
|
-
pool: {
|
|
1706
|
-
id: queueWithPool.pool.id,
|
|
1707
|
-
amount: String(queueWithPool.pool.amount)
|
|
1708
|
-
}
|
|
1709
|
-
})).sort(
|
|
1710
|
-
(left, right) => {
|
|
1711
|
-
const leftQueueId = left.queue.queueId || "";
|
|
1712
|
-
const rightQueueId = right.queue.queueId || "";
|
|
1713
|
-
if (leftQueueId < rightQueueId) return -1;
|
|
1714
|
-
if (leftQueueId > rightQueueId) return 1;
|
|
1715
|
-
const leftPoolId = left.pool.id;
|
|
1716
|
-
const rightPoolId = right.pool.id;
|
|
1717
|
-
if (leftPoolId < rightPoolId) return -1;
|
|
1718
|
-
if (leftPoolId > rightPoolId) return 1;
|
|
1719
|
-
const leftIndex = left.queue.index;
|
|
1720
|
-
const rightIndex = right.queue.index;
|
|
1721
|
-
if (leftIndex < rightIndex) return -1;
|
|
1722
|
-
if (leftIndex > rightIndex) return 1;
|
|
1723
|
-
return 0;
|
|
1724
|
-
}
|
|
1725
|
-
)
|
|
1726
|
-
};
|
|
1727
|
-
return JSON.stringify(normalized);
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
// src/Logger.ts
|
|
1731
|
-
var Logger_exports = {};
|
|
1732
|
-
__export(Logger_exports, {
|
|
1733
|
-
LogLevelValues: () => LogLevelValues,
|
|
1734
|
-
defaultLogger: () => defaultLogger,
|
|
1735
|
-
getLogger: () => getLogger,
|
|
1736
|
-
runWithLogger: () => runWithLogger,
|
|
1737
|
-
silentLogger: () => silentLogger
|
|
2084
|
+
// src/LiquidityStore.ts
|
|
2085
|
+
var LiquidityStore_exports = {};
|
|
2086
|
+
__export(LiquidityStore_exports, {
|
|
2087
|
+
create: () => create2,
|
|
2088
|
+
memory: () => memory2
|
|
1738
2089
|
});
|
|
1739
|
-
var
|
|
1740
|
-
|
|
1741
|
-
"trace",
|
|
1742
|
-
"debug",
|
|
1743
|
-
"info",
|
|
1744
|
-
"warn",
|
|
1745
|
-
"error",
|
|
1746
|
-
"fatal"
|
|
1747
|
-
];
|
|
1748
|
-
function defaultLogger(minLevel) {
|
|
1749
|
-
const threshold = minLevel ?? "trace";
|
|
1750
|
-
const levelIndexByName = LogLevelValues.reduce(
|
|
1751
|
-
(acc, lvl, idx) => {
|
|
1752
|
-
acc[lvl] = idx;
|
|
1753
|
-
return acc;
|
|
1754
|
-
},
|
|
1755
|
-
{}
|
|
1756
|
-
);
|
|
1757
|
-
const isEnabled = (methodLevel) => levelIndexByName[methodLevel] >= levelIndexByName[threshold];
|
|
2090
|
+
var create2 = (config) => {
|
|
2091
|
+
const db = config.db;
|
|
1758
2092
|
return {
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
2093
|
+
getByUserPositionId: async (parameters) => {
|
|
2094
|
+
const up = await db.select().from(userPositions).where(eq(userPositions.id, parameters.userPositionId)).limit(1);
|
|
2095
|
+
if (up.length === 0) return null;
|
|
2096
|
+
const userPositionRow = up[0];
|
|
2097
|
+
const rows = await db.select({ queue: availableLiquidityQueues, pool: availableLiquidityPools }).from(availableLiquidityQueues).innerJoin(
|
|
2098
|
+
availableLiquidityPools,
|
|
2099
|
+
eq(availableLiquidityPools.id, availableLiquidityQueues.availableLiquidityPoolId)
|
|
2100
|
+
).where(eq(availableLiquidityQueues.queueId, userPositionRow.availableLiquidityQueueId));
|
|
2101
|
+
const queues = rows.map((row) => ({
|
|
2102
|
+
queue: row.queue,
|
|
2103
|
+
pool: row.pool
|
|
2104
|
+
}));
|
|
2105
|
+
return { userPosition: userPositionRow, queues };
|
|
1770
2106
|
},
|
|
1771
|
-
|
|
1772
|
-
|
|
2107
|
+
getAll: async () => {
|
|
2108
|
+
const rows = await db.select({
|
|
2109
|
+
userPosition: userPositions,
|
|
2110
|
+
queue: availableLiquidityQueues,
|
|
2111
|
+
pool: availableLiquidityPools
|
|
2112
|
+
}).from(userPositions).innerJoin(
|
|
2113
|
+
availableLiquidityQueues,
|
|
2114
|
+
eq(availableLiquidityQueues.queueId, userPositions.availableLiquidityQueueId)
|
|
2115
|
+
).innerJoin(
|
|
2116
|
+
availableLiquidityPools,
|
|
2117
|
+
eq(availableLiquidityPools.id, availableLiquidityQueues.availableLiquidityPoolId)
|
|
2118
|
+
);
|
|
2119
|
+
const byUserPosition = /* @__PURE__ */ new Map();
|
|
2120
|
+
for (const row of rows) {
|
|
2121
|
+
const id = row.userPosition.id;
|
|
2122
|
+
if (!byUserPosition.has(id)) {
|
|
2123
|
+
byUserPosition.set(id, { userPosition: row.userPosition, queues: [] });
|
|
2124
|
+
}
|
|
2125
|
+
byUserPosition.get(id).queues.push({ queue: row.queue, pool: row.pool });
|
|
2126
|
+
}
|
|
2127
|
+
return Array.from(byUserPosition.values());
|
|
1773
2128
|
},
|
|
1774
|
-
|
|
2129
|
+
save: async (parameters) => {
|
|
2130
|
+
const { liquidity } = parameters;
|
|
2131
|
+
await db.transaction(async (tx) => {
|
|
2132
|
+
for (const qp of liquidity.queues) {
|
|
2133
|
+
await tx.insert(availableLiquidityPools).values({
|
|
2134
|
+
id: qp.pool.id,
|
|
2135
|
+
amount: qp.pool.amount,
|
|
2136
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2137
|
+
}).onConflictDoUpdate({
|
|
2138
|
+
target: availableLiquidityPools.id,
|
|
2139
|
+
set: {
|
|
2140
|
+
amount: qp.pool.amount,
|
|
2141
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
for (const qp of liquidity.queues) {
|
|
2146
|
+
await tx.insert(availableLiquidityQueues).values({
|
|
2147
|
+
queueId: qp.queue.queueId,
|
|
2148
|
+
availableLiquidityPoolId: qp.pool.id,
|
|
2149
|
+
index: qp.queue.index,
|
|
2150
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2151
|
+
}).onConflictDoUpdate({
|
|
2152
|
+
target: [
|
|
2153
|
+
availableLiquidityQueues.queueId,
|
|
2154
|
+
availableLiquidityQueues.availableLiquidityPoolId
|
|
2155
|
+
],
|
|
2156
|
+
set: {
|
|
2157
|
+
index: qp.queue.index,
|
|
2158
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
await tx.insert(userPositions).values({
|
|
2163
|
+
id: liquidity.userPosition.id,
|
|
2164
|
+
availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
|
|
2165
|
+
user: liquidity.userPosition.user,
|
|
2166
|
+
chainId: liquidity.userPosition.chainId,
|
|
2167
|
+
amount: liquidity.userPosition.amount,
|
|
2168
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2169
|
+
}).onConflictDoUpdate({
|
|
2170
|
+
target: userPositions.id,
|
|
2171
|
+
set: {
|
|
2172
|
+
availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
|
|
2173
|
+
user: liquidity.userPosition.user,
|
|
2174
|
+
chainId: liquidity.userPosition.chainId,
|
|
2175
|
+
amount: liquidity.userPosition.amount,
|
|
2176
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2177
|
+
}
|
|
2178
|
+
});
|
|
2179
|
+
});
|
|
1775
2180
|
}
|
|
1776
2181
|
};
|
|
1777
|
-
}
|
|
1778
|
-
function
|
|
2182
|
+
};
|
|
2183
|
+
function memory2() {
|
|
2184
|
+
const poolsById = /* @__PURE__ */ new Map();
|
|
2185
|
+
const queuesByComposite = /* @__PURE__ */ new Map();
|
|
2186
|
+
const queueIndexByQueueId = /* @__PURE__ */ new Map();
|
|
2187
|
+
const userPositionsById = /* @__PURE__ */ new Map();
|
|
1779
2188
|
return {
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
2189
|
+
getByUserPositionId: async (parameters) => {
|
|
2190
|
+
const up = userPositionsById.get(parameters.userPositionId);
|
|
2191
|
+
if (!up) return null;
|
|
2192
|
+
const compositeKeys = queueIndexByQueueId.get(up.availableLiquidityQueueId) || /* @__PURE__ */ new Set();
|
|
2193
|
+
const queues = [];
|
|
2194
|
+
for (const key of compositeKeys) {
|
|
2195
|
+
const q = queuesByComposite.get(key);
|
|
2196
|
+
if (!q) continue;
|
|
2197
|
+
const p = poolsById.get(q.availableLiquidityPoolId);
|
|
2198
|
+
if (!p) continue;
|
|
2199
|
+
queues.push({ queue: q, pool: p });
|
|
2200
|
+
}
|
|
2201
|
+
return { userPosition: up, queues };
|
|
1787
2202
|
},
|
|
1788
|
-
|
|
2203
|
+
getAll: async () => {
|
|
2204
|
+
const results = [];
|
|
2205
|
+
for (const up of userPositionsById.values()) {
|
|
2206
|
+
const compositeKeys = queueIndexByQueueId.get(up.availableLiquidityQueueId) || /* @__PURE__ */ new Set();
|
|
2207
|
+
const queues = [];
|
|
2208
|
+
for (const key of compositeKeys) {
|
|
2209
|
+
const q = queuesByComposite.get(key);
|
|
2210
|
+
if (!q) continue;
|
|
2211
|
+
const p = poolsById.get(q.availableLiquidityPoolId);
|
|
2212
|
+
if (!p) continue;
|
|
2213
|
+
queues.push({ queue: q, pool: p });
|
|
2214
|
+
}
|
|
2215
|
+
results.push({ userPosition: up, queues });
|
|
2216
|
+
}
|
|
2217
|
+
return results;
|
|
1789
2218
|
},
|
|
1790
|
-
|
|
2219
|
+
save: async (parameters) => {
|
|
2220
|
+
const { liquidity } = parameters;
|
|
2221
|
+
for (const qp of liquidity.queues) {
|
|
2222
|
+
poolsById.set(qp.pool.id, {
|
|
2223
|
+
id: qp.pool.id,
|
|
2224
|
+
amount: qp.pool.amount,
|
|
2225
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2226
|
+
});
|
|
2227
|
+
}
|
|
2228
|
+
for (const qp of liquidity.queues) {
|
|
2229
|
+
const qid = qp.queue.queueId;
|
|
2230
|
+
if (!qid) continue;
|
|
2231
|
+
const composite = `${qid}::${qp.pool.id}`;
|
|
2232
|
+
queuesByComposite.set(composite, {
|
|
2233
|
+
queueId: qid,
|
|
2234
|
+
availableLiquidityPoolId: qp.pool.id,
|
|
2235
|
+
index: qp.queue.index,
|
|
2236
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2237
|
+
});
|
|
2238
|
+
if (!queueIndexByQueueId.has(qid)) queueIndexByQueueId.set(qid, /* @__PURE__ */ new Set());
|
|
2239
|
+
queueIndexByQueueId.get(qid).add(composite);
|
|
2240
|
+
}
|
|
2241
|
+
userPositionsById.set(liquidity.userPosition.id, {
|
|
2242
|
+
id: liquidity.userPosition.id,
|
|
2243
|
+
availableLiquidityQueueId: liquidity.userPosition.availableLiquidityQueueId,
|
|
2244
|
+
user: liquidity.userPosition.user,
|
|
2245
|
+
chainId: liquidity.userPosition.chainId,
|
|
2246
|
+
amount: liquidity.userPosition.amount,
|
|
2247
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2248
|
+
});
|
|
1791
2249
|
}
|
|
1792
2250
|
};
|
|
1793
2251
|
}
|
|
1794
|
-
var loggerContext = new AsyncLocalStorage();
|
|
1795
|
-
function runWithLogger(logger, fn) {
|
|
1796
|
-
return loggerContext.run(logger, fn);
|
|
1797
|
-
}
|
|
1798
|
-
function getLogger() {
|
|
1799
|
-
return loggerContext.getStore() ?? defaultLogger();
|
|
1800
|
-
}
|
|
1801
2252
|
|
|
1802
|
-
// src/
|
|
1803
|
-
var
|
|
1804
|
-
__export(
|
|
1805
|
-
|
|
2253
|
+
// src/OfferStore/index.ts
|
|
2254
|
+
var OfferStore_exports = {};
|
|
2255
|
+
__export(OfferStore_exports, {
|
|
2256
|
+
create: () => create3
|
|
1806
2257
|
});
|
|
1807
|
-
|
|
1808
|
-
const
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
2258
|
+
function create3(config) {
|
|
2259
|
+
const db = config.db;
|
|
2260
|
+
return {
|
|
2261
|
+
create: async (parameters) => {
|
|
2262
|
+
const { offer, status, metadata } = parameters;
|
|
2263
|
+
return await db.transaction(async (tx) => {
|
|
2264
|
+
const callbackId = getCallbackIdForOffer(offer);
|
|
2265
|
+
const result = await tx.insert(offers).values({
|
|
2266
|
+
hash: offer.hash.toLowerCase(),
|
|
2267
|
+
offering: offer.offering.toLowerCase(),
|
|
2268
|
+
assets: offer.assets.toString(),
|
|
2269
|
+
rate: offer.rate,
|
|
2270
|
+
maturity: offer.maturity,
|
|
2271
|
+
expiry: offer.expiry,
|
|
2272
|
+
start: offer.start,
|
|
2273
|
+
nonce: offer.nonce,
|
|
2274
|
+
buy: offer.buy,
|
|
2275
|
+
chainId: offer.chainId,
|
|
2276
|
+
loanToken: offer.loanToken.toLowerCase(),
|
|
2277
|
+
callbackAddress: offer.callback.address.toLowerCase(),
|
|
2278
|
+
callbackData: offer.callback.data,
|
|
2279
|
+
callbackGasLimit: offer.callback.gasLimit,
|
|
2280
|
+
signature: offer.signature,
|
|
2281
|
+
callbackId: callbackId ?? null
|
|
2282
|
+
}).onConflictDoNothing().returning();
|
|
2283
|
+
if (result.length === 0) {
|
|
2284
|
+
return offer.hash;
|
|
2285
|
+
}
|
|
2286
|
+
for (const collateral of offer.collaterals) {
|
|
2287
|
+
await tx.insert(offerCollaterals).values({
|
|
2288
|
+
offerHash: offer.hash.toLowerCase(),
|
|
2289
|
+
asset: collateral.asset.toLowerCase(),
|
|
2290
|
+
oracle: collateral.oracle.toLowerCase(),
|
|
2291
|
+
lltv: collateral.lltv
|
|
2292
|
+
});
|
|
2293
|
+
}
|
|
2294
|
+
await tx.insert(offerStatus).values({
|
|
2295
|
+
offerHash: offer.hash.toLowerCase(),
|
|
2296
|
+
status,
|
|
2297
|
+
metadata
|
|
2298
|
+
});
|
|
2299
|
+
return offer.hash;
|
|
2300
|
+
});
|
|
2301
|
+
},
|
|
2302
|
+
createMany: async (parameters) => {
|
|
2303
|
+
return await db.transaction(async (tx) => {
|
|
2304
|
+
const hashes = [];
|
|
2305
|
+
for (const { offer, status, metadata } of parameters) {
|
|
2306
|
+
const callbackId = getCallbackIdForOffer(offer);
|
|
2307
|
+
const result = await tx.insert(offers).values({
|
|
2308
|
+
hash: offer.hash.toLowerCase(),
|
|
2309
|
+
offering: offer.offering.toLowerCase(),
|
|
2310
|
+
assets: offer.assets.toString(),
|
|
2311
|
+
rate: offer.rate,
|
|
2312
|
+
maturity: offer.maturity,
|
|
2313
|
+
expiry: offer.expiry,
|
|
2314
|
+
start: offer.start,
|
|
2315
|
+
nonce: offer.nonce,
|
|
2316
|
+
buy: offer.buy,
|
|
2317
|
+
chainId: offer.chainId,
|
|
2318
|
+
loanToken: offer.loanToken.toLowerCase(),
|
|
2319
|
+
callbackAddress: offer.callback.address.toLowerCase(),
|
|
2320
|
+
callbackData: offer.callback.data,
|
|
2321
|
+
callbackGasLimit: offer.callback.gasLimit,
|
|
2322
|
+
signature: offer.signature,
|
|
2323
|
+
callbackId: callbackId ?? null
|
|
2324
|
+
}).onConflictDoNothing().returning();
|
|
2325
|
+
if (result.length === 0) continue;
|
|
2326
|
+
for (const collateral of offer.collaterals) {
|
|
2327
|
+
await tx.insert(offerCollaterals).values({
|
|
2328
|
+
offerHash: offer.hash.toLowerCase(),
|
|
2329
|
+
asset: collateral.asset.toLowerCase(),
|
|
2330
|
+
oracle: collateral.oracle.toLowerCase(),
|
|
2331
|
+
lltv: collateral.lltv
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
await tx.insert(offerStatus).values({
|
|
2335
|
+
offerHash: offer.hash.toLowerCase(),
|
|
2336
|
+
status,
|
|
2337
|
+
metadata
|
|
2338
|
+
});
|
|
2339
|
+
hashes.push(offer.hash);
|
|
1821
2340
|
}
|
|
2341
|
+
return hashes;
|
|
2342
|
+
});
|
|
2343
|
+
},
|
|
2344
|
+
getAll: async (params) => {
|
|
2345
|
+
const { query } = params ?? {};
|
|
2346
|
+
const conditions = [];
|
|
2347
|
+
const now = Time.now();
|
|
2348
|
+
if (query?.creators && query.creators.length > 0) {
|
|
2349
|
+
conditions.push(
|
|
2350
|
+
inArray(
|
|
2351
|
+
offers.offering,
|
|
2352
|
+
query.creators.map((c) => c.toLowerCase())
|
|
2353
|
+
)
|
|
2354
|
+
);
|
|
1822
2355
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
2356
|
+
if (query?.side) {
|
|
2357
|
+
conditions.push(eq(offers.buy, query.side === "buy"));
|
|
2358
|
+
}
|
|
2359
|
+
if (query?.chains && query.chains.length > 0) {
|
|
2360
|
+
conditions.push(
|
|
2361
|
+
inArray(
|
|
2362
|
+
offers.chainId,
|
|
2363
|
+
query.chains.map((chain) => BigInt(chain))
|
|
2364
|
+
)
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
if (query?.loanTokens && query.loanTokens.length > 0) {
|
|
2368
|
+
conditions.push(
|
|
2369
|
+
inArray(
|
|
2370
|
+
offers.loanToken,
|
|
2371
|
+
query.loanTokens.map((t) => t.toLowerCase())
|
|
2372
|
+
)
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2375
|
+
if (query?.callbackAddresses && query.callbackAddresses.length > 0) {
|
|
2376
|
+
conditions.push(
|
|
2377
|
+
inArray(
|
|
2378
|
+
offers.callbackAddress,
|
|
2379
|
+
query.callbackAddresses.map((c) => c.toLowerCase())
|
|
2380
|
+
)
|
|
2381
|
+
);
|
|
2382
|
+
}
|
|
2383
|
+
conditions.push(gte(offers.expiry, now));
|
|
2384
|
+
if (query?.minAmount !== void 0) {
|
|
2385
|
+
conditions.push(gte(offers.assets, query.minAmount.toString()));
|
|
2386
|
+
}
|
|
2387
|
+
if (query?.maxAmount !== void 0) {
|
|
2388
|
+
conditions.push(lte(offers.assets, query.maxAmount.toString()));
|
|
2389
|
+
}
|
|
2390
|
+
if (query?.minRate !== void 0) {
|
|
2391
|
+
conditions.push(gte(offers.rate, query.minRate));
|
|
2392
|
+
}
|
|
2393
|
+
if (query?.maxRate !== void 0) {
|
|
2394
|
+
conditions.push(lte(offers.rate, query.maxRate));
|
|
2395
|
+
}
|
|
2396
|
+
if (query?.minMaturity !== void 0) {
|
|
2397
|
+
conditions.push(gte(offers.maturity, query.minMaturity));
|
|
2398
|
+
}
|
|
2399
|
+
if (query?.maxMaturity !== void 0) {
|
|
2400
|
+
conditions.push(lte(offers.maturity, query.maxMaturity));
|
|
2401
|
+
}
|
|
2402
|
+
if (query?.minExpiry !== void 0) {
|
|
2403
|
+
conditions.push(gte(offers.expiry, query.minExpiry));
|
|
2404
|
+
}
|
|
2405
|
+
if (query?.maxExpiry !== void 0) {
|
|
2406
|
+
conditions.push(lte(offers.expiry, query.maxExpiry));
|
|
2407
|
+
}
|
|
2408
|
+
if (query?.collateralAssets && query.collateralAssets.length > 0) {
|
|
2409
|
+
conditions.push(
|
|
2410
|
+
inArray(
|
|
2411
|
+
offerCollaterals.asset,
|
|
2412
|
+
query.collateralAssets.map((a) => a.toLowerCase())
|
|
2413
|
+
)
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
if (query?.collateralOracles && query.collateralOracles.length > 0) {
|
|
2417
|
+
conditions.push(
|
|
2418
|
+
inArray(
|
|
2419
|
+
offerCollaterals.oracle,
|
|
2420
|
+
query.collateralOracles.map((o) => o.toLowerCase())
|
|
2421
|
+
)
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
if (query?.collateralTuple && query.collateralTuple.length > 0) {
|
|
2425
|
+
const tupleClauses = query.collateralTuple.map((tuple) => {
|
|
2426
|
+
const parts = [
|
|
2427
|
+
sql`${offerCollaterals.asset} = ${tuple.asset.toLowerCase()}`
|
|
2428
|
+
];
|
|
2429
|
+
if (tuple.oracle) {
|
|
2430
|
+
parts.push(sql`${offerCollaterals.oracle} = ${tuple.oracle.toLowerCase()}`);
|
|
2431
|
+
}
|
|
2432
|
+
if (tuple.lltv !== void 0) {
|
|
2433
|
+
parts.push(sql`${offerCollaterals.lltv} = ${tuple.lltv}`);
|
|
2434
|
+
}
|
|
2435
|
+
let clause = parts[0];
|
|
2436
|
+
for (let i = 1; i < parts.length; i++) {
|
|
2437
|
+
clause = sql`${clause} AND ${parts[i]}`;
|
|
1831
2438
|
}
|
|
2439
|
+
return sql`(${clause})`;
|
|
2440
|
+
}).filter((c) => c !== void 0);
|
|
2441
|
+
if (tupleClauses.length > 0) {
|
|
2442
|
+
let combined = tupleClauses[0];
|
|
2443
|
+
for (let i = 1; i < tupleClauses.length; i++) {
|
|
2444
|
+
combined = sql`${combined} OR ${tupleClauses[i]}`;
|
|
2445
|
+
}
|
|
2446
|
+
conditions.push(sql`(${combined})`);
|
|
1832
2447
|
}
|
|
1833
|
-
}
|
|
1834
|
-
if (
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
2448
|
+
}
|
|
2449
|
+
if (query?.minLltv !== void 0) {
|
|
2450
|
+
conditions.push(gte(offerCollaterals.lltv, parseUnits(query.minLltv.toString(), 16)));
|
|
2451
|
+
}
|
|
2452
|
+
if (query?.maxLltv !== void 0) {
|
|
2453
|
+
conditions.push(lte(offerCollaterals.lltv, parseUnits(query.maxLltv.toString(), 16)));
|
|
2454
|
+
}
|
|
2455
|
+
const sortBy = query?.sortBy ?? "expiry";
|
|
2456
|
+
const sortOrder = query?.sortOrder ?? "desc";
|
|
2457
|
+
const sortColumn = (() => {
|
|
2458
|
+
switch (sortBy) {
|
|
2459
|
+
case "rate":
|
|
2460
|
+
return offers.rate;
|
|
2461
|
+
case "maturity":
|
|
2462
|
+
return offers.maturity;
|
|
2463
|
+
case "expiry":
|
|
2464
|
+
return offers.expiry;
|
|
2465
|
+
case "amount":
|
|
2466
|
+
return offers.assets;
|
|
2467
|
+
default:
|
|
2468
|
+
return offers.expiry;
|
|
2469
|
+
}
|
|
2470
|
+
})();
|
|
2471
|
+
const cursor = decode(query?.cursor);
|
|
2472
|
+
if (cursor) {
|
|
2473
|
+
if (cursor.sort !== sortBy || cursor.dir !== sortOrder) {
|
|
2474
|
+
throw new Error("Cursor does not match the current sort parameters");
|
|
2475
|
+
}
|
|
2476
|
+
const op = sortOrder === "asc" ? sql`>` : sql`<`;
|
|
2477
|
+
const cursorVal = (() => {
|
|
2478
|
+
switch (sortBy) {
|
|
2479
|
+
case "rate":
|
|
2480
|
+
return BigInt(cursor.rate);
|
|
2481
|
+
case "amount":
|
|
2482
|
+
return BigInt(cursor.assets);
|
|
2483
|
+
case "maturity":
|
|
2484
|
+
return cursor.maturity;
|
|
2485
|
+
case "expiry":
|
|
2486
|
+
return cursor.expiry;
|
|
2487
|
+
default:
|
|
2488
|
+
return cursor.expiry;
|
|
2489
|
+
}
|
|
2490
|
+
})();
|
|
2491
|
+
conditions.push(sql`(${sortColumn}, ${offers.hash}) ${op} (${cursorVal}, ${cursor.hash})`);
|
|
2492
|
+
}
|
|
2493
|
+
const limit = query?.limit ?? 20;
|
|
2494
|
+
const latestStatus = db.select({
|
|
2495
|
+
status: offerStatus.status,
|
|
2496
|
+
metadata: offerStatus.metadata
|
|
2497
|
+
}).from(offerStatus).where(eq(offerStatus.offerHash, offers.hash)).orderBy(desc(offerStatus.createdAt)).limit(1).as("latest_status");
|
|
2498
|
+
const sumConsumed = db.select({
|
|
2499
|
+
consumed: sql`COALESCE(SUM(${consumed.consumed}), 0)`.as("consumed")
|
|
2500
|
+
}).from(consumed).where(
|
|
2501
|
+
and(
|
|
2502
|
+
eq(consumed.offering, offers.offering),
|
|
2503
|
+
eq(consumed.nonce, offers.nonce),
|
|
2504
|
+
eq(consumed.chainId, offers.chainId)
|
|
2505
|
+
)
|
|
2506
|
+
).as("sum_consumed");
|
|
2507
|
+
const results = await db.select({
|
|
2508
|
+
hash: offers.hash,
|
|
2509
|
+
offering: offers.offering,
|
|
2510
|
+
assets: offers.assets,
|
|
2511
|
+
consumed: sumConsumed.consumed,
|
|
2512
|
+
rate: offers.rate,
|
|
2513
|
+
maturity: offers.maturity,
|
|
2514
|
+
expiry: offers.expiry,
|
|
2515
|
+
start: offers.start,
|
|
2516
|
+
nonce: offers.nonce,
|
|
2517
|
+
buy: offers.buy,
|
|
2518
|
+
chainId: offers.chainId,
|
|
2519
|
+
loanToken: offers.loanToken,
|
|
2520
|
+
callbackAddress: offers.callbackAddress,
|
|
2521
|
+
callbackData: offers.callbackData,
|
|
2522
|
+
callbackGasLimit: offers.callbackGasLimit,
|
|
2523
|
+
signature: offers.signature,
|
|
2524
|
+
createdAt: offers.createdAt,
|
|
2525
|
+
collateralAsset: offerCollaterals.asset,
|
|
2526
|
+
collateralOracle: offerCollaterals.oracle,
|
|
2527
|
+
collateralLltv: offerCollaterals.lltv,
|
|
2528
|
+
status: latestStatus.status,
|
|
2529
|
+
metadata: latestStatus.metadata
|
|
2530
|
+
}).from(offers).leftJoin(offerCollaterals, eq(offers.hash, offerCollaterals.offerHash)).leftJoinLateral(latestStatus, sql`true`).leftJoinLateral(sumConsumed, sql`true`).where(
|
|
2531
|
+
and(
|
|
2532
|
+
conditions.length > 0 ? and(...conditions) : sql`true`,
|
|
2533
|
+
query?.status && query.status.length > 0 ? inArray(latestStatus.status, query.status) : eq(latestStatus.status, "valid"),
|
|
2534
|
+
sql`( ${offers.assets} - COALESCE(${sumConsumed.consumed}, 0) ) > 0`
|
|
2535
|
+
)
|
|
2536
|
+
).orderBy(
|
|
2537
|
+
...sortOrder === "asc" ? [asc(sortColumn), asc(offers.hash)] : [desc(sortColumn), desc(offers.hash)]
|
|
2538
|
+
).limit(limit);
|
|
2539
|
+
const offerMap = /* @__PURE__ */ new Map();
|
|
2540
|
+
for (const row of results) {
|
|
2541
|
+
const offer = offerMap.get(row.hash) || {
|
|
2542
|
+
hash: row.hash,
|
|
2543
|
+
offering: row.offering,
|
|
2544
|
+
assets: BigInt(row.assets),
|
|
2545
|
+
consumed: BigInt(row.consumed),
|
|
2546
|
+
rate: row.rate,
|
|
2547
|
+
maturity: row.maturity,
|
|
2548
|
+
expiry: row.expiry,
|
|
2549
|
+
start: row.start,
|
|
2550
|
+
nonce: row.nonce,
|
|
2551
|
+
buy: row.buy,
|
|
2552
|
+
chainId: row.chainId,
|
|
2553
|
+
loanToken: row.loanToken,
|
|
2554
|
+
callbackAddress: row.callbackAddress,
|
|
2555
|
+
callbackData: row.callbackData,
|
|
2556
|
+
callbackGasLimit: row.callbackGasLimit,
|
|
2557
|
+
signature: row.signature,
|
|
2558
|
+
createdAt: row.createdAt,
|
|
2559
|
+
collaterals: [],
|
|
2560
|
+
status: row.status,
|
|
2561
|
+
metadata: row.metadata
|
|
2562
|
+
};
|
|
2563
|
+
if (row.collateralAsset && row.collateralOracle && row.collateralLltv) {
|
|
2564
|
+
offer.collaterals.push({
|
|
2565
|
+
asset: row.collateralAsset,
|
|
2566
|
+
oracle: row.collateralOracle,
|
|
2567
|
+
lltv: LLTV.from(row.collateralLltv)
|
|
2568
|
+
});
|
|
2569
|
+
}
|
|
2570
|
+
offerMap.set(row.hash, offer);
|
|
2571
|
+
}
|
|
2572
|
+
let nextCursor = null;
|
|
2573
|
+
if (results.length === limit && results.length > 0) {
|
|
2574
|
+
const lastRow = results[results.length - 1];
|
|
2575
|
+
const base = {
|
|
2576
|
+
sort: sortBy,
|
|
2577
|
+
dir: sortOrder,
|
|
2578
|
+
hash: lastRow.hash
|
|
2579
|
+
};
|
|
2580
|
+
switch (sortBy) {
|
|
2581
|
+
case "rate":
|
|
2582
|
+
base.rate = lastRow.rate.toString();
|
|
2583
|
+
break;
|
|
2584
|
+
case "amount":
|
|
2585
|
+
base.assets = lastRow.assets.toString();
|
|
2586
|
+
break;
|
|
2587
|
+
case "maturity":
|
|
2588
|
+
base.maturity = lastRow.maturity;
|
|
2589
|
+
break;
|
|
2590
|
+
default:
|
|
2591
|
+
base.expiry = lastRow.expiry;
|
|
2592
|
+
}
|
|
2593
|
+
nextCursor = encode(base);
|
|
2594
|
+
}
|
|
2595
|
+
const transformedResults = [];
|
|
2596
|
+
for (const offerData of offerMap.values()) {
|
|
2597
|
+
const offer = Offer.from({
|
|
2598
|
+
offering: offerData.offering,
|
|
2599
|
+
assets: offerData.assets,
|
|
2600
|
+
rate: offerData.rate,
|
|
2601
|
+
maturity: Maturity.from(offerData.maturity),
|
|
2602
|
+
expiry: offerData.expiry,
|
|
2603
|
+
start: offerData.start,
|
|
2604
|
+
nonce: offerData.nonce,
|
|
2605
|
+
buy: offerData.buy,
|
|
2606
|
+
chainId: offerData.chainId,
|
|
2607
|
+
loanToken: offerData.loanToken,
|
|
2608
|
+
collaterals: offerData.collaterals.map((c) => ({
|
|
2609
|
+
asset: c.asset,
|
|
2610
|
+
oracle: c.oracle,
|
|
2611
|
+
lltv: LLTV.from(c.lltv)
|
|
2612
|
+
})).sort((a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())),
|
|
2613
|
+
callback: {
|
|
2614
|
+
address: offerData.callbackAddress,
|
|
2615
|
+
data: offerData.callbackData,
|
|
2616
|
+
gasLimit: offerData.callbackGasLimit
|
|
2617
|
+
},
|
|
2618
|
+
...offerData.signature !== null ? { signature: offerData.signature } : void 0
|
|
2619
|
+
});
|
|
2620
|
+
transformedResults.push({
|
|
2621
|
+
...offer,
|
|
2622
|
+
consumed: offerData.consumed,
|
|
2623
|
+
status: offerData.status,
|
|
2624
|
+
...offerData.metadata !== null ? { metadata: offerData.metadata } : void 0
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
return { offers: transformedResults, nextCursor };
|
|
2628
|
+
},
|
|
2629
|
+
/**
|
|
2630
|
+
* Returns offers that match specified offer based on the provided parameters.
|
|
2631
|
+
*
|
|
2632
|
+
* Rules for filtering:
|
|
2633
|
+
* - If offer expiry is in the past compared to now, it should not be included
|
|
2634
|
+
* - If offer maturity is in the past compared to now, it should not be included
|
|
2635
|
+
* - It should return if buyOffer.rate >= sellOffer.rate
|
|
2636
|
+
* - It should return if buyOffer.userAddress != sellOffer.userAddress
|
|
2637
|
+
* - It should return if maturity matches
|
|
2638
|
+
* - It should return if loanToken matches
|
|
2639
|
+
* - If the incoming intent is a buy / lend offer, only consider existing sell / borrow offers.
|
|
2640
|
+
* - If the incoming intent is a sell / borrow offer, only consider existing buy / lend offers.
|
|
2641
|
+
* - When the intent is lend: require offer_collaterals ⊆ intent_collaterals
|
|
2642
|
+
* - When the intent is borrow: require intent_collaterals ⊆ offer_collaterals
|
|
2643
|
+
* - It should sort by increasing rate if the input offer is a buy/lend offer
|
|
2644
|
+
* - It should sort by decreasing rate if the input offer is a sell/borrow offer
|
|
2645
|
+
*/
|
|
2646
|
+
findMatchingOffers: async (params) => {
|
|
2647
|
+
const {
|
|
2648
|
+
side,
|
|
2649
|
+
chainId,
|
|
2650
|
+
rate,
|
|
2651
|
+
collaterals = [],
|
|
2652
|
+
maturity,
|
|
2653
|
+
minMaturity,
|
|
2654
|
+
maxMaturity,
|
|
2655
|
+
loanToken,
|
|
2656
|
+
creator,
|
|
2657
|
+
cursor,
|
|
2658
|
+
limit = 20
|
|
2659
|
+
} = params;
|
|
2660
|
+
const isIncomingBuy = side === "buy";
|
|
2661
|
+
const nowEpochSeconds = Math.floor(Date.now() / 1e3);
|
|
2662
|
+
const rateSortDirection = isIncomingBuy ? "desc" : "asc";
|
|
2663
|
+
const baseWhereClauses = [];
|
|
2664
|
+
baseWhereClauses.push(eq(offers.buy, !isIncomingBuy));
|
|
2665
|
+
baseWhereClauses.push(eq(offers.chainId, BigInt(chainId)));
|
|
2666
|
+
baseWhereClauses.push(gte(offers.expiry, nowEpochSeconds));
|
|
2667
|
+
baseWhereClauses.push(gte(offers.maturity, nowEpochSeconds));
|
|
2668
|
+
if (maturity) baseWhereClauses.push(eq(offers.maturity, maturity));
|
|
2669
|
+
if (minMaturity) baseWhereClauses.push(gte(offers.maturity, minMaturity));
|
|
2670
|
+
if (maxMaturity) baseWhereClauses.push(lte(offers.maturity, maxMaturity));
|
|
2671
|
+
if (loanToken) baseWhereClauses.push(eq(offers.loanToken, loanToken.toLowerCase()));
|
|
2672
|
+
if (creator) baseWhereClauses.push(eq(offers.offering, creator.toLowerCase()));
|
|
2673
|
+
if (rate)
|
|
2674
|
+
baseWhereClauses.push(isIncomingBuy ? gte(offers.rate, rate) : lte(offers.rate, rate));
|
|
2675
|
+
const parsedCursor = decode(cursor);
|
|
2676
|
+
if (parsedCursor) {
|
|
2677
|
+
if (parsedCursor.sort !== "rate" || parsedCursor.dir !== rateSortDirection) {
|
|
2678
|
+
throw new Error("Cursor does not match the current sort parameters");
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
if (collaterals.length > 0) {
|
|
2682
|
+
const collateralValues = collaterals.map(
|
|
2683
|
+
(c) => `('${c.asset.toLowerCase()}', '${c.oracle.toLowerCase()}', ${c.lltv.toString()})`
|
|
2684
|
+
).join(", ");
|
|
2685
|
+
const userCollaterals = sql`(VALUES ${sql.raw(collateralValues)}) AS user_collaterals(asset, oracle, lltv)`;
|
|
2686
|
+
const sellPredicate = sql`
|
|
2687
|
+
NOT EXISTS (
|
|
2688
|
+
SELECT 1 FROM ${userCollaterals}
|
|
2689
|
+
WHERE NOT EXISTS (
|
|
2690
|
+
SELECT 1 FROM ${offerCollaterals} offer_collaterals
|
|
2691
|
+
WHERE offer_collaterals.offer_hash = ${offers.hash}
|
|
2692
|
+
AND offer_collaterals.asset = user_collaterals.asset
|
|
2693
|
+
AND offer_collaterals.oracle = user_collaterals.oracle
|
|
2694
|
+
AND offer_collaterals.lltv = user_collaterals.lltv
|
|
2695
|
+
)
|
|
2696
|
+
)
|
|
2697
|
+
`;
|
|
2698
|
+
const buyPredicate = sql`
|
|
2699
|
+
NOT EXISTS (
|
|
2700
|
+
SELECT 1 FROM ${offerCollaterals} offer_collaterals
|
|
2701
|
+
WHERE offer_collaterals.offer_hash = ${offers.hash}
|
|
2702
|
+
AND NOT EXISTS (
|
|
2703
|
+
SELECT 1 FROM ${userCollaterals}
|
|
2704
|
+
WHERE user_collaterals.asset = offer_collaterals.asset
|
|
2705
|
+
AND user_collaterals.oracle = offer_collaterals.oracle
|
|
2706
|
+
AND user_collaterals.lltv = offer_collaterals.lltv
|
|
2707
|
+
)
|
|
2708
|
+
)
|
|
2709
|
+
`;
|
|
2710
|
+
baseWhereClauses.push(isIncomingBuy ? buyPredicate : sellPredicate);
|
|
2711
|
+
}
|
|
2712
|
+
const latestStatus = db.select({
|
|
2713
|
+
status: offerStatus.status,
|
|
2714
|
+
metadata: offerStatus.metadata
|
|
2715
|
+
}).from(offerStatus).where(eq(offerStatus.offerHash, offers.hash)).orderBy(desc(offerStatus.createdAt)).limit(1).as("latest_status");
|
|
2716
|
+
const sumConsumed = db.select({
|
|
2717
|
+
consumed: sql`COALESCE(SUM(${consumed.consumed}), 0)`.as("consumed")
|
|
2718
|
+
}).from(consumed).where(
|
|
2719
|
+
and(
|
|
2720
|
+
eq(consumed.offering, offers.offering),
|
|
2721
|
+
eq(consumed.nonce, offers.nonce),
|
|
2722
|
+
eq(consumed.chainId, offers.chainId)
|
|
2723
|
+
)
|
|
2724
|
+
).as("sum_consumed");
|
|
2725
|
+
const statusCondition = eq(latestStatus.status, "valid");
|
|
2726
|
+
const bestOffers = db.selectDistinctOn(
|
|
2727
|
+
// group key
|
|
2728
|
+
[offers.offering, offers.nonce, offers.buy],
|
|
2729
|
+
{
|
|
2730
|
+
hash: offers.hash,
|
|
2731
|
+
offering: offers.offering,
|
|
2732
|
+
assets: offers.assets,
|
|
2733
|
+
consumed: sumConsumed.consumed,
|
|
2734
|
+
remaining: sql`${offers.assets} - COALESCE(${sumConsumed.consumed}, 0)`.as("remaining"),
|
|
2735
|
+
rate: offers.rate,
|
|
2736
|
+
maturity: offers.maturity,
|
|
2737
|
+
expiry: offers.expiry,
|
|
2738
|
+
start: offers.start,
|
|
2739
|
+
nonce: offers.nonce,
|
|
2740
|
+
buy: offers.buy,
|
|
2741
|
+
chainId: offers.chainId,
|
|
2742
|
+
loanToken: offers.loanToken,
|
|
2743
|
+
callbackAddress: offers.callbackAddress,
|
|
2744
|
+
callbackData: offers.callbackData,
|
|
2745
|
+
callbackGasLimit: offers.callbackGasLimit,
|
|
2746
|
+
signature: offers.signature,
|
|
2747
|
+
callbackId: offers.callbackId,
|
|
2748
|
+
status: latestStatus.status,
|
|
2749
|
+
metadata: latestStatus.metadata
|
|
2750
|
+
}
|
|
2751
|
+
).from(offers).leftJoinLateral(latestStatus, sql`true`).leftJoinLateral(sumConsumed, sql`true`).where(
|
|
2752
|
+
and(
|
|
2753
|
+
and(...baseWhereClauses),
|
|
2754
|
+
statusCondition,
|
|
2755
|
+
sql`( ${offers.assets} - COALESCE(${sumConsumed.consumed}, 0) ) > 0`
|
|
2756
|
+
)
|
|
2757
|
+
).orderBy(
|
|
2758
|
+
offers.offering,
|
|
2759
|
+
offers.nonce,
|
|
2760
|
+
offers.buy,
|
|
2761
|
+
// 1 price (direction depends on side)
|
|
2762
|
+
sql`CASE WHEN ${offers.buy} THEN ${offers.rate} ELSE -${offers.rate} END`,
|
|
2763
|
+
// 2 size (remaining)
|
|
2764
|
+
sql`( ${offers.assets} - COALESCE(${sumConsumed.consumed}, 0) ) DESC`,
|
|
2765
|
+
// 3 term (longer maturity)
|
|
2766
|
+
desc(offers.maturity)
|
|
2767
|
+
).as("best_offers");
|
|
2768
|
+
const queueLiquidity = db.select({
|
|
2769
|
+
callbackId: userPositions.id,
|
|
2770
|
+
userAmount: userPositions.amount,
|
|
2771
|
+
// user's per-callback cap
|
|
2772
|
+
queueLiquidity: sql`COALESCE(SUM(${availableLiquidityPools.amount}), 0)`.as(
|
|
2773
|
+
"queue_liquidity"
|
|
2774
|
+
)
|
|
2775
|
+
}).from(userPositions).leftJoin(
|
|
2776
|
+
availableLiquidityQueues,
|
|
2777
|
+
eq(userPositions.availableLiquidityQueueId, availableLiquidityQueues.queueId)
|
|
2778
|
+
).leftJoin(
|
|
2779
|
+
availableLiquidityPools,
|
|
2780
|
+
eq(availableLiquidityQueues.availableLiquidityPoolId, availableLiquidityPools.id)
|
|
2781
|
+
).groupBy(userPositions.id).as("queue_liquidity");
|
|
2782
|
+
const sortExpr = sql`CASE WHEN ${bestOffers.buy} THEN ${bestOffers.rate} ELSE -${bestOffers.rate} END`;
|
|
2783
|
+
const offersWithEligibility = db.select({
|
|
2784
|
+
hash: bestOffers.hash,
|
|
2785
|
+
offering: bestOffers.offering,
|
|
2786
|
+
assets: bestOffers.assets,
|
|
2787
|
+
consumed: bestOffers.consumed,
|
|
2788
|
+
remaining: bestOffers.remaining,
|
|
2789
|
+
rate: bestOffers.rate,
|
|
2790
|
+
maturity: bestOffers.maturity,
|
|
2791
|
+
expiry: bestOffers.expiry,
|
|
2792
|
+
start: bestOffers.start,
|
|
2793
|
+
nonce: bestOffers.nonce,
|
|
2794
|
+
buy: bestOffers.buy,
|
|
2795
|
+
chainId: bestOffers.chainId,
|
|
2796
|
+
loanToken: bestOffers.loanToken,
|
|
2797
|
+
callbackAddress: bestOffers.callbackAddress,
|
|
2798
|
+
callbackData: bestOffers.callbackData,
|
|
2799
|
+
callbackGasLimit: bestOffers.callbackGasLimit,
|
|
2800
|
+
signature: bestOffers.signature,
|
|
2801
|
+
callbackId: bestOffers.callbackId,
|
|
2802
|
+
status: bestOffers.status,
|
|
2803
|
+
metadata: bestOffers.metadata,
|
|
2804
|
+
// liquidity caps
|
|
2805
|
+
userAmount: sql`COALESCE(${queueLiquidity.userAmount}, 0)`.as("user_amount"),
|
|
2806
|
+
queueLiquidity: sql`COALESCE(${queueLiquidity.queueLiquidity}, 0)`.as(
|
|
2807
|
+
"queue_liquidity"
|
|
2808
|
+
),
|
|
2809
|
+
// running total of remaining per callback, ordered by the *same* priority as selection
|
|
2810
|
+
cumulativeRemaining: sql`
|
|
2811
|
+
SUM(${bestOffers.remaining}) OVER (
|
|
2812
|
+
PARTITION BY ${bestOffers.callbackId}
|
|
2813
|
+
ORDER BY ${sortExpr}, ${asc(bestOffers.hash)}
|
|
2814
|
+
ROWS UNBOUNDED PRECEDING
|
|
2815
|
+
)
|
|
2816
|
+
`.as("cumulative_remaining"),
|
|
2817
|
+
eligible: sql`
|
|
2818
|
+
CASE
|
|
2819
|
+
WHEN ${bestOffers.buy} = false THEN true
|
|
2820
|
+
WHEN ${bestOffers.remaining} <= 0 THEN false
|
|
2821
|
+
ELSE ( SUM(${bestOffers.remaining}) OVER (
|
|
2822
|
+
PARTITION BY ${bestOffers.callbackId}
|
|
2823
|
+
ORDER BY ${sortExpr}, ${asc(bestOffers.hash)}
|
|
2824
|
+
ROWS UNBOUNDED PRECEDING
|
|
2825
|
+
)
|
|
2826
|
+
<= LEAST(
|
|
2827
|
+
COALESCE(${queueLiquidity.userAmount}, 0),
|
|
2828
|
+
COALESCE(${queueLiquidity.queueLiquidity}, 0)
|
|
2829
|
+
)
|
|
2830
|
+
)
|
|
2831
|
+
END
|
|
2832
|
+
`.as("eligible")
|
|
2833
|
+
}).from(bestOffers).leftJoin(queueLiquidity, eq(bestOffers.callbackId, queueLiquidity.callbackId)).as("offers_with_eligibility");
|
|
2834
|
+
const hardCap = limit * 5 + 1 + (parsedCursor ? 1 : 0);
|
|
2835
|
+
const validOffers = db.select({
|
|
2836
|
+
// pass-through all fields + a global row_number for hard cap
|
|
2837
|
+
hash: offersWithEligibility.hash,
|
|
2838
|
+
offering: offersWithEligibility.offering,
|
|
2839
|
+
assets: offersWithEligibility.assets,
|
|
2840
|
+
consumed: offersWithEligibility.consumed,
|
|
2841
|
+
remaining: offersWithEligibility.remaining,
|
|
2842
|
+
rate: offersWithEligibility.rate,
|
|
2843
|
+
maturity: offersWithEligibility.maturity,
|
|
2844
|
+
expiry: offersWithEligibility.expiry,
|
|
2845
|
+
start: offersWithEligibility.start,
|
|
2846
|
+
nonce: offersWithEligibility.nonce,
|
|
2847
|
+
buy: offersWithEligibility.buy,
|
|
2848
|
+
chainId: offersWithEligibility.chainId,
|
|
2849
|
+
loanToken: offersWithEligibility.loanToken,
|
|
2850
|
+
callbackAddress: offersWithEligibility.callbackAddress,
|
|
2851
|
+
callbackData: offersWithEligibility.callbackData,
|
|
2852
|
+
callbackGasLimit: offersWithEligibility.callbackGasLimit,
|
|
2853
|
+
signature: offersWithEligibility.signature,
|
|
2854
|
+
callbackId: offersWithEligibility.callbackId,
|
|
2855
|
+
status: offersWithEligibility.status,
|
|
2856
|
+
metadata: offersWithEligibility.metadata,
|
|
2857
|
+
userAmount: offersWithEligibility.userAmount,
|
|
2858
|
+
queueLiquidity: offersWithEligibility.queueLiquidity,
|
|
2859
|
+
cumulativeRemaining: offersWithEligibility.cumulativeRemaining,
|
|
2860
|
+
eligible: offersWithEligibility.eligible,
|
|
2861
|
+
// sort expression is the same again as in offersWithEligibility
|
|
2862
|
+
rowNumber: sql`
|
|
2863
|
+
ROW_NUMBER() OVER (
|
|
2864
|
+
ORDER BY
|
|
2865
|
+
CASE WHEN ${offersWithEligibility.buy} THEN ${offersWithEligibility.rate} ELSE -${offersWithEligibility.rate} END,
|
|
2866
|
+
${asc(offersWithEligibility.hash)}
|
|
2867
|
+
)
|
|
2868
|
+
`.as("row_number")
|
|
2869
|
+
}).from(offersWithEligibility).where(sql`${offersWithEligibility.eligible} = true`).limit(hardCap).as("valid_offers");
|
|
2870
|
+
const cursorTuple = parsedCursor ? {
|
|
2871
|
+
rate: BigInt(parsedCursor.rate),
|
|
2872
|
+
hash: parsedCursor.hash
|
|
2873
|
+
} : null;
|
|
2874
|
+
const withCollats = await db.select({
|
|
2875
|
+
// base fields
|
|
2876
|
+
hash: sql`${validOffers.hash}`,
|
|
2877
|
+
offering: sql`${validOffers.offering}`,
|
|
2878
|
+
assets: validOffers.assets,
|
|
2879
|
+
consumed: validOffers.consumed,
|
|
2880
|
+
rate: validOffers.rate,
|
|
2881
|
+
maturity: validOffers.maturity,
|
|
2882
|
+
expiry: validOffers.expiry,
|
|
2883
|
+
start: validOffers.start,
|
|
2884
|
+
nonce: validOffers.nonce,
|
|
2885
|
+
buy: validOffers.buy,
|
|
2886
|
+
chainId: validOffers.chainId,
|
|
2887
|
+
loanToken: sql`${validOffers.loanToken}`,
|
|
2888
|
+
callbackAddress: sql`${validOffers.callbackAddress}`,
|
|
2889
|
+
callbackData: sql`${validOffers.callbackData}`,
|
|
2890
|
+
callbackGasLimit: validOffers.callbackGasLimit,
|
|
2891
|
+
signature: sql`${validOffers.signature}`,
|
|
2892
|
+
callbackId: validOffers.callbackId,
|
|
2893
|
+
status: sql`${validOffers.status}`,
|
|
2894
|
+
metadata: validOffers.metadata,
|
|
2895
|
+
// collateral fields
|
|
2896
|
+
collateralAsset: sql`${offerCollaterals.asset}`,
|
|
2897
|
+
collateralOracle: sql`${offerCollaterals.oracle}`,
|
|
2898
|
+
collateralLltv: offerCollaterals.lltv
|
|
2899
|
+
}).from(validOffers).leftJoin(offerCollaterals, eq(validOffers.hash, offerCollaterals.offerHash)).where(
|
|
2900
|
+
and(
|
|
2901
|
+
...cursorTuple ? [
|
|
2902
|
+
sql`(${validOffers.rate}, ${validOffers.hash}) ${rateSortDirection === "asc" ? sql`>` : sql`<`} (${cursorTuple.rate}, ${cursorTuple.hash})`
|
|
2903
|
+
] : []
|
|
2904
|
+
)
|
|
2905
|
+
).orderBy(
|
|
2906
|
+
rateSortDirection === "asc" ? asc(validOffers.rate) : desc(validOffers.rate),
|
|
2907
|
+
asc(validOffers.hash)
|
|
2908
|
+
);
|
|
2909
|
+
const buildOffersMap = (rows, skipHash) => {
|
|
2910
|
+
const map = /* @__PURE__ */ new Map();
|
|
2911
|
+
for (const row of rows) {
|
|
2912
|
+
const entry = map.get(row.hash) ?? { base: row, collaterals: [] };
|
|
2913
|
+
if (row.collateralAsset && row.collateralOracle && row.collateralLltv) {
|
|
2914
|
+
entry.collaterals.push({
|
|
2915
|
+
asset: row.collateralAsset,
|
|
2916
|
+
oracle: row.collateralOracle,
|
|
2917
|
+
lltv: LLTV.from(row.collateralLltv)
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
map.set(row.hash, entry);
|
|
1838
2921
|
}
|
|
2922
|
+
return map;
|
|
2923
|
+
};
|
|
2924
|
+
const allEntries = Array.from(buildOffersMap(withCollats).values());
|
|
2925
|
+
const pageEntries = allEntries.slice(0, limit);
|
|
2926
|
+
let nextCursor = null;
|
|
2927
|
+
if (allEntries.length > limit) {
|
|
2928
|
+
const last = pageEntries[pageEntries.length - 1].base;
|
|
2929
|
+
nextCursor = encode({
|
|
2930
|
+
sort: "rate",
|
|
2931
|
+
dir: rateSortDirection,
|
|
2932
|
+
hash: last.hash,
|
|
2933
|
+
rate: last.rate.toString()
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2936
|
+
const data = pageEntries.map(({ base, collaterals: cols }) => ({
|
|
2937
|
+
...Offer.from({
|
|
2938
|
+
offering: base.offering,
|
|
2939
|
+
assets: BigInt(base.assets),
|
|
2940
|
+
rate: base.rate,
|
|
2941
|
+
maturity: Maturity.from(base.maturity),
|
|
2942
|
+
expiry: base.expiry,
|
|
2943
|
+
start: base.start,
|
|
2944
|
+
nonce: base.nonce,
|
|
2945
|
+
buy: base.buy,
|
|
2946
|
+
chainId: base.chainId,
|
|
2947
|
+
loanToken: base.loanToken,
|
|
2948
|
+
collaterals: cols.sort(
|
|
2949
|
+
(a, b) => a.asset.toLowerCase().localeCompare(b.asset.toLowerCase())
|
|
2950
|
+
),
|
|
2951
|
+
callback: {
|
|
2952
|
+
address: base.callbackAddress,
|
|
2953
|
+
data: base.callbackData,
|
|
2954
|
+
gasLimit: base.callbackGasLimit
|
|
2955
|
+
},
|
|
2956
|
+
...base.signature !== null ? { signature: base.signature } : void 0
|
|
2957
|
+
}),
|
|
2958
|
+
consumed: BigInt(base.consumed),
|
|
2959
|
+
status: base.status,
|
|
2960
|
+
...base.metadata ? { metadata: base.metadata } : {}
|
|
2961
|
+
}));
|
|
2962
|
+
return { offers: data, nextCursor };
|
|
2963
|
+
},
|
|
2964
|
+
delete: async (hash) => {
|
|
2965
|
+
const result = await db.delete(offers).where(eq(offers.hash, hash.toLowerCase()));
|
|
2966
|
+
return result.affectedRows > 0;
|
|
2967
|
+
},
|
|
2968
|
+
deleteMany: async (hashes) => {
|
|
2969
|
+
if (hashes.length === 0) {
|
|
2970
|
+
return 0;
|
|
1839
2971
|
}
|
|
2972
|
+
return await db.transaction(async (tx) => {
|
|
2973
|
+
const normalizedHashes = hashes.map((hash) => hash.toLowerCase());
|
|
2974
|
+
const result = await tx.delete(offers).where(
|
|
2975
|
+
sql`${offers.hash} = ANY(${sql.raw(`ARRAY[${normalizedHashes.map((h) => `'${h}'`).join(", ")}]`)})`
|
|
2976
|
+
);
|
|
2977
|
+
return result.affectedRows;
|
|
2978
|
+
});
|
|
2979
|
+
},
|
|
2980
|
+
updateStatus: async (parameters) => {
|
|
2981
|
+
const latest = await db.select({ status: offerStatus.status }).from(offerStatus).where(eq(offerStatus.offerHash, parameters.offerHash.toLowerCase())).orderBy(desc(offerStatus.createdAt)).limit(1);
|
|
2982
|
+
if (latest.length > 0 && latest[0].status === parameters.status) return;
|
|
2983
|
+
await db.insert(offerStatus).values({
|
|
2984
|
+
offerHash: parameters.offerHash.toLowerCase(),
|
|
2985
|
+
status: parameters.status,
|
|
2986
|
+
metadata: parameters.metadata
|
|
2987
|
+
});
|
|
2988
|
+
},
|
|
2989
|
+
updateConsumedAmount: async (parameters) => {
|
|
2990
|
+
await db.insert(consumed).values({
|
|
2991
|
+
id: parameters.id,
|
|
2992
|
+
chainId: parameters.chainId,
|
|
2993
|
+
offering: parameters.offering.toLowerCase(),
|
|
2994
|
+
nonce: parameters.nonce,
|
|
2995
|
+
consumed: parameters.consumed.toString()
|
|
2996
|
+
}).onConflictDoNothing();
|
|
1840
2997
|
}
|
|
1841
|
-
validItems = validItems.filter((_, i) => !indicesToRemove.has(i));
|
|
1842
|
-
}
|
|
1843
|
-
return {
|
|
1844
|
-
valid: validItems,
|
|
1845
|
-
issues
|
|
1846
2998
|
};
|
|
1847
2999
|
}
|
|
1848
3000
|
|
|
1849
|
-
// src/
|
|
1850
|
-
var
|
|
1851
|
-
__export(
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
3001
|
+
// src/OfferStore/PG.ts
|
|
3002
|
+
var PG_exports = {};
|
|
3003
|
+
__export(PG_exports, {
|
|
3004
|
+
applyMigrations: () => applyMigrations,
|
|
3005
|
+
clean: () => clean,
|
|
3006
|
+
connect: () => connect2
|
|
1855
3007
|
});
|
|
1856
|
-
function
|
|
1857
|
-
|
|
3008
|
+
function connect2(parameters) {
|
|
3009
|
+
if (parameters.type === "pg") {
|
|
3010
|
+
const pool2 = new Pool({ connectionString: parameters.endpoint });
|
|
3011
|
+
const client2 = drizzle(pool2, { schema: schema_exports });
|
|
3012
|
+
return Object.assign(client2, { name: "pg", pool: pool2 });
|
|
3013
|
+
}
|
|
3014
|
+
const pool = new PGlite();
|
|
3015
|
+
const client = drizzle$1(pool, { schema: schema_exports });
|
|
3016
|
+
return Object.assign(client, { name: "pglite", pool });
|
|
1858
3017
|
}
|
|
1859
|
-
function
|
|
1860
|
-
|
|
3018
|
+
async function applyMigrations(pg) {
|
|
3019
|
+
const migrationsFolder = process.env.AWS_LAMBDA_FUNCTION_NAME ? path.join(process.cwd(), "drizzle", VERSION) : path.join(__dirname, "drizzle", VERSION);
|
|
3020
|
+
await pg.execute(`create schema if not exists "${VERSION}"`);
|
|
3021
|
+
if (pg.name === "pg") {
|
|
3022
|
+
await migrate(pg, { migrationsFolder });
|
|
3023
|
+
return;
|
|
3024
|
+
}
|
|
3025
|
+
await migrate$1(pg, { migrationsFolder });
|
|
1861
3026
|
}
|
|
1862
|
-
function
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
3027
|
+
async function clean(pg) {
|
|
3028
|
+
await pg.execute(`drop schema if exists "${VERSION}" cascade`);
|
|
3029
|
+
await pg.execute(`create schema "${VERSION}"`);
|
|
3030
|
+
await pg.execute("drop schema if exists drizzle cascade");
|
|
3031
|
+
}
|
|
3032
|
+
|
|
3033
|
+
// src/Services.ts
|
|
3034
|
+
var Services_exports = {};
|
|
3035
|
+
__export(Services_exports, {
|
|
3036
|
+
from: () => from2
|
|
3037
|
+
});
|
|
3038
|
+
var from2 = async (config) => {
|
|
3039
|
+
const { chain, rpcUrl, dbConfig } = config;
|
|
3040
|
+
const walletClient = createWalletClient({
|
|
3041
|
+
chain,
|
|
3042
|
+
transport: http(rpcUrl)
|
|
3043
|
+
}).extend(publicActions);
|
|
3044
|
+
if (dbConfig.type === "pg" && !dbConfig.endpoint) {
|
|
3045
|
+
throw new Error("dbOffer.endpoint is required when dbOffer.type is pg");
|
|
3046
|
+
}
|
|
3047
|
+
const DB = dbConfig.type === "pg" ? connect2({ type: "pg", endpoint: dbConfig.endpoint }) : connect2({ type: "pglite" });
|
|
3048
|
+
const offerStore = create3({ db: DB });
|
|
3049
|
+
const collectorBlockStore = create({
|
|
3050
|
+
db: DB
|
|
1869
3051
|
});
|
|
1870
|
-
const
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
3052
|
+
const liquidityStore = create2({ db: DB });
|
|
3053
|
+
const mempoolOffersCollector = createMempoolCollector({
|
|
3054
|
+
mempool: Mempool.connect({
|
|
3055
|
+
client: walletClient,
|
|
3056
|
+
mempoolAddress: chain.mempool.address
|
|
3057
|
+
}),
|
|
3058
|
+
offerStore,
|
|
3059
|
+
collectorBlockStore,
|
|
3060
|
+
chain,
|
|
3061
|
+
options: {
|
|
3062
|
+
interval: 1e4
|
|
1878
3063
|
}
|
|
1879
3064
|
});
|
|
1880
|
-
const
|
|
1881
|
-
|
|
1882
|
-
|
|
3065
|
+
const consumedEventsCollector = createConsumedEventsCollector({
|
|
3066
|
+
client: walletClient,
|
|
3067
|
+
contractAddress: chain.morpho,
|
|
3068
|
+
offerStore,
|
|
3069
|
+
collectorBlockStore,
|
|
3070
|
+
chainId: chain.id,
|
|
3071
|
+
options: {
|
|
3072
|
+
interval: 1e4
|
|
1883
3073
|
}
|
|
1884
3074
|
});
|
|
1885
|
-
const
|
|
1886
|
-
|
|
1887
|
-
|
|
3075
|
+
const buyWithEmptyCallbackLiquidityCollector = createBuyWithEmptyCallbackLiquidityCollector({
|
|
3076
|
+
client: walletClient,
|
|
3077
|
+
offerStore,
|
|
3078
|
+
collectorBlockStore,
|
|
3079
|
+
liquidityStore,
|
|
3080
|
+
chain,
|
|
3081
|
+
options: {
|
|
3082
|
+
interval: 1e4
|
|
1888
3083
|
}
|
|
1889
3084
|
});
|
|
1890
|
-
return
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
}
|
|
3085
|
+
return {
|
|
3086
|
+
offerStore,
|
|
3087
|
+
collectorBlockStore,
|
|
3088
|
+
liquidityStore,
|
|
3089
|
+
mempoolOffersCollector,
|
|
3090
|
+
consumedEventsCollector,
|
|
3091
|
+
buyWithEmptyCallbackLiquidityCollector,
|
|
3092
|
+
DB
|
|
3093
|
+
};
|
|
3094
|
+
};
|
|
1899
3095
|
|
|
1900
|
-
export { apiSchema_exports as ApiSchema, Callback_exports as Callback, Cursor_exports as Cursor, Liquidity_exports as Liquidity, Logger_exports as Logger, OfferStore_exports as OfferStore, router_exports as Router, RouterOffer_exports as RouterOffer, Validation_exports as Validation, ValidationRule_exports as ValidationRule };
|
|
3096
|
+
export { apiSchema_exports as ApiSchema, Callback_exports as Callback, Collector_exports as Collector, CollectorBlockStore_exports as CollectorBlockStore, Cursor_exports as Cursor, Liquidity_exports as Liquidity, LiquidityStore_exports as LiquidityStore, Logger_exports as Logger, OfferStore_exports as OfferStore, schema_exports as OffersSchema, PG_exports as PG, router_exports as Router, RouterOffer_exports as RouterOffer, Services_exports as Services, Validation_exports as Validation, ValidationRule_exports as ValidationRule };
|
|
1901
3097
|
//# sourceMappingURL=index.node.mjs.map
|
|
1902
3098
|
//# sourceMappingURL=index.node.mjs.map
|