@gearbox-protocol/sdk 18.3.0 → 18.4.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 (181) hide show
  1. package/dist/cjs/abi/310/iKillSwitchProxy.js +130 -0
  2. package/dist/cjs/onchain/accounts/intents/maxBorrowAndWithdraw.js +3 -1
  3. package/dist/cjs/onchain/index.js +18 -0
  4. package/dist/cjs/onchain/market/adapters/abi/adapters/iTreehouseFastlaneAdapter.js +113 -0
  5. package/dist/cjs/onchain/market/adapters/abi/adapters/iTreehouseRedemptionGatewayAdapter.js +258 -0
  6. package/dist/cjs/onchain/market/adapters/abi/adapters/iTreehouseRouterAdapter.js +161 -0
  7. package/dist/cjs/onchain/market/adapters/abi/adapters/index.js +6 -0
  8. package/dist/cjs/onchain/market/adapters/abi/index.js +13 -0
  9. package/dist/cjs/onchain/market/adapters/abi/treehouse/iTreehouseFastlane.js +39 -0
  10. package/dist/cjs/onchain/market/adapters/abi/treehouse/iTreehouseRedemptionGateway.js +230 -0
  11. package/dist/cjs/onchain/market/adapters/abi/treehouse/iTreehouseRouter.js +29 -0
  12. package/dist/cjs/onchain/market/adapters/abi/treehouse/index.js +7 -0
  13. package/dist/cjs/onchain/market/adapters/contracts/TreehouseFastlaneAdapterContract.js +72 -0
  14. package/dist/cjs/onchain/market/adapters/contracts/TreehouseRedemptionGatewayAdapterContract.js +117 -0
  15. package/dist/cjs/onchain/market/adapters/contracts/TreehouseRouterAdapterContract.js +61 -0
  16. package/dist/cjs/onchain/market/adapters/contracts/index.js +6 -0
  17. package/dist/cjs/onchain/market/adapters/createAdapter.js +6 -0
  18. package/dist/cjs/onchain/market/adapters/index.js +18 -0
  19. package/dist/cjs/onchain/market/index.js +18 -0
  20. package/dist/cjs/permissionless/api/index.js +31 -0
  21. package/dist/cjs/permissionless/api/oracles/PermissionlessOracles.js +637 -65
  22. package/dist/cjs/permissionless/api/oracles/enrich.js +99 -0
  23. package/dist/cjs/permissionless/api/oracles/index.js +33 -0
  24. package/dist/cjs/permissionless/api/oracles/onchain-feed.js +25 -0
  25. package/dist/cjs/permissionless/api/oracles/schemas.js +84 -59
  26. package/dist/cjs/permissionless/api/oracles/score/formula.js +56 -0
  27. package/dist/cjs/permissionless/api/oracles/score/index.js +11 -0
  28. package/dist/cjs/permissionless/api/oracles/score/run.js +252 -0
  29. package/dist/cjs/permissionless/api/oracles/score/types.js +1 -0
  30. package/dist/cjs/permissionless/api/oracles/stress/classify.js +258 -0
  31. package/dist/cjs/permissionless/api/oracles/stress/index.js +24 -0
  32. package/dist/cjs/permissionless/api/oracles/stress/override.js +125 -0
  33. package/dist/cjs/permissionless/api/oracles/stress/run.js +485 -0
  34. package/dist/cjs/permissionless/api/oracles/stress/score.js +98 -0
  35. package/dist/cjs/permissionless/api/oracles/stress/types.js +1 -0
  36. package/dist/cjs/permissionless/api/schemas.js +0 -3
  37. package/dist/cjs/permissionless/api/transactions/InstanceOwnerTransactions.js +14 -3
  38. package/dist/cjs/permissionless/api/transactions/lp-price-feeds.js +10 -4
  39. package/dist/cjs/permissionless/bindings/bytecode-repository.js +8 -0
  40. package/dist/cjs/permissionless/bindings/governance/governor.js +1 -1
  41. package/dist/cjs/permissionless/bindings/index.js +8 -3
  42. package/dist/cjs/permissionless/bindings/kill-switch-proxy.js +188 -0
  43. package/dist/cjs/permissionless/bindings/market-configurator-factory.js +39 -1
  44. package/dist/cjs/permissionless/bindings/market-configurator.js +142 -0
  45. package/dist/cjs/permissionless/index.js +42 -3
  46. package/dist/cjs/permissionless/utils/index.js +3 -0
  47. package/dist/cjs/permissionless/utils/literals.js +2 -0
  48. package/dist/cjs/permissionless/utils/price-update/get-prices.js +64 -0
  49. package/dist/cjs/permissionless/utils/price-update/index.js +2 -0
  50. package/dist/cjs/testnets/TestnetsAPI.js +179 -0
  51. package/dist/cjs/testnets/errors/TestnetNotFoundError.js +15 -0
  52. package/dist/cjs/testnets/errors/TestnetRequestFailedError.js +24 -0
  53. package/dist/cjs/testnets/errors/TestnetStatusError.js +19 -0
  54. package/dist/cjs/testnets/errors/TestnetValidationError.js +24 -0
  55. package/dist/cjs/testnets/errors/TestnetWaitTimeoutError.js +30 -0
  56. package/dist/cjs/testnets/errors/TestnetsError.js +34 -0
  57. package/dist/cjs/testnets/errors/index.js +13 -0
  58. package/dist/cjs/testnets/index.js +32 -0
  59. package/dist/cjs/testnets/package.json +1 -0
  60. package/dist/cjs/testnets/schemas.js +242 -0
  61. package/dist/cjs/testnets/types.js +1 -0
  62. package/dist/esm/abi/310/iKillSwitchProxy.js +129 -0
  63. package/dist/esm/onchain/accounts/intents/maxBorrowAndWithdraw.js +3 -1
  64. package/dist/esm/onchain/index.js +10 -1
  65. package/dist/esm/onchain/market/adapters/abi/adapters/iTreehouseFastlaneAdapter.js +112 -0
  66. package/dist/esm/onchain/market/adapters/abi/adapters/iTreehouseRedemptionGatewayAdapter.js +257 -0
  67. package/dist/esm/onchain/market/adapters/abi/adapters/iTreehouseRouterAdapter.js +160 -0
  68. package/dist/esm/onchain/market/adapters/abi/adapters/index.js +4 -1
  69. package/dist/esm/onchain/market/adapters/abi/index.js +8 -1
  70. package/dist/esm/onchain/market/adapters/abi/treehouse/iTreehouseFastlane.js +38 -0
  71. package/dist/esm/onchain/market/adapters/abi/treehouse/iTreehouseRedemptionGateway.js +229 -0
  72. package/dist/esm/onchain/market/adapters/abi/treehouse/iTreehouseRouter.js +28 -0
  73. package/dist/esm/onchain/market/adapters/abi/treehouse/index.js +4 -0
  74. package/dist/esm/onchain/market/adapters/contracts/TreehouseFastlaneAdapterContract.js +71 -0
  75. package/dist/esm/onchain/market/adapters/contracts/TreehouseRedemptionGatewayAdapterContract.js +116 -0
  76. package/dist/esm/onchain/market/adapters/contracts/TreehouseRouterAdapterContract.js +60 -0
  77. package/dist/esm/onchain/market/adapters/contracts/index.js +4 -1
  78. package/dist/esm/onchain/market/adapters/createAdapter.js +6 -0
  79. package/dist/esm/onchain/market/adapters/index.js +10 -1
  80. package/dist/esm/onchain/market/index.js +10 -1
  81. package/dist/esm/permissionless/api/index.js +7 -1
  82. package/dist/esm/permissionless/api/oracles/PermissionlessOracles.js +639 -67
  83. package/dist/esm/permissionless/api/oracles/enrich.js +94 -0
  84. package/dist/esm/permissionless/api/oracles/index.js +10 -1
  85. package/dist/esm/permissionless/api/oracles/onchain-feed.js +24 -0
  86. package/dist/esm/permissionless/api/oracles/schemas.js +80 -58
  87. package/dist/esm/permissionless/api/oracles/score/formula.js +51 -0
  88. package/dist/esm/permissionless/api/oracles/score/index.js +4 -0
  89. package/dist/esm/permissionless/api/oracles/score/run.js +250 -0
  90. package/dist/esm/permissionless/api/oracles/score/types.js +1 -0
  91. package/dist/esm/permissionless/api/oracles/stress/classify.js +256 -0
  92. package/dist/esm/permissionless/api/oracles/stress/index.js +6 -0
  93. package/dist/esm/permissionless/api/oracles/stress/override.js +119 -0
  94. package/dist/esm/permissionless/api/oracles/stress/run.js +478 -0
  95. package/dist/esm/permissionless/api/oracles/stress/score.js +95 -0
  96. package/dist/esm/permissionless/api/oracles/stress/types.js +1 -0
  97. package/dist/esm/permissionless/api/schemas.js +2 -4
  98. package/dist/esm/permissionless/api/transactions/InstanceOwnerTransactions.js +14 -3
  99. package/dist/esm/permissionless/api/transactions/lp-price-feeds.js +10 -4
  100. package/dist/esm/permissionless/bindings/bytecode-repository.js +8 -0
  101. package/dist/esm/permissionless/bindings/governance/governor.js +1 -1
  102. package/dist/esm/permissionless/bindings/index.js +5 -4
  103. package/dist/esm/permissionless/bindings/kill-switch-proxy.js +184 -0
  104. package/dist/esm/permissionless/bindings/market-configurator-factory.js +37 -0
  105. package/dist/esm/permissionless/bindings/market-configurator.js +143 -1
  106. package/dist/esm/permissionless/index.js +13 -6
  107. package/dist/esm/permissionless/utils/index.js +3 -3
  108. package/dist/esm/permissionless/utils/literals.js +2 -1
  109. package/dist/esm/permissionless/utils/price-update/get-prices.js +63 -1
  110. package/dist/esm/permissionless/utils/price-update/index.js +2 -2
  111. package/dist/esm/testnets/TestnetsAPI.js +178 -0
  112. package/dist/esm/testnets/errors/TestnetNotFoundError.js +14 -0
  113. package/dist/esm/testnets/errors/TestnetRequestFailedError.js +23 -0
  114. package/dist/esm/testnets/errors/TestnetStatusError.js +18 -0
  115. package/dist/esm/testnets/errors/TestnetValidationError.js +23 -0
  116. package/dist/esm/testnets/errors/TestnetWaitTimeoutError.js +29 -0
  117. package/dist/esm/testnets/errors/TestnetsError.js +33 -0
  118. package/dist/esm/testnets/errors/index.js +7 -0
  119. package/dist/esm/testnets/index.js +11 -0
  120. package/dist/esm/testnets/package.json +1 -0
  121. package/dist/esm/testnets/schemas.js +228 -0
  122. package/dist/esm/testnets/types.js +1 -0
  123. package/dist/types/abi/310/iKillSwitchProxy.d.ts +114 -0
  124. package/dist/types/onchain/index.d.ts +10 -1
  125. package/dist/types/onchain/market/adapters/abi/adapters/iTreehouseFastlaneAdapter.d.ts +101 -0
  126. package/dist/types/onchain/market/adapters/abi/adapters/iTreehouseRedemptionGatewayAdapter.d.ts +237 -0
  127. package/dist/types/onchain/market/adapters/abi/adapters/iTreehouseRouterAdapter.d.ts +146 -0
  128. package/dist/types/onchain/market/adapters/abi/adapters/index.d.ts +4 -1
  129. package/dist/types/onchain/market/adapters/abi/index.d.ts +8 -1
  130. package/dist/types/onchain/market/adapters/abi/treehouse/iTreehouseFastlane.d.ts +33 -0
  131. package/dist/types/onchain/market/adapters/abi/treehouse/iTreehouseRedemptionGateway.d.ts +207 -0
  132. package/dist/types/onchain/market/adapters/abi/treehouse/iTreehouseRouter.d.ts +27 -0
  133. package/dist/types/onchain/market/adapters/abi/treehouse/index.d.ts +4 -0
  134. package/dist/types/onchain/market/adapters/contracts/TreehouseFastlaneAdapterContract.d.ts +156 -0
  135. package/dist/types/onchain/market/adapters/contracts/TreehouseRedemptionGatewayAdapterContract.d.ts +481 -0
  136. package/dist/types/onchain/market/adapters/contracts/TreehouseRouterAdapterContract.d.ts +193 -0
  137. package/dist/types/onchain/market/adapters/contracts/index.d.ts +4 -1
  138. package/dist/types/onchain/market/adapters/index.d.ts +10 -1
  139. package/dist/types/onchain/market/index.d.ts +10 -1
  140. package/dist/types/permissionless/api/index.d.ts +10 -2
  141. package/dist/types/permissionless/api/oracles/PermissionlessOracles.d.ts +237 -47
  142. package/dist/types/permissionless/api/oracles/enrich.d.ts +59 -0
  143. package/dist/types/permissionless/api/oracles/index.d.ts +12 -2
  144. package/dist/types/permissionless/api/oracles/onchain-feed.d.ts +15 -0
  145. package/dist/types/permissionless/api/oracles/schemas.d.ts +147 -41
  146. package/dist/types/permissionless/api/oracles/score/formula.d.ts +36 -0
  147. package/dist/types/permissionless/api/oracles/score/index.d.ts +4 -0
  148. package/dist/types/permissionless/api/oracles/score/run.d.ts +42 -0
  149. package/dist/types/permissionless/api/oracles/score/types.d.ts +118 -0
  150. package/dist/types/permissionless/api/oracles/stress/classify.d.ts +57 -0
  151. package/dist/types/permissionless/api/oracles/stress/index.d.ts +6 -0
  152. package/dist/types/permissionless/api/oracles/stress/override.d.ts +59 -0
  153. package/dist/types/permissionless/api/oracles/stress/run.d.ts +100 -0
  154. package/dist/types/permissionless/api/oracles/stress/score.d.ts +57 -0
  155. package/dist/types/permissionless/api/oracles/stress/types.d.ts +272 -0
  156. package/dist/types/permissionless/api/oracles/types.d.ts +320 -125
  157. package/dist/types/permissionless/api/schemas.d.ts +0 -2
  158. package/dist/types/permissionless/api/transactions/lp-price-feeds.d.ts +9 -1
  159. package/dist/types/permissionless/bindings/bytecode-repository.d.ts +10 -1
  160. package/dist/types/permissionless/bindings/index.d.ts +4 -3
  161. package/dist/types/permissionless/bindings/kill-switch-proxy.d.ts +205 -0
  162. package/dist/types/permissionless/bindings/market-configurator-factory.d.ts +30 -0
  163. package/dist/types/permissionless/bindings/market-configurator.d.ts +30 -1
  164. package/dist/types/permissionless/bindings/types.d.ts +27 -0
  165. package/dist/types/permissionless/index.d.ts +15 -6
  166. package/dist/types/permissionless/utils/index.d.ts +3 -3
  167. package/dist/types/permissionless/utils/literals.d.ts +1 -0
  168. package/dist/types/permissionless/utils/price-update/get-prices.d.ts +33 -0
  169. package/dist/types/permissionless/utils/price-update/index.d.ts +2 -2
  170. package/dist/types/testnets/TestnetsAPI.d.ts +108 -0
  171. package/dist/types/testnets/errors/TestnetNotFoundError.d.ts +12 -0
  172. package/dist/types/testnets/errors/TestnetRequestFailedError.d.ts +11 -0
  173. package/dist/types/testnets/errors/TestnetStatusError.d.ts +15 -0
  174. package/dist/types/testnets/errors/TestnetValidationError.d.ts +16 -0
  175. package/dist/types/testnets/errors/TestnetWaitTimeoutError.d.ts +21 -0
  176. package/dist/types/testnets/errors/TestnetsError.d.ts +47 -0
  177. package/dist/types/testnets/errors/index.d.ts +7 -0
  178. package/dist/types/testnets/index.d.ts +11 -0
  179. package/dist/types/testnets/schemas.d.ts +554 -0
  180. package/dist/types/testnets/types.d.ts +717 -0
  181. package/package.json +6 -1
