@gearbox-protocol/sdk 16.0.0-next.53 → 16.0.0-next.55
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/dev/mode-parity/compareOpportunities.js +4 -5
- package/dist/cjs/dev/mode-parity/comparePositions.js +6 -7
- package/dist/cjs/dev/mode-parity/fieldDiff.js +4 -6
- package/dist/cjs/dev/mode-parity/scriptUtils.js +145 -5
- package/dist/cjs/model/analytics.schema.js +117 -0
- package/dist/cjs/model/charts.schema.js +1 -1
- package/dist/cjs/model/index.js +18 -7
- package/dist/cjs/model/liquidations.schema.js +2 -2
- package/dist/cjs/model/opportunities.schema.js +3 -3
- package/dist/cjs/model/positions.schema.js +2 -2
- package/dist/cjs/offchain/GearboxAPI.js +5 -0
- package/dist/cjs/offchain/analytics/OffchainAnalytics.js +12 -0
- package/dist/cjs/offchain/analytics/OffchainAnalyticsPositions.js +24 -0
- package/dist/cjs/offchain/analytics/index.js +6 -0
- package/dist/cjs/offchain/index.js +5 -0
- package/dist/cjs/rewards/errors.js +27 -0
- package/dist/cjs/rewards/index.js +6 -7
- package/dist/cjs/rewards/merkl-api.js +45 -0
- package/dist/cjs/rewards/multichain.js +40 -0
- package/dist/cjs/rewards/{rewards/api.js → toMerklRewards.js} +26 -28
- package/dist/cjs/sdk/GearboxSDK.js +5 -0
- package/dist/cjs/sdk/analytics/AnalyticsNamespace.js +11 -0
- package/dist/cjs/sdk/analytics/index.js +4 -0
- package/dist/cjs/sdk/index.js +3 -0
- package/dist/esm/dev/mode-parity/compareOpportunities.js +4 -5
- package/dist/esm/dev/mode-parity/comparePositions.js +6 -7
- package/dist/esm/dev/mode-parity/fieldDiff.js +4 -6
- package/dist/esm/dev/mode-parity/scriptUtils.js +142 -6
- package/dist/esm/model/analytics.schema.js +108 -0
- package/dist/esm/model/charts.schema.js +1 -1
- package/dist/esm/model/index.js +10 -8
- package/dist/esm/model/liquidations.schema.js +2 -2
- package/dist/esm/model/opportunities.schema.js +3 -3
- package/dist/esm/model/positions.schema.js +2 -2
- package/dist/esm/offchain/GearboxAPI.js +5 -0
- package/dist/esm/offchain/analytics/OffchainAnalytics.js +11 -0
- package/dist/esm/offchain/analytics/OffchainAnalyticsPositions.js +23 -0
- package/dist/esm/offchain/analytics/index.js +4 -0
- package/dist/esm/offchain/index.js +4 -1
- package/dist/esm/rewards/errors.js +26 -0
- package/dist/esm/rewards/index.js +4 -5
- package/dist/esm/rewards/merkl-api.js +42 -0
- package/dist/esm/rewards/multichain.js +39 -0
- package/dist/esm/rewards/{rewards/api.js → toMerklRewards.js} +26 -28
- package/dist/esm/sdk/GearboxSDK.js +5 -0
- package/dist/esm/sdk/analytics/AnalyticsNamespace.js +10 -0
- package/dist/esm/sdk/analytics/index.js +3 -0
- package/dist/esm/sdk/index.js +3 -1
- package/dist/types/dev/mode-parity/compareOpportunities.d.ts +3 -7
- package/dist/types/dev/mode-parity/comparePositions.d.ts +4 -8
- package/dist/types/dev/mode-parity/fieldDiff.d.ts +9 -8
- package/dist/types/dev/mode-parity/scriptUtils.d.ts +26 -1
- package/dist/types/model/analytics.d.ts +62 -0
- package/dist/types/model/analytics.schema.d.ts +149 -0
- package/dist/types/model/index.d.ts +10 -8
- package/dist/types/model/opportunities.d.ts +1 -1
- package/dist/types/model/previews.d.ts +2 -2
- package/dist/types/offchain/AbstractOffchainNamespace.d.ts +1 -1
- package/dist/types/offchain/GearboxAPI.d.ts +4 -0
- package/dist/types/offchain/analytics/OffchainAnalytics.d.ts +11 -0
- package/dist/types/offchain/analytics/OffchainAnalyticsPositions.d.ts +14 -0
- package/dist/types/offchain/analytics/index.d.ts +4 -0
- package/dist/types/offchain/analytics/types.d.ts +17 -0
- package/dist/types/offchain/index.d.ts +5 -1
- package/dist/types/offchain/opportunities/OffchainOpportunities.d.ts +1 -1
- package/dist/types/offchain/opportunities/types.d.ts +1 -1
- package/dist/types/offchain/positions/OffchainPositions.d.ts +1 -1
- package/dist/types/offchain/positions/types.d.ts +1 -1
- package/dist/types/rewards/errors.d.ts +18 -0
- package/dist/types/rewards/index.d.ts +4 -7
- package/dist/types/rewards/merkl-api.d.ts +64 -0
- package/dist/types/rewards/multichain.d.ts +33 -0
- package/dist/types/rewards/toMerklRewards.d.ts +42 -0
- package/dist/types/sdk/GearboxSDK.d.ts +4 -0
- package/dist/types/sdk/analytics/AnalyticsNamespace.d.ts +11 -0
- package/dist/types/sdk/analytics/index.d.ts +3 -0
- package/dist/types/sdk/analytics/types.d.ts +15 -0
- package/dist/types/sdk/index.d.ts +4 -1
- package/dist/types/sdk/opportunities/OpportunitiesNamespace.d.ts +1 -1
- package/dist/types/sdk/opportunities/types.d.ts +1 -1
- package/dist/types/sdk/positions/PositionsNamespace.d.ts +1 -1
- package/dist/types/sdk/positions/types.d.ts +1 -1
- package/dist/types/sdk/types.d.ts +3 -0
- package/package.json +1 -3
- package/dist/cjs/_virtual/_rolldown/runtime.js +0 -23
- package/dist/cjs/rewards/apy/index.js +0 -3
- package/dist/cjs/rewards/rewards/extra-apy.js +0 -71
- package/dist/cjs/rewards/rewards/index.js +0 -6
- package/dist/cjs/rewards/rewards/merkl-api.js +0 -22
- package/dist/esm/rewards/apy/index.js +0 -3
- package/dist/esm/rewards/rewards/extra-apy.js +0 -67
- package/dist/esm/rewards/rewards/index.js +0 -3
- package/dist/esm/rewards/rewards/merkl-api.js +0 -19
- package/dist/types/rewards/apy/index.d.ts +0 -3
- package/dist/types/rewards/apy/output-details.d.ts +0 -99
- package/dist/types/rewards/apy/output.d.ts +0 -25
- package/dist/types/rewards/rewards/api.d.ts +0 -60
- package/dist/types/rewards/rewards/common.d.ts +0 -9
- package/dist/types/rewards/rewards/extra-apy.d.ts +0 -37
- package/dist/types/rewards/rewards/index.d.ts +0 -3
- package/dist/types/rewards/rewards/merkl-api.d.ts +0 -54
- /package/dist/cjs/{rewards/apy/output-details.js → model/analytics.js} +0 -0
- /package/dist/cjs/{rewards/apy/output.js → offchain/analytics/types.js} +0 -0
- /package/dist/cjs/{rewards/rewards/common.js → sdk/analytics/types.js} +0 -0
- /package/dist/esm/{rewards/apy/output-details.js → model/analytics.js} +0 -0
- /package/dist/esm/{rewards/apy/output.js → offchain/analytics/types.js} +0 -0
- /package/dist/esm/{rewards/rewards/common.js → sdk/analytics/types.js} +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_rewards_errors = require("./errors.js");
|
|
3
|
+
//#region src/rewards/merkl-api.ts
|
|
4
|
+
/**
|
|
5
|
+
* Merkl's own host and the Angle mirror, tried in this order.
|
|
6
|
+
*/
|
|
7
|
+
const MERKL_DOMAINS = ["https://api.merkl.xyz", "https://api-merkl.angle.money"];
|
|
8
|
+
const MERKL_API_KEY_HEADER = "X-API-Key";
|
|
9
|
+
/**
|
|
10
|
+
* Per-attempt budget. Merkl has no timeout of its own, and a hung connection
|
|
11
|
+
* would otherwise stall its leg of a fan-out for as long as the socket lives.
|
|
12
|
+
*/
|
|
13
|
+
const ATTEMPT_TIMEOUT = 1e4;
|
|
14
|
+
/**
|
|
15
|
+
* The wallet's raw Merkl rewards on one chain.
|
|
16
|
+
*
|
|
17
|
+
* Rejects with {@link MerklRequestFailedError} when neither domain answers, so
|
|
18
|
+
* a caller can tell an unreachable Merkl from a wallet with nothing to claim.
|
|
19
|
+
* A non-2xx counts as no answer and moves to the next domain: it carries no
|
|
20
|
+
* rewards either way, and treating it as success would report emptiness that
|
|
21
|
+
* was never established.
|
|
22
|
+
*/
|
|
23
|
+
async function fetchMerklUserRewards({ chainId, user, apiKey }) {
|
|
24
|
+
const path = `/v4/users/${user}/rewards?chainId=${chainId}`;
|
|
25
|
+
const headers = apiKey ? { [MERKL_API_KEY_HEADER]: apiKey } : void 0;
|
|
26
|
+
const attempts = [];
|
|
27
|
+
for (const domain of MERKL_DOMAINS) try {
|
|
28
|
+
const response = await fetch(`${domain}${path}`, {
|
|
29
|
+
headers,
|
|
30
|
+
signal: AbortSignal.timeout(ATTEMPT_TIMEOUT)
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
attempts.push([domain, /* @__PURE__ */ new Error(`answered ${response.status}`)]);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
return await response.json();
|
|
37
|
+
} catch (error) {
|
|
38
|
+
attempts.push([domain, error]);
|
|
39
|
+
}
|
|
40
|
+
throw new require_rewards_errors.MerklRequestFailedError(chainId, path, attempts);
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
exports.MERKL_API_KEY_HEADER = MERKL_API_KEY_HEADER;
|
|
44
|
+
exports.MERKL_DOMAINS = MERKL_DOMAINS;
|
|
45
|
+
exports.fetchMerklUserRewards = fetchMerklUserRewards;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_onchain_base_MultichainConstruct = require("../onchain/base/MultichainConstruct.js");
|
|
3
|
+
require("../onchain/index.js");
|
|
4
|
+
const require_rewards_merkl_api = require("./merkl-api.js");
|
|
5
|
+
const require_rewards_toMerklRewards = require("./toMerklRewards.js");
|
|
6
|
+
let viem = require("viem");
|
|
7
|
+
//#region src/rewards/multichain.ts
|
|
8
|
+
/**
|
|
9
|
+
* The fan-out itself. Private because rewards are not an SDK namespace yet:
|
|
10
|
+
* the read is a free function, and this is only how it reaches `queryChains`.
|
|
11
|
+
**/
|
|
12
|
+
var MerklRewardsFanOut = class extends require_onchain_base_MultichainConstruct.MultichainConstruct {
|
|
13
|
+
async list(wallet, chainIds, apiKey) {
|
|
14
|
+
const user = (0, viem.getAddress)(wallet);
|
|
15
|
+
return this.queryChains({
|
|
16
|
+
chainIds,
|
|
17
|
+
label: "list rewards",
|
|
18
|
+
block: "state",
|
|
19
|
+
run: async (sdk) => require_rewards_toMerklRewards.toMerklRewards(sdk, await require_rewards_merkl_api.fetchMerklUserRewards({
|
|
20
|
+
chainId: sdk.chainId,
|
|
21
|
+
user,
|
|
22
|
+
apiKey
|
|
23
|
+
}))
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Every claimable Merkl reward a wallet holds, across the chains the handle
|
|
29
|
+
* carries.
|
|
30
|
+
*
|
|
31
|
+
* Answers the read model's own envelope, so a chain that could not be reached
|
|
32
|
+
* is `status: "error"` in `meta.chains` while a chain with nothing to claim is
|
|
33
|
+
* a `"success"` that contributed no rows. That distinction is the point: the
|
|
34
|
+
* single-chain read this replaces resolved empty either way.
|
|
35
|
+
**/
|
|
36
|
+
async function getMerklRewardsMultichain({ sdk, wallet, chainIds, apiKey }) {
|
|
37
|
+
return new MerklRewardsFanOut(sdk).list(wallet, chainIds, apiKey);
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
exports.getMerklRewardsMultichain = getMerklRewardsMultichain;
|
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_onchain_utils_AddressMap = require("
|
|
3
|
-
const require_onchain_utils_bigint_math = require("
|
|
4
|
-
const require_onchain_utils_formatter = require("
|
|
5
|
-
require("
|
|
6
|
-
const require_rewards_rewards_merkl_api = require("./merkl-api.js");
|
|
2
|
+
const require_onchain_utils_AddressMap = require("../onchain/utils/AddressMap.js");
|
|
3
|
+
const require_onchain_utils_bigint_math = require("../onchain/utils/bigint-math.js");
|
|
4
|
+
const require_onchain_utils_formatter = require("../onchain/utils/formatter.js");
|
|
5
|
+
require("../onchain/index.js");
|
|
7
6
|
let viem = require("viem");
|
|
8
|
-
//#region src/rewards/
|
|
7
|
+
//#region src/rewards/toMerklRewards.ts
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Never rejects on a transport failure: the fetch is settled rather than
|
|
13
|
-
* awaited, and a failure goes to `reportError` and yields an empty list. A
|
|
14
|
-
* caller that must tell "this chain is down" from "this chain has no rewards"
|
|
15
|
-
* has to watch that callback.
|
|
9
|
+
* Merkl's answer for one chain, turned into rows of the read model.
|
|
16
10
|
*/
|
|
17
|
-
|
|
18
|
-
const [merkleXYZLMResponse] = await Promise.allSettled([require_rewards_rewards_merkl_api.MerkleXYZApi.fetchWithFallback(require_rewards_rewards_merkl_api.MerkleXYZApi.getUserRewardsUrl({ params: {
|
|
19
|
-
chainId: sdk.chainId,
|
|
20
|
-
user: (0, viem.getAddress)(account)
|
|
21
|
-
} }), apiKey)]);
|
|
22
|
-
const merkleXYZLm = extractFulfilled(merkleXYZLMResponse, reportError, "merkleXYZLm")?.data;
|
|
11
|
+
function toMerklRewards(sdk, response) {
|
|
23
12
|
const poolByItsToken = require_onchain_utils_AddressMap.AddressMap.fromMappedArray(sdk.marketRegister.pools.map(({ pool }) => pool.address), (address) => address);
|
|
24
13
|
const claimable = /* @__PURE__ */ new Map();
|
|
25
|
-
for (const chainRewards of
|
|
14
|
+
for (const chainRewards of response) for (const reward of chainRewards.rewards) {
|
|
26
15
|
if (!(0, viem.isAddress)(reward.token.address, { strict: false })) continue;
|
|
27
16
|
const rewardTokenAddress = (0, viem.getAddress)(reward.token.address);
|
|
28
17
|
for (const reason of reward.breakdowns) {
|
|
@@ -30,9 +19,9 @@ async function getMerklRewards({ sdk, account, reportError, apiKey }) {
|
|
|
30
19
|
if (!(0, viem.isAddress)(poolTokenAddress, { strict: false })) continue;
|
|
31
20
|
const pool = poolByItsToken.get(poolTokenAddress);
|
|
32
21
|
if (!pool) continue;
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const amount = require_onchain_utils_bigint_math.BigIntMath.max(total - claimed, 0n);
|
|
22
|
+
const amounts = toAmounts(reason);
|
|
23
|
+
if (!amounts) continue;
|
|
24
|
+
const amount = require_onchain_utils_bigint_math.BigIntMath.max(amounts.total - amounts.claimed, 0n);
|
|
36
25
|
if (amount === 0n) continue;
|
|
37
26
|
const key = `${pool}_${rewardTokenAddress}`;
|
|
38
27
|
const seen = claimable.get(key);
|
|
@@ -54,6 +43,20 @@ async function getMerklRewards({ sdk, account, reportError, apiKey }) {
|
|
|
54
43
|
}
|
|
55
44
|
return [...claimable.values()].map(toReward);
|
|
56
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* `toBigInt` throws on anything `BigInt()` cannot parse, and Merkl's amounts
|
|
48
|
+
* are free-form strings.
|
|
49
|
+
*/
|
|
50
|
+
function toAmounts(reason) {
|
|
51
|
+
try {
|
|
52
|
+
return {
|
|
53
|
+
total: require_onchain_utils_formatter.toBigInt(reason.amount || 0),
|
|
54
|
+
claimed: require_onchain_utils_formatter.toBigInt(reason.claimed || 0)
|
|
55
|
+
};
|
|
56
|
+
} catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
57
60
|
function toReward({ price, token, value, ...rest }) {
|
|
58
61
|
return {
|
|
59
62
|
...rest,
|
|
@@ -78,10 +81,5 @@ function toRewardToken(sdk, address, merkl) {
|
|
|
78
81
|
decimals: merkl.decimals || 18
|
|
79
82
|
};
|
|
80
83
|
}
|
|
81
|
-
function extractFulfilled(r, reportError, description) {
|
|
82
|
-
if (r.status === "fulfilled") return r.value;
|
|
83
|
-
if (reportError) reportError(r.reason, description);
|
|
84
|
-
else console.error(r.reason);
|
|
85
|
-
}
|
|
86
84
|
//#endregion
|
|
87
|
-
exports.
|
|
85
|
+
exports.toMerklRewards = toMerklRewards;
|
|
@@ -7,6 +7,8 @@ require("../offchain/index.js");
|
|
|
7
7
|
const require_sdk_errors_assertSameChains = require("./errors/assertSameChains.js");
|
|
8
8
|
const require_sdk_errors_MissingSourceError = require("./errors/MissingSourceError.js");
|
|
9
9
|
require("./errors/index.js");
|
|
10
|
+
const require_sdk_analytics_AnalyticsNamespace = require("./analytics/AnalyticsNamespace.js");
|
|
11
|
+
require("./analytics/index.js");
|
|
10
12
|
const require_sdk_liquidations_LiquidationsNamespace = require("./liquidations/LiquidationsNamespace.js");
|
|
11
13
|
require("./liquidations/index.js");
|
|
12
14
|
require("./utils/mergeChains.js");
|
|
@@ -51,6 +53,8 @@ var GearboxSDK = class {
|
|
|
51
53
|
* Chains this instance covers, which every read is scoped to.
|
|
52
54
|
**/
|
|
53
55
|
networks;
|
|
56
|
+
/** Protocol-wide analytics. Undefined in `onchain` mode. */
|
|
57
|
+
analytics;
|
|
54
58
|
/**
|
|
55
59
|
* Namespace for pool and strategy opportunities.
|
|
56
60
|
**/
|
|
@@ -131,6 +135,7 @@ var GearboxSDK = class {
|
|
|
131
135
|
ensureFresh: this.#onchain ? (chainIds) => this.#ensureFresh(chainIds) : void 0,
|
|
132
136
|
logger
|
|
133
137
|
};
|
|
138
|
+
this.analytics = this.#offchain ? new require_sdk_analytics_AnalyticsNamespace.AnalyticsNamespace(this.#offchain) : void 0;
|
|
134
139
|
this.opportunities = new require_sdk_opportunities_OpportunitiesNamespace.OpportunitiesNamespace(this.#onchain, this.#offchain, namespaceOptions);
|
|
135
140
|
this.positions = new require_sdk_positions_PositionsNamespace.PositionsNamespace(this.#onchain, this.#offchain, namespaceOptions);
|
|
136
141
|
this.liquidations = this.#onchain ? new require_sdk_liquidations_LiquidationsNamespace.LiquidationsNamespace(this.#onchain, namespaceOptions) : void 0;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/sdk/analytics/AnalyticsNamespace.ts
|
|
3
|
+
/** Backend-only protocol analytics exposed by {@link GearboxSDK}. */
|
|
4
|
+
var AnalyticsNamespace = class {
|
|
5
|
+
positions;
|
|
6
|
+
constructor(backend) {
|
|
7
|
+
this.positions = backend.analytics.positions;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
exports.AnalyticsNamespace = AnalyticsNamespace;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_sdk_analytics_AnalyticsNamespace = require("./AnalyticsNamespace.js");
|
|
3
|
+
require("./types.js");
|
|
4
|
+
exports.AnalyticsNamespace = require_sdk_analytics_AnalyticsNamespace.AnalyticsNamespace;
|
package/dist/cjs/sdk/index.js
CHANGED
|
@@ -9,6 +9,8 @@ const require_sdk_errors_NoSourceServedError = require("./errors/NoSourceServedE
|
|
|
9
9
|
const require_sdk_errors_SourceUnavailableError = require("./errors/SourceUnavailableError.js");
|
|
10
10
|
require("./errors/index.js");
|
|
11
11
|
const require_sdk_AbstractNamespace = require("./AbstractNamespace.js");
|
|
12
|
+
const require_sdk_analytics_AnalyticsNamespace = require("./analytics/AnalyticsNamespace.js");
|
|
13
|
+
require("./analytics/index.js");
|
|
12
14
|
const require_sdk_liquidations_LiquidationsNamespace = require("./liquidations/LiquidationsNamespace.js");
|
|
13
15
|
require("./liquidations/index.js");
|
|
14
16
|
const require_sdk_execute_ExecuteApi = require("./execute/ExecuteApi.js");
|
|
@@ -29,6 +31,7 @@ const require_sdk_GearboxSDK = require("./GearboxSDK.js");
|
|
|
29
31
|
require("./types.js");
|
|
30
32
|
exports.AbstractNamespace = require_sdk_AbstractNamespace.AbstractNamespace;
|
|
31
33
|
exports.AllSourcesFailedError = require_sdk_errors_AllSourcesFailedError.AllSourcesFailedError;
|
|
34
|
+
exports.AnalyticsNamespace = require_sdk_analytics_AnalyticsNamespace.AnalyticsNamespace;
|
|
32
35
|
exports.DEFAULT_MAX_OFFCHAIN_LAG = require_sdk_utils_mergeChains.DEFAULT_MAX_OFFCHAIN_LAG;
|
|
33
36
|
exports.DEFAULT_MAX_STATE_AGE = require_sdk_GearboxSDK.DEFAULT_MAX_STATE_AGE;
|
|
34
37
|
exports.ExecuteApi = require_sdk_execute_ExecuteApi.ExecuteApi;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { opportunityId } from "../../model/opportunities.js";
|
|
2
1
|
import { poolOpportunitySchema, strategyOpportunitySchema } from "../../model/opportunities.schema.js";
|
|
2
|
+
import { opportunityId } from "../../model/opportunities.js";
|
|
3
3
|
import "../../model/index.js";
|
|
4
4
|
import { countPaths, diffObjects, toCompareCounts, union } from "./fieldDiff.js";
|
|
5
5
|
import { compileCompareRules, makeTagDiff } from "./compareRules.js";
|
|
@@ -14,8 +14,8 @@ const tagDiff = makeTagDiff({
|
|
|
14
14
|
*
|
|
15
15
|
* Nothing is filtered out. A field only the backend can fill, or a USD value
|
|
16
16
|
* that drifted within snapshot-lag noise, is still reported — tagged
|
|
17
|
-
* {@link FieldDiff.expected} so that {@link CompareCounts.
|
|
18
|
-
*
|
|
17
|
+
* {@link FieldDiff.expected} so that {@link CompareCounts.similar} can ignore
|
|
18
|
+
* it.
|
|
19
19
|
**/
|
|
20
20
|
function compareOpportunities(input) {
|
|
21
21
|
const onchainRows = indexById(input.onchain.data);
|
|
@@ -36,8 +36,7 @@ function compareOpportunities(input) {
|
|
|
36
36
|
chainId: row.chainId,
|
|
37
37
|
onchainName: row.name,
|
|
38
38
|
offchainName: counterpart.name,
|
|
39
|
-
|
|
40
|
-
clean: diffs.every((diff) => diff.expected),
|
|
39
|
+
similar: diffs.every((diff) => diff.expected),
|
|
41
40
|
diffs
|
|
42
41
|
});
|
|
43
42
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { liquidationPositionSchema } from "../../model/liquidations.schema.js";
|
|
2
|
-
import { positionId } from "../../model/positions.js";
|
|
3
2
|
import { poolPositionSchema, strategyPositionSchema } from "../../model/positions.schema.js";
|
|
3
|
+
import { positionId } from "../../model/positions.js";
|
|
4
4
|
import "../../model/index.js";
|
|
5
5
|
import { countPaths, diffObjects, isRecord, toCompareCounts, union } from "./fieldDiff.js";
|
|
6
6
|
import { compileCompareRules, makeTagDiff } from "./compareRules.js";
|
|
@@ -17,8 +17,8 @@ const tagDiff = makeTagDiff({
|
|
|
17
17
|
* Nothing is filtered out. A field only one mode can fill, a strategy field
|
|
18
18
|
* both-mode merge overlays from the backend, or a USD value that drifted
|
|
19
19
|
* within snapshot-lag noise, is still reported — tagged
|
|
20
|
-
* {@link FieldDiff.expected} so that {@link CompareCounts.
|
|
21
|
-
*
|
|
20
|
+
* {@link FieldDiff.expected} so that {@link CompareCounts.similar} can ignore
|
|
21
|
+
* it.
|
|
22
22
|
**/
|
|
23
23
|
function comparePositions(input) {
|
|
24
24
|
const failures = input.failures ?? [];
|
|
@@ -65,8 +65,7 @@ function compareWallet(input) {
|
|
|
65
65
|
chainId: row.chainId,
|
|
66
66
|
onchainName: row.name,
|
|
67
67
|
offchainName: counterpart.name,
|
|
68
|
-
|
|
69
|
-
clean: diffs.every((diff) => diff.expected),
|
|
68
|
+
similar: diffs.every((diff) => diff.expected),
|
|
70
69
|
diffs
|
|
71
70
|
});
|
|
72
71
|
}
|
|
@@ -129,11 +128,11 @@ function summarize(inputs, wallets, failed) {
|
|
|
129
128
|
...wallet.error ? { error: wallet.error } : {},
|
|
130
129
|
...toCompareCounts(wallet.matched.length + wallet.onlyOnchain.length, wallet.matched.length + wallet.onlyOffchain.length, wallet.onlyOnchain.length, wallet.onlyOffchain.length, wallet.matched)
|
|
131
130
|
}));
|
|
132
|
-
const
|
|
131
|
+
const walletsSimilar = compared.filter((wallet) => wallet.onlyOnchain.length === 0 && wallet.onlyOffchain.length === 0 && wallet.matched.every((match) => match.similar)).length;
|
|
133
132
|
return {
|
|
134
133
|
...toCompareCounts(allOnchain.length, allOffchain.length, onlyOnchain.length, onlyOffchain.length, matched),
|
|
135
134
|
wallets: wallets.length,
|
|
136
|
-
|
|
135
|
+
walletsSimilar,
|
|
137
136
|
walletsFailed: failed,
|
|
138
137
|
byChain,
|
|
139
138
|
byWallet,
|
|
@@ -53,7 +53,7 @@ function diffObjects(onchain, offchain, options) {
|
|
|
53
53
|
return diffs;
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* Mark a diff expected, so a later `
|
|
56
|
+
* Mark a diff expected, so a later `similar` count can ignore it.
|
|
57
57
|
**/
|
|
58
58
|
function withExpected(diff, reason) {
|
|
59
59
|
return {
|
|
@@ -117,15 +117,13 @@ function collapseArrayKeys(path) {
|
|
|
117
117
|
* Membership and match totals of one comparison, from already-built lists.
|
|
118
118
|
**/
|
|
119
119
|
function toCompareCounts(onchainRows, offchainRows, onlyOnchain, onlyOffchain, matched) {
|
|
120
|
-
const
|
|
121
|
-
const clean = matched.filter((match) => match.clean).length;
|
|
120
|
+
const similar = matched.filter((match) => match.similar).length;
|
|
122
121
|
return {
|
|
123
122
|
onchainRows,
|
|
124
123
|
offchainRows,
|
|
125
124
|
matched: matched.length,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
differing: matched.length - identical,
|
|
125
|
+
similar,
|
|
126
|
+
different: matched.length - similar,
|
|
129
127
|
onlyOnchain,
|
|
130
128
|
onlyOffchain
|
|
131
129
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getAlchemyUrl } from "../providers.js";
|
|
2
|
+
import { appendFileSync } from "node:fs";
|
|
2
3
|
import { pino } from "pino";
|
|
3
4
|
//#region src/dev/mode-parity/scriptUtils.ts
|
|
4
5
|
const NETWORKS = [
|
|
@@ -76,6 +77,40 @@ function worstColumns(worst) {
|
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
80
|
+
* Reasons a compare run should fail CI: unexpected membership or field diffs,
|
|
81
|
+
* both sources empty, a chain that could not be read, or wallets that failed
|
|
82
|
+
* to list. Expected diffs (mode-scoped, backend-preferred, tolerance) do not
|
|
83
|
+
* fail the run.
|
|
84
|
+
**/
|
|
85
|
+
function evaluateCompareFailure(report) {
|
|
86
|
+
const { summary } = report;
|
|
87
|
+
const reasons = [];
|
|
88
|
+
if (summary.onchainRows === 0 && summary.offchainRows === 0) reasons.push("both sources produced no data");
|
|
89
|
+
if (summary.different > 0) reasons.push(`${summary.different} matched rows are different`);
|
|
90
|
+
if (summary.onlyOnchain > 0) reasons.push(`${summary.onlyOnchain} rows only onchain`);
|
|
91
|
+
if (summary.onlyOffchain > 0) reasons.push(`${summary.onlyOffchain} rows only offchain`);
|
|
92
|
+
if ((summary.walletsFailed ?? 0) > 0) reasons.push(`${summary.walletsFailed} wallets failed to list`);
|
|
93
|
+
for (const chain of [...report.onchainChains, ...report.offchainChains]) if (chain.status === "error") reasons.push(`chain ${chain.chainId} failed on ${chain.source ?? "unknown"}: ${errorMessage(chain.error)}`);
|
|
94
|
+
return reasons;
|
|
95
|
+
}
|
|
96
|
+
function splitDiffs(summary) {
|
|
97
|
+
return {
|
|
98
|
+
unexpected: summary.diffsByPath.filter((entry) => entry.unexpected > 0),
|
|
99
|
+
expected: summary.diffsByPath.filter((entry) => entry.unexpected === 0 && entry.expected > 0)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function markdownCell(value) {
|
|
103
|
+
return String(value).replaceAll("|", "\\|").replaceAll("\n", " ");
|
|
104
|
+
}
|
|
105
|
+
function markdownTable(headers, rows) {
|
|
106
|
+
const line = (cells) => `| ${cells.map(markdownCell).join(" | ")} |`;
|
|
107
|
+
return [
|
|
108
|
+
line(headers),
|
|
109
|
+
line(headers.map(() => "---")),
|
|
110
|
+
...rows.map(line)
|
|
111
|
+
].join("\n");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
79
114
|
* Prints the shared membership table, the fields that differed most often,
|
|
80
115
|
* and any chain that failed to answer.
|
|
81
116
|
**/
|
|
@@ -87,15 +122,13 @@ function printCompareSummary(noun, report, extraLines = []) {
|
|
|
87
122
|
onchain: chain.onchainRows,
|
|
88
123
|
offchain: chain.offchainRows,
|
|
89
124
|
matched: chain.matched,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
differing: chain.differing,
|
|
125
|
+
similar: chain.similar,
|
|
126
|
+
different: chain.different,
|
|
93
127
|
"only onchain": chain.onlyOnchain,
|
|
94
128
|
"only offchain": chain.onlyOffchain
|
|
95
129
|
})));
|
|
96
130
|
for (const line of extraLines) console.log(line);
|
|
97
|
-
const unexpected = summary
|
|
98
|
-
const expected = summary.diffsByPath.filter((entry) => entry.unexpected === 0 && entry.expected > 0);
|
|
131
|
+
const { unexpected, expected } = splitDiffs(summary);
|
|
99
132
|
if (unexpected.length) {
|
|
100
133
|
console.log("\nunexpected fields that differed most often:");
|
|
101
134
|
console.table(unexpected.slice(0, 25).map((entry) => ({
|
|
@@ -117,5 +150,108 @@ function printCompareSummary(noun, report, extraLines = []) {
|
|
|
117
150
|
}
|
|
118
151
|
for (const chain of [...report.onchainChains, ...report.offchainChains]) if (chain.status === "error") console.log(`chain ${chain.chainId} failed on ${chain.source}:`, chain.error);
|
|
119
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Markdown of the same summary printed to the console, for GitHub job summaries.
|
|
155
|
+
**/
|
|
156
|
+
function formatCompareMarkdown(noun, report, extraLines = [], reasons = []) {
|
|
157
|
+
const { summary } = report;
|
|
158
|
+
const { unexpected, expected } = splitDiffs(summary);
|
|
159
|
+
const parts = [
|
|
160
|
+
`# ${noun} compare`,
|
|
161
|
+
"",
|
|
162
|
+
reasons.length ? "**Failed**" : "**Passed**",
|
|
163
|
+
"",
|
|
164
|
+
`${noun} from \`${report.backendUrl}\` vs the chain`,
|
|
165
|
+
""
|
|
166
|
+
];
|
|
167
|
+
if (reasons.length) {
|
|
168
|
+
parts.push("## Failures", "");
|
|
169
|
+
for (const reason of reasons) parts.push(`- ${reason}`);
|
|
170
|
+
parts.push("");
|
|
171
|
+
}
|
|
172
|
+
if (extraLines.length) {
|
|
173
|
+
for (const line of extraLines) parts.push(line);
|
|
174
|
+
parts.push("");
|
|
175
|
+
}
|
|
176
|
+
if (summary.byChain.length) parts.push(markdownTable([
|
|
177
|
+
"chain",
|
|
178
|
+
"onchain",
|
|
179
|
+
"offchain",
|
|
180
|
+
"matched",
|
|
181
|
+
"similar",
|
|
182
|
+
"different",
|
|
183
|
+
"only onchain",
|
|
184
|
+
"only offchain"
|
|
185
|
+
], summary.byChain.map((chain) => [
|
|
186
|
+
chain.chainId,
|
|
187
|
+
chain.onchainRows,
|
|
188
|
+
chain.offchainRows,
|
|
189
|
+
chain.matched,
|
|
190
|
+
chain.similar,
|
|
191
|
+
chain.different,
|
|
192
|
+
chain.onlyOnchain,
|
|
193
|
+
chain.onlyOffchain
|
|
194
|
+
])), "");
|
|
195
|
+
if (unexpected.length) parts.push("## Unexpected fields that differed most often", "", markdownTable([
|
|
196
|
+
"field",
|
|
197
|
+
"unexpected",
|
|
198
|
+
"expected",
|
|
199
|
+
"kinds",
|
|
200
|
+
"max diff",
|
|
201
|
+
"worst entity"
|
|
202
|
+
], unexpected.slice(0, 25).map((entry) => {
|
|
203
|
+
const worst = worstColumns(entry.worstUnexpected);
|
|
204
|
+
return [
|
|
205
|
+
entry.path,
|
|
206
|
+
entry.unexpected,
|
|
207
|
+
entry.expected,
|
|
208
|
+
entry.kinds.join(", "),
|
|
209
|
+
worst["max diff"],
|
|
210
|
+
worst["worst entity"]
|
|
211
|
+
];
|
|
212
|
+
})), "");
|
|
213
|
+
if (expected.length) parts.push("## Expected fields (mode-scoped, backend-preferred, or within tolerance)", "", markdownTable([
|
|
214
|
+
"field",
|
|
215
|
+
"rows",
|
|
216
|
+
"kinds",
|
|
217
|
+
"max diff",
|
|
218
|
+
"worst entity"
|
|
219
|
+
], expected.slice(0, 25).map((entry) => {
|
|
220
|
+
const worst = worstColumns(entry.worstExpected);
|
|
221
|
+
return [
|
|
222
|
+
entry.path,
|
|
223
|
+
entry.expected,
|
|
224
|
+
entry.kinds.join(", "),
|
|
225
|
+
worst["max diff"],
|
|
226
|
+
worst["worst entity"]
|
|
227
|
+
];
|
|
228
|
+
})), "");
|
|
229
|
+
const failedChains = [...report.onchainChains, ...report.offchainChains].filter((chain) => chain.status === "error");
|
|
230
|
+
if (failedChains.length) {
|
|
231
|
+
parts.push("## Chain errors", "");
|
|
232
|
+
for (const chain of failedChains) parts.push(`- chain ${chain.chainId} failed on ${chain.source ?? "unknown"}: ${errorMessage(chain.error)}`);
|
|
233
|
+
parts.push("");
|
|
234
|
+
}
|
|
235
|
+
return parts.join("\n");
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Appends {@link formatCompareMarkdown} to `GITHUB_STEP_SUMMARY` when running
|
|
239
|
+
* in GitHub Actions. No-op locally.
|
|
240
|
+
**/
|
|
241
|
+
function writeGithubJobSummary(noun, report, extraLines = [], reasons = []) {
|
|
242
|
+
const path = process.env.GITHUB_STEP_SUMMARY;
|
|
243
|
+
if (!path) return;
|
|
244
|
+
appendFileSync(path, `${formatCompareMarkdown(noun, report, extraLines, reasons)}\n`);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Console + job-summary output for a compare run. Returns the failure reasons
|
|
248
|
+
* so the caller can exit 1 when the report is not similar.
|
|
249
|
+
**/
|
|
250
|
+
function reportCompare(noun, report, extraLines = []) {
|
|
251
|
+
printCompareSummary(noun, report, extraLines);
|
|
252
|
+
const reasons = evaluateCompareFailure(report);
|
|
253
|
+
writeGithubJobSummary(noun, report, extraLines, reasons);
|
|
254
|
+
return reasons;
|
|
255
|
+
}
|
|
120
256
|
//#endregion
|
|
121
|
-
export { BACKEND_URL, NETWORKS, TIMEOUT, createLogger, errorMessage, formatBpsAsPercent, mapPool, printCompareSummary, requireEnv, rpcUrls };
|
|
257
|
+
export { BACKEND_URL, NETWORKS, TIMEOUT, createLogger, errorMessage, evaluateCompareFailure, formatBpsAsPercent, formatCompareMarkdown, mapPool, printCompareSummary, reportCompare, requireEnv, rpcUrls, writeGithubJobSummary };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ZodAddress } from "../onchain/utils/zod.js";
|
|
2
|
+
import { isFilterSet } from "./filters.js";
|
|
3
|
+
import { encodeFlag, filterable } from "./filters.schema.js";
|
|
4
|
+
import { assetTypeSchema, chainIdSchema } from "./primitives.schema.js";
|
|
5
|
+
import { liquidationPositionSchema } from "./liquidations.schema.js";
|
|
6
|
+
import { poolPositionSchema, positionKindSchema, strategyPositionSchema } from "./positions.schema.js";
|
|
7
|
+
import { isAddress } from "viem";
|
|
8
|
+
import { z } from "zod/v4";
|
|
9
|
+
//#region src/model/analytics.schema.ts
|
|
10
|
+
/** Default page size of the protocol-wide positions list. */
|
|
11
|
+
const ANALYTICS_POSITIONS_DEFAULT_LIMIT = 25;
|
|
12
|
+
/** Largest page the protocol-wide positions list accepts. */
|
|
13
|
+
const ANALYTICS_POSITIONS_MAX_LIMIT = 100;
|
|
14
|
+
/** {@link AnalyticsPositionSortField} */
|
|
15
|
+
const analyticsPositionSortFieldSchema = z.enum([
|
|
16
|
+
"netValueUsd",
|
|
17
|
+
"totalValueUsd",
|
|
18
|
+
"totalDebtUsd",
|
|
19
|
+
"pnlUsd",
|
|
20
|
+
"apy",
|
|
21
|
+
"healthFactor",
|
|
22
|
+
"leverage",
|
|
23
|
+
"chainId",
|
|
24
|
+
"name",
|
|
25
|
+
"borrower"
|
|
26
|
+
]);
|
|
27
|
+
/** {@link AnalyticsSortDirection} */
|
|
28
|
+
const analyticsSortDirectionSchema = z.enum(["asc", "desc"]);
|
|
29
|
+
const analyticsOwnerShape = { borrower: ZodAddress() };
|
|
30
|
+
const addressSchema = z.custom((value) => typeof value === "string" && isAddress(value, { strict: false }));
|
|
31
|
+
/** {@link AnalyticsPosition} */
|
|
32
|
+
const analyticsPositionSchema = z.discriminatedUnion("kind", [
|
|
33
|
+
z.object({
|
|
34
|
+
...poolPositionSchema.shape,
|
|
35
|
+
...analyticsOwnerShape
|
|
36
|
+
}),
|
|
37
|
+
z.object({
|
|
38
|
+
...strategyPositionSchema.shape,
|
|
39
|
+
...analyticsOwnerShape
|
|
40
|
+
}),
|
|
41
|
+
z.object({
|
|
42
|
+
...liquidationPositionSchema.shape,
|
|
43
|
+
...analyticsOwnerShape
|
|
44
|
+
})
|
|
45
|
+
]);
|
|
46
|
+
/** {@link AnalyticsPositionListOptions} */
|
|
47
|
+
const analyticsPositionListOptionsSchema = z.object({
|
|
48
|
+
borrower: addressSchema.optional(),
|
|
49
|
+
kind: filterable(positionKindSchema).optional(),
|
|
50
|
+
isZeroDebt: filterable(z.boolean()).optional(),
|
|
51
|
+
chainIds: z.array(chainIdSchema).optional(),
|
|
52
|
+
underlyingType: filterable(assetTypeSchema).optional(),
|
|
53
|
+
sortBy: analyticsPositionSortFieldSchema.optional(),
|
|
54
|
+
sortDirection: analyticsSortDirectionSchema.optional(),
|
|
55
|
+
offset: z.number().int().nonnegative().optional(),
|
|
56
|
+
limit: z.number().int().positive().max(100).optional()
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* {@link AnalyticsPositionListOptions} as URL query parameters.
|
|
60
|
+
**/
|
|
61
|
+
const analyticsPositionListQueryParamsSchema = z.object({
|
|
62
|
+
borrower: z.string().refine((value) => isAddress(value, { strict: false }), "invalid address").optional(),
|
|
63
|
+
kind: positionKindSchema.optional(),
|
|
64
|
+
isZeroDebt: z.enum(["true", "false"]).optional(),
|
|
65
|
+
chainIds: z.string().regex(/^$|^\d+(,\d+)*$/).optional(),
|
|
66
|
+
underlyingType: assetTypeSchema.optional(),
|
|
67
|
+
sortBy: analyticsPositionSortFieldSchema.optional(),
|
|
68
|
+
sortDirection: analyticsSortDirectionSchema.optional(),
|
|
69
|
+
offset: z.string().regex(/^\d+$/).optional(),
|
|
70
|
+
limit: z.string().regex(/^[1-9]\d*$/).optional()
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Codec for an analytics position-list query. It is shared by the SDK client
|
|
74
|
+
* and backend controller so both sides interpret every parameter identically.
|
|
75
|
+
**/
|
|
76
|
+
const analyticsPositionListQuerySchema = z.codec(analyticsPositionListQueryParamsSchema, analyticsPositionListOptionsSchema, {
|
|
77
|
+
decode: (params) => ({
|
|
78
|
+
...params.borrower === void 0 ? {} : { borrower: params.borrower },
|
|
79
|
+
...params.kind === void 0 ? {} : { kind: params.kind },
|
|
80
|
+
...params.isZeroDebt === void 0 ? {} : { isZeroDebt: params.isZeroDebt === "true" },
|
|
81
|
+
...params.chainIds === void 0 ? {} : { chainIds: params.chainIds === "" ? [] : params.chainIds.split(",").map(Number) },
|
|
82
|
+
...params.underlyingType === void 0 ? {} : { underlyingType: params.underlyingType },
|
|
83
|
+
...params.sortBy === void 0 ? {} : { sortBy: params.sortBy },
|
|
84
|
+
...params.sortDirection === void 0 ? {} : { sortDirection: params.sortDirection },
|
|
85
|
+
...params.offset === void 0 ? {} : { offset: Number(params.offset) },
|
|
86
|
+
...params.limit === void 0 ? {} : { limit: Number(params.limit) }
|
|
87
|
+
}),
|
|
88
|
+
encode: (options) => ({
|
|
89
|
+
borrower: options.borrower,
|
|
90
|
+
kind: isFilterSet(options.kind) ? options.kind : void 0,
|
|
91
|
+
isZeroDebt: encodeFlag(options.isZeroDebt),
|
|
92
|
+
chainIds: options.chainIds?.join(","),
|
|
93
|
+
underlyingType: isFilterSet(options.underlyingType) ? options.underlyingType : void 0,
|
|
94
|
+
sortBy: options.sortBy,
|
|
95
|
+
sortDirection: options.sortDirection,
|
|
96
|
+
offset: options.offset?.toString(),
|
|
97
|
+
limit: options.limit?.toString()
|
|
98
|
+
})
|
|
99
|
+
});
|
|
100
|
+
/** {@link AnalyticsPositionPage} */
|
|
101
|
+
const analyticsPositionPageSchema = z.object({
|
|
102
|
+
items: z.array(analyticsPositionSchema),
|
|
103
|
+
total: z.number().int().nonnegative(),
|
|
104
|
+
offset: z.number().int().nonnegative(),
|
|
105
|
+
limit: z.number().int().positive().max(100)
|
|
106
|
+
});
|
|
107
|
+
//#endregion
|
|
108
|
+
export { ANALYTICS_POSITIONS_DEFAULT_LIMIT, ANALYTICS_POSITIONS_MAX_LIMIT, analyticsPositionListOptionsSchema, analyticsPositionListQueryParamsSchema, analyticsPositionListQuerySchema, analyticsPositionPageSchema, analyticsPositionSchema, analyticsPositionSortFieldSchema, analyticsSortDirectionSchema };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CHART_METRIC_UNITS, CHART_RANGES, CHART_UNAVAILABLE_CODES, POOL_OPPORTUNITY_CHART_METRICS, POOL_POSITION_CHART_METRICS, STRATEGY_OPPORTUNITY_CHART_METRICS, STRATEGY_POSITION_CHART_METRICS } from "./charts.js";
|
|
2
1
|
import { timestampSchema, tokenSchema } from "./primitives.schema.js";
|
|
2
|
+
import { CHART_METRIC_UNITS, CHART_RANGES, CHART_UNAVAILABLE_CODES, POOL_OPPORTUNITY_CHART_METRICS, POOL_POSITION_CHART_METRICS, STRATEGY_OPPORTUNITY_CHART_METRICS, STRATEGY_POSITION_CHART_METRICS } from "./charts.js";
|
|
3
3
|
import { z } from "zod/v4";
|
|
4
4
|
//#region src/model/charts.schema.ts
|
|
5
5
|
/**
|