@gearbox-protocol/sdk 16.0.0-next.15 → 16.0.0-next.16

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 (34) hide show
  1. package/dist/cjs/onchain/accounts/intents/open-strategy.js +12 -3
  2. package/dist/cjs/onchain/accounts/intents/realize.js +11 -0
  3. package/dist/cjs/onchain/accounts/intents/testing/market.js +1 -0
  4. package/dist/cjs/onchain/accounts/intents/testing/sdk-mock.js +6 -4
  5. package/dist/cjs/onchain/accounts/intents/tests/deposit.fixtures.js +5 -2
  6. package/dist/cjs/onchain/accounts/intents/utils/index.js +4 -0
  7. package/dist/cjs/onchain/accounts/intents/utils/price-impact.js +99 -0
  8. package/dist/cjs/onchain/accounts/intents/utils/router-path.js +98 -40
  9. package/dist/esm/onchain/accounts/intents/open-strategy.js +12 -3
  10. package/dist/esm/onchain/accounts/intents/realize.js +11 -0
  11. package/dist/esm/onchain/accounts/intents/testing/market.js +1 -0
  12. package/dist/esm/onchain/accounts/intents/testing/sdk-mock.js +6 -4
  13. package/dist/esm/onchain/accounts/intents/tests/deposit.fixtures.js +5 -2
  14. package/dist/esm/onchain/accounts/intents/utils/index.js +2 -1
  15. package/dist/esm/onchain/accounts/intents/utils/price-impact.js +96 -0
  16. package/dist/esm/onchain/accounts/intents/utils/router-path.js +98 -40
  17. package/dist/types/onchain/accounts/index.d.ts +4 -4
  18. package/dist/types/onchain/accounts/intents/index.d.ts +4 -4
  19. package/dist/types/onchain/accounts/intents/open-strategy.d.ts +3 -0
  20. package/dist/types/onchain/accounts/intents/realize.d.ts +1 -1
  21. package/dist/types/onchain/accounts/intents/tail.d.ts +1 -1
  22. package/dist/types/onchain/accounts/intents/testing/expect.d.ts +1 -1
  23. package/dist/types/onchain/accounts/intents/testing/market.d.ts +2 -0
  24. package/dist/types/onchain/accounts/intents/testing/sdk-mock.d.ts +7 -0
  25. package/dist/types/onchain/accounts/intents/tests/deposit.fixtures.d.ts +1 -1
  26. package/dist/types/onchain/accounts/intents/types.d.ts +18 -2
  27. package/dist/types/onchain/accounts/intents/utils/index.d.ts +2 -1
  28. package/dist/types/onchain/accounts/intents/utils/price-impact.d.ts +49 -0
  29. package/dist/types/onchain/accounts/intents/utils/router-path.d.ts +9 -0
  30. package/dist/types/onchain/index.d.ts +4 -4
  31. package/dist/types/sdk/index.d.ts +2 -2
  32. package/dist/types/sdk/prepare/index.d.ts +2 -2
  33. package/dist/types/sdk/prepare/types.d.ts +4 -4
  34. package/package.json +1 -1
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_onchain_accounts_intents_refusal = require("./refusal.js");
3
3
  const require_onchain_accounts_intents_utils_convert_amount = require("./utils/convert-amount.js");
4
4
  const require_onchain_accounts_intents_guards = require("./guards.js");
5
+ const require_onchain_accounts_intents_utils_price_impact = require("./utils/price-impact.js");
5
6
  const require_onchain_accounts_intents_utils_quotas_for_update = require("./utils/quotas-for-update.js");
6
7
  const require_onchain_accounts_intents_utils_router_path = require("./utils/router-path.js");
7
8
  require("./utils/index.js");
@@ -43,12 +44,14 @@ async function previewOpenStrategy(props) {
43
44
  };
44
45
  require_onchain_accounts_intents_math.assertDebtInBand(debt, suite.creditFacade, underlying);
45
46
  require_onchain_accounts_intents_guards.assertCanBorrow(suite, debt);
