@gearbox-protocol/sdk 15.1.0-next.10 → 15.1.0-next.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cjs/dev/mode-parity/compareOpportunities.js +105 -0
  2. package/dist/cjs/dev/mode-parity/comparePositions.js +160 -0
  3. package/dist/cjs/dev/mode-parity/compareRules.js +93 -0
  4. package/dist/cjs/dev/mode-parity/fieldDiff.js +288 -0
  5. package/dist/cjs/dev/mode-parity/scriptUtils.js +131 -0
  6. package/dist/cjs/model/compare.schema.js +33 -0
  7. package/dist/cjs/model/curators.schema.js +2 -1
  8. package/dist/cjs/model/index.js +5 -0
  9. package/dist/cjs/model/opportunities.schema.js +13 -12
  10. package/dist/cjs/model/positions.schema.js +15 -14
  11. package/dist/cjs/model/primitives.schema.js +2 -1
  12. package/dist/cjs/sdk/market/math.js +8 -1
  13. package/dist/cjs/sdk/pools/PoolService.js +1 -1
  14. package/dist/cjs/sdk/positions/PositionsService.js +6 -1
  15. package/dist/esm/common-utils/index.js +3 -3
  16. package/dist/esm/common-utils/utils/index.js +1 -1
  17. package/dist/esm/dev/mode-parity/compareOpportunities.js +103 -0
  18. package/dist/esm/dev/mode-parity/comparePositions.js +158 -0
  19. package/dist/esm/dev/mode-parity/compareRules.js +91 -0
  20. package/dist/esm/dev/mode-parity/fieldDiff.js +270 -0
  21. package/dist/esm/dev/mode-parity/scriptUtils.js +121 -0
  22. package/dist/esm/model/compare.schema.js +29 -0
  23. package/dist/esm/model/curators.schema.js +2 -1
  24. package/dist/esm/model/index.js +2 -1
  25. package/dist/esm/model/opportunities.schema.js +13 -12
  26. package/dist/esm/model/positions.schema.js +15 -14
  27. package/dist/esm/model/primitives.schema.js +2 -1
  28. package/dist/esm/plugins/apy/ApyPlugin.js +2 -2
  29. package/dist/esm/sdk/market/math.js +8 -1
  30. package/dist/esm/sdk/pools/PoolService.js +1 -1
  31. package/dist/esm/sdk/positions/PositionsService.js +6 -1
  32. package/dist/types/dev/{compareOpportunities.d.ts → mode-parity/compareOpportunities.d.ts} +18 -66
  33. package/dist/types/dev/mode-parity/comparePositions.d.ts +149 -0
  34. package/dist/types/dev/mode-parity/compareRules.d.ts +33 -0
  35. package/dist/types/dev/mode-parity/fieldDiff.d.ts +209 -0
  36. package/dist/types/dev/mode-parity/scriptUtils.d.ts +47 -0
  37. package/dist/types/model/compare.schema.d.ts +47 -0
  38. package/dist/types/model/index.d.ts +2 -1
  39. package/dist/types/sdk/market/math.d.ts +2 -0
  40. package/package.json +1 -1
  41. package/dist/cjs/dev/compareOpportunities.js +0 -218
  42. package/dist/esm/dev/compareOpportunities.js +0 -216
