@varla/sdk 1.13.2 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -9
- package/CHANGELOG.md +67 -0
- package/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/abi/full/OracleUpdaterRouter.d.ts +10 -0
- package/dist/abi/full/OracleUpdaterRouter.d.ts.map +1 -1
- package/dist/abi/full/OracleUpdaterRouter.js +13 -0
- package/dist/abi/full/OracleUpdaterRouter.js.map +1 -1
- package/dist/abi/full/VarlaLiquidator.d.ts +13 -1
- package/dist/abi/full/VarlaLiquidator.d.ts.map +1 -1
- package/dist/abi/full/VarlaLiquidator.js +17 -1
- package/dist/abi/full/VarlaLiquidator.js.map +1 -1
- package/dist/abi/full/VarlaOracle.d.ts +18 -0
- package/dist/abi/full/VarlaOracle.d.ts.map +1 -1
- package/dist/abi/full/VarlaOracle.js +23 -0
- package/dist/abi/full/VarlaOracle.js.map +1 -1
- package/dist/actions/oracle.d.ts +26 -0
- package/dist/actions/oracle.d.ts.map +1 -1
- package/dist/actions/oracle.js +36 -0
- package/dist/actions/oracle.js.map +1 -1
- package/dist/actions/oracleUpdaterRouter.d.ts +4 -6
- package/dist/actions/oracleUpdaterRouter.d.ts.map +1 -1
- package/dist/actions/oracleUpdaterRouter.js +5 -5
- package/dist/actions/oracleUpdaterRouter.js.map +1 -1
- package/dist/actions/rbac.d.ts +6 -0
- package/dist/actions/rbac.d.ts.map +1 -1
- package/dist/actions/rbac.js +6 -0
- package/dist/actions/rbac.js.map +1 -1
- package/dist/addresses/polygon.js +9 -9
- package/dist/generated.d.ts +41 -1
- package/dist/generated.d.ts.map +1 -1
- package/dist/views/liquidators.d.ts +82 -1
- package/dist/views/liquidators.d.ts.map +1 -1
- package/dist/views/liquidators.js +87 -0
- package/dist/views/liquidators.js.map +1 -1
- package/dist/views/oracle.d.ts +1 -0
- package/dist/views/oracle.d.ts.map +1 -1
- package/dist/views/oracle.js +3 -0
- package/dist/views/oracle.js.map +1 -1
- package/package.json +1 -1
- package/src/abi/full/OracleUpdaterRouter.ts +13 -0
- package/src/abi/full/VarlaLiquidator.ts +17 -1
- package/src/abi/full/VarlaOracle.ts +23 -0
- package/src/actions/oracle.ts +62 -0
- package/src/actions/oracleUpdaterRouter.ts +7 -7
- package/src/actions/rbac.ts +6 -0
- package/src/addresses/polygon.json +9 -9
- package/src/addresses/polygon.ts +9 -9
- package/src/views/liquidators.ts +151 -1
- package/src/views/oracle.ts +4 -0
package/src/views/liquidators.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Note: explicit .js extension is required for Node ESM resolution.
|
|
2
2
|
|
|
3
|
-
import type { Address } from "viem";
|
|
3
|
+
import type { Address, PublicClient } from "viem";
|
|
4
|
+
|
|
5
|
+
import { readAccountPositionsFull } from "./core.js";
|
|
6
|
+
import { type ReadPositionSnapshot, readManyPositionSnapshots } from "./oracle.js";
|
|
4
7
|
|
|
5
8
|
type LiquidatorLike = {
|
|
6
9
|
read: {
|
|
@@ -225,3 +228,150 @@ export async function readConvertLiquidatorBasics(params: {
|
|
|
225
228
|
const negRiskAdapter = await params.liquidator.read.negRiskAdapter();
|
|
226
229
|
return { ...base, negRiskAdapter };
|
|
227
230
|
}
|
|
231
|
+
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// Liquidation candidate ranking
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* A single position candidate for liquidation, enriched with pricing data.
|
|
238
|
+
*/
|
|
239
|
+
export type LiquidationCandidate = {
|
|
240
|
+
/** The ERC-1155 position token ID. */
|
|
241
|
+
positionId: bigint;
|
|
242
|
+
/** User's balance (raw token units). */
|
|
243
|
+
balance: bigint;
|
|
244
|
+
/** Oracle price (8 decimals). 0 if price unavailable. */
|
|
245
|
+
priceE8: bigint;
|
|
246
|
+
/** Whether the oracle price is valid / not stale. */
|
|
247
|
+
priceOk: boolean;
|
|
248
|
+
/** Whether the position has been manually invalidated. */
|
|
249
|
+
manuallyInvalidated: boolean;
|
|
250
|
+
/** Notional value = balance × priceE8 / 1e8 (floor). Used for sorting. */
|
|
251
|
+
notional: bigint;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Pure utility: rank position candidates by notional value (descending).
|
|
256
|
+
*
|
|
257
|
+
* Filters out positions with zero balance, unavailable prices, or manual invalidation.
|
|
258
|
+
* The returned array is ordered so the highest-value positions come first — when passed
|
|
259
|
+
* directly to `prepareLiquidatorLiquidateMulti`, the liquidator seizes the most valuable
|
|
260
|
+
* tokens first.
|
|
261
|
+
*
|
|
262
|
+
* @param positions - Parallel arrays of position IDs and balances.
|
|
263
|
+
* @param snapshots - Oracle snapshots (one per position ID, same order or keyed by ID).
|
|
264
|
+
* @param maxPositions - Optional cap on returned candidates.
|
|
265
|
+
* @returns Ranked candidates, highest notional first.
|
|
266
|
+
*/
|
|
267
|
+
export function rankPositionsForLiquidation(params: {
|
|
268
|
+
positionIds: readonly bigint[];
|
|
269
|
+
balances: readonly bigint[];
|
|
270
|
+
snapshots: readonly ReadPositionSnapshot[];
|
|
271
|
+
maxPositions?: number;
|
|
272
|
+
}): LiquidationCandidate[] {
|
|
273
|
+
const snapshotByPid = new Map(params.snapshots.map((s) => [s.positionId.toString(), s]));
|
|
274
|
+
|
|
275
|
+
const candidates: LiquidationCandidate[] = [];
|
|
276
|
+
for (let i = 0; i < params.positionIds.length; i++) {
|
|
277
|
+
const positionId = params.positionIds[i]!;
|
|
278
|
+
const balance = params.balances[i] ?? 0n;
|
|
279
|
+
if (balance <= 0n) continue;
|
|
280
|
+
|
|
281
|
+
const snap = snapshotByPid.get(positionId.toString());
|
|
282
|
+
const priceOk = snap?.priceOk ?? false;
|
|
283
|
+
const priceE8 = snap?.priceE8 ?? 0n;
|
|
284
|
+
const manuallyInvalidated = snap?.manuallyInvalidated ?? false;
|
|
285
|
+
|
|
286
|
+
if (!priceOk || manuallyInvalidated || priceE8 <= 0n) continue;
|
|
287
|
+
|
|
288
|
+
const notional = (balance * priceE8) / 100_000_000n;
|
|
289
|
+
candidates.push({ positionId, balance, priceE8, priceOk, manuallyInvalidated, notional });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Sort by notional descending — highest value first.
|
|
293
|
+
candidates.sort((a, b) => (a.notional > b.notional ? -1 : a.notional < b.notional ? 1 : 0));
|
|
294
|
+
|
|
295
|
+
if (params.maxPositions != null && params.maxPositions > 0) {
|
|
296
|
+
return candidates.slice(0, params.maxPositions);
|
|
297
|
+
}
|
|
298
|
+
return candidates;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Result of `readLiquidationCandidates`.
|
|
303
|
+
*/
|
|
304
|
+
export type ReadLiquidationCandidates = {
|
|
305
|
+
user: Address;
|
|
306
|
+
/** All candidates ranked by notional value descending. */
|
|
307
|
+
candidates: LiquidationCandidate[];
|
|
308
|
+
/** Convenience: just the position IDs, ready to pass to `prepareLiquidatorLiquidateMulti`. */
|
|
309
|
+
positionIds: bigint[];
|
|
310
|
+
/** Total notional value across all candidates. */
|
|
311
|
+
totalNotional: bigint;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Fetch a user's collateral positions, enrich with oracle prices, and return
|
|
316
|
+
* ranked candidates suitable for multi-position liquidation.
|
|
317
|
+
*
|
|
318
|
+
* This is a high-level helper that combines `readAccountPositionsFull` +
|
|
319
|
+
* `readManyPositionSnapshots` + `rankPositionsForLiquidation` into one call.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* const result = await readLiquidationCandidates({
|
|
324
|
+
* core: contracts.core,
|
|
325
|
+
* oracle: contracts.oracle,
|
|
326
|
+
* client: publicClient,
|
|
327
|
+
* user: "0x...",
|
|
328
|
+
* maxPositions: 10,
|
|
329
|
+
* });
|
|
330
|
+
* // result.positionIds → ready for prepareLiquidatorLiquidateMulti
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
// wraps: VarlaCore.getPositions,VarlaOracle.getPositionSnapshot
|
|
334
|
+
export async function readLiquidationCandidates(params: {
|
|
335
|
+
core: {
|
|
336
|
+
read: {
|
|
337
|
+
getPositions: (args: readonly [Address]) => Promise<unknown>;
|
|
338
|
+
};
|
|
339
|
+
};
|
|
340
|
+
oracle: { address: Address };
|
|
341
|
+
client: Pick<PublicClient, "multicall">;
|
|
342
|
+
user: Address;
|
|
343
|
+
maxPositions?: number;
|
|
344
|
+
}): Promise<ReadLiquidationCandidates> {
|
|
345
|
+
const positions = await readAccountPositionsFull({
|
|
346
|
+
core: params.core,
|
|
347
|
+
user: params.user,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
const heldIds = positions.positionIds.filter((_, i) => (positions.balances[i] ?? 0n) > 0n);
|
|
351
|
+
|
|
352
|
+
if (heldIds.length === 0) {
|
|
353
|
+
return { user: params.user, candidates: [], positionIds: [], totalNotional: 0n };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const snapshots = await readManyPositionSnapshots({
|
|
357
|
+
oracle: params.oracle,
|
|
358
|
+
client: params.client,
|
|
359
|
+
positionIds: heldIds,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const candidates = rankPositionsForLiquidation({
|
|
363
|
+
positionIds: positions.positionIds,
|
|
364
|
+
balances: positions.balances,
|
|
365
|
+
snapshots,
|
|
366
|
+
maxPositions: params.maxPositions,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const totalNotional = candidates.reduce((sum, c) => sum + c.notional, 0n);
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
user: params.user,
|
|
373
|
+
candidates,
|
|
374
|
+
positionIds: candidates.map((c) => c.positionId),
|
|
375
|
+
totalNotional,
|
|
376
|
+
};
|
|
377
|
+
}
|
package/src/views/oracle.ts
CHANGED
|
@@ -446,6 +446,7 @@ export type ReadPositionSnapshot = {
|
|
|
446
446
|
manuallyInvalidated: boolean;
|
|
447
447
|
riskTier: number;
|
|
448
448
|
earlyClosureFactorWad: bigint;
|
|
449
|
+
isFinalized: boolean;
|
|
449
450
|
lastRecoveryFromStale: bigint;
|
|
450
451
|
};
|
|
451
452
|
|
|
@@ -458,6 +459,7 @@ function normalizePositionSnapshot(raw: unknown, positionId: bigint): ReadPositi
|
|
|
458
459
|
const riskTier = r.riskTier ?? r[3];
|
|
459
460
|
const earlyClosureFactorWad = r.earlyClosureFactorWad ?? r[4];
|
|
460
461
|
const lastRecoveryFromStale = r.lastRecoveryFromStale_ ?? r.lastRecoveryFromStale ?? r[5];
|
|
462
|
+
const isFinalized = r.isFinalized_ ?? r.isFinalized ?? r[6];
|
|
461
463
|
|
|
462
464
|
if (
|
|
463
465
|
typeof priceOk !== "boolean" ||
|
|
@@ -465,6 +467,7 @@ function normalizePositionSnapshot(raw: unknown, positionId: bigint): ReadPositi
|
|
|
465
467
|
typeof manuallyInvalidated !== "boolean" ||
|
|
466
468
|
(typeof riskTier !== "number" && typeof riskTier !== "bigint") ||
|
|
467
469
|
typeof earlyClosureFactorWad !== "bigint" ||
|
|
470
|
+
typeof isFinalized !== "boolean" ||
|
|
468
471
|
typeof lastRecoveryFromStale !== "bigint"
|
|
469
472
|
) {
|
|
470
473
|
throw new Error("Unexpected getPositionSnapshot() return shape");
|
|
@@ -477,6 +480,7 @@ function normalizePositionSnapshot(raw: unknown, positionId: bigint): ReadPositi
|
|
|
477
480
|
manuallyInvalidated,
|
|
478
481
|
riskTier: Number(riskTier),
|
|
479
482
|
earlyClosureFactorWad,
|
|
483
|
+
isFinalized,
|
|
480
484
|
lastRecoveryFromStale,
|
|
481
485
|
};
|
|
482
486
|
}
|