@gearbox-protocol/sdk 16.0.0-next.11 → 16.0.0-next.13

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 (40) hide show
  1. package/dist/cjs/dev/mode-parity/comparePositions.js +3 -2
  2. package/dist/cjs/dev/mode-parity/compareRules.js +12 -1
  3. package/dist/cjs/dev/mode-parity/scriptUtils.js +1 -1
  4. package/dist/cjs/model/compare.schema.js +8 -0
  5. package/dist/cjs/model/index.js +3 -1
  6. package/dist/cjs/model/positions.js +6 -0
  7. package/dist/cjs/model/positions.schema.js +4 -3
  8. package/dist/cjs/model/previews.js +4 -3
  9. package/dist/cjs/onchain/market/adapters/contracts/AbstractAdapter.js +14 -0
  10. package/dist/cjs/onchain/market/adapters/contracts/ERC4626AdapterContract.js +54 -0
  11. package/dist/cjs/onchain/market/adapters/contracts/MidasGatewayAdapterContract.js +13 -0
  12. package/dist/cjs/onchain/positions/PositionsService.js +20 -17
  13. package/dist/cjs/preview/preview/replayInnerOperations.js +10 -25
  14. package/dist/esm/dev/mode-parity/comparePositions.js +3 -2
  15. package/dist/esm/dev/mode-parity/compareRules.js +12 -1
  16. package/dist/esm/dev/mode-parity/scriptUtils.js +1 -1
  17. package/dist/esm/model/compare.schema.js +8 -1
  18. package/dist/esm/model/index.js +4 -4
  19. package/dist/esm/model/positions.js +6 -1
  20. package/dist/esm/model/positions.schema.js +5 -4
  21. package/dist/esm/model/previews.js +4 -3
  22. package/dist/esm/onchain/market/adapters/contracts/AbstractAdapter.js +14 -0
  23. package/dist/esm/onchain/market/adapters/contracts/ERC4626AdapterContract.js +55 -1
  24. package/dist/esm/onchain/market/adapters/contracts/MidasGatewayAdapterContract.js +14 -1
  25. package/dist/esm/onchain/positions/PositionsService.js +21 -18
  26. package/dist/esm/preview/preview/replayInnerOperations.js +11 -26
  27. package/dist/types/dev/mode-parity/comparePositions.d.ts +5 -3
  28. package/dist/types/dev/mode-parity/fieldDiff.d.ts +3 -1
  29. package/dist/types/model/compare.schema.d.ts +9 -2
  30. package/dist/types/model/index.d.ts +4 -4
  31. package/dist/types/model/positions.d.ts +19 -1
  32. package/dist/types/model/positions.schema.d.ts +2 -0
  33. package/dist/types/model/previews.d.ts +4 -3
  34. package/dist/types/onchain/market/adapters/contracts/AbstractAdapter.d.ts +12 -0
  35. package/dist/types/onchain/market/adapters/contracts/ERC4626AdapterContract.d.ts +8 -1
  36. package/dist/types/onchain/market/adapters/contracts/MidasGatewayAdapterContract.d.ts +7 -0
  37. package/package.json +1 -1
  38. package/dist/cjs/preview/preview/applyRWAWrapUnwrap.js +0 -67
  39. package/dist/esm/preview/preview/applyRWAWrapUnwrap.js +0 -66
  40. package/dist/types/preview/preview/applyRWAWrapUnwrap.d.ts +0 -17
@@ -15,8 +15,9 @@ const tagDiff = require_dev_mode_parity_compareRules.makeTagDiff({
15
15
  * Matches two position listings per wallet by {@link positionId} and reports
16
16
  * every field the two sources disagree on.
17
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
18
+ * Nothing is filtered out. A field only one mode can fill, a strategy field
19
+ * both-mode merge overlays from the backend, or a USD value that drifted
20
+ * within snapshot-lag noise, is still reported — tagged
20
21
  * {@link FieldDiff.expected} so that {@link CompareCounts.clean} can ignore it
21
22
  * while {@link CompareCounts.identical} stays strict.
22
23
  **/
@@ -25,15 +25,26 @@ function makeTagDiff(rulesByKind) {
25
25
  if (!rules) return diff;
26
26
  const path = require_dev_mode_parity_fieldDiff.collapseArrayKeys(diff.path);
27
27
  if (isModeScoped(path, rules)) return require_dev_mode_parity_fieldDiff.withExpected(diff, "mode-scoped");
28
+ if (isBackendPreferred(path, rules)) return require_dev_mode_parity_fieldDiff.withExpected(diff, "backend-preferred");
28
29
  const tag = rules.get(path);
29
30
  if (tag && typeof tag === "object" && withinTolerance(tag.tolerance, diff)) return require_dev_mode_parity_fieldDiff.withExpected(diff, "tolerance");
30
31
  return diff;
31
32
  };
32
33
  }
34
+ function pathMatchesRule(path, rulePath) {
35
+ return path === rulePath || path.startsWith(`${rulePath}.`) || path.startsWith(`${rulePath}[`);
36
+ }
33
37
  function isModeScoped(path, rules) {
34
38
  for (const [rulePath, tag] of rules) {
35
39
  if (tag !== "offchainOnly" && tag !== "onchainOnly") continue;
36
- if (path === rulePath || path.startsWith(`${rulePath}.`) || path.startsWith(`${rulePath}[`)) return true;
40
+ if (pathMatchesRule(path, rulePath)) return true;
41
+ }
42
+ return false;
43
+ }
44
+ function isBackendPreferred(path, rules) {
45
+ for (const [rulePath, tag] of rules) {
46
+ if (tag !== "backendPreferred") continue;
47
+ if (pathMatchesRule(path, rulePath)) return true;
37
48
  }
38
49
  return false;
39
50
  }
@@ -108,7 +108,7 @@ function printCompareSummary(noun, report, extraLines = []) {
108
108
  })));
109
109
  }
