@gearbox-protocol/sdk 18.2.0-next.10 → 18.2.0-next.12

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 (56) hide show
  1. package/dist/cjs/permissionless/api/index.js +30 -0
  2. package/dist/cjs/permissionless/api/oracles/PermissionlessOracles.js +191 -0
  3. package/dist/cjs/permissionless/api/oracles/index.js +32 -0
  4. package/dist/cjs/permissionless/api/oracles/schemas.js +17 -0
  5. package/dist/cjs/permissionless/api/oracles/score/formula.js +61 -0
  6. package/dist/cjs/permissionless/api/oracles/score/index.js +11 -0
  7. package/dist/cjs/permissionless/api/oracles/score/run.js +171 -0
  8. package/dist/cjs/permissionless/api/oracles/score/types.js +1 -0
  9. package/dist/cjs/permissionless/api/oracles/stress/classify.js +258 -0
  10. package/dist/cjs/permissionless/api/oracles/stress/index.js +23 -0
  11. package/dist/cjs/permissionless/api/oracles/stress/override.js +125 -0
  12. package/dist/cjs/permissionless/api/oracles/stress/run.js +435 -0
  13. package/dist/cjs/permissionless/api/oracles/stress/score.js +98 -0
  14. package/dist/cjs/permissionless/api/oracles/stress/types.js +1 -0
  15. package/dist/cjs/permissionless/index.js +31 -0
  16. package/dist/cjs/permissionless/utils/index.js +1 -0
  17. package/dist/cjs/permissionless/utils/price-update/get-prices.js +39 -0
  18. package/dist/cjs/permissionless/utils/price-update/index.js +1 -0
  19. package/dist/esm/permissionless/api/index.js +7 -1
  20. package/dist/esm/permissionless/api/oracles/PermissionlessOracles.js +193 -2
  21. package/dist/esm/permissionless/api/oracles/index.js +10 -1
  22. package/dist/esm/permissionless/api/oracles/schemas.js +16 -1
  23. package/dist/esm/permissionless/api/oracles/score/formula.js +55 -0
  24. package/dist/esm/permissionless/api/oracles/score/index.js +4 -0
  25. package/dist/esm/permissionless/api/oracles/score/run.js +170 -0
  26. package/dist/esm/permissionless/api/oracles/score/types.js +1 -0
  27. package/dist/esm/permissionless/api/oracles/stress/classify.js +256 -0
  28. package/dist/esm/permissionless/api/oracles/stress/index.js +6 -0
  29. package/dist/esm/permissionless/api/oracles/stress/override.js +119 -0
  30. package/dist/esm/permissionless/api/oracles/stress/run.js +429 -0
  31. package/dist/esm/permissionless/api/oracles/stress/score.js +95 -0
  32. package/dist/esm/permissionless/api/oracles/stress/types.js +1 -0
  33. package/dist/esm/permissionless/index.js +8 -2
  34. package/dist/esm/permissionless/utils/index.js +2 -2
  35. package/dist/esm/permissionless/utils/price-update/get-prices.js +39 -1
  36. package/dist/esm/permissionless/utils/price-update/index.js +2 -2
  37. package/dist/types/permissionless/api/index.d.ts +10 -2
  38. package/dist/types/permissionless/api/oracles/PermissionlessOracles.d.ts +115 -1
  39. package/dist/types/permissionless/api/oracles/index.d.ts +12 -2
  40. package/dist/types/permissionless/api/oracles/schemas.d.ts +47 -0
  41. package/dist/types/permissionless/api/oracles/score/formula.d.ts +39 -0
  42. package/dist/types/permissionless/api/oracles/score/index.d.ts +4 -0
  43. package/dist/types/permissionless/api/oracles/score/run.d.ts +24 -0
  44. package/dist/types/permissionless/api/oracles/score/types.d.ts +113 -0
  45. package/dist/types/permissionless/api/oracles/stress/classify.d.ts +57 -0
  46. package/dist/types/permissionless/api/oracles/stress/index.d.ts +6 -0
  47. package/dist/types/permissionless/api/oracles/stress/override.d.ts +59 -0
  48. package/dist/types/permissionless/api/oracles/stress/run.d.ts +89 -0
  49. package/dist/types/permissionless/api/oracles/stress/score.d.ts +57 -0
  50. package/dist/types/permissionless/api/oracles/stress/types.d.ts +272 -0
  51. package/dist/types/permissionless/api/oracles/types.d.ts +58 -0
  52. package/dist/types/permissionless/index.d.ts +11 -3
  53. package/dist/types/permissionless/utils/index.d.ts +2 -2
  54. package/dist/types/permissionless/utils/price-update/get-prices.d.ts +16 -0
  55. package/dist/types/permissionless/utils/price-update/index.d.ts +2 -2
  56. package/package.json +1 -1
@@ -7,6 +7,12 @@ require("./chains/index.js");
7
7
  const require_permissionless_api_deploy_effective_staleness = require("./deploy/effective-staleness.js");
8
8
  const require_permissionless_api_deploy_PermissionlessDeploy = require("./deploy/PermissionlessDeploy.js");
9
9
  require("./deploy/index.js");
