@gearbox-protocol/sdk 18.2.0-next.1 → 18.2.0-next.11

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 (135) hide show
  1. package/dist/cjs/abi/compressors/subcompressors/liquidation/treehouseLiquidationSubcompressor.js +179 -0
  2. package/dist/cjs/abi/compressors/subcompressors/withdrawal/treehouseWithdrawalSubcompressor.js +534 -0
  3. package/dist/cjs/abi/router/treehouseWorker.js +885 -0
  4. package/dist/cjs/dev/AccountOpener.js +1 -1
  5. package/dist/cjs/dev/withdrawalUtils.js +1 -1
  6. package/dist/cjs/history/trace/extractTransfers.js +1 -1
  7. package/dist/cjs/onchain/accounts/CreditAccountsServiceV310.js +2 -2
  8. package/dist/cjs/onchain/accounts/liquidations/LiquidationsService.js +1 -1
  9. package/dist/cjs/onchain/accounts/withdrawal-compressor/RedemptionLoggerV310Contract.js +1 -2
  10. package/dist/cjs/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV310Contract.js +1 -2
  11. package/dist/cjs/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV311Contract.js +1 -2
  12. package/dist/cjs/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV313Contract.js +1 -1
  13. package/dist/cjs/onchain/base/TokensMeta.js +3 -3
  14. package/dist/cjs/onchain/core/createAddressProvider.js +1 -1
  15. package/dist/cjs/onchain/market/adapters/abi/actionAbi.js +1 -0
  16. package/dist/cjs/onchain/market/adapters/abi/conctructorAbi.js +3 -0
  17. package/dist/cjs/onchain/market/adapters/contracts/AccountMigratorAdapterContract.js +1 -1
  18. package/dist/cjs/onchain/market/adapters/contracts/ERC4626AdapterContract.js +1 -1
  19. package/dist/cjs/onchain/market/adapters/types.js +3 -0
  20. package/dist/cjs/onchain/market/credit/CreditFacadeV310BaseContract.js +1 -1
  21. package/dist/cjs/onchain/market/pool/PoolV310Contract.js +1 -1
  22. package/dist/cjs/onchain/market/rwa/createDegenNFT.js +1 -1
  23. package/dist/cjs/onchain/market/zapper/IETHZapperContract.js +1 -2
  24. package/dist/cjs/onchain/market/zapper/ZapperContract.js +1 -1
  25. package/dist/cjs/onchain/preview/preview/previewPoolPositionOperation.js +2 -2
  26. package/dist/cjs/onchain/utils/viem/simulateWithPriceUpdates.js +1 -1
  27. package/dist/cjs/permissionless/api/index.js +26 -0
  28. package/dist/cjs/permissionless/api/oracles/PermissionlessOracles.js +585 -47
  29. package/dist/cjs/permissionless/api/oracles/enrich.js +99 -0
  30. package/dist/cjs/permissionless/api/oracles/index.js +28 -0
  31. package/dist/cjs/permissionless/api/oracles/schemas.js +102 -35
  32. package/dist/cjs/permissionless/api/oracles/score/formula.js +99 -0
  33. package/dist/cjs/permissionless/api/oracles/score/index.js +8 -0
  34. package/dist/cjs/permissionless/api/oracles/score/run.js +88 -0
  35. package/dist/cjs/permissionless/api/oracles/score/types.js +1 -0
  36. package/dist/cjs/permissionless/api/oracles/stress/classify.js +258 -0
  37. package/dist/cjs/permissionless/api/oracles/stress/index.js +22 -0
  38. package/dist/cjs/permissionless/api/oracles/stress/override.js +125 -0
  39. package/dist/cjs/permissionless/api/oracles/stress/run.js +432 -0
  40. package/dist/cjs/permissionless/api/oracles/stress/score.js +98 -0
  41. package/dist/cjs/permissionless/api/oracles/stress/types.js +1 -0
  42. package/dist/cjs/permissionless/api/schemas.js +0 -3
  43. package/dist/cjs/permissionless/bindings/index.js +1 -1
  44. package/dist/cjs/permissionless/index.js +29 -1
  45. package/dist/cjs/permissionless/utils/index.js +2 -0
  46. package/dist/cjs/permissionless/utils/price-update/get-prices.js +64 -0
  47. package/dist/cjs/permissionless/utils/price-update/index.js +2 -0
  48. package/dist/cjs/testnets/TestnetsAPI.js +179 -0
  49. package/dist/cjs/testnets/errors/TestnetNotFoundError.js +15 -0
  50. package/dist/cjs/testnets/errors/TestnetRequestFailedError.js +24 -0
  51. package/dist/cjs/testnets/errors/TestnetStatusError.js +19 -0
  52. package/dist/cjs/testnets/errors/TestnetValidationError.js +24 -0
  53. package/dist/cjs/testnets/errors/TestnetWaitTimeoutError.js +30 -0
  54. package/dist/cjs/testnets/errors/TestnetsError.js +34 -0
  55. package/dist/cjs/testnets/errors/index.js +13 -0
  56. package/dist/cjs/testnets/index.js +32 -0
  57. package/dist/cjs/testnets/package.json +1 -0
  58. package/dist/cjs/testnets/schemas.js +242 -0
  59. package/dist/cjs/testnets/types.js +1 -0
  60. package/dist/esm/abi/compressors/subcompressors/liquidation/treehouseLiquidationSubcompressor.js +178 -0
  61. package/dist/esm/abi/compressors/subcompressors/withdrawal/treehouseWithdrawalSubcompressor.js +533 -0
  62. package/dist/esm/abi/router/treehouseWorker.js +884 -0
  63. package/dist/esm/onchain/market/adapters/abi/actionAbi.js +1 -0
  64. package/dist/esm/onchain/market/adapters/abi/conctructorAbi.js +3 -0
  65. package/dist/esm/onchain/market/adapters/types.js +3 -0
  66. package/dist/esm/permissionless/api/index.js +7 -1
  67. package/dist/esm/permissionless/api/oracles/PermissionlessOracles.js +587 -49
  68. package/dist/esm/permissionless/api/oracles/enrich.js +94 -0
  69. package/dist/esm/permissionless/api/oracles/index.js +10 -1
  70. package/dist/esm/permissionless/api/oracles/schemas.js +95 -35
  71. package/dist/esm/permissionless/api/oracles/score/formula.js +96 -0
  72. package/dist/esm/permissionless/api/oracles/score/index.js +4 -0
  73. package/dist/esm/permissionless/api/oracles/score/run.js +87 -0
  74. package/dist/esm/permissionless/api/oracles/score/types.js +1 -0
  75. package/dist/esm/permissionless/api/oracles/stress/classify.js +256 -0
  76. package/dist/esm/permissionless/api/oracles/stress/index.js +6 -0
  77. package/dist/esm/permissionless/api/oracles/stress/override.js +119 -0
  78. package/dist/esm/permissionless/api/oracles/stress/run.js +427 -0
  79. package/dist/esm/permissionless/api/oracles/stress/score.js +95 -0
  80. package/dist/esm/permissionless/api/oracles/stress/types.js +1 -0
  81. package/dist/esm/permissionless/api/schemas.js +2 -4
  82. package/dist/esm/permissionless/bindings/index.js +1 -1
  83. package/dist/esm/permissionless/index.js +9 -3
  84. package/dist/esm/permissionless/utils/index.js +2 -2
  85. package/dist/esm/permissionless/utils/price-update/get-prices.js +63 -1
  86. package/dist/esm/permissionless/utils/price-update/index.js +2 -2
  87. package/dist/esm/testnets/TestnetsAPI.js +178 -0
  88. package/dist/esm/testnets/errors/TestnetNotFoundError.js +14 -0
  89. package/dist/esm/testnets/errors/TestnetRequestFailedError.js +23 -0
  90. package/dist/esm/testnets/errors/TestnetStatusError.js +18 -0
  91. package/dist/esm/testnets/errors/TestnetValidationError.js +23 -0
  92. package/dist/esm/testnets/errors/TestnetWaitTimeoutError.js +29 -0
  93. package/dist/esm/testnets/errors/TestnetsError.js +33 -0
  94. package/dist/esm/testnets/errors/index.js +7 -0
  95. package/dist/esm/testnets/index.js +11 -0
  96. package/dist/esm/testnets/package.json +1 -0
  97. package/dist/esm/testnets/schemas.js +228 -0
  98. package/dist/esm/testnets/types.js +1 -0
  99. package/dist/types/abi/compressors/subcompressors/liquidation/treehouseLiquidationSubcompressor.d.ts +148 -0
  100. package/dist/types/abi/compressors/subcompressors/withdrawal/treehouseWithdrawalSubcompressor.d.ts +440 -0
  101. package/dist/types/abi/router/treehouseWorker.d.ts +728 -0
  102. package/dist/types/onchain/market/adapters/types.d.ts +4 -1
  103. package/dist/types/permissionless/api/index.d.ts +10 -2
  104. package/dist/types/permissionless/api/oracles/PermissionlessOracles.d.ts +232 -35
  105. package/dist/types/permissionless/api/oracles/enrich.d.ts +59 -0
  106. package/dist/types/permissionless/api/oracles/index.d.ts +12 -2
  107. package/dist/types/permissionless/api/oracles/schemas.d.ts +209 -22
  108. package/dist/types/permissionless/api/oracles/score/formula.d.ts +46 -0
  109. package/dist/types/permissionless/api/oracles/score/index.d.ts +4 -0
  110. package/dist/types/permissionless/api/oracles/score/run.d.ts +21 -0
  111. package/dist/types/permissionless/api/oracles/score/types.d.ts +85 -0
  112. package/dist/types/permissionless/api/oracles/stress/classify.d.ts +57 -0
  113. package/dist/types/permissionless/api/oracles/stress/index.d.ts +6 -0
  114. package/dist/types/permissionless/api/oracles/stress/override.d.ts +59 -0
  115. package/dist/types/permissionless/api/oracles/stress/run.d.ts +73 -0
  116. package/dist/types/permissionless/api/oracles/stress/score.d.ts +57 -0
  117. package/dist/types/permissionless/api/oracles/stress/types.d.ts +272 -0
  118. package/dist/types/permissionless/api/oracles/types.d.ts +408 -63
  119. package/dist/types/permissionless/api/schemas.d.ts +0 -2
  120. package/dist/types/permissionless/index.d.ts +11 -3
  121. package/dist/types/permissionless/utils/index.d.ts +2 -2
  122. package/dist/types/permissionless/utils/price-update/get-prices.d.ts +33 -0
  123. package/dist/types/permissionless/utils/price-update/index.d.ts +2 -2
  124. package/dist/types/testnets/TestnetsAPI.d.ts +108 -0
  125. package/dist/types/testnets/errors/TestnetNotFoundError.d.ts +12 -0
  126. package/dist/types/testnets/errors/TestnetRequestFailedError.d.ts +11 -0
  127. package/dist/types/testnets/errors/TestnetStatusError.d.ts +15 -0
  128. package/dist/types/testnets/errors/TestnetValidationError.d.ts +16 -0
  129. package/dist/types/testnets/errors/TestnetWaitTimeoutError.d.ts +21 -0
  130. package/dist/types/testnets/errors/TestnetsError.d.ts +47 -0
  131. package/dist/types/testnets/errors/index.d.ts +7 -0
  132. package/dist/types/testnets/index.d.ts +11 -0
  133. package/dist/types/testnets/schemas.d.ts +554 -0
  134. package/dist/types/testnets/types.d.ts +717 -0
  135. package/package.json +9 -3
