@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
@@ -1,20 +1,50 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_onchain_chain_chains = require("../../../onchain/chain/chains.js");
3
+ const require_onchain_constants_address_provider = require("../../../onchain/constants/address-provider.js");
2
4
  const require_model_opportunities_schema = require("../../../model/opportunities.schema.js");
5
+ const require_abi_compressors_priceFeedCompressor = require("../../../abi/compressors/priceFeedCompressor.js");
6
+ require("../../../onchain/index.js");
3
7
  const require_offchain_AbstractOffchainNamespace = require("../../../offchain/AbstractOffchainNamespace.js");
8
+ require("../../deployment/addresses.js");
9
+ const require_permissionless_bindings_address_provider = require("../../bindings/address-provider.js");
4
10
  const require_permissionless_utils_price_update_get_prices = require("../../utils/price-update/get-prices.js");
11
+ const require_permissionless_api_oracles_enrich = require("./enrich.js");
5
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");
15
+ let viem = require("viem");
6
16
  //#region src/permissionless/api/oracles/PermissionlessOracles.ts
7
17
  const GET_PRICES_GAS_LIMIT_BY_CHAIN_ID = { 143: 100000000n };
8
18
  const GET_PRICES_CHUNK_SIZE = 200;
9
19
  /**
20
+ * What a market slot is: an oracle, a token, and whether it is the reserve
21
+ * one. Not the market — a market that has switched oracles has rows under
22
+ * each, and the slots are the old oracle's and the new one's separately.
23
+ */
24
+ function slotKey(oracle, token, reserve) {
25
+ return `${oracle.toLowerCase()}:${token.toLowerCase()}:${reserve}`;
26
+ }
27
+ /** An unknown thrown value, as something a log line can carry. */
28
+ function describe(e) {
29
+ return e instanceof Error ? e.message : String(e);
30
+ }
31
+ /**
10
32
  * The price feeds of one chain: the PriceFeedStore as the backend has indexed
11
33
  * it, and the prices behind it.
12
34
  *
13
35
  * ```ts
14
36
  * const store = await permissionless.oracles.store({ chainId: 1 });
15
- * const { prices } = await permissionless.oracles.prices({ chainId: 1 });
37
+ * const prices = await permissionless.oracles.getPrices(
38
+ * store.priceFeeds.map(({ address }) => ({ chainId: 1, address })),
39
+ * );
16
40
  * ```
17
41
  *
42
+ * The backend answers what it has indexed; anything that is only true at a
43
+ * block — a price, a market cap, a quota in one currency, the period an
44
+ * oracle holds for a slot — is read here, off the chain, and folded into what
45
+ * it answered. That is why these reads mix a request with a multicall rather
46
+ * than being either one.
47
+ *
18
48
  * Read-only. What the store carries is decided on chain, so putting a feed
19
49
  * there is a batch the instance owner executes — see
20
50
  * {@link InstanceOwnerTransactions} — not a row a client writes here.
@@ -30,11 +60,64 @@ var PermissionlessOracles = class extends require_offchain_AbstractOffchainNames
30
60
  }
31
61
  /**
32
62
  * The store with the assets and feeds of one chain.
63
+ *
64
+ * {@link stores} narrowed to one and unwrapped, which is the whole of it:
65
+ * there is one route either way, and a chain the backend does not serve has
66
+ * no store to answer with rather than an empty one.
67
+ *
68
+ * @throws When the backend serves no store for the chain.
33
69
  **/
34
70
  async store({ chainId }) {
71
+ const [store] = await this.stores({ chainId });
72
+ if (!store) throw new Error(`no PriceFeedStore served for chain ${chainId}`);
73
+ return store;
74
+ }
75
+ /**
76
+ * The same stores for every chain the backend serves.
77
+ *
78
+ * One request rather than {@link store} per chain, which is what a page
79
+ * showing all of them would otherwise do: the backend assembles them in the
80
+ * same few queries it assembles one in, so the chains cost little more than
81
+ * the largest of them.
82
+ *
83
+ * Each entry names its own chain, so a store is never attributed by
84
+ * position. `chainId` narrows it to one, for a caller that wants the list
85
+ * shape either way.
86
+ *
87
+ * ```ts
88
+ * const stores = await permissionless.oracles.stores();
89
+ * ```
90
+ **/
91
+ async stores({ chainId } = {}) {
35
92
  return this.getData({
36
- path: `/pfs/${chainId}`,
37
- schema: require_permissionless_api_oracles_schemas.priceFeedStoreSchema
93
+ path: "/pfs/stores",
94
+ query: { chainId: chainId?.toString() },
95
+ schema: require_permissionless_api_oracles_schemas.priceFeedStoresSchema
96
+ });
97
+ }
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
38
121
  });