10
+ const require_permissionless_api_oracles_stress_classify = require("./oracles/stress/classify.js");
11
+ const require_permissionless_api_oracles_stress_override = require("./oracles/stress/override.js");
12
+ const require_permissionless_api_oracles_stress_score = require("./oracles/stress/score.js");
13
+ const require_permissionless_api_oracles_stress_run = require("./oracles/stress/run.js");
14
+ const require_permissionless_api_oracles_score_formula = require("./oracles/score/formula.js");
15
+ const require_permissionless_api_oracles_score_run = require("./oracles/score/run.js");
10
16
  const require_permissionless_api_oracles_PermissionlessOracles = require("./oracles/PermissionlessOracles.js");
11
17
  const require_permissionless_api_transactions_InstanceOwnerTransactions = require("./transactions/InstanceOwnerTransactions.js");
12
18
  const require_permissionless_api_GearboxPermissionless = require("./GearboxPermissionless.js");
@@ -15,6 +21,7 @@ require("./transactions/index.js");
15
21
  require("./types.js");
16
22
  exports.ChainsNamespace = require_permissionless_api_chains_ChainsNamespace.ChainsNamespace;
17
23
  exports.DEFAULT_DEPLOY_SALT = require_permissionless_api_deploy_PermissionlessDeploy.DEFAULT_DEPLOY_SALT;
24
+ exports.DEFAULT_SCENARIOS = require_permissionless_api_oracles_stress_run.DEFAULT_SCENARIOS;
18
25
  exports.GET_PRICES_CHUNK_SIZE = require_permissionless_api_oracles_PermissionlessOracles.GET_PRICES_CHUNK_SIZE;
19
26
  exports.GET_PRICES_GAS_LIMIT_BY_CHAIN_ID = require_permissionless_api_oracles_PermissionlessOracles.GET_PRICES_GAS_LIMIT_BY_CHAIN_ID;
20
27
  exports.GearboxPermissionless = require_permissionless_api_GearboxPermissionless.GearboxPermissionless;
@@ -27,5 +34,28 @@ exports.PermissionlessOracles = require_permissionless_api_oracles_Permissionles
27
34
  exports.PriceFeedNotInStoreError = require_permissionless_api_errors.PriceFeedNotInStoreError;
28
35
  exports.PriceFeedSaltMissingError = require_permissionless_api_errors.PriceFeedSaltMissingError;
29
36
  exports.PriceFeedStalenessUnknownError = require_permissionless_api_errors.PriceFeedStalenessUnknownError;
37
+ exports.RATE_FEED_TYPE = require_permissionless_api_oracles_stress_run.RATE_FEED_TYPE;
38
+ exports.REVERT_SCORE = require_permissionless_api_oracles_score_formula.REVERT_SCORE;
39
+ exports.SCORE_MULTIPLIER = require_permissionless_api_oracles_score_formula.SCORE_MULTIPLIER;
40
+ exports.UNBOUNDED_DOWN = require_permissionless_api_oracles_stress_override.UNBOUNDED_DOWN;
41
+ exports.UNBOUNDED_UP = require_permissionless_api_oracles_stress_override.UNBOUNDED_UP;
42
+ exports.classifySources = require_permissionless_api_oracles_stress_classify.classifySources;
30
43
  exports.deriveEffectiveStalenessPeriod = require_permissionless_api_deploy_effective_staleness.deriveEffectiveStalenessPeriod;
44
+ exports.factorOf = require_permissionless_api_oracles_score_formula.factorOf;
45
+ exports.finalPrice = require_permissionless_api_oracles_score_formula.finalPrice;
46
+ exports.groupOverride = require_permissionless_api_oracles_stress_override.groupOverride;
47
+ exports.perturb = require_permissionless_api_oracles_stress_override.perturb;
31
48
  exports.priceFeedSalt = require_permissionless_api_deploy_PermissionlessDeploy.priceFeedSalt;
49
+ exports.rateStub = require_permissionless_api_oracles_stress_override.rateStub;
50
+ exports.readForest = require_permissionless_api_oracles_stress_run.readForest;
51
+ exports.readRateTargets = require_permissionless_api_oracles_stress_run.readRateTargets;
52
+ exports.readUnder = require_permissionless_api_oracles_stress_run.readUnder;
53
+ exports.roundDataStub = require_permissionless_api_oracles_stress_override.roundDataStub;
54
+ exports.runAssetScores = require_permissionless_api_oracles_score_run.runAssetScores;
55
+ exports.runStressTest = require_permissionless_api_oracles_stress_run.runStressTest;
56
+ exports.scoreGroup = require_permissionless_api_oracles_stress_score.scoreGroup;
57
+ exports.scoreOfFactor = require_permissionless_api_oracles_score_formula.scoreOfFactor;
58
+ exports.slotHarm = require_permissionless_api_oracles_stress_score.slotHarm;
59
+ exports.sourcesByFeed = require_permissionless_api_oracles_stress_classify.sourcesByFeed;
60
+ exports.verdictOf = require_permissionless_api_oracles_stress_score.verdictOf;
61
+ exports.worstOf = require_permissionless_api_oracles_score_formula.worstOf;
@@ -10,6 +10,8 @@ const require_permissionless_bindings_address_provider = require("../../bindings
10
10
  const require_permissionless_utils_price_update_get_prices = require("../../utils/price-update/get-prices.js");
11
11
  const require_permissionless_api_oracles_enrich = require("./enrich.js");
12
12
  const require_permissionless_api_oracles_schemas = require("./schemas.js");
13
+ const require_permissionless_api_oracles_stress_run = require("./stress/run.js");
14
+ const require_permissionless_api_oracles_score_run = require("./score/run.js");
13
15
  let viem = require("viem");
14
16
  //#region src/permissionless/api/oracles/PermissionlessOracles.ts
15
17
  const GET_PRICES_GAS_LIMIT_BY_CHAIN_ID = { 143: 100000000n };
@@ -94,6 +96,31 @@ var PermissionlessOracles = class extends require_offchain_AbstractOffchainNames
94
96
  });