@@ -0,0 +1,94 @@
1
+ import { formatUnits } from "viem";
2
+ //#region src/permissionless/api/oracles/enrich.ts
3
+ /**
4
+ * What the oracles namespace works out for itself once the backend has
5
+ * answered.
6
+ *
7
+ * All of it used to be the backend's, and all of it needs the chain: a quota
8
+ * is denominated in its market's own underlying and a market cap is a supply
9
+ * times a price, so none of these can be indexed — they can only be read at
10
+ * the block the reader is on. Keeping them here means one description of each
11
+ * rule rather than one per service that wants it, and a backend that holds no
12
+ * viem client at all.
13
+ **/
14
+ /** Identifies a feed across chains, since an address alone does not. */
15
+ function feedKey(chainId, address) {
16
+ return `${chainId}:${address.toLowerCase()}`;
17
+ }
18
+ /** Prices looked up the way {@link totalQuotas} and the caps need them. */
19
+ function pricesByFeed(prices) {
20
+ return new Map(prices.map(({ chainId, address, price }) => [feedKey(chainId, address), price]));
21
+ }
22
+ /**
23
+ * The asset's quota across its markets, totalled in USD.
24
+ *
25
+ * Each market's figures are converted at the price that market's own oracle
26
+ * holds for its underlying, which is the same number that market's accounting
27
+ * uses.
28
+ *
29
+ * Limits count only the markets still running, because a shut down market's
30
+ * limit is not borrowable; what is already quoted counts everywhere, because
31
+ * debt outstanding is exposure either way. A market whose underlying could not
32
+ * be priced is skipped, which makes both figures floors, and `null` is nothing
33
+ * to total rather than a zero.
34
+ *
35
+ * @param priceOf - The price each underlying feed reports, in the 8 decimals
36
+ * every Gearbox feed uses.
37
+ */
38
+ function totalQuotas(quotas, priceOf) {
39
+ let limit = 0;
40
+ let used = 0;
41
+ let anyLimit = false;
42
+ let anyUsed = false;
43
+ for (const quota of quotas) {
44
+ const raw = quota.underlyingPriceFeed === null ? null : priceOf(quota.underlyingPriceFeed);
45
+ if (raw === null || raw === void 0) continue;
46
+ const price = Number(formatUnits(raw, 8));
47
+ if (quota.quotaUsed !== null) {
48
+ used += quota.quotaUsed * price;
49
+ anyUsed = true;
50
+ }
51
+ if (quota.quotaLimit !== null && !quota.isShutdown) {
52
+ limit += quota.quotaLimit * price;
53
+ anyLimit = true;
54
+ }
55
+ }
56
+ return {
57
+ quotaLimitUsd: anyLimit ? limit : null,
58
+ quotaUsedUsd: anyUsed ? used : null
59
+ };
60
+ }
61
+ /**
62
+ * What one feed values one asset's supply at: `price × totalSupply`.
63
+ *
64
+ * Multiplied as integers and formatted once, rather than through two floats:
65
+ * an 8-decimal price times an 18-decimal supply loses its small end to a JS
66
+ * number long before it loses the large one, and the figure is a total rather
67
+ * than an estimate.
68
+ *
69
+ * `null` if either side could not be read — an asset nobody could price is
70
+ * not an asset worth nothing.
71
+ */
72
+ function toCap(price, totalSupply, decimals) {
73
+ if (price === null || price === void 0 || totalSupply === null || totalSupply === void 0) return null;
74
+ return formatUnits(price * totalSupply, 8 + decimals);
75
+ }
76
+ /** Larger first, with `null` after every figure including a real zero. */
77
+ function descending(a, b) {
78
+ return (b ?? -1) - (a ?? -1);
79
+ }
80
+ /**
81
+ * Assets ordered the way a reader scans them: what the protocol is built on
82
+ * first, then what is actually borrowed against it, then what it is merely
83
+ * allowed to be borrowed against.
84
+ *
85
+ * Applied here and not by the backend because the two middle terms are the
86
+ * converted quotas, which only exist once the chain has been read. The
87
+ * backend orders by the first term and the tie-breakers, so a caller reading
88
+ * the route directly still gets a stable order; this refines it.
89
+ */
90
+ function byCentrality(a, b) {
91
+ return b.underlyingMarketCount - a.underlyingMarketCount || descending(a.quotaUsedUsd, b.quotaUsedUsd) || descending(a.quotaLimitUsd, b.quotaLimitUsd) || a.symbol.localeCompare(b.symbol) || a.address.localeCompare(b.address) || a.chainId - b.chainId;
92
+ }
93
+ //#endregion
94
+ export { byCentrality, feedKey, pricesByFeed, toCap, totalQuotas };
@@ -1,2 +1,11 @@
1
- export { GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles } from "./PermissionlessOracles.js";
1
+ import { classifySources, sourcesByFeed } from "./stress/classify.js";
2
+ import { UNBOUNDED_DOWN, UNBOUNDED_UP, groupOverride, perturb, rateStub, roundDataStub } from "./stress/override.js";
3
+ import { scoreGroup, slotHarm, verdictOf } from "./stress/score.js";
4
+ import { DEFAULT_SCENARIOS, RATE_FEED_TYPE, readForest, readRateTargets, runStressTest } from "./stress/run.js";
5
+ import { BOUND_WEIGHT, INDEPENDENCE_WEIGHT, scoreMarket } from "./score/formula.js";
6
+ import { runAssetScores } from "./score/run.js";
7
+ import { GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles } from "./PermissionlessOracles.js";
8
+ import "./score/index.js";
9
+ import "./stress/index.js";
2
10
  import "./types.js";