39
122
  }
40
123
  /**
@@ -52,14 +135,291 @@ var PermissionlessOracles = class extends require_offchain_AbstractOffchainNames
52
135
  * });
53
136
  * ```
54
137
  *
138
+ * Each market row also carries the staleness period that market's own
139
+ * oracle holds for the slot, read off the chain here — see
140
+ * {@link PermissionlessPriceFeedMarket.marketStalenessPeriod}.
141
+ *
55
142
  * @throws When the store does not carry the feed. A private draft is not
56
143
  * here — it lives in whichever app wrote it.
57
144
  **/
58
145
  async feed({ chainId, address }) {
59
- return this.getData({
146
+ const feed = address.toLowerCase();
147
+ const indexed = await this.getData({
60
148
  path: `/pfs/${chainId}/price-feed/${address}`,
61
149
  schema: require_permissionless_api_oracles_schemas.priceFeedDetailsSchema
62
150
  });
151
+ const periods = await this.#stalenessPeriods(chainId, feed, indexed.markets);
152
+ return {
153
+ ...indexed,
154
+ markets: indexed.markets.map((market) => ({
155
+ ...market,
156
+ marketStalenessPeriod: periods.get(slotKey(market.priceOracle, market.token, market.reserve)) ?? null
157
+ }))
158
+ };
159
+ }
160
+ /**
161
+ * Staleness period each market's own oracle holds for the feed's slots,
162
+ * keyed by {@link slotKey}.
163
+ *
164
+ * One call per oracle rather than per slot: the compressor answers for a
165
+ * whole oracle at once, and a feed in a dozen markets of the same curator
166
+ * would otherwise pay a round trip per token. Scoped to the tokens actually
167
+ * asked about, since the unscoped overload returns the oracle's entire feed
168
+ * tree as well.
169
+ *
170
+ * Failures are warnings and not errors. A slot missing from the answer keeps
171
+ * its `null`, which the caller reports as "the oracle would not say" — the
172
+ * alternative is failing a read whose indexed half is perfectly good.
173
+ */
174
+ async #stalenessPeriods(chainId, feed, rows) {
175
+ const periods = /* @__PURE__ */ new Map();
176
+ if (rows.length === 0) return periods;
177
+ const tokensByOracle = /* @__PURE__ */ new Map();
178
+ for (const row of rows) {
179
+ let tokens = tokensByOracle.get(row.priceOracle);
180
+ if (!tokens) {
181
+ tokens = /* @__PURE__ */ new Set();
182
+ tokensByOracle.set(row.priceOracle, tokens);
183
+ }
184
+ tokens.add(row.token);
185
+ }
186
+ let client;
187
+ let compressor;
188
+ try {
189
+ client = this.#chains.client(chainId);
190
+ compressor = await new require_permissionless_bindings_address_provider.AddressProviderContract("0xF7f0a609BfAb9a0A98786951ef10e5FE26cC1E38", client).getLatestVersionAddress(require_onchain_constants_address_provider.AP_PRICE_FEED_COMPRESSOR);
191
+ } catch (e) {
192
+ this.logger?.warn(`no PriceFeedCompressor for chain ${chainId}, reporting no market staleness periods: ${describe(e)}`);
193
+ return periods;
194
+ }
195
+ const oracles = [...tokensByOracle.keys()];
196
+ const results = await client.multicall({
197
+ contracts: oracles.map((oracle) => ({
198
+ address: compressor,
199
+ abi: require_abi_compressors_priceFeedCompressor.priceFeedCompressorAbi,
200
+ functionName: "getPriceOracleState",
201
+ args: [oracle, [...tokensByOracle.get(oracle) ?? []]]
202
+ })),
203
+ allowFailure: true
204
+ });
205
+ oracles.forEach((oracle, i) => {
206
+ const result = results[i];
207
+ if (result?.status !== "success") {
208
+ this.logger?.warn(`price oracle ${oracle} of chain ${chainId} would not report its feed map`);
209
+ return;
210
+ }
211
+ const { priceFeedMap } = result.result;
212
+ for (const entry of priceFeedMap) {
213
+ if (entry.priceFeed.toLowerCase() !== feed) continue;
214
+ periods.set(slotKey(oracle, entry.token, entry.reserve), Number(entry.stalenessPeriod));
215
+ }
216
+ });
217
+ return periods;
218
+ }
219
+ /**
220
+ * One asset of the store: the feeds allowed for it, and the markets that
221
+ * take it as collateral.
222
+ *
223
+ * The mirror of {@link feed}, and the call to reach for when the question is
224
+ * "what prices this token and what is it backing" rather than "where is this
225
+ * feed used". Both read the same market slots; which end they are indexed
226
+ * from is the difference.
227
+ *
228
+ * ```ts
229
+ * const { priceFeeds, markets, quotaUsedUsd } =
230
+ * await permissionless.oracles.asset({ chainId: 1, address: token });
231
+ * ```
232
+ *
233
+ * The quota totals are worked out here, over the very market rows this
234
+ * answers with — see {@link PermissionlessQuotaTotals}.
235
+ *
236
+ * @throws When the chain has no metadata for the address. An asset nothing
237
+ * was indexed for has no symbol or decimals, so there is no row to answer.
238
+ **/
239
+ async asset({ chainId, address }) {
240
+ const indexed = await this.getData({
241
+ path: `/pfs/${chainId}/asset/${address}`,
242
+ schema: require_permissionless_api_oracles_schemas.assetDetailsSchema
243
+ });
244
+ const priceOf = await this.#underlyingPrices(indexed.markets.map((market) => ({
245
+ chainId,
246
+ underlyingPriceFeed: market.underlyingPriceFeed
247
+ })));
248
+ return {
249
+ ...indexed,
250
+ ...require_permissionless_api_oracles_enrich.totalQuotas(indexed.markets, (feed) => priceOf(chainId, feed))
251
+ };
252
+ }
253
+ /**
254
+ * The price of every underlying the given quota rows name, looked up by
255
+ * chain and feed.
256
+ *
257
+ * Distinct feeds only, because an asset in a dozen markets of the same
258
+ * underlying names the same feed a dozen times, and a list of every chain's
259
+ * assets names a handful of feeds per chain between them all.
260
+ */
261
+ async #underlyingPrices(rows) {
262
+ const refs = /* @__PURE__ */ new Map();
263
+ for (const { chainId, underlyingPriceFeed } of rows) {
264
+ if (underlyingPriceFeed === null) continue;
265
+ const address = underlyingPriceFeed.toLowerCase();
266
+ refs.set(require_permissionless_api_oracles_enrich.feedKey(chainId, address), {
267
+ chainId,
268
+ address
269
+ });
270
+ }
271
+ const prices = require_permissionless_api_oracles_enrich.pricesByFeed(await this.getPrices([...refs.values()]));
272
+ return (chainId, feed) => prices.get(require_permissionless_api_oracles_enrich.feedKey(chainId, feed));
273
+ }
274
+ /**
275
+ * Every asset the store carries, with what its markets make of it.
276
+ *
277
+ * Enough on its own to render a list of assets: the alternative is the
278
+ * store's whole read for the names plus a call per row for the quotas, and
279
+ * this is one request either way you scope it.
280
+ *
281
+ * Ordered by how central each asset is — the markets it underlies first,
282
+ * then what is quoted against it, then what it is allowed — so a consumer
283
+ * renders the rows as they arrive. Omit `chainId` for every chain the
284
+ * backend serves, ordered the same way across all of them.
285
+ *
286
+ * Each row also carries its quota totals and what this SDK's tables say the
287
+ * asset is, neither of which is served — see
288
+ * {@link PermissionlessAssetSummary}. The totals are what the last two terms
289
+ * of the order are over, so this waits for them rather than handing back a
290
+ * list that would reshuffle a moment later.
291
+ *
292
+ * ```ts
293
+ * const assets = await permissionless.oracles.assets({ chainId: 1 });
294
+ * const everywhere = await permissionless.oracles.assets();
295
+ * ```
296
+ **/
297
+ async assets({ chainId } = {}) {
298
+ const indexed = await this.getData({
299
+ path: "/pfs/assets",
300
+ query: { chainId: chainId?.toString() },
301
+ schema: require_permissionless_api_oracles_schemas.assetSummariesSchema
302
+ });
303
+ const networks = /* @__PURE__ */ new Map();
304
+ for (const { chainId: id } of indexed) if (!networks.has(id)) networks.set(id, require_onchain_chain_chains.isSupportedNetwork(id) ? require_onchain_chain_chains.getNetworkType(id) : void 0);
305
+ const priceOf = await this.#underlyingPrices(indexed.flatMap((asset) => asset.marketQuotas.map((quota) => ({
306
+ chainId: asset.chainId,
307
+ underlyingPriceFeed: quota.underlyingPriceFeed
308
+ }))));
309
+ return indexed.map((asset) => {
310
+ const network = networks.get(asset.chainId);
311
+ return {
312
+ ...asset,
313
+ ...require_permissionless_api_oracles_enrich.totalQuotas(asset.marketQuotas, (feed) => priceOf(asset.chainId, feed)),
314
+ assetType: network === void 0 ? null : require_onchain_chain_chains.getAssetType(asset.address, network) ?? null,
315
+ isRwa: network === void 0 ? false : require_onchain_chain_chains.isRWAToken(asset.address, network)
316
+ };
317
+ }).sort(require_permissionless_api_oracles_enrich.byCentrality);
318
+ }
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
+ * A market scores 10 when its reserve feed is a second feed on other
366
+ * sources than the main one, and one of the two is bounded — a
367
+ * `BoundedPriceFeed` or a Gearbox LP feed. It loses 6 points as the two
368
+ * feeds' sources overlap — all of them if there is no reserve, it is a
369
+ * `ZeroPriceFeed`, or it is the main feed again — and 5 if neither is
370
+ * bounded. An asset scores its worst running market that takes it as
371
+ * collateral.
372
+ *
373
+ * Read off the chain, one multicall of feed trees per chain, so the score
374
+ * is of the trees as they stand rather than as last indexed. A chain with
375
+ * no client configured 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
+ });
63
423
  }
