@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.
Files changed (50) hide show
  1. package/AGENTS.md +9 -9
  2. package/CHANGELOG.md +67 -0
  3. package/LICENSE +21 -0
  4. package/README.md +1 -1
  5. package/dist/abi/full/OracleUpdaterRouter.d.ts +10 -0
  6. package/dist/abi/full/OracleUpdaterRouter.d.ts.map +1 -1
  7. package/dist/abi/full/OracleUpdaterRouter.js +13 -0
  8. package/dist/abi/full/OracleUpdaterRouter.js.map +1 -1
  9. package/dist/abi/full/VarlaLiquidator.d.ts +13 -1
  10. package/dist/abi/full/VarlaLiquidator.d.ts.map +1 -1
  11. package/dist/abi/full/VarlaLiquidator.js +17 -1
  12. package/dist/abi/full/VarlaLiquidator.js.map +1 -1
  13. package/dist/abi/full/VarlaOracle.d.ts +18 -0
  14. package/dist/abi/full/VarlaOracle.d.ts.map +1 -1
  15. package/dist/abi/full/VarlaOracle.js +23 -0
  16. package/dist/abi/full/VarlaOracle.js.map +1 -1
  17. package/dist/actions/oracle.d.ts +26 -0
  18. package/dist/actions/oracle.d.ts.map +1 -1
  19. package/dist/actions/oracle.js +36 -0
  20. package/dist/actions/oracle.js.map +1 -1
  21. package/dist/actions/oracleUpdaterRouter.d.ts +4 -6
  22. package/dist/actions/oracleUpdaterRouter.d.ts.map +1 -1
  23. package/dist/actions/oracleUpdaterRouter.js +5 -5
  24. package/dist/actions/oracleUpdaterRouter.js.map +1 -1
  25. package/dist/actions/rbac.d.ts +6 -0
  26. package/dist/actions/rbac.d.ts.map +1 -1
  27. package/dist/actions/rbac.js +6 -0
  28. package/dist/actions/rbac.js.map +1 -1
  29. package/dist/addresses/polygon.js +9 -9
  30. package/dist/generated.d.ts +41 -1
  31. package/dist/generated.d.ts.map +1 -1
  32. package/dist/views/liquidators.d.ts +82 -1
  33. package/dist/views/liquidators.d.ts.map +1 -1
  34. package/dist/views/liquidators.js +87 -0
  35. package/dist/views/liquidators.js.map +1 -1
  36. package/dist/views/oracle.d.ts +1 -0
  37. package/dist/views/oracle.d.ts.map +1 -1
  38. package/dist/views/oracle.js +3 -0
  39. package/dist/views/oracle.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/abi/full/OracleUpdaterRouter.ts +13 -0
  42. package/src/abi/full/VarlaLiquidator.ts +17 -1
  43. package/src/abi/full/VarlaOracle.ts +23 -0
  44. package/src/actions/oracle.ts +62 -0
  45. package/src/actions/oracleUpdaterRouter.ts +7 -7
  46. package/src/actions/rbac.ts +6 -0
  47. package/src/addresses/polygon.json +9 -9
  48. package/src/addresses/polygon.ts +9 -9
  49. package/src/views/liquidators.ts +151 -1
  50. package/src/views/oracle.ts +4 -0
@@ -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
+ }
@@ -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
  }