11
+ export { BOUND_WEIGHT, DEFAULT_SCENARIOS, GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, INDEPENDENCE_WEIGHT, PermissionlessOracles, RATE_FEED_TYPE, UNBOUNDED_DOWN, UNBOUNDED_UP, classifySources, groupOverride, perturb, rateStub, readForest, readRateTargets, roundDataStub, runAssetScores, runStressTest, scoreGroup, scoreMarket, slotHarm, sourcesByFeed, verdictOf };
@@ -1,4 +1,5 @@
1
- import { addressRecord, addressSchema } from "../schemas.js";
1
+ import { chainSchema } from "../chains/schemas.js";
2
+ import { addressSchema } from "../schemas.js";
2
3
  import { z } from "zod/v4";
3
4
  //#region src/permissionless/api/oracles/schemas.ts
4
5
  /**
@@ -18,62 +19,121 @@ const priceFeedSchema = z.looseObject({
18
19
  stalenessPeriod: z.number(),
19
20
  name: z.string()
20
21
  });
21
- /** {@link PermissionlessAsset} */
22
- const assetSchema = z.looseObject({
22
+ /**
23
+ * {@link PermissionlessPriceFeedAsset}
24
+ *
25
+ * What identifies an asset, which is all a feed's own listing of one says.
26
+ **/
27
+ const priceFeedAssetSchema = z.looseObject({
23
28
  address: addressSchema,
24
29
  symbol: z.string(),
25
30
  name: z.string(),
26
- decimals: z.number(),
31
+ decimals: z.number()
32
+ });
33
+ /** {@link PermissionlessAsset} — identified as above, plus what prices it. */
34
+ const assetSchema = priceFeedAssetSchema.extend({
27
35
  owner: addressSchema,
28
36
  priceFeeds: z.array(priceFeedSchema)
29
37
  });
30
- /** {@link PermissionlessPriceFeedStore} */
31
- const priceFeedStoreSchema = z.looseObject({
32
- chainId: z.number(),
33
- name: z.string(),
34
- isPublic: z.boolean(),
35
- isActivated: z.boolean(),
36
- explorerUrl: z.string(),
38
+ /**
39
+ * {@link PermissionlessPriceFeedStore}
40
+ *
41
+ * A store is a chain's, and is described by the same five fields the chain
42
+ * list answers with, so those are extended rather than restated.
43
+ **/
44
+ const priceFeedStoreSchema = chainSchema.extend({
37
45
  assets: z.array(assetSchema),
38
46
  priceFeeds: z.array(priceFeedSchema)
39
47
  });
40
- /** {@link PermissionlessPrices} */
41
- const pricesSchema = z.looseObject({
42
- prices: addressRecord(z.string().nullable()),
43
- marketCaps: addressRecord(addressRecord(z.string().nullable())),
44
- time: z.number()
45
- });
46
- /** {@link PermissionlessPriceFeedAsset} */
47
- const priceFeedAssetSchema = z.looseObject({
48
- address: addressSchema,
49
- symbol: z.string(),
50
- name: z.string(),
51
- decimals: z.number()
52
- });
53
- /** {@link PermissionlessPriceFeedMarket} */
54
- const priceFeedMarketSchema = z.looseObject({
48
+ /** Every chain's {@link PermissionlessPriceFeedStore}, one per chain. */
49
+ const priceFeedStoresSchema = z.array(priceFeedStoreSchema);
50
+ /**
51
+ * What names a market, whichever side it is being listed from.
52
+ *
53
+ * A feed's markets and an asset's markets are different reads, but they
54
+ * answer with the same market: who curates it, what it is denominated in and
55
+ * how much of the quota is taken. Only what the read is about differs, which
56
+ * is what each of the two below adds.
57
+ **/
58
+ const marketSchema = z.looseObject({
55
59
  market: addressSchema,
56
60
  marketConfigurator: addressSchema,
57
61
  curatorName: z.string().nullable(),
58
62
  marketName: z.string().nullable(),
63
+ underlyingSymbol: z.string().nullable(),
64
+ isUnderlying: z.boolean(),
65
+ quotaLimit: z.number().nullable(),
66
+ quotaUsed: z.number().nullable()
67
+ });
68
+ /**
69
+ * {@link PermissionlessIndexedPriceFeedMarket} — the market as the feed's slot
70
+ * in it, before the oracle is asked what period it holds for the slot.
71
+ **/
72
+ const priceFeedMarketSchema = marketSchema.extend({
59
73
  token: addressSchema,
60
74
  tokenSymbol: z.string().nullable(),
61
- underlyingSymbol: z.string().nullable(),
62
75
  reserve: z.boolean(),
63
- isUnderlying: z.boolean(),
64
- quotaLimit: z.number().nullable()
76
+ priceOracle: addressSchema
77
+ });
78
+ /**
79
+ * {@link PermissionlessMarketSlot} — the same slot read from the chain's end,
80
+ * so the feed is a column rather than what was asked for.
81
+ **/
82
+ const marketSlotSchema = priceFeedMarketSchema.extend({
83
+ chainId: z.number(),
84
+ underlyingToken: addressSchema,
85
+ isShutdown: z.boolean(),
86
+ priceFeed: addressSchema,
87
+ priceFeedName: z.string().nullable(),
88
+ priceFeedInStore: z.boolean(),
89
+ underlyingPriceFeed: addressSchema.nullable()
65
90
  });
66
- /** {@link PermissionlessPriceFeedDetails} */
91
+ /** Every slot in scope, across whichever chains the read covered. */
92
+ const marketSlotsSchema = z.array(marketSlotSchema);
93
+ /** {@link PermissionlessIndexedPriceFeedDetails} */
67
94
  const priceFeedDetailsSchema = z.looseObject({
68
95
  chainId: z.number(),
69
96
  priceFeed: priceFeedSchema,
70
97
  assets: z.array(priceFeedAssetSchema),
71
98
  markets: z.array(priceFeedMarketSchema)
72
99
  });
73
- /** {@link PermissionlessPriceFeedMarketCaps} */
74
- const priceFeedMarketCapsSchema = z.looseObject({
75
- marketCaps: addressRecord(z.string().nullable()),
76
- time: z.number()
100
+ /** {@link PermissionlessAssetMarketFeed} */
101
+ const assetMarketFeedSchema = z.looseObject({
102
+ address: addressSchema,
103
+ name: z.string().nullable(),
104
+ inStore: z.boolean()
105
+ });
106
+ /** {@link PermissionlessAssetMarket} — the market as what prices the asset in it. */
107
+ const assetMarketSchema = marketSchema.extend({
108
+ underlyingToken: addressSchema,
109
+ underlyingPriceFeed: addressSchema.nullable(),
110
+ isShutdown: z.boolean(),
111
+ mainFeed: assetMarketFeedSchema.nullable(),
112
+ reserveFeed: assetMarketFeedSchema.nullable()
113
+ });
114
+ /** {@link PermissionlessIndexedAssetDetails} */
115
+ const assetDetailsSchema = z.looseObject({
116
+ chainId: z.number(),
117
+ asset: priceFeedAssetSchema,
118
+ priceFeeds: z.array(priceFeedSchema),
119
+ markets: z.array(assetMarketSchema)
120
+ });
121
+ /** {@link PermissionlessAssetQuota} */
122
+ const assetQuotaSchema = z.looseObject({
123
+ quotaLimit: z.number().nullable(),
124
+ quotaUsed: z.number().nullable(),
125
+ isShutdown: z.boolean(),
126
+ underlyingPriceFeed: addressSchema.nullable()
127
+ });
128
+ /** {@link PermissionlessIndexedAssetSummary} */
129
+ const assetSummarySchema = z.looseObject({
130
+ chainId: z.number(),
131
+ address: addressSchema,
132
+ symbol: z.string(),
133
+ underlyingMarketCount: z.number(),
134
+ marketQuotas: z.array(assetQuotaSchema)
77
135
  });
136
+ /** The list of {@link PermissionlessAssetSummary}, already ordered. */
137
+ const assetSummariesSchema = z.array(assetSummarySchema);
78
138
  //#endregion
79
- export { assetSchema, priceFeedAssetSchema, priceFeedDetailsSchema, priceFeedMarketCapsSchema, priceFeedMarketSchema, priceFeedSchema, priceFeedStoreSchema, pricesSchema };
139
+ export { assetDetailsSchema, assetMarketFeedSchema, assetMarketSchema, assetQuotaSchema, assetSchema, assetSummariesSchema, assetSummarySchema, marketSlotSchema, marketSlotsSchema, priceFeedAssetSchema, priceFeedDetailsSchema, priceFeedMarketSchema, priceFeedSchema, priceFeedStoreSchema, priceFeedStoresSchema };
@@ -0,0 +1,96 @@
1
+ //#region src/permissionless/api/oracles/score/formula.ts
2
+ /**
3
+ * How well a market guards an asset's price, from its two feeds alone.
4
+ *
5
+ * Two questions, and the points are lost for each separately:
6
+ *
7
+ * - Would the reserve disagree with a main feed that has gone wrong? Only if
8
+ * it is a second feed on other sources. A missing reserve, a
9
+ * `ZeroPriceFeed`, the main feed again, and a different feed on the same
10
+ * sources all answer no.
11
+ * - Is either feed bounded? A `BoundedPriceFeed` caps the answer from above,
12
+ * and a Gearbox LP feed clamps its exchange rate to a window.
13
+ *
14
+ * Independence costs more than the bound, 6 points against 5, because it is
15
+ * the broader protection: a bounded feed caps only from above, and an LP
16
+ * feed's window holds only its rate, not the price of what the rate is in.
17
+ * Either failure alone lands a market at 4 or 5; both together at 0.
18
+ */
19
+ const INDEPENDENCE_WEIGHT = 6;
20
+ const BOUND_WEIGHT = 5;
21
+ /**
22
+ * The feed types that bound what they answer: the `BoundedPriceFeed`, and
23
+ * every Gearbox LP feed, all of which clamp the rate they read.
24
+ */
25
+ const BOUNDED_TYPE = /::(BOUNDED|ERC4626|CURVE_\w+|WSTETH|YEARN|MELLOW\w*|BALANCER\w*)$/;
26
+ const ZERO_TYPE = "PRICE_FEED::ZERO";
27
+ /**
28
+ * The share of the two sides' sources that are not shared, by key.
29
+ *
30
+ * Nothing on either side — two hardcoded prices — is 0, because a reserve is
31
+ * there to be a second opinion and neither of them is one.
32
+ */
33
+ function independenceOf(main, reserve) {
34
+ const a = new Set(main.map((source) => source.key));
35
+ const b = new Set(reserve.map((source) => source.key));
36
+ const union = /* @__PURE__ */ new Set([...a, ...b]);
37
+ if (union.size === 0) return 0;
38
+ return 1 - [...a].filter((key) => b.has(key)).length / union.size;
39
+ }
40
+ /** Down to the half point, so that a score is never rounded up into a band. */
41
+ function toHalf(value) {
42
+ return Math.floor(value * 2 + 1e-9) / 2;
43
+ }
44
+ /**
45
+ * One market's score for one asset.
46
+ *
47
+ * `reserve` is `null` where the market holds no reserve for the asset at all.
48
+ * A source is passed per side and keyed by the caller, which is where it is
49
+ * decided that an unnamed source is its own contract and a named one is its
50
+ * group.
51
+ */
52
+ function scoreMarket(market, main, reserve) {
53
+ const reasons = [];
54
+ let hasReserve = true;
55
+ if (reserve === null) {
56
+ hasReserve = false;
57
+ reasons.push("no reserve feed");
58
+ } else if (reserve.feed.contractType === ZERO_TYPE) {
59
+ hasReserve = false;
60
+ reasons.push("reserve is a ZeroPriceFeed");
61
+ } else if (reserve.feed.feed.toLowerCase() === main.feed.feed.toLowerCase()) {
62
+ hasReserve = false;
63
+ reasons.push("reserve is the main feed");
64
+ }
65
+ const reserveSources = hasReserve && reserve ? reserve.sources : [];
66
+ const independence = hasReserve ? independenceOf(main.sources, reserveSources) : 0;
67
+ const reserveKeys = new Set(reserveSources.map((source) => source.key));
68
+ const mainKeys = new Set(main.sources.map((source) => source.key));
69
+ const shared = main.sources.filter((source) => reserveKeys.has(source.key));
70
+ const names = (sources) => [...new Set(sources.map((source) => source.name))];
71
+ if (hasReserve && independence < 1) reasons.push(shared.length > 0 ? `main and reserve share ${names(shared).join(", ")}` : "neither feed reads a price source");
72
+ const unverified = hasReserve && [...main.sources.filter((source) => !reserveKeys.has(source.key)), ...reserveSources.filter((source) => !mainKeys.has(source.key))].some((source) => !source.identified);
73
+ const bounded = BOUNDED_TYPE.test(main.feed.contractType) || reserve !== null && BOUNDED_TYPE.test(reserve.feed.contractType);
74
+ if (!bounded) reasons.push("neither feed is bounded");
75
+ const score = toHalf(Math.max(0, 10 - 6 * (1 - independence) - 5 * (bounded ? 0 : 1)));
76
+ return {
77
+ ...market,
78
+ main: {
79
+ ...main.feed,
80
+ sources: names(main.sources)
81
+ },
82
+ reserve: reserve ? {
83
+ ...reserve.feed,
84
+ sources: names(reserve.sources)
85
+ } : null,
86
+ hasReserve,
87
+ independence,
88
+ bounded,
89
+ sharedSources: names(shared),
90
+ unverified,
91
+ score,
92
+ reasons
93
+ };
94
+ }
95
+ //#endregion
96
+ export { BOUND_WEIGHT, INDEPENDENCE_WEIGHT, scoreMarket };
@@ -0,0 +1,4 @@
1
+ import { BOUND_WEIGHT, INDEPENDENCE_WEIGHT, scoreMarket } from "./formula.js";
2
+ import { runAssetScores } from "./run.js";
3
+ import "./types.js";
4
+ export { BOUND_WEIGHT, INDEPENDENCE_WEIGHT, runAssetScores, scoreMarket };
@@ -0,0 +1,87 @@
1
+ import { classifySources, sourcesByFeed } from "../stress/classify.js";
2
+ import { RATE_FEED_TYPE, readForest, readRateTargets } from "../stress/run.js";
3
+ import { scoreMarket } from "./formula.js";
4
+ //#region src/permissionless/api/oracles/score/run.ts
5
+ /**
6
+ * How a source is told apart from another when two feeds are compared.
7
+ *
8
+ * A named source by its group, since two Chainlink aggregators are one
9
+ * provider failing together. An unnamed one by its contract, since nothing
10
+ * says what else it fails with — so the same contract on both sides is
11
+ * shared, and two different ones are not.
12
+ */
13
+ function toScoreSource(source) {
14
+ return {
15
+ key: source.identified ? `group:${source.group}` : `contract:${source.target}`,
16
+ name: source.group,
17
+ identified: source.identified
18
+ };
19
+ }
20
+ /**
21
+ * Every asset of one chain, scored by how its running markets price it.
22
+ *
23
+ * One read of every oracle's feed tree and one of the LP feeds' rate
24
+ * contracts, both multicalls: the score is a reading of the trees and needs
25
+ * no simulation.
26
+ **/
27
+ async function runAssetScores({ client, chainId, slots, compressor, logger }) {
28
+ const running = slots.filter((slot) => !slot.isShutdown);
29
+ const oracles = [...new Set(running.map((slot) => slot.priceOracle.toLowerCase()))];
30
+ const { nodes, unreadableOracles } = await readForest(client, compressor, oracles);
31
+ 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`);
32
+ const { targets } = await readRateTargets(client, [...nodes.values()].filter((node) => RATE_FEED_TYPE.test(node.contractType)).map((node) => node.address));
33
+ const { sources } = classifySources(chainId, [...nodes.values()], targets, () => null);
34
+ const byTarget = new Map(sources.filter((source) => source.perturbable).map((source) => [source.target, toScoreSource(source)]));
35
+ const feedOf = (slot) => slot.priceFeed.toLowerCase();
36
+ const reach = sourcesByFeed([...new Set(running.map(feedOf))], nodes, sources);
37
+ const sourcesOf = (feed) => [...reach.get(feed) ?? []].map((target) => byTarget.get(target)).filter((source) => source !== void 0);
38
+ const positions = /* @__PURE__ */ new Map();
39
+ for (const slot of running) {
40
+ const key = `${slot.market.toLowerCase()}:${slot.token.toLowerCase()}`;
41
+ const position = positions.get(key) ?? {};
42
+ position[slot.reserve ? "reserve" : "main"] = slot;
43
+ positions.set(key, position);
44
+ }
45
+ const byAsset = /* @__PURE__ */ new Map();
46
+ for (const { main, reserve } of positions.values()) {
47
+ if (!main || main.isUnderlying) continue;
48
+ const asset = main.token.toLowerCase();
49
+ const entry = byAsset.get(asset) ?? {
50
+ symbol: main.tokenSymbol,
51
+ markets: []
52
+ };
53
+ byAsset.set(asset, entry);
54
+ const mainNode = nodes.get(feedOf(main));
55
+ if (!mainNode) continue;
56
+ if (mainNode.contractType === "PRICE_FEED::ZERO") continue;
57
+ const side = (slot) => ({
58
+ feed: {
59
+ feed: feedOf(slot),
60
+ name: slot.priceFeedName,
61
+ contractType: nodes.get(feedOf(slot))?.contractType ?? ""
62
+ },
63
+ sources: sourcesOf(feedOf(slot))
64
+ });
65
+ entry.markets.push(scoreMarket({
66
+ market: main.market,
67
+ marketName: main.marketName,
68
+ marketConfigurator: main.marketConfigurator,
69
+ curatorName: main.curatorName
70
+ }, side(main), reserve ? side(reserve) : null));
71
+ }
72
+ return [...byAsset].map(([asset, { symbol, markets }]) => {
73
+ const sorted = markets.sort((a, b) => a.score - b.score);
74
+ const worst = sorted[0] ?? null;
75
+ return {
76
+ chainId,
77
+ asset,
78
+ symbol,
79
+ score: worst?.score ?? null,
80
+ unverified: sorted.some((market) => market.unverified),
81
+ worst,
82
+ markets: sorted
83
+ };
84
+ });
85
+ }
86
+ //#endregion
87
+ export { runAssetScores };
@@ -0,0 +1 @@
1
+ export {};