64
424
  /**
65
425
  * The feeds one feed reads from, recursively.
@@ -82,69 +442,247 @@ var PermissionlessOracles = class extends require_offchain_AbstractOffchainNames
82
442
  });
83
443
  }
84
444
  /**
85
- * What the market values each of one feed's assets at.
445
+ * What the named feeds price, read off the chain.
86
446
  *
87
- * Prefer this over {@link prices} whenever the caller is looking at a single
88
- * feed. That one prices the whole store, which costs the backend one
89
- * `totalSupply` call per asset of every market on the chain; this costs it
90
- * one price lookup and one multicall over the feed's own assets.
447
+ * Prices exactly what is asked for, at the current block, across as many
448
+ * chains as the list names — one read per chain, so a screen listing every
449
+ * chain's feeds pays per chain and not per row. A feed the store does not
450
+ * carry prices the same way, which is what makes this the call to reach for
451
+ * after a deploy, or while a form is still choosing between candidates.
452
+ *
453
+ * Read in two passes. The first asks every feed what it reports as it
454
+ * stands, which is one multicall per chain; a feed that will not answer
455
+ * until it has been given a fresh one is then priced inside a simulation
456
+ * that updates it first, so a push-based feed prices as readily as a
457
+ * pull-based one. The second pass costs an `OnchainSDK` to attach, hence
458
+ * only the feeds that need it rather than all of them.
459
+ *
460
+ * `null` is a feed neither pass could read, and a chain with no client
461
+ * configured is every one of its feeds reading `null` rather than a failed
462
+ * call — the figures built on them are floors either way.
91
463
  *
92
464
  * ```ts
93
- * const { marketCaps } = await permissionless.oracles.feedMarketCaps({
94
- * chainId: 1,
95
- * address: feed,
96
- * });
465
+ * const [{ price }] = await permissionless.oracles.getPrices([
466
+ * { chainId: 1, address: feed },
467
+ * ]);
97
468
  * ```
469
+ *
470
+ * @returns One entry per feed asked for, in the order asked, each price in
471
+ * the 8 decimals every Gearbox feed reports.
98
472
  **/