95
97
  }
96
98
  /**
99
+ * Every market slot in force, with the feed holding it and what the market
100
+ * has riding on it.
101
+ *
102
+ * The chain's whole slot table, which {@link feed} and {@link asset} answer
103
+ * one subject's slice of. What reads it this way is analysis over every
104
+ * market at once — which feed holds which slot, and what breaks if it
105
+ * lies — and asking a feed at a time would be a few hundred requests for
106
+ * the same rows.
107
+ *
108
+ * Served as indexed, with nothing folded in: the quotas are in each
109
+ * market's own underlying, as everywhere else, and pricing them is
110
+ * {@link getPrices} over the `underlyingPriceFeed` column.
111
+ *
112
+ * ```ts
113
+ * const slots = await permissionless.oracles.slots({ chainId: 1 });
114
+ * ```
115
+ **/
116
+ async slots({ chainId } = {}) {
117
+ return this.getData({
118
+ path: "/pfs/slots",
119
+ query: { chainId: chainId?.toString() },
120
+ schema: require_permissionless_api_oracles_schemas.marketSlotsSchema
121
+ });
122
+ }
123
+ /**
97
124
  * One feed of the store: what it is, which assets it may price, and which
98
125
  * market slots it currently fills.
99
126
  *
@@ -290,6 +317,111 @@ var PermissionlessOracles = class extends require_offchain_AbstractOffchainNames
290
317
  }).sort(require_permissionless_api_oracles_enrich.byCentrality);
291
318
  }
292
319
  /**
320
+ * What each source of price information can do to the chain's markets if it
321
+ * lies.
322
+ *
323
+ * The feeds under a chain's oracles are partitioned by where their numbers
324
+ * actually come from — Chainlink, Redstone, Pyth, Lido, Curve, each vault's
325
+ * own issuer — on the grounds that a source fails as a whole: whoever
326
+ * trusts one Chainlink aggregator trusts the operator behind all of them.
327
+ * Each group is then made to report a multiple of what it reports now, and
328
+ * every feed the markets read is read again inside that same call.
329
+ *
330
+ * No fork and no harness. The lie is a state override on an `eth_call`, so
331
+ * the composite feeds above it, their bounds and the oracle's own checks all
332
+ * run as deployed, against real state, at the current block — what comes
333
+ * back is what the market would have read.
334
+ *
335
+ * Each slot is scored by how its collateral moved *against its market's own
336
+ * underlying*, because that is what an account is valued by: a group that
337
+ * moves both legs has moved nothing. Beyond the two unbounded ends, each
338
+ * group is bisected for the multiple at which something first breaks, which
339
+ * is the figure that separates a source held in check by a bound from one
340
+ * nothing is checking.
341
+ *
342
+ * ```ts
343
+ * const report = await permissionless.oracles.stressTest({ chainId: 1 });
344
+ * for (const group of report.groups) {
345
+ * console.log(group.verdict);
346
+ * }
347
+ * ```
348
+ *
349
+ * Report {@link StressReport.unidentifiedUsd} alongside anything taken from
350
+ * this: it is the quota priced through a source the partition could only
351
+ * name after its own description, and nothing says what that fails with.
352
+ *
353
+ * Reads the chain directly, so the client has to have attached — see
354
+ * {@link GearboxPermissionless.attach}. Costs one request for the slots and
355
+ * then, per group, one `eth_call` per scenario plus the bisection's; a whole
356
+ * chain comes to a minute or so, and `thresholds: false` cuts it to seconds.
357
+ *
358
+ * @throws When the client has not attached, when the chain has no client
359
+ * configured, or when no PriceFeedCompressor is deployed on it — none of the
360
+ * three leaves anything to simulate.
361
+ **/
362
+ /**
363
+ * Every asset's score out of 10 for how its markets guard its price.
364
+ *
365
+ * Each source group behind an asset's feeds — Chainlink, Redstone, Lido and
366
+ * so on — is made in turn to answer five times what it answers now, and a
367
+ * market scores by how far that moves its final price, `min(main,
368
+ * reserve)`: 10 for not at all, 0 for the full five times, on a log scale.
369
+ * A price that stops answering scores 5. An asset scores its worst running
370
+ * market that takes it as collateral.
371
+ *
372
+ * Simulated on the chain as it stands, through state overrides in
373
+ * `eth_call`: one call per source group per chain, so the node behind the
374
+ * client has to accept overrides. A chain with no client configured, or
375
+ * whose node refuses, is left out with a warning rather than failing the
376
+ * rest.
377
+ *
378
+ * ```ts
379
+ * const scores = await permissionless.oracles.assetScores({ chainId: 1 });
380
+ * ```
381
+ *
382
+ * @throws {PermissionlessNotAttachedError} before
383
+ * {@link GearboxPermissionless.attach} has resolved.
384
+ **/
385
+ async assetScores({ chainId } = {}) {
386
+ const slots = await this.slots({ chainId });
387
+ const byChain = /* @__PURE__ */ new Map();
388
+ for (const slot of slots) byChain.set(slot.chainId, [...byChain.get(slot.chainId) ?? [], slot]);
389
+ return (await Promise.all([...byChain].map(async ([id, chainSlots]) => {
390
+ let client;
391
+ try {
392
+ client = this.#chains.client(id);
393
+ } catch (e) {
394
+ this.logger?.warn(`chain ${id} has no client, leaving its assets unscored: ${describe(e)}`);
395
+ return [];
396
+ }
397
+ try {
398
+ const compressor = await new require_permissionless_bindings_address_provider.AddressProviderContract("0xF7f0a609BfAb9a0A98786951ef10e5FE26cC1E38", client).getLatestVersionAddress(require_onchain_constants_address_provider.AP_PRICE_FEED_COMPRESSOR);
399
+ return await require_permissionless_api_oracles_score_run.runAssetScores({
400
+ client,
401
+ chainId: id,
402
+ slots: chainSlots,
403
+ compressor,
404
+ logger: this.logger ?? void 0
405
+ });
406
+ } catch (e) {
407
+ this.logger?.warn(`chain ${id} would not report its feed trees, leaving its assets unscored: ${describe(e)}`);
408
+ return [];
409
+ }
410
+ }))).flat();
411
+ }
412
+ async stressTest({ chainId, ...options }) {
413
+ const client = this.#chains.client(chainId);
414
+ const compressor = await new require_permissionless_bindings_address_provider.AddressProviderContract("0xF7f0a609BfAb9a0A98786951ef10e5FE26cC1E38", client).getLatestVersionAddress(require_onchain_constants_address_provider.AP_PRICE_FEED_COMPRESSOR);
415
+ return require_permissionless_api_oracles_stress_run.runStressTest({
416
+ client,
417
+ chainId,
418
+ compressor,
419
+ slots: await this.slots({ chainId }),
420
+ logger: this.logger ?? void 0,
421
+ ...options
422
+ });
423
+ }
424
+ /**
293
425
  * The feeds one feed reads from, recursively.
294
426
  *
295
427
  * Answers for any feed the chain has indexed, store member or not: a
@@ -354,6 +486,65 @@ var PermissionlessOracles = class extends require_offchain_AbstractOffchainNames
354
486
  price: prices.get(chainId)?.[address.toLowerCase()] ?? null
355
487
  }));
356
488
  }
489
+ /**
490
+ * The scale each of the named feeds answers in.
491
+ *
492
+ * The companion to {@link getPrices} for the callers whose feeds are not
493
+ * all an oracle's: everything an oracle holds reports 8 decimals, but the
494
+ * feeds under a composite are whatever was deployed, and a rate leg at 18
495
+ * read as 8 is wrong by ten orders of magnitude rather than visibly wrong.
496
+ *
497
+ * Separate from the price because the two keep different company: this is
498
+ * fixed for the life of the contract and worth caching for the session,
499
+ * while a price is re-read whenever the reader asks. One multicall per
500
+ * chain either way.
501
+ *
502
+ * `null` is a feed that would not answer, or one on a chain with no client
503
+ * configured.
504
+ *
505
+ * ```ts
506
+ * const [{ decimals }] = await permissionless.oracles.getDecimals([
507
+ * { chainId: 1, address: feed },
508
+ * ]);
509
+ * ```
510
+ *
511
+ * @returns One entry per feed asked for, in the order asked.
512
+ **/
513
+ async getDecimals(feeds) {
514
+ const byChain = /* @__PURE__ */ new Map();
515
+ for (const { chainId, address } of feeds) {
516
+ const addresses = byChain.get(chainId) ?? /* @__PURE__ */ new Set();
517
+ addresses.add(address.toLowerCase());
518
+ byChain.set(chainId, addresses);
519
+ }
520
+ const chainIds = [...byChain.keys()];
521
+ const results = await Promise.all(chainIds.map((chainId) => this.#decimalsOf(chainId, [...byChain.get(chainId) ?? []])));
522
+ const decimals = new Map(chainIds.map((id, i) => [id, results[i] ?? {}]));
523
+ return feeds.map(({ chainId, address }) => ({
524
+ chainId,
525
+ address,
526
+ decimals: decimals.get(chainId)?.[address.toLowerCase()] ?? null
527
+ }));
528
+ }
529
+ /** One chain's half of {@link getDecimals}. */
530
+ async #decimalsOf(chainId, priceFeeds) {
531
+ let client;
532
+ try {
533
+ client = this.#chains.client(chainId);
534
+ } catch (e) {
535
+ this.logger?.warn(`chain ${chainId} has no client, reporting its ${priceFeeds.length} feed(s) without a scale: ${describe(e)}`);
536
+ return {};
537
+ }
538
+ try {
539
+ return await require_permissionless_utils_price_update_get_prices.getFeedDecimals({
540
+ client,
541
+ priceFeeds
542
+ });
543
+ } catch (e) {
544
+ this.logger?.warn(`chain ${chainId} would not answer decimals for its ${priceFeeds.length} feed(s): ${describe(e)}`);
545
+ return {};
546
+ }
547
+ }
357
548
  /** One chain's half of {@link getPrices}: the cheap pass, then the rest. */
358
549
  async #pricesOf(chainId, priceFeeds) {
359
550
  let client;
@@ -1,6 +1,38 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_permissionless_api_oracles_stress_classify = require("./stress/classify.js");
3
+ const require_permissionless_api_oracles_stress_override = require("./stress/override.js");
4
+ const require_permissionless_api_oracles_stress_score = require("./stress/score.js");
5
+ const require_permissionless_api_oracles_stress_run = require("./stress/run.js");
6
+ const require_permissionless_api_oracles_score_formula = require("./score/formula.js");
7
+ const require_permissionless_api_oracles_score_run = require("./score/run.js");
2
8
  const require_permissionless_api_oracles_PermissionlessOracles = require("./PermissionlessOracles.js");
9
+ require("./score/index.js");
10
+ require("./stress/index.js");
3
11
  require("./types.js");
12
+ exports.DEFAULT_SCENARIOS = require_permissionless_api_oracles_stress_run.DEFAULT_SCENARIOS;
4
13
  exports.GET_PRICES_CHUNK_SIZE = require_permissionless_api_oracles_PermissionlessOracles.GET_PRICES_CHUNK_SIZE;
5
14
  exports.GET_PRICES_GAS_LIMIT_BY_CHAIN_ID = require_permissionless_api_oracles_PermissionlessOracles.GET_PRICES_GAS_LIMIT_BY_CHAIN_ID;
6
15
  exports.PermissionlessOracles = require_permissionless_api_oracles_PermissionlessOracles.PermissionlessOracles;
16
+ exports.RATE_FEED_TYPE = require_permissionless_api_oracles_stress_run.RATE_FEED_TYPE;
17
+ exports.REVERT_SCORE = require_permissionless_api_oracles_score_formula.REVERT_SCORE;
18
+ exports.SCORE_MULTIPLIER = require_permissionless_api_oracles_score_formula.SCORE_MULTIPLIER;
19
+ exports.UNBOUNDED_DOWN = require_permissionless_api_oracles_stress_override.UNBOUNDED_DOWN;
20
+ exports.UNBOUNDED_UP = require_permissionless_api_oracles_stress_override.UNBOUNDED_UP;
21
+ exports.classifySources = require_permissionless_api_oracles_stress_classify.classifySources;
22
+ exports.factorOf = require_permissionless_api_oracles_score_formula.factorOf;
23
+ exports.finalPrice = require_permissionless_api_oracles_score_formula.finalPrice;
24
+ exports.groupOverride = require_permissionless_api_oracles_stress_override.groupOverride;
25
+ exports.perturb = require_permissionless_api_oracles_stress_override.perturb;
26
+ exports.rateStub = require_permissionless_api_oracles_stress_override.rateStub;
27
+ exports.readForest = require_permissionless_api_oracles_stress_run.readForest;
28
+ exports.readRateTargets = require_permissionless_api_oracles_stress_run.readRateTargets;
29
+ exports.readUnder = require_permissionless_api_oracles_stress_run.readUnder;
30
+ exports.roundDataStub = require_permissionless_api_oracles_stress_override.roundDataStub;
31
+ exports.runAssetScores = require_permissionless_api_oracles_score_run.runAssetScores;
32
+ exports.runStressTest = require_permissionless_api_oracles_stress_run.runStressTest;
33
+ exports.scoreGroup = require_permissionless_api_oracles_stress_score.scoreGroup;
34
+ exports.scoreOfFactor = require_permissionless_api_oracles_score_formula.scoreOfFactor;
35
+ exports.slotHarm = require_permissionless_api_oracles_stress_score.slotHarm;
36
+ exports.sourcesByFeed = require_permissionless_api_oracles_stress_classify.sourcesByFeed;
37
+ exports.verdictOf = require_permissionless_api_oracles_stress_score.verdictOf;
38
+ exports.worstOf = require_permissionless_api_oracles_score_formula.worstOf;
@@ -76,6 +76,21 @@ const priceFeedMarketSchema = marketSchema.extend({
76
76
  reserve: zod_v4.z.boolean(),
77
77
  priceOracle: require_permissionless_api_schemas.addressSchema
78
78
  });
79
+ /**
80
+ * {@link PermissionlessMarketSlot} — the same slot read from the chain's end,
81
+ * so the feed is a column rather than what was asked for.
82
+ **/
83
+ const marketSlotSchema = priceFeedMarketSchema.extend({
84
+ chainId: zod_v4.z.number(),
85
+ underlyingToken: require_permissionless_api_schemas.addressSchema,
86
+ isShutdown: zod_v4.z.boolean(),
87
+ priceFeed: require_permissionless_api_schemas.addressSchema,
88
+ priceFeedName: zod_v4.z.string().nullable(),
89
+ priceFeedInStore: zod_v4.z.boolean(),
90
+ underlyingPriceFeed: require_permissionless_api_schemas.addressSchema.nullable()
91
+ });
92
+ /** Every slot in scope, across whichever chains the read covered. */
93
+ const marketSlotsSchema = zod_v4.z.array(marketSlotSchema);
79
94
  /** {@link PermissionlessIndexedPriceFeedDetails} */
80
95
  const priceFeedDetailsSchema = zod_v4.z.looseObject({
81
96
  chainId: zod_v4.z.number(),
@@ -129,6 +144,8 @@ exports.assetQuotaSchema = assetQuotaSchema;
129
144
  exports.assetSchema = assetSchema;
130
145
  exports.assetSummariesSchema = assetSummariesSchema;
131
146
  exports.assetSummarySchema = assetSummarySchema;
147
+ exports.marketSlotSchema = marketSlotSchema;
148
+ exports.marketSlotsSchema = marketSlotsSchema;
132
149
  exports.priceFeedAssetSchema = priceFeedAssetSchema;
133
150
  exports.priceFeedDetailsSchema = priceFeedDetailsSchema;
134
151
  exports.priceFeedMarketSchema = priceFeedMarketSchema;
@@ -0,0 +1,61 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/permissionless/api/oracles/score/formula.ts
3
+ /** What every source of a group is made to answer, as a multiple of now. */
4
+ const SCORE_MULTIPLIER = 5;
5
+ /**
6
+ * The score of a price that stops answering under the lie.
7
+ *
8
+ * Half marks: nobody can borrow against a wrong number, but nobody can be
9
+ * liquidated either until the feed is replaced.
10
+ */
11
+ const REVERT_SCORE = 5;
12
+ /**
13
+ * 0 to 10 by how far the final price moved, on a log scale against the lie
14
+ * itself: 10 for a price that did not move, 0 for one that moved by the full
15
+ * {@link SCORE_MULTIPLIER} or more, either way. Rounded to a half point.
16
+ *
17
+ * A bound holding a price within a few percent keeps a 10; a price that let
18
+ * half the lie through, ×√5, scores 5.
19
+ */
20
+ function scoreOfFactor(factor) {
21
+ if (factor === null || !Number.isFinite(factor) || factor <= 0) return 5;
22
+ const moved = Math.abs(Math.log(factor)) / Math.log(5);
23
+ return Math.round(10 * Math.max(0, 1 - moved) * 2) / 2;
24
+ }
25
+ /**
26
+ * The price a market settles on: the lower of main and reserve, or the main
27
+ * feed's alone where there is no reserve to compare it with. `null` where a
28
+ * feed it needs does not answer.
29
+ */
30
+ function finalPrice(main, reserve) {
31
+ if (main === null || reserve === null) return null;
32
+ if (reserve === void 0) return main;
33
+ return main < reserve ? main : reserve;
34
+ }
35
+ /** `stressed / base` as a float, `null` where the stressed price is gone. */
36
+ function factorOf(base, stressed) {
37
+ if (stressed === null) return null;
38
+ return Number(stressed) / Number(base);
39
+ }
40
+ /**
41
+ * The market's score and the group it comes from: the lowest of its groups,
42
+ * ties going to the group that moved the price further. 10 where no source
43
+ * feeds the price at all, since nothing can be made to lie about it.
44
+ */
45
+ function worstOf(impacts) {
46
+ const distance = (impact) => impact.factor === null ? Infinity : Math.abs(Math.log(impact.factor));
47
+ const sorted = [...impacts].sort((a, b) => a.score - b.score || distance(b) - distance(a));
48
+ const worst = sorted[0] ?? null;
49
+ return {
50
+ score: worst?.score ?? 10,
51
+ impacts: sorted,
52
+ worst
53
+ };
54
+ }
55
+ //#endregion
56
+ exports.REVERT_SCORE = REVERT_SCORE;
57
+ exports.SCORE_MULTIPLIER = SCORE_MULTIPLIER;
58
+ exports.factorOf = factorOf;
59
+ exports.finalPrice = finalPrice;
60
+ exports.scoreOfFactor = scoreOfFactor;
61
+ exports.worstOf = worstOf;
@@ -0,0 +1,11 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_permissionless_api_oracles_score_formula = require("./formula.js");
3
+ const require_permissionless_api_oracles_score_run = require("./run.js");
4
+ require("./types.js");
5
+ exports.REVERT_SCORE = require_permissionless_api_oracles_score_formula.REVERT_SCORE;
6
+ exports.SCORE_MULTIPLIER = require_permissionless_api_oracles_score_formula.SCORE_MULTIPLIER;
7
+ exports.factorOf = require_permissionless_api_oracles_score_formula.factorOf;
8
+ exports.finalPrice = require_permissionless_api_oracles_score_formula.finalPrice;
9
+ exports.runAssetScores = require_permissionless_api_oracles_score_run.runAssetScores;
10
+ exports.scoreOfFactor = require_permissionless_api_oracles_score_formula.scoreOfFactor;
11
+ exports.worstOf = require_permissionless_api_oracles_score_formula.worstOf;
@@ -0,0 +1,171 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_permissionless_api_oracles_stress_classify = require("../stress/classify.js");
3
+ const require_permissionless_api_oracles_stress_run = require("../stress/run.js");
4
+ const require_permissionless_api_oracles_score_formula = require("./formula.js");
5
+ let viem = require("viem");
6
+ //#region src/permissionless/api/oracles/score/run.ts
7
+ const ZERO_TYPE = "PRICE_FEED::ZERO";
8
+ /** Groups simulated at once: one `eth_call` each, all against one node. */
9
+ const CONCURRENCY = 6;
10
+ const feedOf = (slot) => slot.priceFeed.toLowerCase();
11
+ /**
12
+ * Every asset of one chain, scored by what each of its source groups can do
13
+ * to the price its running markets hold for it.
14
+ *
15
+ * One read of the oracles' feed trees, one of the LP feeds' rate contracts,
16
+ * one of every market feed as it stands — then one `eth_call` per source
17
+ * group, with the group's sources overridden to answer
18
+ * {@link SCORE_MULTIPLIER} times what they answer now and every market feed
19
+ * read under it.
20
+ **/
21
+ async function runAssetScores({ client, chainId, slots, compressor, logger }) {
22
+ const running = slots.filter((slot) => !slot.isShutdown);
23
+ const oracles = [...new Set(running.map((slot) => slot.priceOracle.toLowerCase()))];
24
+ const { nodes, answers, unreadableOracles } = await require_permissionless_api_oracles_stress_run.readForest(client, compressor, oracles);
25
+ for (const oracle of unreadableOracles) logger?.warn(`price oracle ${oracle} of chain ${chainId} would not report its feed tree; the markets on it are not scored`);
26
+ const { targets, baselines: rateBaselines } = await require_permissionless_api_oracles_stress_run.readRateTargets(client, [...nodes.values()].filter((node) => require_permissionless_api_oracles_stress_run.RATE_FEED_TYPE.test(node.contractType)).map((node) => node.address));
27
+ const { sources, unresolved } = require_permissionless_api_oracles_stress_classify.classifySources(chainId, [...nodes.values()], targets, (target) => rateBaselines.get(target) ?? answers.get(target) ?? null);
28
+ for (const node of unresolved) logger?.warn(`feed ${node.address} of chain ${chainId} would not say what it reads its rate from; its protocol is not perturbed`);
29
+ const slotsByPosition = /* @__PURE__ */ new Map();
30
+ for (const slot of running) {
31
+ const key = `${slot.market.toLowerCase()}:${slot.token.toLowerCase()}`;
32
+ const entry = slotsByPosition.get(key) ?? {};
33
+ entry[slot.reserve ? "reserve" : "main"] = slot;
34
+ slotsByPosition.set(key, entry);
35
+ }
36
+ const positions = [];
37
+ for (const { main, reserve } of slotsByPosition.values()) {
38
+ if (!main || main.isUnderlying) continue;
39
+ const mainNode = nodes.get(feedOf(main));
40
+ if (!mainNode || mainNode.contractType === ZERO_TYPE) continue;
41
+ const notes = [];
42
+ let reserveFeed;
43
+ if (!reserve) notes.push("no reserve feed");
44
+ else if (nodes.get(feedOf(reserve))?.contractType === ZERO_TYPE) notes.push("reserve is a ZeroPriceFeed");
45
+ else {
46
+ reserveFeed = feedOf(reserve);
47
+ if (reserveFeed === feedOf(main)) notes.push("reserve is the main feed");
48
+ }
49
+ positions.push({
50
+ main,
51
+ reserve,
52
+ reserveFeed,
53
+ notes
54
+ });
55
+ }
56
+ const priced = [...new Set(positions.flatMap(({ main, reserveFeed }) => reserveFeed ? [feedOf(main), reserveFeed] : [feedOf(main)]))];
57
+ if (priced.length === 0) return [];
58
+ const underlyingOf = (slot) => slot.underlyingPriceFeed?.toLowerCase();
59
+ const roots = [.../* @__PURE__ */ new Set([...priced, ...positions.map(({ main }) => underlyingOf(main)).filter((feed) => feed !== void 0)])];
60
+ const updatedAt = BigInt(Math.floor(Date.now() / 1e3));
61
+ const drivable = (source) => source !== void 0 && source.perturbable && source.baseline !== null;
62
+ const fresh = sources.filter((source) => drivable(source) && source.kind === "feed");
63
+ const asIs = await require_permissionless_api_oracles_stress_run.readUnder(client, roots, [], 1, updatedAt);
64
+ const stale = roots.filter((root) => (asIs.get(root) ?? null) === null);
65
+ const freshened = stale.length > 0 ? await require_permissionless_api_oracles_stress_run.readUnder(client, stale, [], 1, updatedAt, fresh) : /* @__PURE__ */ new Map();
66
+ const base = new Map(roots.map((root) => [root, asIs.get(root) ?? freshened.get(root) ?? null]));
67
+ const asIsRoots = roots.filter((root) => (asIs.get(root) ?? null) !== null);
68
+ const freshRoots = stale.filter((root) => (freshened.get(root) ?? null) !== null);
69
+ /** Every root under a lie, each read the way its base was. */
70
+ const readLying = async (members) => {
71
+ const [plain, refreshed] = await Promise.all([asIsRoots.length > 0 ? require_permissionless_api_oracles_stress_run.readUnder(client, asIsRoots, members, 5, updatedAt) : /* @__PURE__ */ new Map(), freshRoots.length > 0 ? require_permissionless_api_oracles_stress_run.readUnder(client, freshRoots, members, 5, updatedAt, fresh) : /* @__PURE__ */ new Map()]);
72
+ return new Map([...plain, ...refreshed]);
73
+ };
74
+ for (const position of positions) if (position.reserveFeed && (base.get(position.reserveFeed) ?? null) === null) {
75
+ position.reserveFeed = void 0;
76
+ position.notes.push("reserve does not answer");
77
+ }
78
+ const reach = require_permissionless_api_oracles_stress_classify.sourcesByFeed(roots, nodes, sources);
79
+ const sourceByTarget = new Map(sources.map((source) => [source.target, source]));
80
+ const byGroup = /* @__PURE__ */ new Map();
81
+ for (const source of sources.filter(drivable)) byGroup.set(source.group, [...byGroup.get(source.group) ?? [], source]);
82
+ const reachedGroups = /* @__PURE__ */ new Set();
83
+ for (const root of priced) for (const target of reach.get(root) ?? []) {
84
+ const source = sourceByTarget.get(target);
85
+ if (drivable(source)) reachedGroups.add(source.group);
86
+ }
87
+ const stressed = /* @__PURE__ */ new Map();
88
+ const queue = [...reachedGroups];
89
+ for (let i = 0; i < queue.length; i += CONCURRENCY) await Promise.all(queue.slice(i, i + CONCURRENCY).map(async (group) => {
90
+ stressed.set(group, await readLying(byGroup.get(group) ?? []));
91
+ }));
92
+ const groupsOf = (feed) => {
93
+ const out = /* @__PURE__ */ new Map();
94
+ for (const target of feed ? reach.get(feed) ?? [] : []) {
95
+ const source = sourceByTarget.get(target);
96
+ if (drivable(source)) out.set(source.group, [...out.get(source.group) ?? [], source]);
97
+ }
98
+ return out;
99
+ };
100
+ const byAsset = /* @__PURE__ */ new Map();
101
+ for (const { main, reserve, reserveFeed, notes } of positions) {
102
+ const mainFeed = feedOf(main);
103
+ const before = require_permissionless_api_oracles_score_formula.finalPrice(base.get(mainFeed) ?? null, reserveFeed ? base.get(reserveFeed) ?? null : void 0);
104
+ if (before === null || before <= 0n) {
105
+ logger?.warn(`feed ${mainFeed} of chain ${chainId} does not answer; ${main.tokenSymbol ?? main.token} in ${main.marketName ?? main.market} is not scored`);
106
+ continue;
107
+ }
108
+ const onMain = groupsOf(mainFeed);
109
+ const onReserve = groupsOf(reserveFeed);
110
+ const impacts = [.../* @__PURE__ */ new Set([...onMain.keys(), ...onReserve.keys()])].map((group) => {
111
+ const under = stressed.get(group) ?? /* @__PURE__ */ new Map();
112
+ const after = require_permissionless_api_oracles_score_formula.finalPrice(under.get(mainFeed) ?? null, reserveFeed ? under.get(reserveFeed) ?? null : void 0);
113
+ const factor = require_permissionless_api_oracles_score_formula.factorOf(before, after);
114
+ const underlying = underlyingOf(main);
115
+ const debtBefore = underlying ? base.get(underlying) ?? null : null;
116
+ const debtFactor = debtBefore === null || debtBefore <= 0n || !underlying ? null : require_permissionless_api_oracles_score_formula.factorOf(debtBefore, under.get(underlying) ?? null);
117
+ const ratioFactor = factor === null || debtFactor === null || debtFactor === 0 ? null : factor / debtFactor;
118
+ const members = [...onMain.get(group) ?? [], ...onReserve.get(group) ?? []];
119
+ return {
120
+ group,
121
+ identified: members.every((source) => source.identified),
122
+ feeds: [...new Set(members.map((source) => source.description))],
123
+ reaches: onMain.has(group) ? onReserve.has(group) ? "both" : "main" : "reserve",
124
+ factor,
125
+ ratioFactor,
126
+ score: require_permissionless_api_oracles_score_formula.scoreOfFactor(factor)
127
+ };
128
+ });
129
+ const unverified = reserveFeed !== void 0 && impacts.some((impact) => !impact.identified && impact.reaches !== "both");
130
+ const side = (slot) => ({
131
+ feed: feedOf(slot),
132
+ name: slot.priceFeedName,
133
+ contractType: nodes.get(feedOf(slot))?.contractType ?? "",
134
+ sources: [...groupsOf(feedOf(slot)).keys()]
135
+ });
136
+ const asset = main.token.toLowerCase();
137
+ const entry = byAsset.get(asset) ?? {
138
+ symbol: main.tokenSymbol,
139
+ markets: []
140
+ };
141
+ byAsset.set(asset, entry);
142
+ entry.markets.push({
143
+ market: main.market,
144
+ marketName: main.marketName,
145
+ marketConfigurator: main.marketConfigurator,
146
+ curatorName: main.curatorName,
147
+ main: side(main),
148
+ reserve: reserve ? side(reserve) : null,
149
+ hasReserve: reserveFeed !== void 0,
150
+ price: Number((0, viem.formatUnits)(before, 8)),
151
+ unverified,
152
+ notes,
153
+ ...require_permissionless_api_oracles_score_formula.worstOf(impacts)
154
+ });
155
+ }
156
+ return [...byAsset].map(([asset, { symbol, markets }]) => {
157
+ const sorted = markets.sort((a, b) => a.score - b.score);
158
+ const worst = sorted[0] ?? null;
159
+ return {
160
+ chainId,
161
+ asset,
162
+ symbol,
163
+ score: worst?.score ?? null,
164
+ unverified: sorted.some((market) => market.unverified),
165
+ worst,
166
+ markets: sorted
167
+ };
168
+ });
169
+ }
170
+ //#endregion
171
+ exports.runAssetScores = runAssetScores;
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });