@gearbox-protocol/sdk 15.1.0-next.3 → 15.1.0-next.5

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.
@@ -0,0 +1,218 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_model_opportunities = require("../model/opportunities.js");
3
+ require("../model/index.js");
4
+ //#region src/dev/compareOpportunities.ts
5
+ /**
6
+ * Matches two opportunity listings by {@link opportunityId} and reports every
7
+ * field the two sources disagree on.
8
+ *
9
+ * Nothing is filtered out: a diff that is expected — a field only the backend
10
+ * can fill, a formula the two sides define differently, a USD value smoothed on
11
+ * one side — is reported like any other, tagged by {@link DiffKind} so that a
12
+ * reader can bucket it afterwards.
13
+ **/
14
+ function compareOpportunities(input) {
15
+ const onchainRows = indexById(input.onchain.data);
16
+ const offchainRows = indexById(input.offchain.data);
17
+ const onlyOnchain = [];
18
+ const onlyOffchain = [];
19
+ const matched = [];
20
+ for (const [id, row] of onchainRows) {
21
+ const counterpart = offchainRows.get(id);
22
+ if (!counterpart) {
23
+ onlyOnchain.push(toRef(row));
24
+ continue;
25
+ }
26
+ const diffs = diffOpportunity(row, counterpart);
27
+ matched.push({
28
+ id,
29
+ kind: row.kind,
30
+ chainId: row.chainId,
31
+ onchainName: row.name,
32
+ offchainName: counterpart.name,
33
+ identical: diffs.length === 0,
34
+ diffs
35
+ });
36
+ }
37
+ for (const [id, row] of offchainRows) if (!onchainRows.has(id)) onlyOffchain.push(toRef(row));
38
+ byId(onlyOnchain);
39
+ byId(onlyOffchain);
40
+ matched.sort((a, b) => a.id.localeCompare(b.id));
41
+ return {
42
+ generatedAt: input.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
43
+ backendUrl: input.backendUrl,
44
+ networks: [...input.networks],
45
+ onchainChains: input.onchain.meta.chains,
46
+ offchainChains: input.offchain.meta.chains,
47
+ summary: summarize(input.onchain.data, input.offchain.data, onlyOnchain, onlyOffchain, matched),
48
+ onlyOnchain,
49
+ onlyOffchain,
50
+ matched
51
+ };
52
+ }
53
+ function indexById(rows) {
54
+ return new Map(rows.map((row) => [require_model_opportunities.opportunityId(row), row]));
55
+ }
56
+ function byId(refs) {
57
+ refs.sort((a, b) => a.id.localeCompare(b.id));
58
+ }
59
+ function toRef(row) {
60
+ const base = {
61
+ id: require_model_opportunities.opportunityId(row),
62
+ kind: row.kind,
63
+ chainId: row.chainId,
64
+ name: row.name
65
+ };
66
+ return row.kind === "pool" ? {
67
+ ...base,
68
+ pool: row.pool
69
+ } : {
70
+ ...base,
71
+ creditManager: row.creditManager,
72
+ targetCollateral: row.targetCollateral.address
73
+ };
74
+ }
75
+ /**
76
+ * Every field two versions of one opportunity disagree on.
77
+ **/
78
+ function diffOpportunity(onchain, offchain) {
79
+ const diffs = [];
80
+ diffValue("", onchain, offchain, diffs);
81
+ return diffs;
82
+ }
83
+ function diffValue(path, onchain, offchain, out) {
84
+ if (isAbsent(onchain) && isAbsent(offchain)) return;
85
+ if (isAbsent(onchain) || isAbsent(offchain)) {
86
+ out.push({
87
+ path,
88
+ onchain,
89
+ offchain,
90
+ kind: "presence"
91
+ });
92
+ return;
93
+ }
94
+ if (Array.isArray(onchain) && Array.isArray(offchain)) {
95
+ diffArray(path, onchain, offchain, out);
96
+ return;
97
+ }
98
+ if (isRecord(onchain) && isRecord(offchain)) {
99
+ for (const key of union(Object.keys(onchain), Object.keys(offchain))) diffValue(join(path, key), onchain[key], offchain[key], out);
100
+ return;
101
+ }
102
+ if (!sameScalar(onchain, offchain)) out.push({
103
+ path,
104
+ onchain,
105
+ offchain,
106
+ kind: scalarKind(path, onchain)
107
+ });
108
+ }
109
+ /**
110
+ * Arrays whose elements identify themselves — collateral tokens, points
111
+ * programs — are matched by that identity, so a token present on one side only
112
+ * is reported as such rather than shifting every later element into a diff.
113
+ **/
114
+ function diffArray(path, onchain, offchain, out) {
115
+ const onchainKeyed = keyElements(onchain);
116
+ const offchainKeyed = keyElements(offchain);
117
+ if (!onchainKeyed || !offchainKeyed) {
118
+ if (onchain.length !== offchain.length) {
119
+ out.push({
120
+ path,
121
+ onchain,
122
+ offchain,
123
+ kind: "other"
124
+ });
125
+ return;
126
+ }
127
+ onchain.forEach((element, index) => {
128
+ diffValue(`${path}[${index}]`, element, offchain[index], out);
129
+ });
130
+ return;
131
+ }
132
+ for (const key of union([...onchainKeyed.keys()], [...offchainKeyed.keys()])) diffValue(`${path}[${key}]`, onchainKeyed.get(key), offchainKeyed.get(key), out);
133
+ }
134
+ /**
135
+ * The array indexed by each element's own identity, or `undefined` when its
136
+ * elements have none and order is all there is to go by.
137
+ **/
138
+ function keyElements(values) {
139
+ const keyed = /* @__PURE__ */ new Map();
140
+ for (const value of values) {
141
+ if (!isRecord(value)) return;
142
+ const identity = value.address ?? value.id ?? value.token;
143
+ if (typeof identity !== "string") return;
144
+ keyed.set(identity.toLowerCase(), value);
145
+ }
146
+ return keyed.size === values.length ? keyed : void 0;
147
+ }
148
+ const ADDRESS = /^0x[0-9a-f]{40}$/i;
149
+ /**
150
+ * Only addresses are compared case-insensitively: the backend lowercases them
151
+ * while the chain hands out checksummed ones, which is not a disagreement. A
152
+ * symbol or a name spelled differently is.
153
+ **/
154
+ function sameScalar(onchain, offchain) {
155
+ if (typeof onchain === "string" && typeof offchain === "string" && ADDRESS.test(onchain) && ADDRESS.test(offchain)) return onchain.toLowerCase() === offchain.toLowerCase();
156
+ return onchain === offchain;
157
+ }
158
+ function scalarKind(path, onchain) {
159
+ if (path.endsWith("valueUsd")) return "usd";
160
+ return typeof onchain === "number" || typeof onchain === "bigint" ? "numeric" : "other";
161
+ }
162
+ function summarize(onchain, offchain, onlyOnchain, onlyOffchain, matched) {
163
+ const byChain = union(onchain.map((row) => String(row.chainId)), offchain.map((row) => String(row.chainId))).map((chainId) => ({
164
+ chainId: Number(chainId),
165
+ ...count(onchain.filter((row) => String(row.chainId) === chainId), offchain.filter((row) => String(row.chainId) === chainId), onlyOnchain.filter((ref) => String(ref.chainId) === chainId), onlyOffchain.filter((ref) => String(ref.chainId) === chainId), matched.filter((match) => String(match.chainId) === chainId))
166
+ })).sort((a, b) => a.chainId - b.chainId);
167
+ return {
168
+ ...count(onchain, offchain, onlyOnchain, onlyOffchain, matched),
169
+ byChain,
170
+ diffsByPath: countPaths(matched)
171
+ };
172
+ }
173
+ function count(onchain, offchain, onlyOnchain, onlyOffchain, matched) {
174
+ const identical = matched.filter((match) => match.identical).length;
175
+ return {
176
+ onchainRows: onchain.length,
177
+ offchainRows: offchain.length,
178
+ matched: matched.length,
179
+ identical,
180
+ differing: matched.length - identical,
181
+ onlyOnchain: onlyOnchain.length,
182
+ onlyOffchain: onlyOffchain.length
183
+ };
184
+ }
185
+ /**
186
+ * How often each field differed, with array keys collapsed so that the same
187
+ * field of a hundred collateral tokens counts as one path.
188
+ **/
189
+ function countPaths(matched) {
190
+ const counts = /* @__PURE__ */ new Map();
191
+ for (const match of matched) for (const diff of match.diffs) {
192
+ const path = diff.path.replace(/\[[^\]]*\]/g, "[]");
193
+ const entry = counts.get(path) ?? {
194
+ path,
195
+ kinds: [],
196
+ count: 0
197
+ };
198
+ entry.count += 1;
199
+ if (!entry.kinds.includes(diff.kind)) entry.kinds.push(diff.kind);
200
+ counts.set(path, entry);
201
+ }
202
+ return [...counts.values()].sort((a, b) => b.count - a.count || a.path.localeCompare(b.path));
203
+ }
204
+ function isAbsent(value) {
205
+ return value === void 0 || value === null;
206
+ }
207
+ function isRecord(value) {
208
+ return typeof value === "object" && value !== null && !Array.isArray(value);
209
+ }
210
+ function union(left, right) {
211
+ return [.../* @__PURE__ */ new Set([...left, ...right])];
212
+ }
213
+ function join(path, key) {
214
+ return path ? `${path}.${key}` : key;
215
+ }
216
+ //#endregion
217
+ exports.compareOpportunities = compareOpportunities;
218
+ exports.diffOpportunity = diffOpportunity;
@@ -184,16 +184,26 @@ var CreditSuite = class extends require_sdk_base_SDKConstruct.SDKConstruct {
184
184
  * - is not a phantom token, which only ever appears as the intermediate step
185
185
  * of a withdrawal and cannot be acquired;
186
186
  * - is not an expired token, e.g. a matured Pendle PT;
187
+ * - has a non-zero main price in the market's oracle — a zero or failed
188
+ * answer (e.g. a zero price feed) means the position cannot be valued;
187
189
  * - the market still accepts quota for, see
188
190
  * {@link PoolQuotaKeeperContract.hasActiveQuota}.
191
+ *
192
+ * A suite where no debt can be drawn at all ({@link maxBorrowAmount} is `0`,
193
+ * e.g. its debt limit is exhausted or zeroed out) offers no strategies,
194
+ * whatever its collaterals are.
189
195
  */
190
196
  get strategyCollaterals() {
197
+ if (this.maxBorrowAmount === 0n) return [];
191
198
  const { pqk, unwrappedUnderlying } = this.market.pool;
199
+ const { mainPrices } = this.market.priceOracle;
192
200
  const { tokensMeta } = this;
193
201
  return this.creditManager.leverageableCollaterals.filter((token) => {
194
202
  if ((0, viem.isAddressEqual)(token, unwrappedUnderlying)) return false;
195
203
  const meta = tokensMeta.mustGet(token);
196
204
  if (tokensMeta.isPhantomToken(meta) || meta.isExpired) return false;
205
+ const mainPrice = mainPrices.get(token);
206
+ if (!mainPrice?.success || mainPrice.price === 0n) return false;
197
207
  return pqk.hasActiveQuota(token);
198
208
  });
199
209
  }
@@ -239,7 +249,7 @@ var CreditSuite = class extends require_sdk_base_SDKConstruct.SDKConstruct {
239
249
  curator: market.curator,
240
250
  underlyingToken: market.underlyingToken,
241
251
  totalBorrow: oracle.toAmount(pool.underlying, borrowed),
242
- collateralTokens: market.collateralTokens,
252
+ collateralTokens: this.strategyCollaterals.map((t) => this.tokensMeta.mustGetToken(t)),
243
253
  paused: this.isPaused,
244
254
  rwa: market.rwa,
245
255
  sunset: market.sunset || require_sdk_chain_chains.isSunsetStrategy(cm.address, collateral, this.sdk.networkType),
@@ -0,0 +1,216 @@
1
+ import { opportunityId } from "../model/opportunities.js";
2
+ import "../model/index.js";
3
+ //#region src/dev/compareOpportunities.ts
4
+ /**
5
+ * Matches two opportunity listings by {@link opportunityId} and reports every
6
+ * field the two sources disagree on.
7
+ *
8
+ * Nothing is filtered out: a diff that is expected — a field only the backend
9
+ * can fill, a formula the two sides define differently, a USD value smoothed on
10
+ * one side — is reported like any other, tagged by {@link DiffKind} so that a
11
+ * reader can bucket it afterwards.
12
+ **/
13
+ function compareOpportunities(input) {
14
+ const onchainRows = indexById(input.onchain.data);
15
+ const offchainRows = indexById(input.offchain.data);
16
+ const onlyOnchain = [];
17
+ const onlyOffchain = [];
18
+ const matched = [];
19
+ for (const [id, row] of onchainRows) {
20
+ const counterpart = offchainRows.get(id);
21
+ if (!counterpart) {
22
+ onlyOnchain.push(toRef(row));
23
+ continue;
24
+ }
25
+ const diffs = diffOpportunity(row, counterpart);
26
+ matched.push({
27
+ id,
28
+ kind: row.kind,
29
+ chainId: row.chainId,
30
+ onchainName: row.name,
31
+ offchainName: counterpart.name,
32
+ identical: diffs.length === 0,
33
+ diffs
34
+ });
35
+ }
36
+ for (const [id, row] of offchainRows) if (!onchainRows.has(id)) onlyOffchain.push(toRef(row));
37
+ byId(onlyOnchain);
38
+ byId(onlyOffchain);
39
+ matched.sort((a, b) => a.id.localeCompare(b.id));
40
+ return {
41
+ generatedAt: input.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
42
+ backendUrl: input.backendUrl,
43
+ networks: [...input.networks],
44
+ onchainChains: input.onchain.meta.chains,
45
+ offchainChains: input.offchain.meta.chains,
46
+ summary: summarize(input.onchain.data, input.offchain.data, onlyOnchain, onlyOffchain, matched),
47
+ onlyOnchain,
48
+ onlyOffchain,
49
+ matched
50
+ };
51
+ }
52
+ function indexById(rows) {
53
+ return new Map(rows.map((row) => [opportunityId(row), row]));
54
+ }
55
+ function byId(refs) {
56
+ refs.sort((a, b) => a.id.localeCompare(b.id));
57
+ }
58
+ function toRef(row) {
59
+ const base = {
60
+ id: opportunityId(row),
61
+ kind: row.kind,
62
+ chainId: row.chainId,
63
+ name: row.name
64
+ };
65
+ return row.kind === "pool" ? {
66
+ ...base,
67
+ pool: row.pool
68
+ } : {
69
+ ...base,
70
+ creditManager: row.creditManager,
71
+ targetCollateral: row.targetCollateral.address
72
+ };
73
+ }
74
+ /**
75
+ * Every field two versions of one opportunity disagree on.
76
+ **/
77
+ function diffOpportunity(onchain, offchain) {
78
+ const diffs = [];
79
+ diffValue("", onchain, offchain, diffs);
80
+ return diffs;
81
+ }
82
+ function diffValue(path, onchain, offchain, out) {
83
+ if (isAbsent(onchain) && isAbsent(offchain)) return;
84
+ if (isAbsent(onchain) || isAbsent(offchain)) {
85
+ out.push({
86
+ path,
87
+ onchain,
88
+ offchain,
89
+ kind: "presence"
90
+ });
91
+ return;
92
+ }
93
+ if (Array.isArray(onchain) && Array.isArray(offchain)) {
94
+ diffArray(path, onchain, offchain, out);
95
+ return;
96
+ }
97
+ if (isRecord(onchain) && isRecord(offchain)) {
98
+ for (const key of union(Object.keys(onchain), Object.keys(offchain))) diffValue(join(path, key), onchain[key], offchain[key], out);
99
+ return;
100
+ }
101
+ if (!sameScalar(onchain, offchain)) out.push({
102
+ path,
103
+ onchain,
104
+ offchain,
105
+ kind: scalarKind(path, onchain)
106
+ });
107
+ }
108
+ /**
109
+ * Arrays whose elements identify themselves — collateral tokens, points
110
+ * programs — are matched by that identity, so a token present on one side only
111
+ * is reported as such rather than shifting every later element into a diff.
112
+ **/
113
+ function diffArray(path, onchain, offchain, out) {
114
+ const onchainKeyed = keyElements(onchain);
115
+ const offchainKeyed = keyElements(offchain);
116
+ if (!onchainKeyed || !offchainKeyed) {
117
+ if (onchain.length !== offchain.length) {
118
+ out.push({
119
+ path,
120
+ onchain,
121
+ offchain,
122
+ kind: "other"
123
+ });
124
+ return;
125
+ }
126
+ onchain.forEach((element, index) => {
127
+ diffValue(`${path}[${index}]`, element, offchain[index], out);
128
+ });
129
+ return;
130
+ }
131
+ for (const key of union([...onchainKeyed.keys()], [...offchainKeyed.keys()])) diffValue(`${path}[${key}]`, onchainKeyed.get(key), offchainKeyed.get(key), out);
132
+ }
133
+ /**
134
+ * The array indexed by each element's own identity, or `undefined` when its
135
+ * elements have none and order is all there is to go by.
136
+ **/
137
+ function keyElements(values) {
138
+ const keyed = /* @__PURE__ */ new Map();
139
+ for (const value of values) {
140
+ if (!isRecord(value)) return;
141
+ const identity = value.address ?? value.id ?? value.token;
142
+ if (typeof identity !== "string") return;
143
+ keyed.set(identity.toLowerCase(), value);
144
+ }
145
+ return keyed.size === values.length ? keyed : void 0;
146
+ }
147
+ const ADDRESS = /^0x[0-9a-f]{40}$/i;
148
+ /**
149
+ * Only addresses are compared case-insensitively: the backend lowercases them
150
+ * while the chain hands out checksummed ones, which is not a disagreement. A
151
+ * symbol or a name spelled differently is.
152
+ **/
153
+ function sameScalar(onchain, offchain) {
154
+ if (typeof onchain === "string" && typeof offchain === "string" && ADDRESS.test(onchain) && ADDRESS.test(offchain)) return onchain.toLowerCase() === offchain.toLowerCase();
155
+ return onchain === offchain;
156
+ }
157
+ function scalarKind(path, onchain) {
158
+ if (path.endsWith("valueUsd")) return "usd";
159
+ return typeof onchain === "number" || typeof onchain === "bigint" ? "numeric" : "other";
160
+ }
161
+ function summarize(onchain, offchain, onlyOnchain, onlyOffchain, matched) {
162
+ const byChain = union(onchain.map((row) => String(row.chainId)), offchain.map((row) => String(row.chainId))).map((chainId) => ({
163
+ chainId: Number(chainId),
164
+ ...count(onchain.filter((row) => String(row.chainId) === chainId), offchain.filter((row) => String(row.chainId) === chainId), onlyOnchain.filter((ref) => String(ref.chainId) === chainId), onlyOffchain.filter((ref) => String(ref.chainId) === chainId), matched.filter((match) => String(match.chainId) === chainId))
165
+ })).sort((a, b) => a.chainId - b.chainId);
166
+ return {
167
+ ...count(onchain, offchain, onlyOnchain, onlyOffchain, matched),
168
+ byChain,
169
+ diffsByPath: countPaths(matched)
170
+ };
171
+ }
172
+ function count(onchain, offchain, onlyOnchain, onlyOffchain, matched) {
173
+ const identical = matched.filter((match) => match.identical).length;
174
+ return {
175
+ onchainRows: onchain.length,
176
+ offchainRows: offchain.length,
177
+ matched: matched.length,
178
+ identical,
179
+ differing: matched.length - identical,
180
+ onlyOnchain: onlyOnchain.length,
181
+ onlyOffchain: onlyOffchain.length
182
+ };
183
+ }
184
+ /**
185
+ * How often each field differed, with array keys collapsed so that the same
186
+ * field of a hundred collateral tokens counts as one path.
187
+ **/
188
+ function countPaths(matched) {
189
+ const counts = /* @__PURE__ */ new Map();
190
+ for (const match of matched) for (const diff of match.diffs) {
191
+ const path = diff.path.replace(/\[[^\]]*\]/g, "[]");
192
+ const entry = counts.get(path) ?? {
193
+ path,
194
+ kinds: [],
195
+ count: 0
196
+ };
197
+ entry.count += 1;
198
+ if (!entry.kinds.includes(diff.kind)) entry.kinds.push(diff.kind);
199
+ counts.set(path, entry);
200
+ }
201
+ return [...counts.values()].sort((a, b) => b.count - a.count || a.path.localeCompare(b.path));
202
+ }
203
+ function isAbsent(value) {
204
+ return value === void 0 || value === null;
205
+ }
206
+ function isRecord(value) {
207
+ return typeof value === "object" && value !== null && !Array.isArray(value);
208
+ }
209
+ function union(left, right) {
210
+ return [.../* @__PURE__ */ new Set([...left, ...right])];
211
+ }
212
+ function join(path, key) {
213
+ return path ? `${path}.${key}` : key;
214
+ }
215
+ //#endregion
216
+ export { compareOpportunities, diffOpportunity };
@@ -183,16 +183,26 @@ var CreditSuite = class extends SDKConstruct {
183
183
  * - is not a phantom token, which only ever appears as the intermediate step
184
184
  * of a withdrawal and cannot be acquired;
185
185
  * - is not an expired token, e.g. a matured Pendle PT;
186
+ * - has a non-zero main price in the market's oracle — a zero or failed
187
+ * answer (e.g. a zero price feed) means the position cannot be valued;
186
188
  * - the market still accepts quota for, see
187
189
  * {@link PoolQuotaKeeperContract.hasActiveQuota}.
190
+ *
191
+ * A suite where no debt can be drawn at all ({@link maxBorrowAmount} is `0`,
192
+ * e.g. its debt limit is exhausted or zeroed out) offers no strategies,
193
+ * whatever its collaterals are.
188
194
  */
189
195
  get strategyCollaterals() {
196
+ if (this.maxBorrowAmount === 0n) return [];
190
197
  const { pqk, unwrappedUnderlying } = this.market.pool;
198
+ const { mainPrices } = this.market.priceOracle;
191
199
  const { tokensMeta } = this;
192
200
  return this.creditManager.leverageableCollaterals.filter((token) => {
193
201
  if (isAddressEqual(token, unwrappedUnderlying)) return false;
194
202
  const meta = tokensMeta.mustGet(token);
195
203
  if (tokensMeta.isPhantomToken(meta) || meta.isExpired) return false;
204
+ const mainPrice = mainPrices.get(token);
205
+ if (!mainPrice?.success || mainPrice.price === 0n) return false;
196
206
  return pqk.hasActiveQuota(token);
197
207
  });
198
208
  }
@@ -238,7 +248,7 @@ var CreditSuite = class extends SDKConstruct {
238
248
  curator: market.curator,
239
249
  underlyingToken: market.underlyingToken,
240
250
  totalBorrow: oracle.toAmount(pool.underlying, borrowed),
241
- collateralTokens: market.collateralTokens,
251
+ collateralTokens: this.strategyCollaterals.map((t) => this.tokensMeta.mustGetToken(t)),
242
252
  paused: this.isPaused,
243
253
  rwa: market.rwa,
244
254
  sunset: market.sunset || isSunsetStrategy(cm.address, collateral, this.sdk.networkType),
@@ -0,0 +1,153 @@
1
+ import { ChainId } from "../model/primitives.js";
2
+ import { Opportunity, OpportunityId, OpportunityKind } from "../model/opportunities.js";
3
+ import { ChainMetadata, DataResponse } from "../model/response.js";
4
+ import "../model/index.js";
5
+ import { Address } from "viem";
6
+ //#region src/dev/compareOpportunities.d.ts
7
+ /**
8
+ * What kind of disagreement a {@link FieldDiff} describes, so that a reader can
9
+ * bucket the report without re-deriving it from the values.
10
+ *
11
+ * - `"presence"` — one side has no value at all (`undefined` or `null`).
12
+ * - `"usd"` — an {@link Amount.valueUsd}, i.e. a price-derived float.
13
+ * - `"numeric"` — any other number or bigint.
14
+ * - `"other"` — everything else: strings, booleans, array shapes.
15
+ **/
16
+ type DiffKind = "presence" | "usd" | "numeric" | "other";
17
+ /**
18
+ * One field of one opportunity where the two sources disagree.
19
+ **/
20
+ interface FieldDiff {
21
+ /**
22
+ * Dotted path into the row, with array elements keyed by their own identity
23
+ * rather than by index, e.g. `collateralTokens[0xa0b8...].symbol`.
24
+ **/
25
+ path: string;
26
+ /**
27
+ * Value the chain reported, `undefined` when it has no such field.
28
+ **/
29
+ onchain: unknown;
30
+ /**
31
+ * Value the backend reported, see {@link onchain}.
32
+ **/
33
+ offchain: unknown;
34
+ kind: DiffKind;
35
+ }
36
+ /**
37
+ * Enough of an opportunity to identify it in a report without carrying the
38
+ * whole row.
39
+ **/
40
+ interface OpportunityRef {
41
+ id: OpportunityId;
42
+ kind: OpportunityKind;
43
+ chainId: ChainId;
44
+ name: string;
45
+ /**
46
+ * Set on a pool opportunity.
47
+ **/
48
+ pool?: Address;
49
+ /**
50
+ * Set on a strategy opportunity, together with {@link targetCollateral}.
51
+ **/
52
+ creditManager?: Address;
53
+ targetCollateral?: Address;
54
+ }
55
+ /**
56
+ * One opportunity both sources listed, and everything they disagree on.
57
+ **/
58
+ interface OpportunityMatch {
59
+ id: OpportunityId;
60
+ kind: OpportunityKind;
61
+ chainId: ChainId;
62
+ /**
63
+ * Name each source gave the row, which is itself a frequent diff.
64
+ **/
65
+ onchainName: string;
66
+ offchainName: string;
67
+ identical: boolean;
68
+ diffs: FieldDiff[];
69
+ }
70
+ /**
71
+ * How often one field disagreed across all matched rows, with array keys
72
+ * collapsed, e.g. `collateralTokens[].symbol`.
73
+ **/
74
+ interface DiffPathCount {
75
+ path: string;
76
+ kinds: DiffKind[];
77
+ count: number;
78
+ }
79
+ /**
80
+ * Counts of one chain, or of the whole report when `chainId` is absent.
81
+ **/
82
+ interface CompareCounts {
83
+ onchainRows: number;
84
+ offchainRows: number;
85
+ matched: number;
86
+ identical: number;
87
+ differing: number;
88
+ onlyOnchain: number;
89
+ onlyOffchain: number;
90
+ }
91
+ /**
92
+ * Counts of one chain.
93
+ **/
94
+ interface ChainCompareCounts extends CompareCounts {
95
+ chainId: ChainId;
96
+ }
97
+ /**
98
+ * Totals of the comparison plus the fields that differed most often.
99
+ **/
100
+ interface CompareSummary extends CompareCounts {
101
+ byChain: ChainCompareCounts[];
102
+ diffsByPath: DiffPathCount[];
103
+ }
104
+ /**
105
+ * Everything one comparison run produced, ready to be written out as JSON.
106
+ **/
107
+ interface OpportunityCompareReport {
108
+ generatedAt: string;
109
+ backendUrl: string;
110
+ networks: string[];
111
+ /**
112
+ * Per-chain metadata of the on-chain read, which says which block each chain
113
+ * answered from.
114
+ **/
115
+ onchainChains: ChainMetadata[];
116
+ /**
117
+ * Per-chain metadata of the backend read, see {@link onchainChains}.
118
+ **/
119
+ offchainChains: ChainMetadata[];
120
+ summary: CompareSummary;
121
+ onlyOnchain: OpportunityRef[];
122
+ onlyOffchain: OpportunityRef[];
123
+ matched: OpportunityMatch[];
124
+ }
125
+ /**
126
+ * The two listings to compare, plus what the run was pointed at.
127
+ **/
128
+ interface CompareOpportunitiesInput {
129
+ onchain: DataResponse<Opportunity[]>;
130
+ offchain: DataResponse<Opportunity[]>;
131
+ backendUrl: string;
132
+ networks: string[];
133
+ /**
134
+ * ISO timestamp stamped onto the report, defaulting to now. Pinned by tests.
135
+ **/
136
+ generatedAt?: string;
137
+ }
138
+ /**
139
+ * Matches two opportunity listings by {@link opportunityId} and reports every
140
+ * field the two sources disagree on.
141
+ *
142
+ * Nothing is filtered out: a diff that is expected — a field only the backend
143
+ * can fill, a formula the two sides define differently, a USD value smoothed on
144
+ * one side — is reported like any other, tagged by {@link DiffKind} so that a
145
+ * reader can bucket it afterwards.
146
+ **/
147
+ declare function compareOpportunities(input: CompareOpportunitiesInput): OpportunityCompareReport;
148
+ /**
149
+ * Every field two versions of one opportunity disagree on.
150
+ **/
151
+ declare function diffOpportunity(onchain: Opportunity, offchain: Opportunity): FieldDiff[];
152
+ //#endregion
153
+ export { ChainCompareCounts, CompareCounts, CompareOpportunitiesInput, CompareSummary, DiffKind, DiffPathCount, FieldDiff, OpportunityCompareReport, OpportunityMatch, OpportunityRef, compareOpportunities, diffOpportunity };
@@ -147,8 +147,14 @@ declare class CreditSuite extends SDKConstruct {
147
147
  * - is not a phantom token, which only ever appears as the intermediate step
148
148
  * of a withdrawal and cannot be acquired;
149
149
  * - is not an expired token, e.g. a matured Pendle PT;
150
+ * - has a non-zero main price in the market's oracle — a zero or failed
151
+ * answer (e.g. a zero price feed) means the position cannot be valued;
150
152
  * - the market still accepts quota for, see
151
153
  * {@link PoolQuotaKeeperContract.hasActiveQuota}.
154
+ *
155
+ * A suite where no debt can be drawn at all ({@link maxBorrowAmount} is `0`,
156
+ * e.g. its debt limit is exhausted or zeroed out) offers no strategies,
157
+ * whatever its collaterals are.
152
158
  */
153
159
  get strategyCollaterals(): Address[];
154
160
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gearbox-protocol/sdk",
3
- "version": "15.1.0-next.3",
3
+ "version": "15.1.0-next.5",
4
4
  "description": "Gearbox SDK",
5
5
  "license": "MIT",
6
6
  "repository": {