99
- async feedMarketCaps({ chainId, address }) {
100
- return this.getData({
101
- path: `/pfs/${chainId}/price-feed/${address}/market-caps`,
102
- schema: require_permissionless_api_oracles_schemas.priceFeedMarketCapsSchema
103
- });
473
+ async getPrices(feeds) {
474
+ const byChain = /* @__PURE__ */ new Map();
475
+ for (const { chainId, address } of feeds) {
476
+ const addresses = byChain.get(chainId) ?? /* @__PURE__ */ new Set();
477
+ addresses.add(address.toLowerCase());
478
+ byChain.set(chainId, addresses);
479
+ }
480
+ const chainIds = [...byChain.keys()];
481
+ const results = await Promise.all(chainIds.map((chainId) => this.#pricesOf(chainId, [...byChain.get(chainId) ?? []])));
482
+ const prices = new Map(chainIds.map((id, i) => [id, results[i] ?? {}]));
483
+ return feeds.map(({ chainId, address }) => ({
484
+ chainId,
485
+ address,
486
+ price: prices.get(chainId)?.[address.toLowerCase()] ?? null
487
+ }));
104
488
  }
105
489
  /**
106
- * A price per feed, and a market cap per feed and asset. The feeds the
107
- * indexer has not seen are read off the chain by the backend, so the store
108
- * is priced whole.
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
+ * ```
109
510
  *
110
- * Chain-wide, and priced accordingly — see {@link feedMarketCaps} for one
111
- * feed.
511
+ * @returns One entry per feed asked for, in the order asked.
112
512
  **/
113
- async prices({ chainId }) {
114
- return this.getData({
115
- path: `/pfs/${chainId}/prices`,
116
- schema: require_permissionless_api_oracles_schemas.pricesSchema
117
- });
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
+ }
548
+ /** One chain's half of {@link getPrices}: the cheap pass, then the rest. */
549
+ async #pricesOf(chainId, priceFeeds) {
550
+ let client;
551
+ try {
552
+ client = this.#chains.client(chainId);
553
+ } catch (e) {
554
+ this.logger?.warn(`chain ${chainId} has no client, reporting its ${priceFeeds.length} feed(s) unpriced: ${describe(e)}`);
555
+ return {};
556
+ }
557
+ let prices;
558
+ try {
559
+ prices = await require_permissionless_utils_price_update_get_prices.getLatestPrices({
560
+ client,
561
+ priceFeeds
562
+ });
563
+ } catch (e) {
564
+ this.logger?.warn(`chain ${chainId} would not answer for its ${priceFeeds.length} feed(s), trying each with an update: ${describe(e)}`);
565
+ prices = {};
566
+ }
567
+ const stale = priceFeeds.filter((feed) => (prices[feed] ?? null) === null);
568
+ if (stale.length === 0) return prices;
569
+ try {
570
+ return {
571
+ ...prices,
572
+ ...await require_permissionless_utils_price_update_get_prices.getPrices({
573
+ client,
574
+ priceFeeds: stale,
575
+ chunkSize: 200,
576
+ gasLimit: GET_PRICES_GAS_LIMIT_BY_CHAIN_ID[chainId]
577
+ })
578
+ };
579
+ } catch (e) {
580
+ this.logger?.warn(`chain ${chainId} would not price ${stale.length} of its feed(s) even with an update, reporting them unpriced: ${describe(e)}`);
581
+ return prices;
582
+ }
118
583
  }
119
584
  /**
120
- * The price each of the named feeds reports, read off the chain.
585
+ * What one chain's feeds value one chain's assets at: `price × totalSupply`
586
+ * per pair.
121
587
  *
122
- * Where {@link prices} answers for the store as a whole and at whatever
123
- * moment the backend priced it, this prices exactly the feeds asked for, at
124
- * the current block. A feed the store does not carry prices the same way,
125
- * which is what makes this the call to reach for after a deploy, or while a
126
- * form is still choosing between candidates.
588
+ * Asked from whichever side the caller is looking at — one asset through
589
+ * each of its feeds, or one feed over each of the assets it prices — because
590
+ * a screen shows one row per feed or one row per asset, never the full
591
+ * cross product. Both answer the same pairs in the same shape.
127
592
  *
128
- * Feeds needing a fresh answer before they can be read are updated inside
129
- * the same simulation, so a push-based feed prices as readily as a
130
- * pull-based one. `null` is a feed whose read reverted, not a zero.
593
+ * Two reads per chain: the prices, and one multicall for the assets'
594
+ * supplies and decimals. Several scopes can be asked at once, which is how a
595
+ * table of every asset's feeds pays per chain rather than per row; the pairs
596
+ * come back in the order the scopes named them. A pair neither read could
597
+ * resolve has a `null` cap, which is not a zero.
131
598
  *
132
599
  * ```ts
133
- * const prices = await permissionless.oracles.getPrices({
600
+ * const caps = await permissionless.oracles.getMarketCap({
134
601
  * chainId: 1,
135
- * priceFeeds: [feed],
602
+ * asset: token,
603
+ * priceFeeds: [main, reserve],
136
604
  * });
137
605
  * ```
138
- *
139
- * @returns A price per feed, in the 8 decimals every Gearbox feed reports.
140
606
  **/
141
- async getPrices({ chainId, priceFeeds }) {
142
- return require_permissionless_utils_price_update_get_prices.getPrices({
143
- client: this.#chains.client(chainId),
144
- priceFeeds,
145
- chunkSize: 200,
146
- gasLimit: GET_PRICES_GAS_LIMIT_BY_CHAIN_ID[chainId]
607
+ async getMarketCap(scope) {
608
+ const pairs = (Array.isArray(scope) ? scope : [scope]).flatMap((one) => "asset" in one ? one.priceFeeds.map((priceFeed) => ({
609
+ chainId: one.chainId,
610
+ priceFeed: priceFeed.toLowerCase(),
611
+ asset: one.asset.toLowerCase()
612
+ })) : one.assets.map((asset) => ({
613
+ chainId: one.chainId,
614
+ priceFeed: one.priceFeed.toLowerCase(),
615
+ asset: asset.toLowerCase()
616
+ })));
617
+ if (pairs.length === 0) return [];
618
+ const feeds = /* @__PURE__ */ new Map();
619
+ const assetsByChain = /* @__PURE__ */ new Map();
620
+ for (const { chainId, priceFeed, asset } of pairs) {
621
+ feeds.set(require_permissionless_api_oracles_enrich.feedKey(chainId, priceFeed), {
622
+ chainId,
623
+ address: priceFeed
624
+ });
625
+ const assets = assetsByChain.get(chainId) ?? /* @__PURE__ */ new Set();
626
+ assets.add(asset);
627
+ assetsByChain.set(chainId, assets);
628
+ }
629
+ const chainIds = [...assetsByChain.keys()];
630
+ const [prices, perChain] = await Promise.all([this.getPrices([...feeds.values()]), Promise.all(chainIds.map((chainId) => this.#supplies(chainId, [...assetsByChain.get(chainId) ?? []])))]);
631
+ const priced = require_permissionless_api_oracles_enrich.pricesByFeed(prices);
632
+ const supplies = new Map(chainIds.map((id, i) => [id, perChain[i]]));
633
+ return pairs.map(({ chainId, priceFeed, asset }) => {
634
+ const supply = supplies.get(chainId)?.get(asset);
635
+ return {
636
+ chainId,
637
+ priceFeed,
638
+ asset,
639
+ cap: supply === void 0 ? null : require_permissionless_api_oracles_enrich.toCap(priced.get(require_permissionless_api_oracles_enrich.feedKey(chainId, priceFeed)), supply.totalSupply, supply.decimals)
640
+ };
641
+ });
642
+ }
643
+ /**
644
+ * Each asset's supply and the decimals it is counted in, which is what turns
645
+ * a supply into an amount.
646
+ *
647
+ * Both read rather than one of them taken from the index, so that a cap can
648
+ * be asked for an address nothing was indexed for at all — a token a form is
649
+ * still choosing, say. An asset missing from the answer is left out, and
650
+ * caps over it come back `null`.
651
+ */
652
+ async #supplies(chainId, assets) {
653
+ const supplies = /* @__PURE__ */ new Map();
654
+ let client;
655
+ try {
656
+ client = this.#chains.client(chainId);
657
+ } catch (e) {
658
+ this.logger?.warn(`chain ${chainId} has no client, reporting no market caps: ${describe(e)}`);
659
+ return supplies;
660
+ }
661
+ const results = await client.multicall({
662
+ contracts: assets.flatMap((address) => [{
663
+ address,
664
+ abi: viem.erc20Abi,
665
+ functionName: "totalSupply"
666
+ }, {
667
+ address,
668
+ abi: viem.erc20Abi,
669
+ functionName: "decimals"
670
+ }]),
671
+ allowFailure: true
672
+ });
673
+ assets.forEach((address, i) => {
674
+ const supply = results[2 * i];
675
+ const decimals = results[2 * i + 1];
676
+ if (decimals?.status !== "success") {
677
+ this.logger?.warn(`asset ${address} of chain ${chainId} would not report its decimals, reporting no market cap for it`);
678
+ return;
679
+ }
680
+ supplies.set(address, {
681
+ totalSupply: supply?.status === "success" ? supply.result : null,
682
+ decimals: Number(decimals.result)
683
+ });
147
684
  });
685
+ return supplies;
148
686
  }
149
687
  };
150
688
  //#endregion