@gearbox-protocol/sdk 18.3.1 → 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.
- package/dist/cjs/abi/310/iKillSwitchProxy.js +130 -0
- package/dist/cjs/onchain/index.js +18 -0
- package/dist/cjs/onchain/market/adapters/abi/adapters/iTreehouseFastlaneAdapter.js +113 -0
- package/dist/cjs/onchain/market/adapters/abi/adapters/iTreehouseRedemptionGatewayAdapter.js +258 -0
- package/dist/cjs/onchain/market/adapters/abi/adapters/iTreehouseRouterAdapter.js +161 -0
- package/dist/cjs/onchain/market/adapters/abi/adapters/index.js +6 -0
- package/dist/cjs/onchain/market/adapters/abi/index.js +13 -0
- package/dist/cjs/onchain/market/adapters/abi/treehouse/iTreehouseFastlane.js +39 -0
- package/dist/cjs/onchain/market/adapters/abi/treehouse/iTreehouseRedemptionGateway.js +230 -0
- package/dist/cjs/onchain/market/adapters/abi/treehouse/iTreehouseRouter.js +29 -0
- package/dist/cjs/onchain/market/adapters/abi/treehouse/index.js +7 -0
- package/dist/cjs/onchain/market/adapters/contracts/TreehouseFastlaneAdapterContract.js +72 -0
- package/dist/cjs/onchain/market/adapters/contracts/TreehouseRedemptionGatewayAdapterContract.js +117 -0
- package/dist/cjs/onchain/market/adapters/contracts/TreehouseRouterAdapterContract.js +61 -0
- package/dist/cjs/onchain/market/adapters/contracts/index.js +6 -0
- package/dist/cjs/onchain/market/adapters/createAdapter.js +6 -0
- package/dist/cjs/onchain/market/adapters/index.js +18 -0
- package/dist/cjs/onchain/market/index.js +18 -0
- package/dist/cjs/permissionless/api/index.js +31 -0
- package/dist/cjs/permissionless/api/oracles/PermissionlessOracles.js +637 -65
- package/dist/cjs/permissionless/api/oracles/enrich.js +99 -0
- package/dist/cjs/permissionless/api/oracles/index.js +33 -0
- package/dist/cjs/permissionless/api/oracles/onchain-feed.js +25 -0
- package/dist/cjs/permissionless/api/oracles/schemas.js +84 -59
- package/dist/cjs/permissionless/api/oracles/score/formula.js +56 -0
- package/dist/cjs/permissionless/api/oracles/score/index.js +11 -0
- package/dist/cjs/permissionless/api/oracles/score/run.js +252 -0
- package/dist/cjs/permissionless/api/oracles/score/types.js +1 -0
- package/dist/cjs/permissionless/api/oracles/stress/classify.js +258 -0
- package/dist/cjs/permissionless/api/oracles/stress/index.js +24 -0
- package/dist/cjs/permissionless/api/oracles/stress/override.js +125 -0
- package/dist/cjs/permissionless/api/oracles/stress/run.js +485 -0
- package/dist/cjs/permissionless/api/oracles/stress/score.js +98 -0
- package/dist/cjs/permissionless/api/oracles/stress/types.js +1 -0
- package/dist/cjs/permissionless/api/schemas.js +0 -3
- package/dist/cjs/permissionless/api/transactions/InstanceOwnerTransactions.js +14 -3
- package/dist/cjs/permissionless/api/transactions/lp-price-feeds.js +10 -4
- package/dist/cjs/permissionless/bindings/bytecode-repository.js +8 -0
- package/dist/cjs/permissionless/bindings/governance/governor.js +1 -1
- package/dist/cjs/permissionless/bindings/index.js +8 -3
- package/dist/cjs/permissionless/bindings/kill-switch-proxy.js +188 -0
- package/dist/cjs/permissionless/bindings/market-configurator-factory.js +39 -1
- package/dist/cjs/permissionless/bindings/market-configurator.js +142 -0
- package/dist/cjs/permissionless/index.js +42 -3
- package/dist/cjs/permissionless/utils/index.js +3 -0
- package/dist/cjs/permissionless/utils/literals.js +2 -0
- package/dist/cjs/permissionless/utils/price-update/get-prices.js +64 -0
- package/dist/cjs/permissionless/utils/price-update/index.js +2 -0
- package/dist/cjs/testnets/TestnetsAPI.js +179 -0
- package/dist/cjs/testnets/errors/TestnetNotFoundError.js +15 -0
- package/dist/cjs/testnets/errors/TestnetRequestFailedError.js +24 -0
- package/dist/cjs/testnets/errors/TestnetStatusError.js +19 -0
- package/dist/cjs/testnets/errors/TestnetValidationError.js +24 -0
- package/dist/cjs/testnets/errors/TestnetWaitTimeoutError.js +30 -0
- package/dist/cjs/testnets/errors/TestnetsError.js +34 -0
- package/dist/cjs/testnets/errors/index.js +13 -0
- package/dist/cjs/testnets/index.js +32 -0
- package/dist/cjs/testnets/package.json +1 -0
- package/dist/cjs/testnets/schemas.js +242 -0
- package/dist/cjs/testnets/types.js +1 -0
- package/dist/esm/abi/310/iKillSwitchProxy.js +129 -0
- package/dist/esm/onchain/index.js +10 -1
- package/dist/esm/onchain/market/adapters/abi/adapters/iTreehouseFastlaneAdapter.js +112 -0
- package/dist/esm/onchain/market/adapters/abi/adapters/iTreehouseRedemptionGatewayAdapter.js +257 -0
- package/dist/esm/onchain/market/adapters/abi/adapters/iTreehouseRouterAdapter.js +160 -0
- package/dist/esm/onchain/market/adapters/abi/adapters/index.js +4 -1
- package/dist/esm/onchain/market/adapters/abi/index.js +8 -1
- package/dist/esm/onchain/market/adapters/abi/treehouse/iTreehouseFastlane.js +38 -0
- package/dist/esm/onchain/market/adapters/abi/treehouse/iTreehouseRedemptionGateway.js +229 -0
- package/dist/esm/onchain/market/adapters/abi/treehouse/iTreehouseRouter.js +28 -0
- package/dist/esm/onchain/market/adapters/abi/treehouse/index.js +4 -0
- package/dist/esm/onchain/market/adapters/contracts/TreehouseFastlaneAdapterContract.js +71 -0
- package/dist/esm/onchain/market/adapters/contracts/TreehouseRedemptionGatewayAdapterContract.js +116 -0
- package/dist/esm/onchain/market/adapters/contracts/TreehouseRouterAdapterContract.js +60 -0
- package/dist/esm/onchain/market/adapters/contracts/index.js +4 -1
- package/dist/esm/onchain/market/adapters/createAdapter.js +6 -0
- package/dist/esm/onchain/market/adapters/index.js +10 -1
- package/dist/esm/onchain/market/index.js +10 -1
- package/dist/esm/permissionless/api/index.js +7 -1
- package/dist/esm/permissionless/api/oracles/PermissionlessOracles.js +639 -67
- package/dist/esm/permissionless/api/oracles/enrich.js +94 -0
- package/dist/esm/permissionless/api/oracles/index.js +10 -1
- package/dist/esm/permissionless/api/oracles/onchain-feed.js +24 -0
- package/dist/esm/permissionless/api/oracles/schemas.js +80 -58
- package/dist/esm/permissionless/api/oracles/score/formula.js +51 -0
- package/dist/esm/permissionless/api/oracles/score/index.js +4 -0
- package/dist/esm/permissionless/api/oracles/score/run.js +250 -0
- package/dist/esm/permissionless/api/oracles/score/types.js +1 -0
- package/dist/esm/permissionless/api/oracles/stress/classify.js +256 -0
- package/dist/esm/permissionless/api/oracles/stress/index.js +6 -0
- package/dist/esm/permissionless/api/oracles/stress/override.js +119 -0
- package/dist/esm/permissionless/api/oracles/stress/run.js +478 -0
- package/dist/esm/permissionless/api/oracles/stress/score.js +95 -0
- package/dist/esm/permissionless/api/oracles/stress/types.js +1 -0
- package/dist/esm/permissionless/api/schemas.js +2 -4
- package/dist/esm/permissionless/api/transactions/InstanceOwnerTransactions.js +14 -3
- package/dist/esm/permissionless/api/transactions/lp-price-feeds.js +10 -4
- package/dist/esm/permissionless/bindings/bytecode-repository.js +8 -0
- package/dist/esm/permissionless/bindings/governance/governor.js +1 -1
- package/dist/esm/permissionless/bindings/index.js +5 -4
- package/dist/esm/permissionless/bindings/kill-switch-proxy.js +184 -0
- package/dist/esm/permissionless/bindings/market-configurator-factory.js +37 -0
- package/dist/esm/permissionless/bindings/market-configurator.js +143 -1
- package/dist/esm/permissionless/index.js +13 -6
- package/dist/esm/permissionless/utils/index.js +3 -3
- package/dist/esm/permissionless/utils/literals.js +2 -1
- package/dist/esm/permissionless/utils/price-update/get-prices.js +63 -1
- package/dist/esm/permissionless/utils/price-update/index.js +2 -2
- package/dist/esm/testnets/TestnetsAPI.js +178 -0
- package/dist/esm/testnets/errors/TestnetNotFoundError.js +14 -0
- package/dist/esm/testnets/errors/TestnetRequestFailedError.js +23 -0
- package/dist/esm/testnets/errors/TestnetStatusError.js +18 -0
- package/dist/esm/testnets/errors/TestnetValidationError.js +23 -0
- package/dist/esm/testnets/errors/TestnetWaitTimeoutError.js +29 -0
- package/dist/esm/testnets/errors/TestnetsError.js +33 -0
- package/dist/esm/testnets/errors/index.js +7 -0
- package/dist/esm/testnets/index.js +11 -0
- package/dist/esm/testnets/package.json +1 -0
- package/dist/esm/testnets/schemas.js +228 -0
- package/dist/esm/testnets/types.js +1 -0
- package/dist/types/abi/310/iKillSwitchProxy.d.ts +114 -0
- package/dist/types/onchain/index.d.ts +10 -1
- package/dist/types/onchain/market/adapters/abi/adapters/iTreehouseFastlaneAdapter.d.ts +101 -0
- package/dist/types/onchain/market/adapters/abi/adapters/iTreehouseRedemptionGatewayAdapter.d.ts +237 -0
- package/dist/types/onchain/market/adapters/abi/adapters/iTreehouseRouterAdapter.d.ts +146 -0
- package/dist/types/onchain/market/adapters/abi/adapters/index.d.ts +4 -1
- package/dist/types/onchain/market/adapters/abi/index.d.ts +8 -1
- package/dist/types/onchain/market/adapters/abi/treehouse/iTreehouseFastlane.d.ts +33 -0
- package/dist/types/onchain/market/adapters/abi/treehouse/iTreehouseRedemptionGateway.d.ts +207 -0
- package/dist/types/onchain/market/adapters/abi/treehouse/iTreehouseRouter.d.ts +27 -0
- package/dist/types/onchain/market/adapters/abi/treehouse/index.d.ts +4 -0
- package/dist/types/onchain/market/adapters/contracts/TreehouseFastlaneAdapterContract.d.ts +156 -0
- package/dist/types/onchain/market/adapters/contracts/TreehouseRedemptionGatewayAdapterContract.d.ts +481 -0
- package/dist/types/onchain/market/adapters/contracts/TreehouseRouterAdapterContract.d.ts +193 -0
- package/dist/types/onchain/market/adapters/contracts/index.d.ts +4 -1
- package/dist/types/onchain/market/adapters/index.d.ts +10 -1
- package/dist/types/onchain/market/index.d.ts +10 -1
- package/dist/types/permissionless/api/index.d.ts +10 -2
- package/dist/types/permissionless/api/oracles/PermissionlessOracles.d.ts +237 -47
- package/dist/types/permissionless/api/oracles/enrich.d.ts +59 -0
- package/dist/types/permissionless/api/oracles/index.d.ts +12 -2
- package/dist/types/permissionless/api/oracles/onchain-feed.d.ts +15 -0
- package/dist/types/permissionless/api/oracles/schemas.d.ts +147 -41
- package/dist/types/permissionless/api/oracles/score/formula.d.ts +36 -0
- package/dist/types/permissionless/api/oracles/score/index.d.ts +4 -0
- package/dist/types/permissionless/api/oracles/score/run.d.ts +42 -0
- package/dist/types/permissionless/api/oracles/score/types.d.ts +118 -0
- package/dist/types/permissionless/api/oracles/stress/classify.d.ts +57 -0
- package/dist/types/permissionless/api/oracles/stress/index.d.ts +6 -0
- package/dist/types/permissionless/api/oracles/stress/override.d.ts +59 -0
- package/dist/types/permissionless/api/oracles/stress/run.d.ts +100 -0
- package/dist/types/permissionless/api/oracles/stress/score.d.ts +57 -0
- package/dist/types/permissionless/api/oracles/stress/types.d.ts +272 -0
- package/dist/types/permissionless/api/oracles/types.d.ts +320 -125
- package/dist/types/permissionless/api/schemas.d.ts +0 -2
- package/dist/types/permissionless/api/transactions/lp-price-feeds.d.ts +9 -1
- package/dist/types/permissionless/bindings/bytecode-repository.d.ts +10 -1
- package/dist/types/permissionless/bindings/index.d.ts +4 -3
- package/dist/types/permissionless/bindings/kill-switch-proxy.d.ts +205 -0
- package/dist/types/permissionless/bindings/market-configurator-factory.d.ts +30 -0
- package/dist/types/permissionless/bindings/market-configurator.d.ts +30 -1
- package/dist/types/permissionless/bindings/types.d.ts +27 -0
- package/dist/types/permissionless/index.d.ts +15 -6
- package/dist/types/permissionless/utils/index.d.ts +3 -3
- package/dist/types/permissionless/utils/literals.d.ts +1 -0
- package/dist/types/permissionless/utils/price-update/get-prices.d.ts +33 -0
- package/dist/types/permissionless/utils/price-update/index.d.ts +2 -2
- package/dist/types/testnets/TestnetsAPI.d.ts +108 -0
- package/dist/types/testnets/errors/TestnetNotFoundError.d.ts +12 -0
- package/dist/types/testnets/errors/TestnetRequestFailedError.d.ts +11 -0
- package/dist/types/testnets/errors/TestnetStatusError.d.ts +15 -0
- package/dist/types/testnets/errors/TestnetValidationError.d.ts +16 -0
- package/dist/types/testnets/errors/TestnetWaitTimeoutError.d.ts +21 -0
- package/dist/types/testnets/errors/TestnetsError.d.ts +47 -0
- package/dist/types/testnets/errors/index.d.ts +7 -0
- package/dist/types/testnets/index.d.ts +11 -0
- package/dist/types/testnets/schemas.d.ts +554 -0
- package/dist/types/testnets/types.d.ts +717 -0
- 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
|
-
|
|
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 {
|
|
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
|
-
/**
|
|
22
|
-
|
|
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
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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,
|
|
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 {};
|