@@ -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, readFeedTrees, readForest, readRateTargets, readUnder, runStressTest } from "./stress/run.js";
5
+ import { SCORE_MULTIPLIER, factorOf, finalPrice, scoreOfFactor, worstOf } from "./score/formula.js";
6
+ import { runAssetScores, runFeedPairScore } 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 { DEFAULT_SCENARIOS, GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles, RATE_FEED_TYPE, SCORE_MULTIPLIER, UNBOUNDED_DOWN, UNBOUNDED_UP, classifySources, factorOf, finalPrice, groupOverride, perturb, rateStub, readFeedTrees, readForest, readRateTargets, readUnder, roundDataStub, runAssetScores, runFeedPairScore, runStressTest, scoreGroup, scoreOfFactor, slotHarm, sourcesByFeed, verdictOf, worstOf };
@@ -0,0 +1,24 @@
1
+ import { getAddress, zeroAddress } from "viem";
2
+ //#region src/permissionless/api/oracles/onchain-feed.ts
3
+ /**
4
+ * What the store would say about a feed, from what the chain says instead.
5
+ *
6
+ * Two fields are the store's alone and are answered as unknown rather than
7
+ * guessed: who deployed it is a store row, so it is the zero address, and the
8
+ * staleness period is a store setting a feed that was never added does not
9
+ * have, so it is 0. The compressor reports `PRICE_FEED::EXTERNAL` for a
10
+ * contract it cannot otherwise place; an empty type is the same thing said
11
+ * less clearly.
12
+ */
13
+ function onchainPriceFeed(node) {
14
+ return {
15
+ address: getAddress(node.address),
16
+ contractType: node.contractType.trim() || "PRICE_FEED::EXTERNAL",
17
+ version: node.version,
18
+ deployedBy: zeroAddress,
19
+ stalenessPeriod: 0,
20
+ name: node.description.trim()
21
+ };
22
+ }
23
+ //#endregion
24
+ export { onchainPriceFeed };
@@ -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,100 +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(),
59
- token: addressSchema,
60
- tokenSymbol: z.string().nullable(),
61
63
  underlyingSymbol: z.string().nullable(),