46
- const leg = await require_onchain_accounts_intents_utils_router_path.createRouterPaths({
47
+ const paths = require_onchain_accounts_intents_utils_router_path.createRouterPaths({
47
48
  sdk,
48
49
  creditAccount: account,
49
50
  slippage
50
- }).openStrategy({
51
- expectedBalances: mergeExpectedBalances(collateral, underlying, debt),
51
+ });
52
+ const expectedBalances = mergeExpectedBalances(collateral, underlying, debt);
53
+ const leg = await paths.openStrategy({
54
+ expectedBalances,
52
55
  leftoverBalances,
53
56
  target: targetToken
54
57
  });
@@ -91,10 +94,16 @@ async function previewOpenStrategy(props) {
91
94
  liquidationPrice: sdk.positions.liquidationPrice(snapshot)
92
95
  };
93
96
  require_onchain_accounts_intents_guards.assertCollateralised(metrics.healthFactor, false);
97
+ const priceImpact = await require_onchain_accounts_intents_utils_price_impact.collectPriceImpact(leg.probe ? [leg.probe] : [], {
98
+ totalValue: margin + debt,
99
+ netValue: margin,
100
+ toUnderlying: (from, amount) => convert(from, underlying, amount)
101
+ });
94
102
  return {
95
103
  debt,
96
104
  collateral: margin,
97
105
  totalValue: margin + debt,
106
+ priceImpact,
98
107
  averageAssets: averageAssets.map(priced),
99
108
  minAssets: minAssets.map(priced),
100
109
  averageQuota,
@@ -6,6 +6,7 @@ const require_onchain_accounts_intents_utils_convert_amount = require("./utils/c
6
6
  const require_onchain_accounts_intents_utils_pick_token = require("./utils/pick-token.js");
7
7
  const require_onchain_accounts_intents_guards = require("./guards.js");
8
8
  const require_onchain_accounts_intents_utils_ledger = require("./utils/ledger.js");
9
+ const require_onchain_accounts_intents_utils_price_impact = require("./utils/price-impact.js");
9
10
  const require_onchain_accounts_intents_utils_quotas_for_update = require("./utils/quotas-for-update.js");
10
11
  const require_onchain_accounts_intents_utils_router_path = require("./utils/router-path.js");
11
12
  const require_onchain_accounts_intents_operations = require("./operations.js");
@@ -43,6 +44,8 @@ async function realize(steps, props) {
43
44
  operations.push(op);
44
45
  ledger.apply(op);
45
46
  };
47
+ /** One per routed leg, each already awaiting its quote; folded after the guards. */
48
+ const probes = [];
46
49
  /** Output of the last convert or claim, for `RAISED` amounts. */
47
50
  let raised = 0n;
48
51
  /** The request, before the walk's end state can be attached to it. */
@@ -140,6 +143,7 @@ async function realize(steps, props) {
140
143
  amount,
141
144
  keep: held - amount
142
145
  });
146
+ if (leg.probe) probes.push(leg.probe);
143
147
  push(require_onchain_accounts_intents_operations.buildSwapOperation({
144
148
  tokenIn: step.from,
145
149
  amountIn: amount,
@@ -156,6 +160,7 @@ async function realize(steps, props) {
156
160
  if (pending) throw new require_onchain_accounts_intents_refusal.IntentPreviewError("withdrawalInProgress", { inFlight: pending }, `closeAll: ${pending.token} is a pending withdrawal, claim it first`);
157
161
  if (balances.length > 0) {
158
162
  const leg = await paths.closeAll({ balances });
163
+ if (leg.probe) probes.push(leg.probe);
159
164
  if (leg.calls.length > 0 || leg.minAmount > 0n) push(require_onchain_accounts_intents_operations.buildCloseSwapOperation({
160
165
  from: balances,
161
166
  tokenOut: underlying,
@@ -284,12 +289,18 @@ async function realize(steps, props) {
284
289
  liquidationPrice: sdk.positions.liquidationPrice(snapshot)
285
290
  };
286
291
  require_onchain_accounts_intents_guards.assertCollateralised(paysOut ? sdk.positions.healthFactor(snapshot, { safePrices: true }) : metrics.healthFactor, paysOut);
292
+ const priceImpact = await require_onchain_accounts_intents_utils_price_impact.collectPriceImpact(probes, {
293
+ totalValue,
294
+ netValue: totalValue - debt,
295
+ toUnderlying: (from, amount) => price(from, underlying, amount)
296
+ });
287
297
  const state = {
288
298
  totalValue,
289
299
  accountDebt: debt,
290
300
  leverage: require_onchain_market_math.calcPositionLeverage(totalValue, debt),
291
301
  assets: assets.map((a) => market.priceOracle.toTokenAmount(a.token, a.balance)),
292
302
  quotas: quotasAfter,
303
+ priceImpact,
293
304
  ...metrics
294
305
  };
295
306
  return {
@@ -95,6 +95,7 @@ function buildMarketSdk(extras) {
95
95
  creditManager: CREDIT_MANAGER,
96
96
  creditFacade: CREDIT_FACADE,
97
97
  underlying: UND,
98
+ routeQuote: extras?.routeQuote,
98
99
  rwaAssets: extras?.rwaAssets,
99
100
  phantoms: extras?.phantoms,
100
101
  creditAccounts: extras?.creditAccounts,
@@ -209,18 +209,20 @@ function buildMockSdk(args) {
209
209
  if (asset && from === underlying && to === asset) return [MOCK_RWA_UNWRAP_CALL];
210
210
  return [MOCK_ROUTER_CALL];
211
211
  };
212
+ /** Linear unless the case says otherwise — see `routeQuote`. */
213
+ const quote = args.routeQuote ?? ((amount) => amount);
212
214
  const router = {
213
215
  findOneTokenPath: vitest.vi.fn(async ({ amount, tokenIn, tokenOut }) => ({
214
- amount,
215
- minAmount: amount,
216
+ amount: quote(amount),
217
+ minAmount: quote(amount),
216
218
  calls: routeCalls(tokenIn, tokenOut)
217
219
  })),
218
220
  findManyToOnePath: vitest.vi.fn(async ({ expectedBalances, leftoverBalances, target }) => {
219
221
  const spent = expectedBalances.reduce((acc, a) => acc + a.balance, 0n) - leftoverBalances.reduce((acc, a) => acc + a.balance, 0n);
220
222
  const tokenIn = expectedBalances[0]?.token ?? target;
221
223
  return {
222
- amount: spent,
223
- minAmount: spent,
224
+ amount: quote(spent),
225
+ minAmount: quote(spent),
224
226
  calls: routeCalls(tokenIn, target)
225
227
  };
226
228
  }),
@@ -284,8 +284,11 @@ const case_native_coin = {
284
284
  }
285
285
  ]
286
286
  };
287
- function buildDepositSdk(c) {
288
- return require_market.buildMarketSdk({ rwaAssets: c.rwaAssets });
287
+ function buildDepositSdk(c, routeQuote) {
288
+ return require_market.buildMarketSdk({
289
+ rwaAssets: c.rwaAssets,
290
+ routeQuote
291
+ });
289
292
  }
290
293
  function buildDepositProps(c, sdk) {
291
294
  return {
@@ -7,6 +7,7 @@ const require_onchain_accounts_intents_utils_assemble_operation_calls = require(
7
7
  const require_onchain_accounts_intents_utils_borrowed_amount_plus_interest_and_fees = require("./borrowed-amount-plus-interest-and-fees.js");
8
8
  const require_onchain_accounts_intents_utils_credit_account_slice = require("./credit-account-slice.js");
9
9
  const require_onchain_accounts_intents_utils_ledger = require("./ledger.js");
10
+ const require_onchain_accounts_intents_utils_price_impact = require("./price-impact.js");
10
11
  const require_onchain_accounts_intents_utils_quotas_for_update = require("./quotas-for-update.js");
11
12
  const require_onchain_accounts_intents_utils_router_path = require("./router-path.js");
12
13
  exports.OperationLedger = require_onchain_accounts_intents_utils_ledger.OperationLedger;
@@ -14,6 +15,7 @@ exports.adjustStateToSnapshot = require_onchain_accounts_intents_utils_adjust_st
14
15
  exports.assembleOperationCalls = require_onchain_accounts_intents_utils_assemble_operation_calls.assembleOperationCalls;
15
16
  exports.calcBorrowedAmountPlusInterestAndFees = require_onchain_accounts_intents_utils_borrowed_amount_plus_interest_and_fees.calcBorrowedAmountPlusInterestAndFees;
16
17
  exports.clearedQuotas = require_onchain_accounts_intents_utils_quotas_for_update.clearedQuotas;
18
+ exports.collectPriceImpact = require_onchain_accounts_intents_utils_price_impact.collectPriceImpact;
17
19
  exports.convertAmount = require_onchain_accounts_intents_utils_convert_amount.convertAmount;
18
20
  exports.createOraclePaths = require_onchain_accounts_intents_utils_router_path.createOraclePaths;
19
21
  exports.createRouterPaths = require_onchain_accounts_intents_utils_router_path.createRouterPaths;
@@ -22,9 +24,11 @@ exports.fetchCreditAccountSlice = require_onchain_accounts_intents_utils_credit_
22
24
  exports.getQuotasForUpdate = require_onchain_accounts_intents_utils_quotas_for_update.getQuotasForUpdate;
23
25
  exports.isPhantomToken = require_onchain_accounts_intents_utils_pick_token.isPhantomToken;
24
26
  exports.isRedemptionPhantomToken = require_onchain_accounts_intents_utils_pick_token.isRedemptionPhantomToken;
27
+ exports.lossRate = require_onchain_accounts_intents_utils_price_impact.lossRate;
25
28
  exports.pickFattestNonPhantomToken = require_onchain_accounts_intents_utils_pick_token.pickFattestNonPhantomToken;
26
29
  exports.quotasAfterUpdate = require_onchain_accounts_intents_utils_quotas_for_update.quotasAfterUpdate;
27
30
  exports.rankAccountTokens = require_onchain_accounts_intents_utils_pick_token.rankAccountTokens;
31
+ exports.startProbe = require_onchain_accounts_intents_utils_price_impact.startProbe;
28
32
  exports.toCreditAccountSlice = require_onchain_accounts_intents_utils_credit_account_slice.toCreditAccountSlice;
29
33
  exports.toRouterCaSlice = require_onchain_accounts_intents_utils_common.toRouterCaSlice;
30
34
  exports.toTargetDecimals = require_onchain_accounts_intents_utils_common.toTargetDecimals;
@@ -0,0 +1,99 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_onchain_constants_math = require("../../../constants/math.js");
3
+ //#region src/onchain/accounts/intents/utils/price-impact.ts
4
+ /** `b1 = b0 / V0`: a dollar of the basket, the reference implementation's anchor. */
5
+ const PROBE_UNIT_USD_WAD = require_onchain_constants_math.WAD;
6
+ /**
7
+ * `V0 = Σ b0ᵢ·pᵢ`, then `b1 = b0 / V0`, proportions kept.
8
+ *
9
+ * Refuses only what the reference refuses — a basket worth nothing, or one that
10
+ * rounds away entirely. Stricter guards here would report nothing where the old
11
+ * client reported a number.
12
+ */
13
+ function probeBasket(balances, oracle) {
14
+ if (balances.length === 0) return;
15
+ let basketWad = 0n;
16
+ for (const asset of balances) {
17
+ if (asset.balance <= 0n) continue;
18
+ const usd = oracle.safeConvertToUSD(asset.token, asset.balance);
19
+ if (usd !== null && usd > 0n) basketWad += usd * require_onchain_constants_math.WAD / require_onchain_constants_math.PRICE_DECIMALS;
20
+ }
21
+ if (basketWad <= 0n) return;
22
+ const probeWad = PROBE_UNIT_USD_WAD;
23
+ const scaled = balances.map((asset) => ({
24
+ token: asset.token,
25
+ balance: asset.balance * probeWad / basketWad
26
+ }));
27
+ if (!scaled.some((a) => a.balance > 0n)) return;
28
+ return {
29
+ balances: scaled,
30
+ basketWad,
31
+ probeWad
32
+ };
33
+ }
34
+ /** Fires the marginal-price quote for one leg; `undefined` if it cannot be measured. */
35
+ function startProbe(args) {
36
+ const basket = probeBasket(args.basket, args.oracle);
37
+ if (!basket) return;
38
+ return {
39
+ tokenOut: args.tokenOut,
40
+ basketWad: basket.basketWad,
41
+ probeWad: basket.probeWad,
42
+ probe: args.route(basket.balances).catch(() => void 0)
43
+ };
44
+ }
45
+ /** `convert` answers `0` for a negative amount, so convert the magnitude and re-sign. */
46
+ function toUnderlyingSigned(convert, token, amount) {
47
+ if (amount === 0n) return 0n;
48
+ const converted = convert(token, amount < 0n ? -amount : amount);
49
+ if (converted <= 0n) return;
50
+ return amount < 0n ? -converted : converted;
51
+ }
52
+ /**
53
+ * In `PERCENTAGE_FACTOR_1KK` (1_000_000 = 100%), negative for a loss. A base
54
+ * that is not positive falls back to the routed output.
55
+ */
56
+ function lossRate(args) {
57
+ const { lossUnd, expectedUnd, totalValue, netValue } = args;
58
+ const against = (base) => -(require_onchain_constants_math.PERCENTAGE_FACTOR_1KK * lossUnd / (base > 0n ? base : expectedUnd));
59
+ return {
60
+ pathPriceImpact: against(expectedUnd),
61
+ netValuePriceImpact: against(netValue),
62
+ totalValuePriceImpact: against(totalValue)
63
+ };
64
+ }
65
+ /**
66
+ * Folds every leg into one rate, in the underlying — the unit its bases are in.
67
+ *
68
+ * All or nothing: a partial sum would understate the loss and draw a better
69
+ * price than the route offers.
70
+ */
71
+ async function collectPriceImpact(probes, ctx) {
72
+ if (probes.length === 0) return;
73
+ const quotes = await Promise.all(probes.map((leg) => leg.probe));
74
+ let expectedUnd = 0n;
75
+ let lossUnd = 0n;
76
+ for (const [index, leg] of probes.entries()) {
77
+ const unit = quotes[index];
78
+ if (unit === void 0 || unit <= 0n) return;
79
+ const expected = unit * leg.basketWad / leg.probeWad;
80
+ if (expected <= 0n) return;
81
+ const expectedInUnd = ctx.toUnderlying(leg.tokenOut, expected);
82
+ if (expectedInUnd <= 0n) return;
83
+ const loss = toUnderlyingSigned(ctx.toUnderlying, leg.tokenOut, expected - leg.realAmount);
84
+ if (loss === void 0) return;
85
+ expectedUnd += expectedInUnd;
86
+ lossUnd += loss;
87
+ }
88
+ if (expectedUnd <= 0n) return;
89
+ return lossRate({
90
+ lossUnd,
91
+ expectedUnd,
92
+ totalValue: ctx.totalValue,
93
+ netValue: ctx.netValue
94
+ });
95
+ }
96
+ //#endregion
97
+ exports.collectPriceImpact = collectPriceImpact;
98
+ exports.lossRate = lossRate;
99
+ exports.startProbe = startProbe;
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_onchain_accounts_intents_utils_common = require("./common.js");
3
3
  const require_onchain_accounts_intents_utils_convert_amount = require("./convert-amount.js");
4
+ const require_onchain_accounts_intents_utils_price_impact = require("./price-impact.js");
4
5
  //#region src/onchain/accounts/intents/utils/router-path.ts
5
6
  /**
6
7
  * The engine's only door to the pathfinder.
@@ -18,68 +19,124 @@ function createRouterPaths(args) {
18
19
  collateralTokens: suite.creditManager.collateralTokens.map((t) => t.toLowerCase())
19
20
  };
20
21
  const router = sdk.routerFor({ creditFacade: suite.creditFacade.address });
22
+ const { priceOracle } = sdk.marketRegister.findByCreditManager(creditAccount.creditManager);
23
+ const quoteSwap = (input) => {
24
+ const spending = [{
25
+ token: input.tokenIn,
26
+ balance: input.amount + input.keep
27
+ }];
28
+ return input.keep > 0n ? router.findManyToOnePath({
29
+ creditAccount: require_onchain_accounts_intents_utils_common.toRouterCaSlice(creditAccount, spending),
30
+ creditManager: cmSlice,
31
+ expectedBalances: spending,
32
+ leftoverBalances: [{
33
+ token: input.tokenIn,
34
+ balance: input.keep
35
+ }],
36
+ target: input.tokenOut,
37
+ slippage
38
+ }) : router.findOneTokenPath({
39
+ creditAccount: require_onchain_accounts_intents_utils_common.toRouterCaSlice(creditAccount, spending),
40
+ creditManager: cmSlice,
41
+ tokenIn: input.tokenIn,
42
+ tokenOut: input.tokenOut,
43
+ amount: input.amount,
44
+ slippage
45
+ });
46
+ };
47
+ const quoteClose = (balances) => router.findBestClosePath({
48
+ creditAccount: require_onchain_accounts_intents_utils_common.toRouterCaSlice(creditAccount, balances),
49
+ creditManager: cmSlice,
50
+ balances: {
51
+ expectedBalances: balances,
52
+ leftoverBalances: [],
53
+ tokensToClaim: []
54
+ },
55
+ slippage
56
+ });
57
+ const quoteOpen = (expectedBalances, leftoverBalances, target) => router.findOpenStrategyPath({
58
+ creditManager: cmSlice,
59
+ expectedBalances,
60
+ leftoverBalances,
61
+ target,
62
+ slippage
63
+ });
21
64
  return {
22
65
  async swap({ tokenIn, tokenOut, amount, keep = 0n }) {
23
66
  if (amount <= 0n) return {
24
67
  amount: 0n,
25
68
  minAmount: 0n,
26
- calls: []
69
+ calls: [],
70
+ probe: void 0
27
71
  };
28
72
  if (keep < 0n) throw new Error(`swap: spending ${amount} of ${tokenIn} exceeds its balance`);
29
- if (keep > 0n) {
30
- const expectedBalances = [{
31
- token: tokenIn,
32
- balance: amount + keep
33
- }];
34
- return router.findManyToOnePath({
35
- creditAccount: require_onchain_accounts_intents_utils_common.toRouterCaSlice(creditAccount, expectedBalances),
36
- creditManager: cmSlice,
37
- expectedBalances,
38
- leftoverBalances: [{
39
- token: tokenIn,
40
- balance: keep
41
- }],
42
- target: tokenOut,
43
- slippage
44
- });
45
- }
46
- return router.findOneTokenPath({
47
- creditAccount: require_onchain_accounts_intents_utils_common.toRouterCaSlice(creditAccount, [{
73
+ const probe = require_onchain_accounts_intents_utils_price_impact.startProbe({
74
+ basket: [{
48
75
  token: tokenIn,
49
76
  balance: amount
50
- }]),
51
- creditManager: cmSlice,
77
+ }],
78
+ tokenOut,
79
+ oracle: priceOracle,
80
+ route: async ([only]) => {
81
+ if (!only) return 0n;
82
+ return (await quoteSwap({
83
+ tokenIn: only.token,
84
+ tokenOut,
85
+ amount: only.balance,
86
+ keep: 0n
87
+ })).amount;
88
+ }
89
+ });
90
+ const leg = await quoteSwap({
52
91
  tokenIn,
53
92
  tokenOut,
54
93
  amount,
55
- slippage
94
+ keep
56
95
  });
96
+ return {
97
+ ...leg,
98
+ probe: probe && {
99
+ ...probe,
100
+ realAmount: leg.amount
101
+ }
102
+ };
57
103
  },
58
104
  async closeAll({ balances }) {
59
- const { amount, minAmount, calls } = await router.findBestClosePath({
60
- creditAccount: require_onchain_accounts_intents_utils_common.toRouterCaSlice(creditAccount, balances),
61
- creditManager: cmSlice,
62
- balances: {
63
- expectedBalances: balances,
64
- leftoverBalances: [],
65
- tokensToClaim: []
66
- },
67
- slippage
105
+ const probe = require_onchain_accounts_intents_utils_price_impact.startProbe({
106
+ basket: balances,
107
+ tokenOut: creditAccount.underlying,
108
+ oracle: priceOracle,
109
+ route: async (quoted) => (await quoteClose(quoted)).amount
68
110
  });
69
- return {
111
+ const { amount, minAmount, calls } = await quoteClose(balances);
112
+ const leg = {
70
113
  amount,
71
114
  minAmount,
72
115
  calls: [...calls]
73
116
  };
117
+ return {
118
+ ...leg,
119
+ probe: probe && {
120
+ ...probe,
121
+ realAmount: leg.amount
122
+ }
123
+ };
74
124
  },
75
125
  async openStrategy({ expectedBalances, leftoverBalances, target }) {
76
- return router.findOpenStrategyPath({
77
- creditManager: cmSlice,
78
- expectedBalances,
79
- leftoverBalances,
80
- target,
81
- slippage
126
+ const probe = require_onchain_accounts_intents_utils_price_impact.startProbe({
127
+ basket: expectedBalances,
128
+ tokenOut: target,
129
+ oracle: priceOracle,
130
+ route: async (balances) => (await quoteOpen(balances, [], target)).amount
82
131
  });
132
+ const leg = await quoteOpen(expectedBalances, leftoverBalances, target);
133
+ return {
134
+ ...leg,
135
+ probe: probe && {
136
+ ...probe,
137
+ realAmount: leg.amount
138
+ }
139
+ };
83
140
  }
84
141
  };
85
142
  }
@@ -99,7 +156,8 @@ function createOraclePaths(args) {
99
156
  const estimate = (amount) => ({
100
157
  amount,
101
158
  minAmount: amount,
102
- calls: []
159
+ calls: [],
160
+ probe: void 0
103
161
  });
104
162
  return {
105
163
  async swap({ tokenIn, tokenOut, amount }) {
@@ -1,6 +1,7 @@
1
1
  import { IntentPreviewError } from "./refusal.js";
2
2
  import { convertAmount } from "./utils/convert-amount.js";
3
3
  import { assertCanBorrow, assertCollateralised, assertGrowthAllowed, assertMarketOperable, assertQuotaHeadroom } from "./guards.js";
4
+ import { collectPriceImpact } from "./utils/price-impact.js";
4
5
  import { getQuotasForUpdate } from "./utils/quotas-for-update.js";
5
6
  import { createRouterPaths } from "./utils/router-path.js";
6
7
  import "./utils/index.js";
@@ -42,12 +43,14 @@ async function previewOpenStrategy(props) {
42
43
  };
43
44
  assertDebtInBand(debt, suite.creditFacade, underlying);
44
45
  assertCanBorrow(suite, debt);
45
- const leg = await createRouterPaths({
46
+ const paths = createRouterPaths({
46
47
  sdk,
47
48
  creditAccount: account,
48
49
  slippage
49
- }).openStrategy({
50
- expectedBalances: mergeExpectedBalances(collateral, underlying, debt),
50
+ });
51
+ const expectedBalances = mergeExpectedBalances(collateral, underlying, debt);
52
+ const leg = await paths.openStrategy({
53
+ expectedBalances,
51
54
  leftoverBalances,
52
55
  target: targetToken
53
56
  });
@@ -90,10 +93,16 @@ async function previewOpenStrategy(props) {
90
93
  liquidationPrice: sdk.positions.liquidationPrice(snapshot)
91
94
  };
92
95
  assertCollateralised(metrics.healthFactor, false);
96
+ const priceImpact = await collectPriceImpact(leg.probe ? [leg.probe] : [], {
97
+ totalValue: margin + debt,
98
+ netValue: margin,
99
+ toUnderlying: (from, amount) => convert(from, underlying, amount)
100
+ });
93
101
  return {
94
102
  debt,
95
103
  collateral: margin,
96
104
  totalValue: margin + debt,
105
+ priceImpact,
97
106
  averageAssets: averageAssets.map(priced),
98
107
  minAssets: minAssets.map(priced),
99
108
  averageQuota,
@@ -5,6 +5,7 @@ import { convertAmount } from "./utils/convert-amount.js";
5
5
  import { isRedemptionPhantomToken } from "./utils/pick-token.js";
6
6
  import { assertCanBorrow, assertCollateralised, assertGrowthAllowed, assertQuotaHeadroom } from "./guards.js";
7
7
  import { OperationLedger } from "./utils/ledger.js";
8
+ import { collectPriceImpact } from "./utils/price-impact.js";
8
9
  import { clearedQuotas, getQuotasForUpdate, quotasAfterUpdate } from "./utils/quotas-for-update.js";
9
10
  import { createRouterPaths } from "./utils/router-path.js";
10
11
  import { buildAddCollateralOperation, buildClaimDelayedWithdrawalOperation, buildCloseSwapOperation, buildDecreaseDebtOperation, buildIncreaseDebtOperation, buildQuotaUpdateOperation, buildStartDelayedWithdrawalOperation, buildSwapOperation, buildUnwrapRwaCollateralOperation, buildWithdrawCollateralOperation, buildWrapRwaCollateralOperation, instantOutput } from "./operations.js";
@@ -42,6 +43,8 @@ async function realize(steps, props) {
42
43
  operations.push(op);
43
44
  ledger.apply(op);
44
45
  };
46
+ /** One per routed leg, each already awaiting its quote; folded after the guards. */
47
+ const probes = [];
45
48
  /** Output of the last convert or claim, for `RAISED` amounts. */
46
49
  let raised = 0n;
47
50
  /** The request, before the walk's end state can be attached to it. */
@@ -139,6 +142,7 @@ async function realize(steps, props) {
139
142
  amount,
140
143
  keep: held - amount
141
144
  });
145
+ if (leg.probe) probes.push(leg.probe);
142
146
  push(buildSwapOperation({
143
147
  tokenIn: step.from,
144
148
  amountIn: amount,
@@ -155,6 +159,7 @@ async function realize(steps, props) {
155
159
  if (pending) throw new IntentPreviewError("withdrawalInProgress", { inFlight: pending }, `closeAll: ${pending.token} is a pending withdrawal, claim it first`);
156
160
  if (balances.length > 0) {
157
161
  const leg = await paths.closeAll({ balances });
162
+ if (leg.probe) probes.push(leg.probe);
158
163
  if (leg.calls.length > 0 || leg.minAmount > 0n) push(buildCloseSwapOperation({
159
164
  from: balances,
160
165
  tokenOut: underlying,
@@ -283,12 +288,18 @@ async function realize(steps, props) {
283
288
  liquidationPrice: sdk.positions.liquidationPrice(snapshot)
284
289
  };
285
290
  assertCollateralised(paysOut ? sdk.positions.healthFactor(snapshot, { safePrices: true }) : metrics.healthFactor, paysOut);
291
+ const priceImpact = await collectPriceImpact(probes, {
292
+ totalValue,
293
+ netValue: totalValue - debt,
294
+ toUnderlying: (from, amount) => price(from, underlying, amount)
295
+ });
286
296
  const state = {
287
297
  totalValue,
288
298
  accountDebt: debt,
289
299
  leverage: calcPositionLeverage(totalValue, debt),
290
300
  assets: assets.map((a) => market.priceOracle.toTokenAmount(a.token, a.balance)),
291
301
  quotas: quotasAfter,
302
+ priceImpact,
292
303
  ...metrics
293
304
  };
294
305
  return {
@@ -95,6 +95,7 @@ function buildMarketSdk(extras) {
95
95
  creditManager: CREDIT_MANAGER,
96
96
  creditFacade: CREDIT_FACADE,
97
97
  underlying: UND,
98
+ routeQuote: extras?.routeQuote,
98
99
  rwaAssets: extras?.rwaAssets,
99
100
  phantoms: extras?.phantoms,
100
101
  creditAccounts: extras?.creditAccounts,
@@ -209,18 +209,20 @@ function buildMockSdk(args) {
209
209
  if (asset && from === underlying && to === asset) return [MOCK_RWA_UNWRAP_CALL];
210
210
  return [MOCK_ROUTER_CALL];
211
211
  };
212
+ /** Linear unless the case says otherwise — see `routeQuote`. */
213
+ const quote = args.routeQuote ?? ((amount) => amount);
212
214
  const router = {
213
215
  findOneTokenPath: vi.fn(async ({ amount, tokenIn, tokenOut }) => ({
214
- amount,
215
- minAmount: amount,
216
+ amount: quote(amount),
217
+ minAmount: quote(amount),
216
218
  calls: routeCalls(tokenIn, tokenOut)
217
219
  })),
218
220
  findManyToOnePath: vi.fn(async ({ expectedBalances, leftoverBalances, target }) => {
219
221
  const spent = expectedBalances.reduce((acc, a) => acc + a.balance, 0n) - leftoverBalances.reduce((acc, a) => acc + a.balance, 0n);
220
222
  const tokenIn = expectedBalances[0]?.token ?? target;
221
223
  return {
222
- amount: spent,
223
- minAmount: spent,
224
+ amount: quote(spent),
225
+ minAmount: quote(spent),
224
226
  calls: routeCalls(tokenIn, target)
225
227
  };
226
228
  }),
@@ -283,8 +283,11 @@ const case_native_coin = {
283
283
  }
284
284
  ]
285
285
  };
286
- function buildDepositSdk(c) {
287
- return buildMarketSdk({ rwaAssets: c.rwaAssets });
286
+ function buildDepositSdk(c, routeQuote) {
287
+ return buildMarketSdk({
288
+ rwaAssets: c.rwaAssets,
289
+ routeQuote
290
+ });
288
291
  }
289
292
  function buildDepositProps(c, sdk) {
290
293
  return {
@@ -6,6 +6,7 @@ import { assembleOperationCalls } from "./assemble-operation-calls.js";
6
6
  import { calcBorrowedAmountPlusInterestAndFees } from "./borrowed-amount-plus-interest-and-fees.js";
7
7
  import { fetchCreditAccountSlice, toCreditAccountSlice } from "./credit-account-slice.js";
8
8
  import { OperationLedger } from "./ledger.js";
9
+ import { collectPriceImpact, lossRate, startProbe } from "./price-impact.js";
9
10
  import { clearedQuotas, getQuotasForUpdate, quotasAfterUpdate } from "./quotas-for-update.js";
10
11
  import { createOraclePaths, createRouterPaths } from "./router-path.js";
11
- export { OperationLedger, adjustStateToSnapshot, assembleOperationCalls, calcBorrowedAmountPlusInterestAndFees, clearedQuotas, convertAmount, createOraclePaths, createRouterPaths, eq, fetchCreditAccountSlice, getQuotasForUpdate, isPhantomToken, isRedemptionPhantomToken, pickFattestNonPhantomToken, quotasAfterUpdate, rankAccountTokens, toCreditAccountSlice, toRouterCaSlice, toTargetDecimals };
12
+ export { OperationLedger, adjustStateToSnapshot, assembleOperationCalls, calcBorrowedAmountPlusInterestAndFees, clearedQuotas, collectPriceImpact, convertAmount, createOraclePaths, createRouterPaths, eq, fetchCreditAccountSlice, getQuotasForUpdate, isPhantomToken, isRedemptionPhantomToken, lossRate, pickFattestNonPhantomToken, quotasAfterUpdate, rankAccountTokens, startProbe, toCreditAccountSlice, toRouterCaSlice, toTargetDecimals };