@@ -0,0 +1,105 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_model_opportunities = require("../../model/opportunities.js");
3
+ const require_model_opportunities_schema = require("../../model/opportunities.schema.js");
4
+ require("../../model/index.js");
5
+ const require_dev_mode_parity_fieldDiff = require("./fieldDiff.js");
6
+ const require_dev_mode_parity_compareRules = require("./compareRules.js");
7
+ //#region src/dev/mode-parity/compareOpportunities.ts
8
+ const tagDiff = require_dev_mode_parity_compareRules.makeTagDiff({
9
+ pool: require_dev_mode_parity_compareRules.compileCompareRules(require_model_opportunities_schema.poolOpportunitySchema),
10
+ strategy: require_dev_mode_parity_compareRules.compileCompareRules(require_model_opportunities_schema.strategyOpportunitySchema)
11
+ });
12
+ /**
13
+ * Matches two opportunity listings by {@link opportunityId} and reports every
14
+ * field the two sources disagree on.
15
+ *
16
+ * Nothing is filtered out. A field only the backend can fill, or a USD value
17
+ * that drifted within snapshot-lag noise, is still reported — tagged
18
+ * {@link FieldDiff.expected} so that {@link CompareCounts.clean} can ignore it
19
+ * while {@link CompareCounts.identical} stays strict.
20
+ **/
21
+ function compareOpportunities(input) {
22
+ const onchainRows = indexById(input.onchain.data);
23
+ const offchainRows = indexById(input.offchain.data);
24
+ const onlyOnchain = [];
25
+ const onlyOffchain = [];
26
+ const matched = [];
27
+ for (const [id, row] of onchainRows) {
28
+ const counterpart = offchainRows.get(id);
29
+ if (!counterpart) {
30
+ onlyOnchain.push(toRef(row));
31
+ continue;
32
+ }
33
+ const diffs = diffOpportunity(row, counterpart);
34
+ matched.push({
35
+ id,
36
+ kind: row.kind,
37
+ chainId: row.chainId,
38
+ onchainName: row.name,
39
+ offchainName: counterpart.name,
40
+ identical: diffs.length === 0,
41
+ clean: diffs.every((diff) => diff.expected),
42
+ diffs
43
+ });
44
+ }
45
+ for (const [id, row] of offchainRows) if (!onchainRows.has(id)) onlyOffchain.push(toRef(row));
46
+ byId(onlyOnchain);
47
+ byId(onlyOffchain);
48
+ matched.sort((a, b) => a.id.localeCompare(b.id));
49
+ return {
50
+ generatedAt: input.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
51
+ backendUrl: input.backendUrl,
52
+ networks: [...input.networks],
53
+ onchainChains: input.onchain.meta.chains,
54
+ offchainChains: input.offchain.meta.chains,
55
+ summary: summarize(input.onchain.data, input.offchain.data, onlyOnchain, onlyOffchain, matched),
56
+ onlyOnchain,
57
+ onlyOffchain,
58
+ matched
59
+ };
60
+ }
61
+ function indexById(rows) {
62
+ return new Map(rows.map((row) => [require_model_opportunities.opportunityId(row), row]));
63
+ }
64
+ function byId(refs) {
65
+ refs.sort((a, b) => a.id.localeCompare(b.id));
66
+ }
67
+ function toRef(row) {
68
+ const base = {
69
+ id: require_model_opportunities.opportunityId(row),
70
+ kind: row.kind,
71
+ chainId: row.chainId,
72
+ name: row.name
73
+ };
74
+ return row.kind === "pool" ? {
75
+ ...base,
76
+ pool: row.pool
77
+ } : {
78
+ ...base,
79
+ creditManager: row.creditManager,
80
+ targetCollateral: row.targetCollateral.address
81
+ };
82
+ }
83
+ /**
84
+ * Every field two versions of one opportunity disagree on.
85
+ **/
86
+ function diffOpportunity(onchain, offchain) {
87
+ return require_dev_mode_parity_fieldDiff.diffObjects(onchain, offchain).map((diff) => tagDiff(diff, onchain.kind));
88
+ }
89
+ function summarize(onchain, offchain, onlyOnchain, onlyOffchain, matched) {
90
+ const byChain = require_dev_mode_parity_fieldDiff.union(onchain.map((row) => String(row.chainId)), offchain.map((row) => String(row.chainId))).map((chainId) => ({
91
+ chainId: Number(chainId),
92
+ ...require_dev_mode_parity_fieldDiff.toCompareCounts(onchain.filter((row) => String(row.chainId) === chainId).length, offchain.filter((row) => String(row.chainId) === chainId).length, onlyOnchain.filter((ref) => String(ref.chainId) === chainId).length, onlyOffchain.filter((ref) => String(ref.chainId) === chainId).length, matched.filter((match) => String(match.chainId) === chainId))
93
+ })).sort((a, b) => a.chainId - b.chainId);
94
+ return {
95
+ ...require_dev_mode_parity_fieldDiff.toCompareCounts(onchain.length, offchain.length, onlyOnchain.length, onlyOffchain.length, matched),
96
+ byChain,
97
+ diffsByPath: require_dev_mode_parity_fieldDiff.countPaths(matched.flatMap((match) => match.diffs.map((diff) => ({
98
+ id: match.id,
99
+ diff
100
+ }))))
101
+ };
102
+ }
103
+ //#endregion
104
+ exports.compareOpportunities = compareOpportunities;
105
+ exports.diffOpportunity = diffOpportunity;
@@ -0,0 +1,160 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_model_liquidations_schema = require("../../model/liquidations.schema.js");
3
+ const require_model_positions = require("../../model/positions.js");
4
+ const require_model_positions_schema = require("../../model/positions.schema.js");
5
+ require("../../model/index.js");
6
+ const require_dev_mode_parity_fieldDiff = require("./fieldDiff.js");
7
+ const require_dev_mode_parity_compareRules = require("./compareRules.js");
8
+ //#region src/dev/mode-parity/comparePositions.ts
9
+ const tagDiff = require_dev_mode_parity_compareRules.makeTagDiff({
10
+ pool: require_dev_mode_parity_compareRules.compileCompareRules(require_model_positions_schema.poolPositionSchema),
11
+ strategy: require_dev_mode_parity_compareRules.compileCompareRules(require_model_positions_schema.strategyPositionSchema),
12
+ liquidation: require_dev_mode_parity_compareRules.compileCompareRules(require_model_liquidations_schema.liquidationPositionSchema)
13
+ });
14
+ /**
15
+ * Matches two position listings per wallet by {@link positionId} and reports
16
+ * every field the two sources disagree on.
17
+ *
18
+ * Nothing is filtered out. A field only one mode can fill, or a USD value that
19
+ * drifted within snapshot-lag noise, is still reported — tagged
20
+ * {@link FieldDiff.expected} so that {@link CompareCounts.clean} can ignore it
21
+ * while {@link CompareCounts.identical} stays strict.
22
+ **/
23
+ function comparePositions(input) {
24
+ const failures = input.failures ?? [];
25
+ const wallets = [...input.wallets.map(compareWallet), ...failures.map((failure) => ({
26
+ wallet: failure.wallet,
27
+ error: failure.error,
28
+ onlyOnchain: [],
29
+ onlyOffchain: [],
30
+ matched: []
31
+ }))];
32
+ wallets.sort((a, b) => a.wallet.localeCompare(b.wallet));
33
+ return {
34
+ generatedAt: input.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
35
+ backendUrl: input.backendUrl,
36
+ networks: [...input.networks],
37
+ onchainChains: collectChains(input.wallets, "onchain"),
38
+ offchainChains: collectChains(input.wallets, "offchain"),
39
+ summary: summarize(input.wallets, wallets, failures.length),
40
+ wallets
41
+ };
42
+ }
43
+ /**
44
+ * Every field two versions of one position disagree on.
45
+ **/
46
+ function diffPosition(onchain, offchain) {
47
+ return require_dev_mode_parity_fieldDiff.diffObjects(onchain, offchain, { keyOf: keyOfPositionArray }).map((diff) => tagDiff(diff, onchain.kind));
48
+ }
49
+ function compareWallet(input) {
50
+ const onchainRows = indexById(input.onchain.data);
51
+ const offchainRows = indexById(input.offchain.data);
52
+ const onlyOnchain = [];
53
+ const onlyOffchain = [];
54
+ const matched = [];
55
+ for (const [id, row] of onchainRows) {
56
+ const counterpart = offchainRows.get(id);
57
+ if (!counterpart) {
58
+ onlyOnchain.push(toRef(row));
59
+ continue;
60
+ }
61
+ const diffs = diffPosition(row, counterpart);
62
+ matched.push({
63
+ id,
64
+ kind: row.kind,
65
+ chainId: row.chainId,
66
+ onchainName: row.name,
67
+ offchainName: counterpart.name,
68
+ identical: diffs.length === 0,
69
+ clean: diffs.every((diff) => diff.expected),
70
+ diffs
71
+ });
72
+ }
73
+ for (const [id, row] of offchainRows) if (!onchainRows.has(id)) onlyOffchain.push(toRef(row));
74
+ onlyOnchain.sort((a, b) => a.id.localeCompare(b.id));
75
+ onlyOffchain.sort((a, b) => a.id.localeCompare(b.id));
76
+ matched.sort((a, b) => a.id.localeCompare(b.id));
77
+ return {
78
+ wallet: input.wallet,
79
+ onlyOnchain,
80
+ onlyOffchain,
81
+ matched
82
+ };
83
+ }
84
+ function indexById(rows) {
85
+ return new Map(rows.map((row) => [require_model_positions.positionId(row), row]));
86
+ }
87
+ function toRef(row) {
88
+ const base = {
89
+ id: require_model_positions.positionId(row),
90
+ kind: row.kind,
91
+ chainId: row.chainId,
92
+ name: row.name
93
+ };
94
+ switch (row.kind) {
95
+ case "pool": return {
96
+ ...base,
97
+ pool: row.pool
98
+ };
99
+ case "strategy": return {
100
+ ...base,
101
+ creditAccount: row.creditAccount,
102
+ creditManager: row.creditManager
103
+ };
104
+ case "liquidation": return base;
105
+ }
106
+ }
107
+ /**
108
+ * `collaterals[]` is keyed by the collateral token, not by a top-level
109
+ * address. Rewards-points programs already identify themselves by `id`.
110
+ **/
111
+ function keyOfPositionArray(path, value) {
112
+ if (path !== "collaterals" || !require_dev_mode_parity_fieldDiff.isRecord(value) || !require_dev_mode_parity_fieldDiff.isRecord(value.collateral)) return;
113
+ const token = value.collateral.token;
114
+ return require_dev_mode_parity_fieldDiff.isRecord(token) && typeof token.address === "string" ? token.address : void 0;
115
+ }
116
+ function summarize(inputs, wallets, failed) {
117
+ const compared = wallets.filter((wallet) => !wallet.error);
118
+ const allOnchain = inputs.flatMap((input) => input.onchain.data);
119
+ const allOffchain = inputs.flatMap((input) => input.offchain.data);
120
+ const onlyOnchain = compared.flatMap((wallet) => wallet.onlyOnchain);
121
+ const onlyOffchain = compared.flatMap((wallet) => wallet.onlyOffchain);
122
+ const matched = compared.flatMap((wallet) => wallet.matched);
123
+ const byChain = require_dev_mode_parity_fieldDiff.union(allOnchain.map((row) => String(row.chainId)), allOffchain.map((row) => String(row.chainId))).map((chainId) => ({
124
+ chainId: Number(chainId),
125
+ ...require_dev_mode_parity_fieldDiff.toCompareCounts(allOnchain.filter((row) => String(row.chainId) === chainId).length, allOffchain.filter((row) => String(row.chainId) === chainId).length, onlyOnchain.filter((ref) => String(ref.chainId) === chainId).length, onlyOffchain.filter((ref) => String(ref.chainId) === chainId).length, matched.filter((match) => String(match.chainId) === chainId))
126
+ })).sort((a, b) => a.chainId - b.chainId);
127
+ const byWallet = wallets.map((wallet) => ({
128
+ wallet: wallet.wallet,
129
+ ...wallet.error ? { error: wallet.error } : {},
130
+ ...require_dev_mode_parity_fieldDiff.toCompareCounts(wallet.matched.length + wallet.onlyOnchain.length, wallet.matched.length + wallet.onlyOffchain.length, wallet.onlyOnchain.length, wallet.onlyOffchain.length, wallet.matched)
131
+ }));
132
+ const walletsClean = compared.filter((wallet) => wallet.onlyOnchain.length === 0 && wallet.onlyOffchain.length === 0 && wallet.matched.every((match) => match.clean)).length;
133
+ return {
134
+ ...require_dev_mode_parity_fieldDiff.toCompareCounts(allOnchain.length, allOffchain.length, onlyOnchain.length, onlyOffchain.length, matched),
135
+ wallets: wallets.length,
136
+ walletsClean,
137
+ walletsFailed: failed,
138
+ byChain,
139
+ byWallet,
140
+ diffsByPath: require_dev_mode_parity_fieldDiff.countPaths(matched.flatMap((match) => match.diffs.map((diff) => ({
141
+ id: match.id,
142
+ diff
143
+ }))))
144
+ };
145
+ }
146
+ /**
147
+ * One metadata entry per chain, preferring a success so the report names the
148
+ * block. Later wallets of the same chain are ignored.
149
+ **/
150
+ function collectChains(wallets, side) {
151
+ const byChain = /* @__PURE__ */ new Map();
152
+ for (const wallet of wallets) for (const chain of wallet[side].meta.chains) {
153
+ const existing = byChain.get(chain.chainId);
154
+ if (!existing || existing.status !== "success" && chain.status === "success") byChain.set(chain.chainId, chain);
155
+ }
156
+ return [...byChain.values()].sort((a, b) => a.chainId - b.chainId);
157
+ }
158
+ //#endregion
159
+ exports.comparePositions = comparePositions;
160
+ exports.diffPosition = diffPosition;
@@ -0,0 +1,93 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_model_compare_schema = require("../../model/compare.schema.js");
3
+ const require_dev_mode_parity_fieldDiff = require("./fieldDiff.js");
4
+ //#region src/dev/mode-parity/compareRules.ts
5
+ /**
6
+ * Walks a schema and records every field that carries compare metadata.
7
+ *
8
+ * `"amount"` on an object (an Amount / TokenAmount) is stored
9
+ * at `<path>.value`; every other tag is stored at the field's own path.
10
+ **/
11
+ function compileCompareRules(schema) {
12
+ const rules = /* @__PURE__ */ new Map();
13
+ walk(schema, "", /* @__PURE__ */ new Set(), rules);
14
+ return rules;
15
+ }
16
+ /**
17
+ * Tags diffs using the rules compiled for each row kind.
18
+ *
19
+ * Mode tags match the path or anything nested under it. Tolerance tags match
20
+ * the path exactly and dispatch on {@link CompareTolerance}.
21
+ **/
22
+ function makeTagDiff(rulesByKind) {
23
+ return (diff, kind) => {
24
+ const rules = rulesByKind[kind];
25
+ if (!rules) return diff;
26
+ const path = require_dev_mode_parity_fieldDiff.collapseArrayKeys(diff.path);
27
+ if (isModeScoped(path, rules)) return require_dev_mode_parity_fieldDiff.withExpected(diff, "mode-scoped");
28
+ const tag = rules.get(path);
29
+ if (tag && typeof tag === "object" && withinTolerance(tag.tolerance, diff)) return require_dev_mode_parity_fieldDiff.withExpected(diff, "tolerance");
30
+ return diff;
31
+ };
32
+ }
33
+ function isModeScoped(path, rules) {
34
+ for (const [rulePath, tag] of rules) {
35
+ if (tag !== "offchainOnly" && tag !== "onchainOnly") continue;
36
+ if (path === rulePath || path.startsWith(`${rulePath}.`) || path.startsWith(`${rulePath}[`)) return true;
37
+ }
38
+ return false;
39
+ }
40
+ function withinTolerance(kind, diff) {
41
+ switch (kind) {
42
+ case "usd": return require_dev_mode_parity_fieldDiff.isUsdWithinTolerance(diff.onchain, diff.offchain);
43
+ case "bps": return require_dev_mode_parity_fieldDiff.isBpsWithinTolerance(diff.onchain, diff.offchain);
44
+ case "amount": return require_dev_mode_parity_fieldDiff.isAmountWithinTolerance(diff.onchain, diff.offchain);
45
+ case "float": return require_dev_mode_parity_fieldDiff.withinRelative(require_dev_mode_parity_fieldDiff.asFiniteNumber(diff.onchain), require_dev_mode_parity_fieldDiff.asFiniteNumber(diff.offchain), require_dev_mode_parity_fieldDiff.USD_RELATIVE_EPSILON);
46
+ }
47
+ }
48
+ function walk(schema, path, seen, rules) {
49
+ if (seen.has(schema)) return;
50
+ const nextSeen = new Set(seen);
51
+ nextSeen.add(schema);
52
+ const tag = require_model_compare_schema.compareTagOf(schema);
53
+ if (tag) record(path, tag, schema, rules);
54
+ const def = schema.def;
55
+ switch (def.type) {
56
+ case "optional":
57
+ case "nullable":
58
+ case "default":
59
+ case "prefault":
60
+ case "readonly":
61
+ if (def.innerType) walk(def.innerType, path, nextSeen, rules);
62
+ return;
63
+ case "array":
64
+ if (def.element) walk(def.element, `${path}[]`, nextSeen, rules);
65
+ return;
66
+ case "object":
67
+ if (def.shape) for (const [key, field] of Object.entries(def.shape)) walk(field, join(path, key), nextSeen, rules);
68
+ return;
69
+ case "union":
70
+ for (const option of def.options ?? []) walk(option, path, nextSeen, rules);
71
+ return;
72
+ case "pipe":
73
+ if (def.out) walk(def.out, path, nextSeen, rules);
74
+ return;
75
+ case "lazy":
76
+ if (def.getter) walk(def.getter(), path, nextSeen, rules);
77
+ return;
78
+ default: return;
79
+ }
80
+ }
81
+ function record(path, tag, schema, rules) {
82
+ if (typeof tag === "object" && tag.tolerance === "amount" && schema.def.type === "object") {
83
+ rules.set(join(path, "value"), tag);
84
+ return;
85
+ }
86
+ rules.set(path, tag);
87
+ }
88
+ function join(path, key) {
89
+ return path ? `${path}.${key}` : key;
90
+ }
91
+ //#endregion
92
+ exports.compileCompareRules = compileCompareRules;
93
+ exports.makeTagDiff = makeTagDiff;
@@ -0,0 +1,288 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/dev/mode-parity/fieldDiff.ts
3
+ /**
4
+ * Relative drift allowed on {@link Amount.valueUsd} before a USD float is a
5
+ * real disagreement: 0.1%.
6
+ **/
7
+ const USD_RELATIVE_EPSILON = .001;
8
+ /**
9
+ * Relative drift allowed on lag-bounded bigint amounts: 0.05%.
10
+ **/
11
+ const AMOUNT_RELATIVE_EPSILON = 5e-4;
12
+ /**
13
+ * Absolute drift allowed on bps rates before rounding and a one-block lag are
14
+ * no longer enough to explain it.
15
+ **/
16
+ const BPS_ABSOLUTE_EPSILON = 1;
17
+ /**
18
+ * Every field two versions of one value disagree on. Nothing is filtered or
19
+ * tagged: the caller marks expected diffs afterwards.
20
+ **/
21
+ function diffValue(path, onchain, offchain, out, options) {
22
+ if (isAbsent(onchain) && isAbsent(offchain)) return;
23
+ if (isAbsent(onchain) || isAbsent(offchain)) {
24
+ out.push({
25
+ path,
26
+ onchain,
27
+ offchain,
28
+ kind: "presence"
29
+ });
30
+ return;
31
+ }
32
+ if (Array.isArray(onchain) && Array.isArray(offchain)) {
33
+ diffArray(path, onchain, offchain, out, options);
34
+ return;
35
+ }
36
+ if (isRecord(onchain) && isRecord(offchain)) {
37
+ for (const key of union(Object.keys(onchain), Object.keys(offchain))) diffValue(join(path, key), onchain[key], offchain[key], out, options);
38
+ return;
39
+ }
40
+ if (!sameScalar(onchain, offchain)) out.push({
41
+ path,
42
+ onchain,
43
+ offchain,
44
+ kind: scalarKind(path, onchain)
45
+ });
46
+ }
47
+ /**
48
+ * Deep-diff two values and return the disagreements, in path order of
49
+ * discovery.
50
+ **/
51
+ function diffObjects(onchain, offchain, options) {
52
+ const diffs = [];
53
+ diffValue("", onchain, offchain, diffs, options);
54
+ return diffs;
55
+ }
56
+ /**
57
+ * Mark a diff expected, so a later `clean` count can ignore it.
58
+ **/
59
+ function withExpected(diff, reason) {
60
+ return {
61
+ ...diff,
62
+ expected: true,
63
+ reason
64
+ };
65
+ }
66
+ /**
67
+ * How often each field differed, with array keys collapsed so that the same
68
+ * field of a hundred collateral tokens counts as one path. Sorted so the
69
+ * unexpected disagreements come first.
70
+ **/
71
+ function countPaths(diffs) {
72
+ const counts = /* @__PURE__ */ new Map();
73
+ for (const { id, diff } of diffs) {
74
+ const path = collapseArrayKeys(diff.path);
75
+ const entry = counts.get(path) ?? {
76
+ path,
77
+ kinds: [],
78
+ count: 0,
79
+ expected: 0,
80
+ unexpected: 0
81
+ };
82
+ entry.count += 1;
83
+ if (diff.expected) entry.expected += 1;
84
+ else entry.unexpected += 1;
85
+ if (!entry.kinds.includes(diff.kind)) entry.kinds.push(diff.kind);
86
+ recordWorst(entry, id, diff);
87
+ counts.set(path, entry);
88
+ }
89
+ return [...counts.values()].sort((a, b) => b.unexpected - a.unexpected || b.count - a.count || a.path.localeCompare(b.path));
90
+ }
91
+ function recordWorst(entry, id, diff) {
92
+ const bps = relativeDiffBps(diff.onchain, diff.offchain);
93
+ if (bps === void 0) return;
94
+ const worst = {
95
+ id,
96
+ path: diff.path,
97
+ bps,
98
+ onchain: diff.onchain,
99
+ offchain: diff.offchain
100
+ };
101
+ if (diff.expected) {
102
+ if (isWorse(worst, entry.worstExpected)) entry.worstExpected = worst;
103
+ return;
104
+ }
105
+ if (isWorse(worst, entry.worstUnexpected)) entry.worstUnexpected = worst;
106
+ }
107
+ function isWorse(candidate, current) {
108
+ if (!current) return true;
109
+ return candidate.bps > current.bps || candidate.bps === current.bps && candidate.id.localeCompare(current.id) < 0;
110
+ }
111
+ /**
112
+ * Collapse `collateralTokens[0xa0b8...].symbol` to `collateralTokens[].symbol`.
113
+ **/
114
+ function collapseArrayKeys(path) {
115
+ return path.replace(/\[[^\]]*\]/g, "[]");
116
+ }
117
+ /**
118
+ * Membership and match totals of one comparison, from already-built lists.
119
+ **/
120
+ function toCompareCounts(onchainRows, offchainRows, onlyOnchain, onlyOffchain, matched) {
121
+ const identical = matched.filter((match) => match.identical).length;
122
+ const clean = matched.filter((match) => match.clean).length;
123
+ return {
124
+ onchainRows,
125
+ offchainRows,
126
+ matched: matched.length,
127
+ identical,
128
+ clean,
129
+ differing: matched.length - identical,
130
+ onlyOnchain,
131
+ onlyOffchain
132
+ };
133
+ }
134
+ /**
135
+ * USD floats within {@link USD_RELATIVE_EPSILON}.
136
+ **/
137
+ function isUsdWithinTolerance(onchain, offchain) {
138
+ return withinRelative(asFiniteNumber(onchain), asFiniteNumber(offchain), USD_RELATIVE_EPSILON);
139
+ }
140
+ /**
141
+ * Bps rates that differ by at most {@link BPS_ABSOLUTE_EPSILON}.
142
+ **/
143
+ function isBpsWithinTolerance(onchain, offchain) {
144
+ const left = asFiniteNumber(onchain);
145
+ const right = asFiniteNumber(offchain);
146
+ return left !== void 0 && right !== void 0 && Math.abs(left - right) <= 1;
147
+ }
148
+ /**
149
+ * Bigint amounts within {@link AMOUNT_RELATIVE_EPSILON}.
150
+ **/
151
+ function isAmountWithinTolerance(onchain, offchain) {
152
+ return typeof onchain === "bigint" && typeof offchain === "bigint" && withinRelativeBigint(onchain, offchain, 5e-4);
153
+ }
154
+ /**
155
+ * Finite numbers within a relative epsilon of each other.
156
+ **/
157
+ function withinRelative(onchain, offchain, epsilon) {
158
+ if (onchain === void 0 || offchain === void 0) return false;
159
+ const scale = Math.max(Math.abs(onchain), Math.abs(offchain));
160
+ return scale === 0 ? true : Math.abs(onchain - offchain) / scale <= epsilon;
161
+ }
162
+ /**
163
+ * `diff / max(|a|, |b|) <= epsilon`, computed in integer arithmetic so a
164
+ * 1e18-scale amount does not round through `Number`.
165
+ **/
166
+ function withinRelativeBigint(onchain, offchain, epsilon) {
167
+ if (onchain === offchain) return true;
168
+ const diff = onchain > offchain ? onchain - offchain : offchain - onchain;
169
+ const scale = abs(onchain) > abs(offchain) ? abs(onchain) : abs(offchain);
170
+ if (scale === 0n) return true;
171
+ return diff * BigInt(Math.round(1 / epsilon)) <= scale;
172
+ }
173
+ function asFiniteNumber(value) {
174
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
175
+ }
176
+ /**
177
+ * Relative difference of two numbers or bigints, in bps:
178
+ * `|a − b| / max(|a|, |b|) * 10_000`. `undefined` when the values are not
179
+ * a comparable pair of finite numbers or of bigints.
180
+ **/
181
+ function relativeDiffBps(onchain, offchain) {
182
+ if (typeof onchain === "bigint" && typeof offchain === "bigint") return relativeDiffBpsBigint(onchain, offchain);
183
+ const left = asFiniteNumber(onchain);
184
+ const right = asFiniteNumber(offchain);
185
+ if (left === void 0 || right === void 0) return;
186
+ const scale = Math.max(Math.abs(left), Math.abs(right));
187
+ return scale === 0 ? 0 : Math.abs(left - right) / scale * 1e4;
188
+ }
189
+ /**
190
+ * Same formula as {@link relativeDiffBps} for bigints, computed in integer
191
+ * arithmetic to milli-bps so a 1e18-scale amount does not round through
192
+ * `Number`.
193
+ **/
194
+ function relativeDiffBpsBigint(onchain, offchain) {
195
+ if (onchain === offchain) return 0;
196
+ const diff = onchain > offchain ? onchain - offchain : offchain - onchain;
197
+ const scale = abs(onchain) > abs(offchain) ? abs(onchain) : abs(offchain);
198
+ if (scale === 0n) return 0;
199
+ return Number(diff * 10000n * 1000n / scale) / 1e3;
200
+ }
201
+ function union(left, right) {
202
+ return [.../* @__PURE__ */ new Set([...left, ...right])];
203
+ }
204
+ function isRecord(value) {
205
+ return typeof value === "object" && value !== null && !Array.isArray(value);
206
+ }
207
+ function diffArray(path, onchain, offchain, out, options) {
208
+ const onchainKeyed = keyElements(path, onchain, options?.keyOf);
209
+ const offchainKeyed = keyElements(path, offchain, options?.keyOf);
210
+ if (!onchainKeyed || !offchainKeyed) {
211
+ if (onchain.length !== offchain.length) {
212
+ out.push({
213
+ path,
214
+ onchain,
215
+ offchain,
216
+ kind: "other"
217
+ });
218
+ return;
219
+ }
220
+ onchain.forEach((element, index) => {
221
+ diffValue(`${path}[${index}]`, element, offchain[index], out, options);
222
+ });
223
+ return;
224
+ }
225
+ for (const key of union([...onchainKeyed.keys()], [...offchainKeyed.keys()])) diffValue(`${path}[${key}]`, onchainKeyed.get(key), offchainKeyed.get(key), out, options);
226
+ }
227
+ /**
228
+ * The array indexed by each element's own identity, or `undefined` when its
229
+ * elements have none and order is all there is to go by.
230
+ **/
231
+ function keyElements(path, values, keyOf) {
232
+ const keyed = /* @__PURE__ */ new Map();
233
+ for (const value of values) {
234
+ const identity = keyOf?.(path, value) ?? defaultIdentity(value);
235
+ if (typeof identity !== "string") return;
236
+ keyed.set(identity.toLowerCase(), value);
237
+ }
238
+ return keyed.size === values.length ? keyed : void 0;
239
+ }
240
+ function defaultIdentity(value) {
241
+ if (!isRecord(value)) return;
242
+ if (typeof value.address === "string") return value.address;
243
+ if (typeof value.id === "string") return value.id;
244
+ if (typeof value.token === "string") return value.token;
245
+ if (isRecord(value.token) && typeof value.token.address === "string") return value.token.address;
246
+ }
247
+ const ADDRESS = /^0x[0-9a-f]{40}$/i;
248
+ /**
249
+ * Only addresses are compared case-insensitively: the backend lowercases them
250
+ * while the chain hands out checksummed ones, which is not a disagreement. A
251
+ * symbol or a name spelled differently is.
252
+ **/
253
+ function sameScalar(onchain, offchain) {
254
+ if (typeof onchain === "string" && typeof offchain === "string" && ADDRESS.test(onchain) && ADDRESS.test(offchain)) return onchain.toLowerCase() === offchain.toLowerCase();
255
+ return onchain === offchain;
256
+ }
257
+ function scalarKind(path, onchain) {
258
+ if (path.endsWith("valueUsd")) return "usd";
259
+ return typeof onchain === "number" || typeof onchain === "bigint" ? "numeric" : "other";
260
+ }
261
+ function isAbsent(value) {
262
+ return value === void 0 || value === null;
263
+ }
264
+ function join(path, key) {
265
+ return path ? `${path}.${key}` : key;
266
+ }
267
+ function abs(value) {
268
+ return value < 0n ? -value : value;
269
+ }
270
+ //#endregion
271
+ exports.AMOUNT_RELATIVE_EPSILON = AMOUNT_RELATIVE_EPSILON;
272
+ exports.BPS_ABSOLUTE_EPSILON = BPS_ABSOLUTE_EPSILON;
273
+ exports.USD_RELATIVE_EPSILON = USD_RELATIVE_EPSILON;
274
+ exports.asFiniteNumber = asFiniteNumber;
275
+ exports.collapseArrayKeys = collapseArrayKeys;
276
+ exports.countPaths = countPaths;
277
+ exports.diffObjects = diffObjects;
278
+ exports.diffValue = diffValue;
279
+ exports.isAmountWithinTolerance = isAmountWithinTolerance;
280
+ exports.isBpsWithinTolerance = isBpsWithinTolerance;
281
+ exports.isRecord = isRecord;
282
+ exports.isUsdWithinTolerance = isUsdWithinTolerance;
283
+ exports.relativeDiffBps = relativeDiffBps;
284
+ exports.toCompareCounts = toCompareCounts;
285
+ exports.union = union;
286
+ exports.withExpected = withExpected;
287
+ exports.withinRelative = withinRelative;
288
+ exports.withinRelativeBigint = withinRelativeBigint;