62
- reserve: z.boolean(),
63
64
  isUnderlying: z.boolean(),
64
65
  quotaLimit: z.number().nullable(),
65
- quotaUsed: z.number().nullable(),
66
- marketStalenessPeriod: 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({
73
+ token: addressSchema,
74
+ tokenSymbol: z.string().nullable(),
75
+ reserve: z.boolean(),
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()
67
90
  });
68
- /** {@link PermissionlessPriceFeedDetails} */
91
+ /** Every slot in scope, across whichever chains the read covered. */
92
+ const marketSlotsSchema = z.array(marketSlotSchema);
93
+ /** {@link PermissionlessIndexedPriceFeedDetails} */
69
94
  const priceFeedDetailsSchema = z.looseObject({
70
95
  chainId: z.number(),
71
96
  priceFeed: priceFeedSchema,
72
97
  assets: z.array(priceFeedAssetSchema),
73
98
  markets: z.array(priceFeedMarketSchema)
74
99
  });
75
- /** {@link PermissionlessPriceFeedMarketCaps} */
76
- const priceFeedMarketCapsSchema = z.looseObject({
77
- marketCaps: addressRecord(z.string().nullable()),
78
- time: z.number()
79
- });
80
100
  /** {@link PermissionlessAssetMarketFeed} */