110
110
  if (expected.length) {
111
- console.log("\nexpected fields (mode-scoped or within tolerance):");
111
+ console.log("\nexpected fields (mode-scoped, backend-preferred, or within tolerance):");
112
112
  console.table(expected.slice(0, 25).map((entry) => ({
113
113
  field: entry.path,
114
114
  rows: entry.expected,
@@ -13,6 +13,13 @@ function onchainOnly(schema) {
13
13
  return schema.meta({ compare: "onchainOnly" });
14
14
  }
15
15
  /**
16
+ * Marks a field whose backend value both-mode merge overlays onto the chain
17
+ * row, so a source disagreement is expected.
18
+ **/
19
+ function backendPreferred(schema) {
20
+ return schema.meta({ compare: "backendPreferred" });
21
+ }
22
+ /**
16
23
  * Marks a numeric field whose two sources may drift within {@link kind}.
17
24
  **/
18
25
  function tolerance(schema, kind) {
@@ -27,6 +34,7 @@ function compareTagOf(schema) {
27
34
  return meta.compare;
28
35
  }
29
36
  //#endregion
37
+ exports.backendPreferred = backendPreferred;
30
38
  exports.compareTagOf = compareTagOf;
31
39
  exports.offchainOnly = offchainOnly;
32
40
  exports.onchainOnly = onchainOnly;
@@ -28,16 +28,18 @@ exports.ERROR_INVALID_TRANSACTION_VALUE = require_model_previews.ERROR_INVALID_T
28
28
  exports.ERROR_MALFORMED_BRACKET = require_model_previews.ERROR_MALFORMED_BRACKET;
29
29
  exports.ERROR_NON_ADAPTER_CALL_IN_BRACKET = require_model_previews.ERROR_NON_ADAPTER_CALL_IN_BRACKET;
30
30
  exports.ERROR_UNPREVIEWABLE_ADAPTER_CALL = require_model_previews.ERROR_UNPREVIEWABLE_ADAPTER_CALL;
31
- exports.ERROR_UNPREVIEWABLE_RWA_WRAP_UNWRAP = require_model_previews.ERROR_UNPREVIEWABLE_RWA_WRAP_UNWRAP;
32
31
  exports.ERROR_UNPRICEABLE_TOKEN = require_model_previews.ERROR_UNPRICEABLE_TOKEN;
32
+ exports.ERROR_UNSUPPORTED_OUT_OF_BRACKET_CALL = require_model_previews.ERROR_UNSUPPORTED_OUT_OF_BRACKET_CALL;
33
33
  exports.FILTER_ALL = require_model_filters.FILTER_ALL;
34
34
  exports.POOL_OPPORTUNITY_CHART_METRICS = require_model_charts.POOL_OPPORTUNITY_CHART_METRICS;
35
35
  exports.POOL_POSITION_CHART_METRICS = require_model_charts.POOL_POSITION_CHART_METRICS;
36
36
  exports.STRATEGY_OPPORTUNITY_CHART_METRICS = require_model_charts.STRATEGY_OPPORTUNITY_CHART_METRICS;
37
37
  exports.STRATEGY_POSITION_CHART_METRICS = require_model_charts.STRATEGY_POSITION_CHART_METRICS;
38
+ exports.STRATEGY_POSITION_COLLATERAL_ERROR = require_model_positions.STRATEGY_POSITION_COLLATERAL_ERROR;
38
39
  exports.amountSchema = require_model_primitives_schema.amountSchema;
39
40
  exports.apyBreakdownSchema = require_model_opportunities_schema.apyBreakdownSchema;
40
41
  exports.assetTypeSchema = require_model_primitives_schema.assetTypeSchema;
42
+ exports.backendPreferred = require_model_compare_schema.backendPreferred;
41
43
  exports.booleanParamSchema = require_model_filters_schema.booleanParamSchema;
42
44
  exports.borrowRateBreakdownSchema = require_model_positions_schema.borrowRateBreakdownSchema;
43
45
  exports.bpsSchema = require_model_primitives_schema.bpsSchema;
@@ -2,6 +2,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_model_filters = require("./filters.js");
3
3
  //#region src/model/positions.ts
4
4
  /**
5
+ * Set on {@link StrategyPosition.error} when the account could not be fully
6
+ * valued (e.g. a dead price feed).
7
+ **/
8
+ const STRATEGY_POSITION_COLLATERAL_ERROR = "collateral computation failed";
9
+ /**
5
10
  * Builds the canonical id of a pool position.
6
11
  *
7
12
  * @example
@@ -85,6 +90,7 @@ function positionUnderlying(position) {
85
90
  }
86
91
  }
87
92
  //#endregion
93
+ exports.STRATEGY_POSITION_COLLATERAL_ERROR = STRATEGY_POSITION_COLLATERAL_ERROR;
88
94
  exports.liquidationPositionId = liquidationPositionId;
89
95
  exports.matchesPositionFilter = matchesPositionFilter;
90
96
  exports.poolPositionId = poolPositionId;
@@ -96,12 +96,12 @@ const borrowRateBreakdownSchema = zod_v4.z.object({
96
96
  **/
97
97
  const strategyPositionSchema = zod_v4.z.object({
98
98
  kind: zod_v4.z.literal("strategy"),
99
- name: zod_v4.z.string(),
99
+ name: require_model_compare_schema.backendPreferred(zod_v4.z.string()),
100
100
  chainId: require_model_primitives_schema.chainIdSchema,
101
101
  creditManager: require_onchain_utils_zod.ZodAddress(),
102
102
  creditAccount: require_onchain_utils_zod.ZodAddress(),
103
103
  underlyingToken: require_model_primitives_schema.underlyingTokenSchema,
104
- targetCollateral: require_model_primitives_schema.tokenSchema.nullable(),
104
+ targetCollateral: require_model_compare_schema.backendPreferred(require_model_primitives_schema.tokenSchema.nullable()),
105
105
  leverage: require_model_compare_schema.tolerance(require_model_primitives_schema.leverageSchema, "float"),
106
106
  borrowApy: require_model_compare_schema.tolerance(require_model_primitives_schema.bpsSchema, "bps"),
107
107
  borrowApyAvg7D: require_model_compare_schema.offchainOnly(require_model_primitives_schema.bpsSchema).optional(),
@@ -115,7 +115,8 @@ const strategyPositionSchema = zod_v4.z.object({
115
115
  timeToLiquidation: require_model_compare_schema.onchainOnly(require_onchain_utils_zod.ZodBigInt().nullable()).optional(),
116
116
  liquidationPrice: require_model_compare_schema.onchainOnly(require_onchain_utils_zod.ZodBigInt().nullable()).optional(),
117
117
  pnl: require_model_compare_schema.offchainOnly(pnlBreakdownSchema).optional(),
118
- collaterals: zod_v4.z.array(positionCollateralSchema)
118
+ collaterals: zod_v4.z.array(positionCollateralSchema),
119
+ error: zod_v4.z.string().optional()
119
120
  });
120
121
  /**
121
122
  * {@link Position}
@@ -21,9 +21,10 @@ const ERROR_NON_ADAPTER_CALL_IN_BRACKET = 1003;
21
21
  **/
22
22
  const ERROR_UNPREVIEWABLE_ADAPTER_CALL = 1004;
23
23
  /**
24
- * Out-of-bracket RWA wrap/unwrap calldata cannot be decoded
24
+ * An out-of-bracket adapter call that is allowed there (e.g. RWA wrap/unwrap)
25
+ * could not be decoded or replayed
25
26
  **/
26
- const ERROR_UNPREVIEWABLE_RWA_WRAP_UNWRAP = 1005;
27
+ const ERROR_UNSUPPORTED_OUT_OF_BRACKET_CALL = 1005;
27
28
  /**
28
29
  * `msg.value` does not fit into the declared WETH collateral
29
30
  * Transactions can have arbitrary value, but the ones that we create
@@ -38,5 +39,5 @@ exports.ERROR_INVALID_TRANSACTION_VALUE = ERROR_INVALID_TRANSACTION_VALUE;
38
39
  exports.ERROR_MALFORMED_BRACKET = ERROR_MALFORMED_BRACKET;
39
40
  exports.ERROR_NON_ADAPTER_CALL_IN_BRACKET = ERROR_NON_ADAPTER_CALL_IN_BRACKET;
40
41
  exports.ERROR_UNPREVIEWABLE_ADAPTER_CALL = ERROR_UNPREVIEWABLE_ADAPTER_CALL;
41
- exports.ERROR_UNPREVIEWABLE_RWA_WRAP_UNWRAP = ERROR_UNPREVIEWABLE_RWA_WRAP_UNWRAP;
42
42
  exports.ERROR_UNPRICEABLE_TOKEN = ERROR_UNPRICEABLE_TOKEN;
43
+ exports.ERROR_UNSUPPORTED_OUT_OF_BRACKET_CALL = ERROR_UNSUPPORTED_OUT_OF_BRACKET_CALL;
@@ -115,6 +115,20 @@ var AbstractAdapterContract = class extends require_onchain_base_BaseContract.Ba
115
115
  await this.applyBalanceChanges(balances, decoded);
116
116
  }
117
117
  /**
118
+ * Replays this adapter call when it appears outside a
119
+ * storeExpectedBalances/compareBalances bracket, mutating `balances` in
120
+ * place, and returns `true` when the call is legal there.
121
+ *
122
+ * Base implementation returns `false`: nothing enforces the outcome of an
123
+ * out-of-bracket adapter call on-chain, so it cannot be previewed.
124
+ *
125
+ * @throws when the call is allowed outside a bracket but its calldata
126
+ * cannot be decoded
127
+ */
128
+ replayOutOfBracketCall(_balances, _calldata) {
129
+ return false;
130
+ }
131
+ /**
118
132
  * Applies the balance changes of a decoded adapter call to the running
119
133
  * balances, mutating them in place. Overrides should express changes via
120
134
  * {@link setLeftover} (diff-style calls) and {@link spendExact}
@@ -98,6 +98,60 @@ var ERC4626AdapterContract = class extends require_onchain_market_adapters_contr
98
98
  };
99
99
  return super.classifyLegacyOperation(parsed, transfers);
100
100
  }
101
+ /**
102
+ * Out-of-bracket calls are legal only on the RWA wrap/unwrap adapter (the
103
+ * share converts 1:1 with the vault asset, so no on-chain preview or
104
+ * slippage bracket is needed); a regular vault-strategy ERC4626 adapter
105
+ * keeps the base behavior and returns false.
106
+ */
107
+ replayOutOfBracketCall(balances, calldata) {
108
+ const meta = this.sdk.tokensMeta.get(this.share);
109
+ if (!meta || !this.sdk.tokensMeta.isRWAUnderlying(meta)) return false;
110
+ const resolved = this.#resolveWrapUnwrap(calldata, balances);
111
+ if (resolved && resolved.amountIn > 0n) {
112
+ balances.dec(resolved.tokenIn, resolved.amountIn);
113
+ balances.inc(resolved.tokenOut, resolved.amountIn);
114
+ }
115
+ return true;
116
+ }
117
+ #resolveWrapUnwrap(calldata, balances) {
118
+ const decoded = (0, viem.decodeFunctionData)({
119
+ abi,
120
+ data: calldata
121
+ });
122
+ const { asset, share } = this;
123
+ switch (decoded.functionName) {
124
+ case "deposit": return {
125
+ tokenIn: asset,
126
+ tokenOut: share,
127
+ amountIn: decoded.args[0]
128
+ };
129
+ case "depositDiff": {
130
+ const [leftoverAmount] = decoded.args;
131
+ const running = balances.getOrZero(asset);
132
+ return {
133
+ tokenIn: asset,
134
+ tokenOut: share,
135
+ amountIn: running > leftoverAmount ? running - leftoverAmount : 0n
136
+ };
137
+ }
138
+ case "redeem": return {
139
+ tokenIn: share,
140
+ tokenOut: asset,
141
+ amountIn: decoded.args[0]
142
+ };
143
+ case "redeemDiff": {
144
+ const [leftoverAmount] = decoded.args;
145
+ const running = balances.getOrZero(share);
146
+ return {
147
+ tokenIn: share,
148
+ tokenOut: asset,
149
+ amountIn: running > leftoverAmount ? running - leftoverAmount : 0n
150
+ };
151
+ }
152
+ default: return;
153
+ }
154
+ }
101
155
  async applyBalanceChanges(balances, decoded) {
102
156
  switch (decoded.functionName) {
103
157
  case "depositDiff": {
@@ -10,6 +10,10 @@ let viem = require("viem");
10
10
  //#region src/onchain/market/adapters/contracts/MidasGatewayAdapterContract.ts
11
11
  const abi = require_onchain_market_adapters_abi_adapters_iMidasGatewayAdapterV311.iMidasGatewayAdapterV311Abi;
12
12
  const protocolAbi = require_onchain_market_adapters_abi_midas_iMidasGatewayV311.iMidasGatewayV311Abi;
13
+ const receiveGreenlistCalldata = (0, viem.encodeFunctionData)({
14
+ abi,
15
+ functionName: "receiveGreenlist"
16
+ });
13
17
  var MidasGatewayAdapterContract = class extends require_onchain_market_adapters_contracts_AbstractAdapter.AbstractAdapterContract {
14
18
  #version;
15
19
  #gateway;
@@ -165,6 +169,15 @@ var MidasGatewayAdapterContract = class extends require_onchain_market_adapters_
165
169
  const [redeemer] = decoded.args;
166
170
  return { redeemer };
167
171
  }
172
+ /**
173
+ * `receiveGreenlist()` is prepended by `prependMidasReceiveGreenlist`
174
+ * before the balance bracket when the multicall mints a permissioned
175
+ * mToken: it only greenlists the credit account and is balance-neutral,
176
+ * so it is legal outside a bracket and leaves balances untouched.
177
+ */
178
+ replayOutOfBracketCall(_balances, calldata) {
179
+ return calldata === receiveGreenlistCalldata;
180
+ }
168
181
  async applyBalanceChanges(balances, decoded) {
169
182
  switch (decoded.functionName) {
170
183
  case "depositInstantDiff": {
@@ -64,12 +64,9 @@ var PositionsService = class extends require_onchain_base_SDKConstruct.SDKConstr
64
64
  **/
65
65
  async listStrategyPositions(props) {
66
66
  const { owner, includeZeroDebt, blockNumber } = props;
67
- const describable = (await this.sdk.accounts.getBorrowerCreditAccounts(owner, { includeZeroDebt }, blockNumber)).filter((ca) => {
68
- if (!ca.success) this.logger?.warn(`cannot describe position of ${this.labelAddress(ca.creditAccount)}: collateral computation failed`);
69
- return ca.success;
70
- });
71
- const withdrawals = await Promise.all(describable.map((ca) => this.#accountWithdrawals(ca, blockNumber)));
72
- return describable.map((ca, i) => this.#toStrategyPosition(ca, withdrawals[i] ?? new require_onchain_utils_AddressMap.AddressMap()));
67
+ const accounts = await this.sdk.accounts.getBorrowerCreditAccounts(owner, { includeZeroDebt }, blockNumber);
68
+ const withdrawals = await Promise.all(accounts.map((ca) => this.#accountWithdrawals(ca, blockNumber)));
69
+ return accounts.map((ca, i) => this.#toStrategyPosition(ca, withdrawals[i] ?? new require_onchain_utils_AddressMap.AddressMap()));
73
70
  }
74
71
  /**
75
72
  * Health factor of an account state, in basis points (`10000` = 1.0).
@@ -180,14 +177,11 @@ var PositionsService = class extends require_onchain_base_SDKConstruct.SDKConstr
180
177
  const token = market.underlyingToken;
181
178
  const totalDebtValue = ca.debt + ca.accruedInterest + ca.accruedFees;
182
179
  const target = require_onchain_chain_chains.getAccountTargetCollateral(ca.creditAccount, this.sdk.chainId) ?? suite.strategyTargetCollateral;
183
- const snapshot = require_onchain_positions_types.accountSnapshotFromCreditAccountData(ca);
184
- const borrowRate = this.borrowRate(snapshot);
185
- const timeToLiquidation = this.timeToLiquidation(snapshot);
186
- const liquidationPrice = this.liquidationPrice(snapshot);
187
- const zeroDebt = ca.debt === 0n;
180
+ const priceFailed = !ca.success;
181
+ const recomputeTotals = ca.debt === 0n || priceFailed;
188
182
  const collaterals = [];
189
- let totalValue = zeroDebt ? 0n : ca.totalValue;
190
- let totalValueUSD = zeroDebt ? 0n : ca.totalValueUSD;
183
+ let totalValue = recomputeTotals ? 0n : ca.totalValue;
184
+ let totalValueUSD = recomputeTotals ? 0n : ca.totalValueUSD;
191
185
  for (const t of ca.tokens) {
192
186
  if (t.balance <= 10n) continue;
193
187
  collaterals.push({
@@ -195,13 +189,21 @@ var PositionsService = class extends require_onchain_base_SDKConstruct.SDKConstr
195
189
  quota: priceOracle.toTokenAmount(market.underlying, t.quota),
196
190
  withdrawals: withdrawals.get(t.token) ?? []
197
191
  });
198
- if (zeroDebt) {
192
+ if (recomputeTotals) {
199
193
  const value = priceOracle.safeConvert(t.token, market.underlying, t.balance) || 0n;
200
194
  totalValue += value;
201
195
  const usd = priceOracle.safeConvertToUSD(t.token, t.balance) || 0n;
202
196
  totalValueUSD += usd;
203
197
  }
204
198
  }
199
+ const snapshot = {
200
+ ...require_onchain_positions_types.accountSnapshotFromCreditAccountData(ca),
201
+ totalValue
202
+ };
203
+ const borrowRate = this.borrowRate(snapshot);
204
+ const timeToLiquidation = this.timeToLiquidation(snapshot);
205
+ const liquidationPrice = this.liquidationPrice(snapshot);
206
+ const totalDebtUSD = priceFailed ? priceOracle.safeConvertToUSD(market.underlying, totalDebtValue) ?? 0n : ca.totalDebtUSD;
205
207
  return {
206
208
  kind: "strategy",
207
209
  chainId: this.sdk.chainId,
@@ -215,18 +217,19 @@ var PositionsService = class extends require_onchain_base_SDKConstruct.SDKConstr
215
217
  totalDebt: {
216
218
  token,
217
219
  value: totalDebtValue,
218
- valueUsd: require_onchain_market_math.usdToNumber(ca.totalDebtUSD)
220
+ valueUsd: require_onchain_market_math.usdToNumber(totalDebtUSD)
219
221
  },
220
222
  totalValue: {
221
223
  token,
222
224
  value: totalValue,
223
225
  valueUsd: require_onchain_market_math.usdToNumber(totalValueUSD)
224
226
  },
225
- healthFactor: require_onchain_market_math.healthFactorBps(ca.healthFactor),
227
+ healthFactor: priceFailed ? this.healthFactor(snapshot) : require_onchain_market_math.healthFactorBps(ca.healthFactor),
226
228
  borrowRate,
227
229
  timeToLiquidation,
228
230
  liquidationPrice,
229
- collaterals
231
+ collaterals,
232
+ ...priceFailed ? { error: require_model_positions.STRATEGY_POSITION_COLLATERAL_ERROR } : {}
230
233
  };
231
234
  }
232
235
  /**
@@ -2,11 +2,9 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_onchain_utils_AssetsMap = require("../../onchain/utils/AssetsMap.js");
3
3
  require("../../onchain/constants/math.js");
4
4
  const require_onchain_market_adapters_contracts_AbstractAdapter = require("../../onchain/market/adapters/contracts/AbstractAdapter.js");
5
- const require_onchain_market_adapters_contracts_ERC4626AdapterContract = require("../../onchain/market/adapters/contracts/ERC4626AdapterContract.js");
6
5
  const require_model_previews = require("../../model/previews.js");
7
6
  require("../../model/index.js");
8
7
  require("../../onchain/index.js");
9
- const require_preview_preview_applyRWAWrapUnwrap = require("./applyRWAWrapUnwrap.js");
10
8
  //#region src/preview/preview/replayInnerOperations.ts
11
9
  /**
12
10
  * Creates a {@link ReplayState} around the given account seed, with empty
@@ -94,26 +92,25 @@ function applyWithdrawCollateral(state, op) {
94
92
  *
95
93
  * Inside a bracket, the adapter's balance changes are previewed via
96
94
  * {@link AbstractAdapterContract.previewBalanceChanges}. Outside a bracket,
97
- * only RWA wrap/unwrap calls are allowed (see {@link applyRWAWrapUnwrap});
98
- * nothing enforces the outcome of any other out-of-bracket adapter call
99
- * on-chain, so its effect on balances cannot be previewed.
95
+ * only calls that {@link AbstractAdapterContract.replayOutOfBracketCall}
96
+ * accepts are allowed; nothing enforces the outcome of any other out-of-bracket
97
+ * adapter call on-chain, so its effect on balances cannot be previewed.
100
98
  */
101
99
  async function applyExecute(sdk, op, inBracket, balances) {
102
100
  const adapter = sdk.getContract(op.adapter);
103
101
  if (!inBracket) {
104
- if (!isRWAShare(sdk, adapter)) return {
105
- code: require_model_previews.ERROR_ADAPTER_CALL_OUTSIDE_BRACKET,
106
- message: `call to ${op.adapter} outside of a storeExpectedBalances/compareBalances bracket`
107
- };
108
- try {
109
- require_preview_preview_applyRWAWrapUnwrap.applyRWAWrapUnwrap(adapter, op.calldata, balances);
110
- return;
102
+ if (adapter instanceof require_onchain_market_adapters_contracts_AbstractAdapter.AbstractAdapterContract) try {
103
+ if (adapter.replayOutOfBracketCall(balances, op.calldata)) return;
111
104
  } catch (e) {
112
105
  return {
113
- code: require_model_previews.ERROR_UNPREVIEWABLE_RWA_WRAP_UNWRAP,
106
+ code: require_model_previews.ERROR_UNSUPPORTED_OUT_OF_BRACKET_CALL,
114
107
  message: e instanceof Error ? e.message : String(e)
115
108
  };
116
109
  }
110
+ return {
111
+ code: require_model_previews.ERROR_ADAPTER_CALL_OUTSIDE_BRACKET,
112
+ message: `call to ${op.adapter} outside of a storeExpectedBalances/compareBalances bracket`
113
+ };
117
114
  }
118
115
  if (!(adapter instanceof require_onchain_market_adapters_contracts_AbstractAdapter.AbstractAdapterContract)) return {
119
116
  code: require_model_previews.ERROR_NON_ADAPTER_CALL_IN_BRACKET,
@@ -129,18 +126,6 @@ async function applyExecute(sdk, op, inBracket, balances) {
129
126
  };
130
127
  }
131
128
  }
132
- /**
133
- * True when the ERC4626 adapter converts an RWA underlying, i.e. it is the
134
- * wrap/unwrap adapter of an RWA market rather than a regular vault strategy
135
- * adapter.
136
- */
137
- function isRWAShare(sdk, adapter) {
138
- if (adapter instanceof require_onchain_market_adapters_contracts_ERC4626AdapterContract.ERC4626AdapterContract) {
139
- const meta = sdk.tokensMeta.get(adapter.share);
140
- return !!meta && sdk.tokensMeta.isRWAUnderlying(meta);
141
- }
142
- return false;
143
- }
144
129
  //#endregion
145
130
  exports.makeReplayState = makeReplayState;
146
131
  exports.replayInnerOperations = replayInnerOperations;
@@ -14,8 +14,9 @@ const tagDiff = makeTagDiff({
14
14
  * Matches two position listings per wallet by {@link positionId} and reports
15
15
  * every field the two sources disagree on.
16
16
  *
17
- * Nothing is filtered out. A field only one mode can fill, or a USD value that
18
- * drifted within snapshot-lag noise, is still reported tagged
17
+ * Nothing is filtered out. A field only one mode can fill, a strategy field
18
+ * both-mode merge overlays from the backend, or a USD value that drifted
19
+ * within snapshot-lag noise, is still reported — tagged
19
20
  * {@link FieldDiff.expected} so that {@link CompareCounts.clean} can ignore it
20
21
  * while {@link CompareCounts.identical} stays strict.
21
22
  **/
@@ -24,15 +24,26 @@ function makeTagDiff(rulesByKind) {
24
24
  if (!rules) return diff;
25
25
  const path = collapseArrayKeys(diff.path);
26
26
  if (isModeScoped(path, rules)) return withExpected(diff, "mode-scoped");
27
+ if (isBackendPreferred(path, rules)) return withExpected(diff, "backend-preferred");
27
28
  const tag = rules.get(path);
28
29
  if (tag && typeof tag === "object" && withinTolerance(tag.tolerance, diff)) return withExpected(diff, "tolerance");
29
30
  return diff;
30
31
  };
31
32
  }
33
+ function pathMatchesRule(path, rulePath) {
34
+ return path === rulePath || path.startsWith(`${rulePath}.`) || path.startsWith(`${rulePath}[`);
35
+ }
32
36
  function isModeScoped(path, rules) {
33
37
  for (const [rulePath, tag] of rules) {
34
38
  if (tag !== "offchainOnly" && tag !== "onchainOnly") continue;
35
- if (path === rulePath || path.startsWith(`${rulePath}.`) || path.startsWith(`${rulePath}[`)) return true;
39
+ if (pathMatchesRule(path, rulePath)) return true;
40
+ }
41
+ return false;
42
+ }
43
+ function isBackendPreferred(path, rules) {
44
+ for (const [rulePath, tag] of rules) {
45
+ if (tag !== "backendPreferred") continue;
46
+ if (pathMatchesRule(path, rulePath)) return true;
36
47
  }
37
48
  return false;
38
49
  }
@@ -107,7 +107,7 @@ function printCompareSummary(noun, report, extraLines = []) {
107
107
  })));
108
108
  }
109
109
  if (expected.length) {
110
- console.log("\nexpected fields (mode-scoped or within tolerance):");
110
+ console.log("\nexpected fields (mode-scoped, backend-preferred, or within tolerance):");
111
111
  console.table(expected.slice(0, 25).map((entry) => ({
112
112
  field: entry.path,
113
113
  rows: entry.expected,
@@ -12,6 +12,13 @@ function onchainOnly(schema) {
12
12
  return schema.meta({ compare: "onchainOnly" });
13
13
  }
14
14
  /**
15
+ * Marks a field whose backend value both-mode merge overlays onto the chain
16
+ * row, so a source disagreement is expected.
17
+ **/
18
+ function backendPreferred(schema) {
19
+ return schema.meta({ compare: "backendPreferred" });
20
+ }
21
+ /**
15
22
  * Marks a numeric field whose two sources may drift within {@link kind}.
16
23
  **/
17
24
  function tolerance(schema, kind) {
@@ -26,4 +33,4 @@ function compareTagOf(schema) {
26
33
  return meta.compare;
27
34
  }
28
35
  //#endregion
29
- export { compareTagOf, offchainOnly, onchainOnly, tolerance };
36
+ export { backendPreferred, compareTagOf, offchainOnly, onchainOnly, tolerance };
@@ -1,5 +1,5 @@
1
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
- import { compareTagOf, offchainOnly, onchainOnly, tolerance } from "./compare.schema.js";
2
+ import { backendPreferred, compareTagOf, offchainOnly, onchainOnly, tolerance } from "./compare.schema.js";
3
3
  import { amountSchema, assetTypeSchema, bpsSchema, chainIdSchema, leverageSchema, timestampSchema, tokenAmountSchema, tokenSchema, txCallSchema, underlyingTokenSchema } from "./primitives.schema.js";
4
4
  import { chartBundleSchemaFor, chartDenominationSchema, chartMetricSchema, chartQueryCodec, chartQueryParamsSchema, chartQuerySchema, chartRangeSchema, chartSeriesSchema, chartValueSchema, chartWindowSchema, poolOpportunityChartMetricSchema, poolPositionChartMetricSchema, strategyOpportunityChartMetricSchema, strategyPositionChartMetricSchema } from "./charts.schema.js";
5
5
  import "./curators.js";
@@ -13,10 +13,10 @@ import "./notices.js";
13
13
  import { noticeKindSchema, noticeSchema } from "./notices.schema.js";
14
14
  import { matchesOpportunityFilter, opportunityId, poolOpportunityId, strategyOpportunityId } from "./opportunities.js";
15
15
  import { apyBreakdownSchema, opportunityBaseSchema, opportunityDetailSchema, opportunityFilterQueryParamsSchema, opportunityFilterQuerySchema, opportunityFilterSchema, opportunityKeySchema, opportunityKindSchema, opportunitySchema, pointRewardsSchema, pointsProgramSchema, poolOpportunityDetailSchema, poolOpportunityKeySchema, poolOpportunitySchema, priceFeedDataSchema, priceFeedSummarySchema, quotaAssetSchema, rateCurvePointSchema, rateCurveSchema, rewardsSchema, strategyOpportunityDetailSchema, strategyOpportunityKeySchema, strategyOpportunitySchema, tokenRewardsSchema } from "./opportunities.schema.js";
16
- import { liquidationPositionId, matchesPositionFilter, poolPositionId, positionId, strategyPositionId } from "./positions.js";
16
+ import { STRATEGY_POSITION_COLLATERAL_ERROR, liquidationPositionId, matchesPositionFilter, poolPositionId, positionId, strategyPositionId } from "./positions.js";
17
17
  import { borrowRateBreakdownSchema, pnlBreakdownSchema, pointsProgramPnLSchema, pointsRewardsPnLSchema, poolPositionKeySchema, poolPositionSchema, positionCollateralSchema, positionFilterQueryParamsSchema, positionFilterQuerySchema, positionFilterSchema, positionKeySchema, positionKindSchema, positionSchema, positionTransactionKindSchema, positionTransactionSchema, positionsTotalsSchema, rewardsPnLSchema, strategyPositionKeySchema, strategyPositionSchema, tokenQuotaRateSchema, tokenRewardsPnLSchema } from "./positions.schema.js";
18
- import { ERROR_ADAPTER_CALL_OUTSIDE_BRACKET, ERROR_INVALID_TRANSACTION_VALUE, ERROR_MALFORMED_BRACKET, ERROR_NON_ADAPTER_CALL_IN_BRACKET, ERROR_UNPREVIEWABLE_ADAPTER_CALL, ERROR_UNPREVIEWABLE_RWA_WRAP_UNWRAP, ERROR_UNPRICEABLE_TOKEN } from "./previews.js";
18
+ import { ERROR_ADAPTER_CALL_OUTSIDE_BRACKET, ERROR_INVALID_TRANSACTION_VALUE, ERROR_MALFORMED_BRACKET, ERROR_NON_ADAPTER_CALL_IN_BRACKET, ERROR_UNPREVIEWABLE_ADAPTER_CALL, ERROR_UNPRICEABLE_TOKEN, ERROR_UNSUPPORTED_OUT_OF_BRACKET_CALL } from "./previews.js";
19
19
  import "./primitives.js";
20
20
  import "./response.js";
21
21
  import { chainFailedSchema, chainMetadataSchema, chainSucceededSchema, dataSourceSchema, responseMetadataSchema, responseSchema } from "./response.schema.js";
22
- export { CHART_METRIC_UNITS, CHART_RANGES, CHART_UNAVAILABLE_CODES, ERROR_ADAPTER_CALL_OUTSIDE_BRACKET, ERROR_INVALID_TRANSACTION_VALUE, ERROR_MALFORMED_BRACKET, ERROR_NON_ADAPTER_CALL_IN_BRACKET, ERROR_UNPREVIEWABLE_ADAPTER_CALL, ERROR_UNPREVIEWABLE_RWA_WRAP_UNWRAP, ERROR_UNPRICEABLE_TOKEN, FILTER_ALL, POOL_OPPORTUNITY_CHART_METRICS, POOL_POSITION_CHART_METRICS, STRATEGY_OPPORTUNITY_CHART_METRICS, STRATEGY_POSITION_CHART_METRICS, amountSchema, apyBreakdownSchema, assetTypeSchema, booleanParamSchema, borrowRateBreakdownSchema, bpsSchema, chainFailedSchema, chainIdSchema, chainMetadataSchema, chainSucceededSchema, chartBundleSchemaFor, chartDenominationSchema, chartMetricSchema, chartQueryCodec, chartQueryParamsSchema, chartQuerySchema, chartRangeSchema, chartSeriesSchema, chartValueSchema, chartWindowSchema, compareTagOf, curatorNameSchema, curatorSchema, dataSourceSchema, delayedReceivedAssetSchema, encodeFlag, filterAllSchema, filterable, instantReceivedAssetSchema, isFilterSet, leverageSchema, liquidatableAccountFilterSchema, liquidatableAccountSchema, liquidationApprovalSchema, liquidationDetailsSchema, liquidationPositionId, liquidationPositionSchema, matchesLiquidatableAccountFilter, matchesOpportunityFilter, matchesPositionFilter, noticeKindSchema, noticeSchema, offchainOnly, onchainOnly, opportunityBaseSchema, opportunityDetailSchema, opportunityFilterQueryParamsSchema, opportunityFilterQuerySchema, opportunityFilterSchema, opportunityId, opportunityKeySchema, opportunityKindSchema, opportunitySchema, pnlBreakdownSchema, pointRewardsSchema, pointsProgramPnLSchema, pointsProgramSchema, pointsRewardsPnLSchema, poolOpportunityChartMetricSchema, poolOpportunityDetailSchema, poolOpportunityId, poolOpportunityKeySchema, poolOpportunitySchema, poolPositionChartMetricSchema, poolPositionId, poolPositionKeySchema, poolPositionSchema, positionCollateralSchema, positionFilterQueryParamsSchema, positionFilterQuerySchema, positionFilterSchema, positionId, positionKeySchema, positionKindSchema, positionSchema, positionTransactionKindSchema, positionTransactionSchema, positionsTotalsSchema, priceFeedDataSchema, priceFeedSummarySchema, quotaAssetSchema, rateCurvePointSchema, rateCurveSchema, receivedAssetSchema, responseMetadataSchema, responseSchema, rewardsPnLSchema, rewardsSchema, strategyOpportunityChartMetricSchema, strategyOpportunityDetailSchema, strategyOpportunityId, strategyOpportunityKeySchema, strategyOpportunitySchema, strategyPositionChartMetricSchema, strategyPositionId, strategyPositionKeySchema, strategyPositionSchema, timestampSchema, tokenAmountSchema, tokenQuotaRateSchema, tokenRewardsPnLSchema, tokenRewardsSchema, tokenSchema, tolerance, txCallSchema, underlyingTokenSchema };
22
+ export { CHART_METRIC_UNITS, CHART_RANGES, CHART_UNAVAILABLE_CODES, ERROR_ADAPTER_CALL_OUTSIDE_BRACKET, ERROR_INVALID_TRANSACTION_VALUE, ERROR_MALFORMED_BRACKET, ERROR_NON_ADAPTER_CALL_IN_BRACKET, ERROR_UNPREVIEWABLE_ADAPTER_CALL, ERROR_UNPRICEABLE_TOKEN, ERROR_UNSUPPORTED_OUT_OF_BRACKET_CALL, FILTER_ALL, POOL_OPPORTUNITY_CHART_METRICS, POOL_POSITION_CHART_METRICS, STRATEGY_OPPORTUNITY_CHART_METRICS, STRATEGY_POSITION_CHART_METRICS, STRATEGY_POSITION_COLLATERAL_ERROR, amountSchema, apyBreakdownSchema, assetTypeSchema, backendPreferred, booleanParamSchema, borrowRateBreakdownSchema, bpsSchema, chainFailedSchema, chainIdSchema, chainMetadataSchema, chainSucceededSchema, chartBundleSchemaFor, chartDenominationSchema, chartMetricSchema, chartQueryCodec, chartQueryParamsSchema, chartQuerySchema, chartRangeSchema, chartSeriesSchema, chartValueSchema, chartWindowSchema, compareTagOf, curatorNameSchema, curatorSchema, dataSourceSchema, delayedReceivedAssetSchema, encodeFlag, filterAllSchema, filterable, instantReceivedAssetSchema, isFilterSet, leverageSchema, liquidatableAccountFilterSchema, liquidatableAccountSchema, liquidationApprovalSchema, liquidationDetailsSchema, liquidationPositionId, liquidationPositionSchema, matchesLiquidatableAccountFilter, matchesOpportunityFilter, matchesPositionFilter, noticeKindSchema, noticeSchema, offchainOnly, onchainOnly, opportunityBaseSchema, opportunityDetailSchema, opportunityFilterQueryParamsSchema, opportunityFilterQuerySchema, opportunityFilterSchema, opportunityId, opportunityKeySchema, opportunityKindSchema, opportunitySchema, pnlBreakdownSchema, pointRewardsSchema, pointsProgramPnLSchema, pointsProgramSchema, pointsRewardsPnLSchema, poolOpportunityChartMetricSchema, poolOpportunityDetailSchema, poolOpportunityId, poolOpportunityKeySchema, poolOpportunitySchema, poolPositionChartMetricSchema, poolPositionId, poolPositionKeySchema, poolPositionSchema, positionCollateralSchema, positionFilterQueryParamsSchema, positionFilterQuerySchema, positionFilterSchema, positionId, positionKeySchema, positionKindSchema, positionSchema, positionTransactionKindSchema, positionTransactionSchema, positionsTotalsSchema, priceFeedDataSchema, priceFeedSummarySchema, quotaAssetSchema, rateCurvePointSchema, rateCurveSchema, receivedAssetSchema, responseMetadataSchema, responseSchema, rewardsPnLSchema, rewardsSchema, strategyOpportunityChartMetricSchema, strategyOpportunityDetailSchema, strategyOpportunityId, strategyOpportunityKeySchema, strategyOpportunitySchema, strategyPositionChartMetricSchema, strategyPositionId, strategyPositionKeySchema, strategyPositionSchema, timestampSchema, tokenAmountSchema, tokenQuotaRateSchema, tokenRewardsPnLSchema, tokenRewardsSchema, tokenSchema, tolerance, txCallSchema, underlyingTokenSchema };
@@ -1,6 +1,11 @@
1
1
  import { isFilterSet } from "./filters.js";
2
2
  //#region src/model/positions.ts
3
3
  /**
4
+ * Set on {@link StrategyPosition.error} when the account could not be fully
5
+ * valued (e.g. a dead price feed).
6
+ **/
7
+ const STRATEGY_POSITION_COLLATERAL_ERROR = "collateral computation failed";
8
+ /**
4
9
  * Builds the canonical id of a pool position.
5
10
  *
6
11
  * @example
@@ -84,4 +89,4 @@ function positionUnderlying(position) {
84
89
  }
85
90
  }
86
91
  //#endregion
87
- export { liquidationPositionId, matchesPositionFilter, poolPositionId, positionId, strategyPositionId };
92
+ export { STRATEGY_POSITION_COLLATERAL_ERROR, liquidationPositionId, matchesPositionFilter, poolPositionId, positionId, strategyPositionId };
@@ -1,5 +1,5 @@
1
1
  import { ZodAddress, ZodBigInt, ZodHex } from "../onchain/utils/zod.js";
2
- import { offchainOnly, onchainOnly, tolerance } from "./compare.schema.js";
2
+ import { backendPreferred, offchainOnly, onchainOnly, tolerance } from "./compare.schema.js";
3
3
  import { assetTypeSchema, bpsSchema, chainIdSchema, leverageSchema, timestampSchema, tokenAmountSchema, tokenSchema, underlyingTokenSchema } from "./primitives.schema.js";
4
4
  import { isFilterSet } from "./filters.js";
5
5
  import { booleanParamSchema, encodeFlag, filterable } from "./filters.schema.js";
@@ -95,12 +95,12 @@ const borrowRateBreakdownSchema = z.object({
95
95
  **/
96
96
  const strategyPositionSchema = z.object({
97
97
  kind: z.literal("strategy"),
98
- name: z.string(),
98
+ name: backendPreferred(z.string()),
99
99
  chainId: chainIdSchema,
100
100
  creditManager: ZodAddress(),
101
101
  creditAccount: ZodAddress(),
102
102
  underlyingToken: underlyingTokenSchema,
103
- targetCollateral: tokenSchema.nullable(),
103
+ targetCollateral: backendPreferred(tokenSchema.nullable()),
104
104
  leverage: tolerance(leverageSchema, "float"),
105
105
  borrowApy: tolerance(bpsSchema, "bps"),
106
106
  borrowApyAvg7D: offchainOnly(bpsSchema).optional(),
@@ -114,7 +114,8 @@ const strategyPositionSchema = z.object({
114
114
  timeToLiquidation: onchainOnly(ZodBigInt().nullable()).optional(),
115
115
  liquidationPrice: onchainOnly(ZodBigInt().nullable()).optional(),
116
116
  pnl: offchainOnly(pnlBreakdownSchema).optional(),
117
- collaterals: z.array(positionCollateralSchema)
117
+ collaterals: z.array(positionCollateralSchema),
118
+ error: z.string().optional()
118
119
  });
119
120
  /**
120
121
  * {@link Position}
@@ -20,9 +20,10 @@ const ERROR_NON_ADAPTER_CALL_IN_BRACKET = 1003;
20
20
  **/
21
21
  const ERROR_UNPREVIEWABLE_ADAPTER_CALL = 1004;
22
22
  /**
23
- * Out-of-bracket RWA wrap/unwrap calldata cannot be decoded
23
+ * An out-of-bracket adapter call that is allowed there (e.g. RWA wrap/unwrap)
24
+ * could not be decoded or replayed
24
25
  **/
25
- const ERROR_UNPREVIEWABLE_RWA_WRAP_UNWRAP = 1005;
26
+ const ERROR_UNSUPPORTED_OUT_OF_BRACKET_CALL = 1005;
26
27
  /**
27
28
  * `msg.value` does not fit into the declared WETH collateral
28
29
  * Transactions can have arbitrary value, but the ones that we create
@@ -32,4 +33,4 @@ const ERROR_INVALID_TRANSACTION_VALUE = 1006;
32
33
  /** A token in the preview could not be priced by the oracle */
33
34
  const ERROR_UNPRICEABLE_TOKEN = 2001;
34
35
  //#endregion
35
- export { ERROR_ADAPTER_CALL_OUTSIDE_BRACKET, ERROR_INVALID_TRANSACTION_VALUE, ERROR_MALFORMED_BRACKET, ERROR_NON_ADAPTER_CALL_IN_BRACKET, ERROR_UNPREVIEWABLE_ADAPTER_CALL, ERROR_UNPREVIEWABLE_RWA_WRAP_UNWRAP, ERROR_UNPRICEABLE_TOKEN };
36
+ export { ERROR_ADAPTER_CALL_OUTSIDE_BRACKET, ERROR_INVALID_TRANSACTION_VALUE, ERROR_MALFORMED_BRACKET, ERROR_NON_ADAPTER_CALL_IN_BRACKET, ERROR_UNPREVIEWABLE_ADAPTER_CALL, ERROR_UNPRICEABLE_TOKEN, ERROR_UNSUPPORTED_OUT_OF_BRACKET_CALL };
@@ -114,6 +114,20 @@ var AbstractAdapterContract = class extends BaseContract {
114
114
  await this.applyBalanceChanges(balances, decoded);
115
115
  }
116
116
  /**
117
+ * Replays this adapter call when it appears outside a
118
+ * storeExpectedBalances/compareBalances bracket, mutating `balances` in
119
+ * place, and returns `true` when the call is legal there.
120
+ *
121
+ * Base implementation returns `false`: nothing enforces the outcome of an
122
+ * out-of-bracket adapter call on-chain, so it cannot be previewed.
123
+ *
124
+ * @throws when the call is allowed outside a bracket but its calldata
125
+ * cannot be decoded
126
+ */
127
+ replayOutOfBracketCall(_balances, _calldata) {
128
+ return false;
129
+ }
130
+ /**
117
131
  * Applies the balance changes of a decoded adapter call to the running
118
132
  * balances, mutating them in place. Overrides should express changes via
119
133
  * {@link setLeftover} (diff-style calls) and {@link spendExact}