81
101
  const assetMarketFeedSchema = z.looseObject({
82
102
  address: addressSchema,
83
103
  name: z.string().nullable(),
84
104
  inStore: z.boolean()
85
105
  });
86
- /** {@link PermissionlessAssetMarket} */
87
- const assetMarketSchema = z.looseObject({
88
- market: addressSchema,
89
- marketConfigurator: addressSchema,
90
- curatorName: z.string().nullable(),
91
- marketName: z.string().nullable(),
106
+ /** {@link PermissionlessAssetMarket} — the market as what prices the asset in it. */
107
+ const assetMarketSchema = marketSchema.extend({
92
108
  underlyingToken: addressSchema,
93
- underlyingSymbol: z.string().nullable(),
94
- isUnderlying: z.boolean(),
109
+ underlyingPriceFeed: addressSchema.nullable(),
95
110
  isShutdown: z.boolean(),
96
111
  mainFeed: assetMarketFeedSchema.nullable(),
97
- reserveFeed: assetMarketFeedSchema.nullable(),
98
- quotaLimit: z.number().nullable(),
99
- quotaUsed: z.number().nullable()
112
+ reserveFeed: assetMarketFeedSchema.nullable()
100
113
  });
101
- /** {@link PermissionlessAssetDetails} */
114
+ /** {@link PermissionlessIndexedAssetDetails} */
102
115
  const assetDetailsSchema = z.looseObject({
103
116
  chainId: z.number(),
104
117
  asset: priceFeedAssetSchema,
105
118
  priceFeeds: z.array(priceFeedSchema),
106
- markets: z.array(assetMarketSchema),
107
- quotaLimitUsd: z.number().nullable(),
108
- quotaUsedUsd: z.number().nullable()
119
+ markets: z.array(assetMarketSchema)
109
120
  });
110
- /** {@link PermissionlessAssetPrices} */
111
- const assetPricesSchema = z.looseObject({
112
- prices: addressRecord(z.string().nullable()),
113
- marketCaps: addressRecord(z.string().nullable()),
114
- time: z.number()
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)
115
135
  });
136
+ /** The list of {@link PermissionlessAssetSummary}, already ordered. */
137
+ const assetSummariesSchema = z.array(assetSummarySchema);
116
138
  //#endregion
117
- export { assetDetailsSchema, assetMarketFeedSchema, assetMarketSchema, assetPricesSchema, 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,51 @@
1
+ //#region src/permissionless/api/oracles/score/formula.ts
2
+ /** What every source of a group is made to answer, as a multiple of now. */
3
+ const SCORE_MULTIPLIER = 10;
4
+ /**
5
+ * How far the final price moved, as a multiple of where it was, either way:
6
+ * 1 for a price that did not move, 1.04 for one 4% off, and
7
+ * {@link SCORE_MULTIPLIER} for one that took the whole lie — the most it can
8
+ * take from one leg, and where a price over two legs of one group, moved
9
+ * further, is held too.
10
+ *
11
+ * `null` for a price that stopped answering, which is not a move.
12
+ */
13
+ function scoreOfFactor(factor) {
14
+ if (factor === null || !Number.isFinite(factor) || factor <= 0) return null;
15
+ const move = factor >= 1 ? factor : 1 / factor;
16
+ return Math.min(10, move);
17
+ }
18
+ /**
19
+ * The price a market settles on: the lower of main and reserve, or the main
20
+ * feed's alone where there is no reserve to compare it with. `null` where a
21
+ * feed it needs does not answer.
22
+ */
23
+ function finalPrice(main, reserve) {
24
+ if (main === null || reserve === null) return null;
25
+ if (reserve === void 0) return main;
26
+ return main < reserve ? main : reserve;
27
+ }
28
+ /** `stressed / base` as a float, `null` where the stressed price is gone. */
29
+ function factorOf(base, stressed) {
30
+ if (stressed === null) return null;
31
+ return Number(stressed) / Number(base);
32
+ }
33
+ /**
34
+ * The market's score and the group it comes from: the furthest any group
35
+ * moved its price. 1 where nothing moved it — no group did, or no source
36
+ * feeds the price at all.
37
+ *
38
+ * A group under which the price stops answering moves nothing and does not
39
+ * set the score; it is listed after those that do, for a reader to see.
40
+ */
41
+ function worstOf(impacts) {
42
+ const sorted = [...impacts].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
43
+ const worst = sorted.find((impact) => impact.score !== null) ?? null;
44
+ return {
45
+ score: worst?.score ?? 1,
46
+ impacts: sorted,
47
+ worst
48
+ };
49
+ }
50
+ //#endregion
51
+ export { SCORE_MULTIPLIER, factorOf, finalPrice, scoreOfFactor, worstOf };
@@ -0,0 +1,4 @@
1
+ import { SCORE_MULTIPLIER, factorOf, finalPrice, scoreOfFactor, worstOf } from "./formula.js";
2
+ import { runAssetScores, runFeedPairScore } from "./run.js";
3
+ import "./types.js";
4
+ export { SCORE_MULTIPLIER, factorOf, finalPrice, runAssetScores, runFeedPairScore, scoreOfFactor, worstOf };
@@ -0,0 +1,250 @@
1
+ import { classifySources, sourcesByFeed } from "../stress/classify.js";
2
+ import { RATE_FEED_TYPE, readFeedTrees, readForest, readRateTargets, readUnder } from "../stress/run.js";
3
+ import { factorOf, finalPrice, scoreOfFactor, worstOf } from "./formula.js";
4
+ import { formatUnits } from "viem";
5
+ //#region src/permissionless/api/oracles/score/run.ts
6
+ const ZERO_TYPE = "PRICE_FEED::ZERO";
7
+ /** Groups simulated at once: one `eth_call` each, all against one node. */
8
+ const CONCURRENCY = 6;
9
+ const lower = (address) => address.toLowerCase();
10
+ /**
11
+ * A main feed and the reserve a slot or a caller names for it, as the score
12
+ * takes them: a reserve that is a `ZeroPriceFeed` is no reserve.
13
+ */
14
+ function pairOf(main, reserve, nodes) {
15
+ const notes = [];
16
+ if (!reserve) {
17
+ notes.push("no reserve feed");
18
+ return {
19
+ main,
20
+ reserve: void 0,
21
+ notes
22
+ };
23
+ }
24
+ if (nodes.get(reserve)?.contractType === ZERO_TYPE) {
25
+ notes.push("reserve is a ZeroPriceFeed");
26
+ return {
27
+ main,
28
+ reserve: void 0,
29
+ notes
30
+ };
31
+ }
32
+ if (reserve === main) notes.push("reserve is the main feed");
33
+ return {
34
+ main,
35
+ reserve,
36
+ notes
37
+ };
38
+ }
39
+ /**
40
+ * Every pair's final price under each source group lying, from one read of
41
+ * the LP feeds' rate contracts, one of every feed as it stands, and then one
42
+ * `eth_call` per source group with the group's sources overridden to answer
43
+ * {@link SCORE_MULTIPLIER} times what they answer now.
44
+ *
45
+ * A pair whose final price does not answer as it stands comes back `null`:
46
+ * there is no price to move.
47
+ */
48
+ async function simulatePairs({ client, chainId, nodes, answers, pairs, logger }) {
49
+ const { targets, baselines: rateBaselines } = await readRateTargets(client, [...nodes.values()].filter((node) => RATE_FEED_TYPE.test(node.contractType)).map((node) => node.address));
50
+ const { sources, unresolved } = classifySources(chainId, [...nodes.values()], targets, (target) => rateBaselines.get(target) ?? answers.get(target) ?? null);
51
+ 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`);
52
+ const roots = [...new Set(pairs.flatMap(({ main, reserve }) => reserve ? [main, reserve] : [main]))];
53
+ const reach = sourcesByFeed(roots, nodes, sources);
54
+ const sourceByTarget = new Map(sources.map((source) => [source.target, source]));
55
+ const drivable = (source) => source !== void 0 && source.perturbable && source.baseline !== null;
56
+ const groupsOf = (feed) => {
57
+ const out = /* @__PURE__ */ new Map();
58
+ for (const target of feed ? reach.get(feed) ?? [] : []) {
59
+ const source = sourceByTarget.get(target);
60
+ if (drivable(source)) out.set(source.group, [...out.get(source.group) ?? [], source]);
61
+ }
62
+ return out;
63
+ };
64
+ const sourcesOf = (feed) => [...groupsOf(feed).keys()];
65
+ if (roots.length === 0) return {
66
+ results: [],
67
+ sourcesOf
68
+ };
69
+ const updatedAt = BigInt(Math.floor(Date.now() / 1e3));
70
+ const fresh = sources.filter((source) => drivable(source) && source.kind === "feed");
71
+ const asIs = await readUnder(client, roots, [], 1, updatedAt);
72
+ const stale = roots.filter((root) => (asIs.get(root) ?? null) === null);
73
+ const freshened = stale.length > 0 ? await readUnder(client, stale, [], 1, updatedAt, fresh) : /* @__PURE__ */ new Map();
74
+ const base = new Map(roots.map((root) => [root, asIs.get(root) ?? freshened.get(root) ?? null]));
75
+ const asIsRoots = roots.filter((root) => (asIs.get(root) ?? null) !== null);
76
+ const freshRoots = stale.filter((root) => (freshened.get(root) ?? null) !== null);
77
+ /** Every root under a lie, each read the way its base was. */
78
+ const readLying = async (members) => {
79
+ const [plain, refreshed] = await Promise.all([asIsRoots.length > 0 ? readUnder(client, asIsRoots, members, 10, updatedAt) : /* @__PURE__ */ new Map(), freshRoots.length > 0 ? readUnder(client, freshRoots, members, 10, updatedAt, fresh) : /* @__PURE__ */ new Map()]);
80
+ return new Map([...plain, ...refreshed]);
81
+ };
82
+ const effective = pairs.map((pair) => pair.reserve && (base.get(pair.reserve) ?? null) === null ? {
83
+ ...pair,
84
+ reserve: void 0,
85
+ notes: [...pair.notes, "reserve does not answer"]
86
+ } : pair);
87
+ const byGroup = /* @__PURE__ */ new Map();
88
+ for (const source of sources.filter(drivable)) byGroup.set(source.group, [...byGroup.get(source.group) ?? [], source]);
89
+ const reachedGroups = /* @__PURE__ */ new Set();
90
+ for (const root of roots) for (const group of groupsOf(root).keys()) reachedGroups.add(group);
91
+ const stressed = /* @__PURE__ */ new Map();
92
+ const queue = [...reachedGroups];
93
+ for (let i = 0; i < queue.length; i += CONCURRENCY) await Promise.all(queue.slice(i, i + CONCURRENCY).map(async (group) => {
94
+ stressed.set(group, await readLying(byGroup.get(group) ?? []));
95
+ }));
96
+ return {
97
+ results: effective.map(({ main, reserve, notes }) => {
98
+ const before = finalPrice(base.get(main) ?? null, reserve ? base.get(reserve) ?? null : void 0);
99
+ if (before === null || before <= 0n) return null;
100
+ const onMain = groupsOf(main);
101
+ const onReserve = groupsOf(reserve);
102
+ const impacts = [.../* @__PURE__ */ new Set([...onMain.keys(), ...onReserve.keys()])].map((group) => {
103
+ const under = stressed.get(group) ?? /* @__PURE__ */ new Map();
104
+ const after = finalPrice(under.get(main) ?? null, reserve ? under.get(reserve) ?? null : void 0);
105
+ const factor = factorOf(before, after);
106
+ const members = [...onMain.get(group) ?? [], ...onReserve.get(group) ?? []];
107
+ return {
108
+ group,
109
+ identified: members.every((source) => source.identified),
110
+ feeds: [...new Set(members.map((source) => source.description))],
111
+ reaches: onMain.has(group) ? onReserve.has(group) ? "both" : "main" : "reserve",
112
+ factor,
113
+ score: scoreOfFactor(factor)
114
+ };
115
+ });
116
+ return {
117
+ hasReserve: reserve !== void 0,
118
+ price: Number(formatUnits(before, 8)),
119
+ unverified: reserve !== void 0 && impacts.some((impact) => !impact.identified && impact.reaches !== "both"),
120
+ notes,
121
+ ...worstOf(impacts)
122
+ };
123
+ }),
124
+ sourcesOf
125
+ };
126
+ }
127
+ /**
128
+ * Every asset of one chain, scored by what each of its source groups can do
129
+ * to the price its running markets hold for it.
130
+ *
131
+ * One read of the oracles' feed trees, then the simulation of every market's
132
+ * main and reserve pair at once — see {@link simulatePairs}.
133
+ **/
134
+ async function runAssetScores({ client, chainId, slots, compressor, logger }) {
135
+ const running = slots.filter((slot) => !slot.isShutdown);
136
+ const oracles = [...new Set(running.map((slot) => lower(slot.priceOracle)))];
137
+ const { nodes, answers, unreadableOracles } = await readForest(client, compressor, oracles);
138
+ 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`);
139
+ const slotsByPosition = /* @__PURE__ */ new Map();
140
+ for (const slot of running) {
141
+ const key = `${lower(slot.market)}:${lower(slot.token)}`;
142
+ const entry = slotsByPosition.get(key) ?? {};
143
+ entry[slot.reserve ? "reserve" : "main"] = slot;
144
+ slotsByPosition.set(key, entry);
145
+ }
146
+ const positions = [];
147
+ for (const { main, reserve } of slotsByPosition.values()) {
148
+ if (!main) continue;
149
+ const mainNode = nodes.get(lower(main.priceFeed));
150
+ if (!mainNode || mainNode.contractType === ZERO_TYPE) continue;
151
+ positions.push({
152
+ main,
153
+ reserve
154
+ });
155
+ }
156
+ const { results, sourcesOf } = await simulatePairs({
157
+ client,
158
+ chainId,
159
+ nodes,
160
+ answers,
161
+ pairs: positions.map(({ main, reserve }) => pairOf(lower(main.priceFeed), reserve ? lower(reserve.priceFeed) : void 0, nodes)),
162
+ logger
163
+ });
164
+ const side = (slot) => ({
165
+ feed: lower(slot.priceFeed),
166
+ name: slot.priceFeedName,
167
+ contractType: nodes.get(lower(slot.priceFeed))?.contractType ?? "",
168
+ sources: sourcesOf(lower(slot.priceFeed))
169
+ });
170
+ const byAsset = /* @__PURE__ */ new Map();
171
+ positions.forEach(({ main, reserve }, i) => {
172
+ const result = results[i];
173
+ if (!result) {
174
+ logger?.warn(`feed ${lower(main.priceFeed)} of chain ${chainId} does not answer; ${main.tokenSymbol ?? main.token} in ${main.marketName ?? main.market} is not scored`);
175
+ return;
176
+ }
177
+ const asset = lower(main.token);
178
+ const entry = byAsset.get(asset) ?? {
179
+ symbol: main.tokenSymbol,
180
+ markets: []
181
+ };
182
+ byAsset.set(asset, entry);
183
+ entry.markets.push({
184
+ market: main.market,
185
+ marketName: main.marketName,
186
+ marketConfigurator: main.marketConfigurator,
187
+ curatorName: main.curatorName,
188
+ main: side(main),
189
+ reserve: reserve ? side(reserve) : null,
190
+ ...result
191
+ });
192
+ });
193
+ return [...byAsset].map(([asset, { symbol, markets }]) => {
194
+ const sorted = markets.sort((a, b) => b.score - a.score);
195
+ const worst = sorted[0] ?? null;
196
+ return {
197
+ chainId,
198
+ asset,
199
+ symbol,
200
+ score: worst?.score ?? null,
201
+ unverified: sorted.some((market) => market.unverified),
202
+ worst,
203
+ markets: sorted
204
+ };
205
+ });
206
+ }
207
+ /**
208
+ * Any main feed and reserve on a chain, scored the way a market holding them
209
+ * would be — whether or not any market does yet.
210
+ *
211
+ * The two trees are read off the compressor by feed rather than by oracle,
212
+ * so a feed the curator has only just deployed scores as well as one in use.
213
+ *
214
+ * @throws Where there is nothing to score: the compressor cannot read a
215
+ * feed, the main feed is a `ZeroPriceFeed`, or the final price does not
216
+ * answer even with every pull feed under it freshened.
217
+ **/
218
+ async function runFeedPairScore({ client, chainId, compressor, main, reserve, logger }) {
219
+ const mainFeed = lower(main);
220
+ const reserveFeed = reserve ? lower(reserve) : void 0;
221
+ const feeds = reserveFeed && reserveFeed !== mainFeed ? [mainFeed, reserveFeed] : [mainFeed];
222
+ const { nodes, answers } = await readFeedTrees(client, compressor, feeds).catch((cause) => {
223
+ throw new Error(`the PriceFeedCompressor of chain ${chainId} would not read ${feeds.join(" and ")}: not a price feed it knows`, { cause });
224
+ });
225
+ for (const feed of [mainFeed, reserveFeed]) if (feed && !nodes.has(feed)) throw new Error(`feed ${feed} of chain ${chainId} is not one the PriceFeedCompressor can read`);
226
+ if (nodes.get(mainFeed)?.contractType === ZERO_TYPE) throw new Error(`the main feed ${mainFeed} is a ZeroPriceFeed: it prices nothing, so nothing can move`);
227
+ const { results, sourcesOf } = await simulatePairs({
228
+ client,
229
+ chainId,
230
+ nodes,
231
+ answers,
232
+ pairs: [pairOf(mainFeed, reserveFeed, nodes)],
233
+ logger
234
+ });
235
+ const result = results[0];
236
+ if (!result) throw new Error(`the main feed ${mainFeed} of chain ${chainId} does not answer, so there is no price to move`);
237
+ const side = (feed) => ({
238
+ feed,
239
+ name: null,
240
+ contractType: nodes.get(feed)?.contractType ?? "",
241
+ sources: sourcesOf(feed)
242
+ });
243
+ return {
244
+ main: side(mainFeed),
245
+ reserve: reserveFeed ? side(reserveFeed) : null,
246
+ ...result
247
+ };
248
+ }
249
+ //#endregion
250
+ export { runAssetScores, runFeedPairScore };
@@ -0,0 +1 @@
1
+ export {};