@gearbox-protocol/sdk 16.4.0-next.4 → 16.4.0-next.6

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 (58) hide show
  1. package/dist/cjs/dev/midasUtils.js +46 -11
  2. package/dist/cjs/onchain/accounts/intents/borrow.js +139 -0
  3. package/dist/cjs/onchain/accounts/intents/{collateral-money.js → collateral-valuation.js} +7 -7
  4. package/dist/cjs/onchain/accounts/intents/index.js +79 -1
  5. package/dist/cjs/onchain/accounts/intents/leverage-band.js +2 -12
  6. package/dist/cjs/onchain/accounts/intents/maxBorrow.js +85 -0
  7. package/dist/cjs/onchain/accounts/intents/maxWithdrawCollateral.js +14 -14
  8. package/dist/cjs/onchain/accounts/intents/open-strategy.js +6 -45
  9. package/dist/cjs/onchain/accounts/intents/testing/sdk-mock.js +1 -0
  10. package/dist/cjs/onchain/accounts/intents/utils/common.js +19 -0
  11. package/dist/cjs/onchain/accounts/intents/utils/credit-account-slice.js +21 -0
  12. package/dist/cjs/onchain/accounts/intents/utils/index.js +2 -0
  13. package/dist/cjs/onchain/accounts/intents/withdraw-limits.js +8 -8
  14. package/dist/cjs/onchain/positions/calcHealthFactor.js +3 -3
  15. package/dist/cjs/onchain/preview/preview/previewOpenStrategyPosition.js +2 -1
  16. package/dist/cjs/sdk/execute/ExecuteApi.js +65 -5
  17. package/dist/cjs/sdk/prepare/PrepareApi.js +86 -12
  18. package/dist/esm/dev/midasUtils.js +47 -12
  19. package/dist/esm/onchain/accounts/intents/borrow.js +137 -0
  20. package/dist/esm/onchain/accounts/intents/{collateral-money.js → collateral-valuation.js} +7 -7
  21. package/dist/esm/onchain/accounts/intents/index.js +79 -1
  22. package/dist/esm/onchain/accounts/intents/leverage-band.js +2 -12
  23. package/dist/esm/onchain/accounts/intents/maxBorrow.js +83 -0
  24. package/dist/esm/onchain/accounts/intents/maxWithdrawCollateral.js +14 -14
  25. package/dist/esm/onchain/accounts/intents/open-strategy.js +6 -45
  26. package/dist/esm/onchain/accounts/intents/testing/sdk-mock.js +1 -0
  27. package/dist/esm/onchain/accounts/intents/utils/common.js +19 -1
  28. package/dist/esm/onchain/accounts/intents/utils/credit-account-slice.js +21 -1
  29. package/dist/esm/onchain/accounts/intents/utils/index.js +3 -3
  30. package/dist/esm/onchain/accounts/intents/withdraw-limits.js +8 -8
  31. package/dist/esm/onchain/positions/calcHealthFactor.js +3 -3
  32. package/dist/esm/onchain/preview/preview/previewOpenStrategyPosition.js +2 -1
  33. package/dist/esm/sdk/execute/ExecuteApi.js +65 -5
  34. package/dist/esm/sdk/prepare/PrepareApi.js +86 -12
  35. package/dist/types/dev/midasUtils.d.ts +4 -3
  36. package/dist/types/model/previews.d.ts +11 -2
  37. package/dist/types/onchain/accounts/index.d.ts +4 -3
  38. package/dist/types/onchain/accounts/intents/borrow.d.ts +141 -0
  39. package/dist/types/onchain/accounts/intents/{collateral-money.d.ts → collateral-valuation.d.ts} +8 -8
  40. package/dist/types/onchain/accounts/intents/index.d.ts +78 -2
  41. package/dist/types/onchain/accounts/intents/maxBorrow.d.ts +47 -0
  42. package/dist/types/onchain/accounts/intents/maxWithdrawCollateral.d.ts +3 -3
  43. package/dist/types/onchain/accounts/intents/open-strategy.d.ts +3 -17
  44. package/dist/types/onchain/accounts/intents/testing/sdk-mock.d.ts +2 -0
  45. package/dist/types/onchain/accounts/intents/tests/open-strategy.fixtures.d.ts +2 -2
  46. package/dist/types/onchain/accounts/intents/types.d.ts +6 -1
  47. package/dist/types/onchain/accounts/intents/utils/common.d.ts +15 -1
  48. package/dist/types/onchain/accounts/intents/utils/credit-account-slice.d.ts +14 -1
  49. package/dist/types/onchain/accounts/intents/utils/index.d.ts +3 -3
  50. package/dist/types/onchain/accounts/intents/withdraw-limits.d.ts +1 -1
  51. package/dist/types/onchain/index.d.ts +4 -3
  52. package/dist/types/sdk/execute/index.d.ts +2 -2
  53. package/dist/types/sdk/execute/types.d.ts +48 -6
  54. package/dist/types/sdk/index.d.ts +5 -4
  55. package/dist/types/sdk/prepare/PrepareApi.d.ts +15 -3
  56. package/dist/types/sdk/prepare/index.d.ts +4 -3
  57. package/dist/types/sdk/prepare/types.d.ts +169 -49
  58. package/package.json +1 -1
@@ -2,9 +2,9 @@ import { BigIntMath } from "../../utils/bigint-math.js";
2
2
  import { PERCENTAGE_FACTOR } from "../../constants/math.js";
3
3
  import "../../utils/index.js";
4
4
  import { eq } from "./utils/common.js";
5
- //#region src/onchain/accounts/intents/collateral-money.ts
6
- /** {@inheritDoc CollateralMoney} */
7
- function collateralMoney(creditAccount, sdk) {
5
+ //#region src/onchain/accounts/intents/collateral-valuation.ts
6
+ /** {@inheritDoc CollateralValuation} */
7
+ function collateralValuation(creditAccount, sdk) {
8
8
  const { market, creditManager } = sdk.marketRegister.findCreditManager(creditAccount.creditManager);
9
9
  const { priceOracle } = market;
10
10
  const { pqk } = market.pool;
@@ -20,21 +20,21 @@ function collateralMoney(creditAccount, sdk) {
20
20
  const lt = (token) => BigInt(creditManager.liquidationThresholds.get(token) ?? 0);
21
21
  const checkedUsd = (holding) => eq(holding.token, underlying) ? mainUsd(holding.token, holding.balance) ?? 0n : priceOracle.safeConvertMinUSD(holding.token, holding.balance).value;
22
22
  /** A quota is underlying-denominated, and a closed market backs nothing. */
23
- const quotaMoney = (holding) => pqk.hasActiveQuota(holding.token) ? (mainUsd(underlying, holding.quota) ?? 0n) * PERCENTAGE_FACTOR : 0n;
23
+ const quotaValue = (holding) => pqk.hasActiveQuota(holding.token) ? (mainUsd(underlying, holding.quota) ?? 0n) * PERCENTAGE_FACTOR : 0n;
24
24
  const weigh = (holding) => {
25
25
  const weighted = checkedUsd(holding) * lt(holding.token);
26
26
  if (holding.quota === 0n) return weighted;
27
- return BigIntMath.min(quotaMoney(holding), weighted);
27
+ return BigIntMath.min(quotaValue(holding), weighted);
28
28
  };
29
29
  return {
30
30
  underlying,
31
31
  counts: (holding) => holding.balance > 10n && (!masked || (holding.mask & creditAccount.enabledTokensMask) !== 0n),
32
32
  weigh,
33
- quotaMoney,
33
+ quotaValue,
34
34
  mainUsd,
35
35
  checkedUsd,
36
36
  lt
37
37
  };
38
38
  }
39
39
  //#endregion
40
- export { collateralMoney };
40
+ export { collateralValuation };
@@ -7,9 +7,11 @@ import { IntentPreviewError } from "../../validation/raise.js";
7
7
  import "../../validation/index.js";
8
8
  import { isPhantomToken } from "./utils/pick-token.js";
9
9
  import { assertMarketOperable } from "./guards.js";
10
+ import { fetchCreditAccountSlice, toCreditAccountSlice } from "./utils/credit-account-slice.js";
11
+ import { buildBorrowState } from "./borrow.js";
10
12
  import { calcLeverageBand } from "./leverage-band.js";
13
+ import { maxBorrow } from "./maxBorrow.js";
11
14
  import { maxWithdrawCollateral } from "./maxWithdrawCollateral.js";
12
- import { fetchCreditAccountSlice, toCreditAccountSlice } from "./utils/credit-account-slice.js";
13
15
  import { buildOpenStrategyState } from "./open-strategy.js";
14
16
  import { planAddCollateral, planAdjustLeverage, planAdjustLeverageDelayed, planDeposit, planRepay, planWithdraw, planWithdrawAsset, planWithdrawDelayed } from "./plan.js";
15
17
  import { accountView } from "./view.js";
@@ -132,6 +134,31 @@ var CreditAccountOperationsService = class extends SDKConstruct {
132
134
  });
133
135
  }
134
136
  /**
137
+ * Largest loan a given collateral supports at `targetHF`, in the payout
138
+ * token's units — the ceiling a borrow form should offer, and the amount
139
+ * {@link borrowIntent} will accept at the top of its range.
140
+ *
141
+ * Reads no account, like {@link leverageBand}: the borrow opens one. The
142
+ * collateral is valued the way the transaction will be judged, at safe
143
+ * prices and under the quota the borrow buys, and the answer is then held to
144
+ * what the market will lend.
145
+ *
146
+ * The default is {@link MIN_HF_LIMITED}, the threshold a form holds an
147
+ * account to.
148
+ *
149
+ * @param props - The manager, the SDK holding its market, the collateral put
150
+ * up, the token to be paid in, and optionally the health factor to land at
151
+ * @returns Amount in the payout token's units; `0n` where no loan of this
152
+ * shape can be funded
153
+ */
154
+ maxBorrow(props) {
155
+ const { targetHF = MIN_HF_LIMITED, ...rest } = props;
156
+ return maxBorrow({
157
+ ...rest,
158
+ targetHF: targetHF + 2n
159
+ });
160
+ }
161
+ /**
135
162
  * Previews the same operation when its source only redeems through its
136
163
  * issuer: a Securitize dsToken, a Mellow share.
137
164
  *
@@ -277,6 +304,26 @@ var CreditAccountOperationsService = class extends SDKConstruct {
277
304
  };
278
305
  }
279
306
  /**
307
+ * Previews opening an account that holds nothing.
308
+ *
309
+ * Nothing is put up, drawn or routed, so there is no state to build and no
310
+ * guard to run beyond the market's own: a paused or expired facade takes no
311
+ * multicall, and an opening is a multicall like any other. Answers the same
312
+ * envelope its two neighbours do so a caller branches on `ok` throughout.
313
+ *
314
+ * @param props - The SDK holding the market, and the manager to open in
315
+ * @returns `{ ok: true }`, or `{ ok: false, error }` when the market takes
316
+ * no transaction right now
317
+ */
318
+ async openEmptyAccountIntent(props) {
319
+ try {
320
+ assertMarketOperable(props.sdk.marketRegister.findCreditManager(props.creditManager));
321
+ return { ok: true };
322
+ } catch (e) {
323
+ return asSDKError(e);
324
+ }
325
+ }
326
+ /**
280
327
  * Previews opening a brand-new leveraged position.
281
328
  *
282
329
  * Sits apart from {@link startIntent} because there is no account yet: nothing
@@ -298,6 +345,37 @@ var CreditAccountOperationsService = class extends SDKConstruct {
298
345
  return asSDKError(e);
299
346
  }
300
347
  }
348
+ /**
349
+ * Previews taking a loan against collateral, on an account this same
350
+ * transaction opens.
351
+ *
352
+ * Sits beside {@link openStrategyIntent} rather than under
353
+ * {@link startIntent} for the same reason: there is no account yet, and the
354
+ * output feeds `sdk.accounts.openCA`. What sets it apart from an opening is
355
+ * where the loan goes — out to the wallet rather than into a position — so
356
+ * the debt is named outright instead of following from a leverage, and the
357
+ * collateral is the only thing the account is left holding.
358
+ *
359
+ * `creditAccount` draws the loan on one the wallet already holds instead of
360
+ * opening another, as an opening takes one.
361
+ *
362
+ * @param props - Credit manager, the collateral the wallet puts up and the
363
+ * payout it asks for
364
+ * @returns Debt, the payout's two branches and the projection the account
365
+ * lands in, or `{ ok: false, error }` when the loan is not viable — a debt
366
+ * outside the facade's limits, collateral that cannot carry it, a payout the
367
+ * router has no path to
368
+ */
369
+ async borrowIntent(props) {
370
+ try {
371
+ return {
372
+ ok: true,
373
+ state: await buildBorrowState(props)
374
+ };
375
+ } catch (e) {
376
+ return asSDKError(e);
377
+ }
378
+ }
301
379
  /** Plan → realise → wrap. Unviable requests become `{ ok: false }`. */
302
380
  async #preview(props, plan) {
303
381
  try {
@@ -1,5 +1,6 @@
1
1
  import { BigIntMath } from "../../utils/bigint-math.js";
2
2
  import { LEVERAGE_DECIMALS } from "../../constants/math.js";
3
+ import { resolveCreditManager } from "./utils/common.js";
3
4
  //#region src/onchain/accounts/intents/leverage-band.ts
4
5
  /**
5
6
  * The leverages this market will actually fund for a position of this size.
@@ -29,7 +30,7 @@ import { LEVERAGE_DECIMALS } from "../../constants/math.js";
29
30
  * ```
30
31
  **/
31
32
  function calcLeverageBand({ sdk, creditManager, collateral, targetHF }) {
32
- const found = resolve(sdk, creditManager);
33
+ const found = resolveCreditManager(sdk, creditManager);
33
34
  if (!found) return;
34
35
  const { suite, market } = found;
35
36
  const target = suite.strategyTargetCollateral;
@@ -54,16 +55,5 @@ function calcLeverageBand({ sdk, creditManager, collateral, targetHF }) {
54
55
  max
55
56
  };
56
57
  }
57
- /** The manager's suite and market, or nothing while they cannot be resolved. */
58
- function resolve(sdk, creditManager) {
59
- try {
60
- return {
61
- suite: sdk.marketRegister.findCreditManager(creditManager),
62
- market: sdk.marketRegister.findByCreditManager(creditManager)
63
- };
64
- } catch {
65
- return;
66
- }
67
- }
68
58
  //#endregion
69
59
  export { calcLeverageBand };
@@ -0,0 +1,83 @@
1
+ import { BigIntMath } from "../../utils/bigint-math.js";
2
+ import "../../constants/math.js";
3
+ import "../../utils/index.js";
4
+ import { eq, resolveCreditManager, toTargetDecimals } from "./utils/common.js";
5
+ import { unopenedAccountSlice } from "./utils/credit-account-slice.js";
6
+ import "./utils/index.js";
7
+ import { borrowCollateralQuota } from "./borrow.js";
8
+ import { collateralValuation } from "./collateral-valuation.js";
9
+ //#region src/onchain/accounts/intents/maxBorrow.ts
10
+ /**
11
+ * Largest loan this collateral supports at `targetHF` — the ceiling a borrow
12
+ * form should offer, in the payout token's units.
13
+ *
14
+ * The inverse of a borrow rather than a search for one: the loan leaves the
15
+ * account entirely, so the collateral is the whole of what backs the debt, and
16
+ * the health factor is one division away from the amount. Solving it the other
17
+ * way round costs a division too, and no iteration.
18
+ *
19
+ * Collateral is valued the way the transaction will be judged — at safe
20
+ * prices, under its liquidation threshold, capped by the quota the borrow
21
+ * buys for it, all of which is {@link collateralValuation}'s business. The ceiling
22
+ * is then held to what the market will actually lend: the pool's free
23
+ * liquidity, the manager's own allowance and the facade's `maxDebt`, whichever
24
+ * binds first.
25
+ *
26
+ * Nothing is fetched or simulated — the account does not exist yet and every
27
+ * input is loaded market state, so a form can call this on each keystroke.
28
+ *
29
+ * @param props - {@link MaxBorrowProps}
30
+ * @returns Amount in the payout token's units; `0n` when this market will fund
31
+ * no loan of this shape — including a debt that would land under `minDebt`, a
32
+ * collateral that backs nothing at safe prices, and a manager the SDK does not
33
+ * hold yet
34
+ **/
35
+ function maxBorrow(props) {
36
+ const { sdk, creditManager, collateralAmount, targetHF, quotaReserve } = props;
37
+ const found = resolveCreditManager(sdk, creditManager);
38
+ if (!found) return 0n;
39
+ const { suite, market } = found;
40
+ const { priceOracle } = market;
41
+ const underlying = market.pool.underlying.toLowerCase();
42
+ const collateralToken = props.collateralToken.toLowerCase();
43
+ const borrowToken = props.borrowToken.toLowerCase();
44
+ const rwaAsset = sdk.tokensMeta.rwaUnderlyings.get(underlying)?.asset?.toLowerCase();
45
+ if (eq(collateralToken, borrowToken)) return 0n;
46
+ if (rwaAsset && eq(borrowToken, underlying)) return 0n;
47
+ if (collateralAmount <= 10n || targetHF <= 0n) return 0n;
48
+ const quotas = borrowCollateralQuota({
49
+ sdk,
50
+ creditManager,
51
+ assets: [{
52
+ token: collateralToken,
53
+ balance: collateralAmount
54
+ }],
55
+ quotaReserve
56
+ });
57
+ const holding = {
58
+ token: collateralToken,
59
+ balance: collateralAmount,
60
+ quota: quotas.find((q) => eq(q.token, collateralToken))?.balance ?? 0n,
61
+ mask: 0n,
62
+ success: true
63
+ };
64
+ const valuation = collateralValuation({
65
+ ...unopenedAccountSlice({
66
+ creditManager,
67
+ creditFacade: suite.creditFacade.address,
68
+ underlying
69
+ }),
70
+ tokens: [holding]
71
+ }, sdk);
72
+ const weighted = valuation.checkedUsd(holding) * valuation.lt(collateralToken);
73
+ const backed = quotas.some((q) => eq(q.token, collateralToken)) ? BigIntMath.min(valuation.quotaValue(holding), weighted) : weighted;
74
+ if (backed <= 0n) return 0n;
75
+ const ceiling = BigIntMath.min(priceOracle.safeConvertFromUSD(underlying, backed / targetHF).value, suite.maxBorrowAmount().amount.value);
76
+ const unwrapsPayout = !!rwaAsset && eq(borrowToken, rwaAsset);
77
+ const intoPayout = (value) => unwrapsPayout ? toTargetDecimals(value, underlying, borrowToken, sdk) : priceOracle.safeConvert(underlying, borrowToken, value).value;
78
+ const intoUnderlying = (value) => unwrapsPayout ? toTargetDecimals(value, borrowToken, underlying, sdk) : priceOracle.safeConvert(borrowToken, underlying, value).value;
79
+ const amount = eq(borrowToken, underlying) ? ceiling : intoPayout(ceiling);
80
+ return (eq(borrowToken, underlying) ? amount : intoUnderlying(amount)) < suite.creditFacade.minDebt ? 0n : amount;
81
+ }
82
+ //#endregion
83
+ export { maxBorrow };
@@ -2,16 +2,16 @@ import { BigIntMath } from "../../utils/bigint-math.js";
2
2
  import "../../constants/math.js";
3
3
  import "../../utils/index.js";
4
4
  import { eq } from "./utils/common.js";
5
- import { collateralMoney } from "./collateral-money.js";
5
+ import { collateralValuation } from "./collateral-valuation.js";
6
6
  //#region src/onchain/accounts/intents/maxWithdrawCollateral.ts
7
7
  /**
8
8
  * Largest amount of one collateral the account can withdraw while its health
9
9
  * factor stays at or above `targetHF`.
10
10
  *
11
11
  * This is the collateral check solved for one balance, and it counts what that
12
- * check counts — see {@link collateralMoney} for the valuation, safe prices
13
- * included. The debt is valued at the main feed, as the check does. Zero debt
14
- * frees the whole balance.
12
+ * check counts — see {@link collateralValuation} for it, safe prices included.
13
+ * The debt is valued at the main feed, as the check does. Zero debt frees the
14
+ * whole balance.
15
15
  *
16
16
  * Rounding always favours the account, so the answer clears the check rather
17
17
  * than landing a wei short of it.
@@ -25,20 +25,20 @@ function maxWithdrawCollateral(props) {
25
25
  const target = creditAccount.tokens.find((t) => eq(t.token, token));
26
26
  if (!target || target.balance <= 10n) return 0n;
27
27
  if (creditAccount.totalDebt === 0n) return target.balance;
28
- const money = collateralMoney(creditAccount, sdk);
29
- let otherMoney = 0n;
28
+ const valuation = collateralValuation(creditAccount, sdk);
29
+ let otherValue = 0n;
30
30
  for (const t of creditAccount.tokens) {
31
- if (eq(t.token, token) || !money.counts(t)) continue;
32
- otherMoney += money.weigh(t);
31
+ if (eq(t.token, token) || !valuation.counts(t)) continue;
32
+ otherValue += valuation.weigh(t);
33
33
  }
34
- const borrowed = money.mainUsd(money.underlying, creditAccount.totalDebt);
34
+ const borrowed = valuation.mainUsd(valuation.underlying, creditAccount.totalDebt);
35
35
  if (borrowed === void 0 || borrowed <= 0n) return 0n;
36
36
  const required = borrowed * targetHF;
37
- if (required <= otherMoney) return target.balance;
38
- const shortfall = required - otherMoney;
39
- if (target.quota > 0n && money.quotaMoney(target) < shortfall) return 0n;
40
- const targetLt = money.lt(target.token);
41
- const targetUsd = money.checkedUsd(target);
37
+ if (required <= otherValue) return target.balance;
38
+ const shortfall = required - otherValue;
39
+ if (target.quota > 0n && valuation.quotaValue(target) < shortfall) return 0n;
40
+ const targetLt = valuation.lt(target.token);
41
+ const targetUsd = valuation.checkedUsd(target);
42
42
  if (targetLt === 0n || targetUsd === 0n) return 0n;
43
43
  const keptUsd = BigIntMath.ceilDiv(shortfall, targetLt);
44
44
  const kept = BigIntMath.ceilDiv(target.balance * keptUsd, targetUsd);
@@ -3,17 +3,13 @@ import "../../../model/index.js";
3
3
  import { IntentPreviewError } from "../../validation/raise.js";
4
4
  import { assertCanBorrow, assertCollateralised, assertGrowthAllowed, assertMarketOperable, assertQuotaAvailable } from "./guards.js";
5
5
  import { assertDebtLimits, assertLeverageAtLeastOne, debtForLeverage } from "./math.js";
6
+ import { unopenedAccountSlice } from "./utils/credit-account-slice.js";
6
7
  import { collectPriceImpact } from "./utils/price-impact.js";
7
8
  import { getQuotasForUpdate } from "./utils/quotas-for-update.js";
8
9
  import { createRouterPaths } from "./utils/router-path.js";
9
10
  import "./utils/index.js";
10
11
  //#region src/onchain/accounts/intents/open-strategy.ts
11
12
  /**
12
- * Stand-in account address, used when the opening creates its own account:
13
- * nothing exists on chain until the tx lands.
14
- */
15
- const NO_ACCOUNT = "0x0000000000000000000000000000000000000000";
16
- /**
17
13
  * Builds the state opening a leveraged position out of wallet collateral would
18
14
  * reach.
19
15
  *
@@ -26,7 +22,6 @@ const NO_ACCOUNT = "0x0000000000000000000000000000000000000000";
26
22
  * `sdk.accounts.openCA`.
27
23
  */
28
24
  async function buildOpenStrategyState(props) {
29
- if (props.empty) return emptyOpenState(props);
30
25
  const { sdk, creditManager, collateral, targetToken, leverage, slippage = 0, quotaReserve, leftoverBalances = [], creditAccount: existing } = props;
31
26
  assertLeverageAtLeastOne(leverage);
32
27
  const suite = sdk.marketRegister.findCreditManager(creditManager);
@@ -37,16 +32,11 @@ async function buildOpenStrategyState(props) {
37
32
  const margin = collateral.reduce((acc, a) => acc + convert(a.token, underlying, a.balance), 0n);
38
33
  if (margin <= 0n) throw new IntentPreviewError(insufficientBalance(), "openStrategy: collateral is worth nothing in underlying");
39
34
  const debt = debtForLeverage(margin, leverage);
40
- const account = existing ?? {
41
- creditAccount: NO_ACCOUNT,
42
- creditManager: creditManager.toLowerCase(),
43
- creditFacade: suite.creditFacade.address.toLowerCase(),
44
- underlying,
45
- enabledTokensMask: 0n,
46
- totalDebtUSD: 0n,
47
- totalDebt: 0n,
48
- tokens: []
49
- };
35
+ const account = existing ?? unopenedAccountSlice({
36
+ creditManager,
37
+ creditFacade: suite.creditFacade.address,
38
+ underlying
39
+ });
50
40
  assertDebtLimits(sdk, debt, suite.creditFacade, underlying);
51
41
  assertCanBorrow(sdk, suite, debt);
52
42
  const paths = createRouterPaths({
@@ -110,35 +100,6 @@ async function buildOpenStrategyState(props) {
110
100
  creditAccount: existing?.creditAccount
111
101
  };
112
102
  }
113
- /**
114
- * The opening that holds an account and nothing else.
115
- *
116
- * Taken before the walk rather than threaded through it: the router has no
117
- * guard for an empty basket and would still make its `eth_call`, and every
118
- * assertion below reads amounts that are not there.
119
- */
120
- async function emptyOpenState(props) {
121
- const { sdk, creditManager } = props;
122
- assertMarketOperable(sdk.marketRegister.findCreditManager(creditManager));
123
- const snapshot = {
124
- creditManager,
125
- assets: [],
126
- quotas: [],
127
- totalDebt: 0n,
128
- totalValue: 0n
129
- };
130
- const { assets: _assets, quotas: _quotas, ...projection } = sdk.positions.projection(snapshot, { availableLiquidityChange: 0n });
131
- return {
132
- ...projection,
133
- currentPrice: sdk.positions.currentPrice(snapshot),
134
- priceImpact: void 0,
135
- averageAssets: [],
136
- minAssets: [],
137
- averageQuota: [],
138
- minQuota: [],
139
- calls: []
140
- };
141
- }
142
103
  /** Collateral plus the borrowed underlying, folded into one balance per token. */
143
104
  function mergeExpectedBalances(collateral, underlying, debt) {
144
105
  const merged = /* @__PURE__ */ new Map();
@@ -203,6 +203,7 @@ function buildMockSdk(args) {
203
203
  liquidationThresholds,
204
204
  collateralTokens,
205
205
  feeInterest: args.feeInterest ?? 0,
206
+ maxEnabledTokens: args.maxEnabledTokens ?? 4,
206
207
  maxLeverage: (collateral, targetHF) => calcMaxLeverage(liquidationThresholds.get(collateral) ?? 0, targetHF)
207
208
  },
208
209
  creditFacade: {
@@ -2,6 +2,24 @@ import { AddressMap } from "../../../utils/AddressMap.js";
2
2
  //#region src/onchain/accounts/intents/utils/common.ts
3
3
  /** Case-insensitive address equality. */
4
4
  const eq = (a, b) => a.toLowerCase() === b.toLowerCase();
5
+ /**
6
+ * The suite and market behind a credit manager, or nothing where the register
7
+ * has no entry for it.
8
+ *
9
+ * For the reads a form calls on every keystroke, including before the SDK has
10
+ * finished attaching: a question the register cannot answer yet is not an
11
+ * error. Everything that prepares a transaction wants the throw instead.
12
+ */
13
+ function resolveCreditManager(sdk, creditManager) {
14
+ try {
15
+ return {
16
+ suite: sdk.marketRegister.findCreditManager(creditManager),
17
+ market: sdk.marketRegister.findByCreditManager(creditManager)
18
+ };
19
+ } catch {
20
+ return;
21
+ }
22
+ }
5
23
  function toTargetDecimals(fromAmount, fromToken, toToken, sdk) {
6
24
  const fromDecimals = sdk.tokensMeta.get(fromToken)?.decimals ?? 18;
7
25
  const toDecimals = sdk.tokensMeta.get(toToken)?.decimals ?? 18;
@@ -37,4 +55,4 @@ function toRouterCaSlice(creditAccount, expectedBalances = []) {
37
55
  };
38
56
  }
39
57
  //#endregion
40
- export { eq, toRouterCaSlice, toTargetDecimals };
58
+ export { eq, resolveCreditManager, toRouterCaSlice, toTargetDecimals };
@@ -26,6 +26,26 @@ function toCreditAccountSlice(ca) {
26
26
  };
27
27
  }
28
28
  /**
29
+ * The slice a flow that has no account yet quotes against.
30
+ *
31
+ * Nothing of it exists on chain until the transaction lands, and nothing has
32
+ * to: the pathfinder is asked about the credit manager, and every balance the
33
+ * flow reasons about is one the transaction itself puts there. The zero
34
+ * address stands in for the account so the shape is complete.
35
+ */
36
+ function unopenedAccountSlice(args) {
37
+ return {
38
+ creditAccount: "0x0000000000000000000000000000000000000000",
39
+ creditManager: args.creditManager.toLowerCase(),
40
+ creditFacade: args.creditFacade.toLowerCase(),
41
+ underlying: args.underlying.toLowerCase(),
42
+ enabledTokensMask: 0n,
43
+ totalDebtUSD: 0n,
44
+ totalDebt: 0n,
45
+ tokens: []
46
+ };
47
+ }
48
+ /**
29
49
  * Reads an account by address and narrows it to {@link CreditAccountSlice}.
30
50
  *
31
51
  * The shared read model's `StrategyPosition` carries neither `tokens` nor
@@ -40,4 +60,4 @@ async function fetchCreditAccountSlice(sdk, creditAccount) {
40
60
  return toCreditAccountSlice(data);
41
61
  }
42
62
  //#endregion
43
- export { fetchCreditAccountSlice, toCreditAccountSlice };
63
+ export { fetchCreditAccountSlice, toCreditAccountSlice, unopenedAccountSlice };
@@ -1,11 +1,11 @@
1
- import { eq, toRouterCaSlice, toTargetDecimals } from "./common.js";
1
+ import { eq, resolveCreditManager, toRouterCaSlice, toTargetDecimals } from "./common.js";
2
2
  import { isPhantomToken, isRedemptionPhantomToken, pickFattestNonPhantomToken, rankAccountTokens } from "./pick-token.js";
3
3
  import { adjustStateToSnapshot } from "./adjust-state-to-snapshot.js";
4
4
  import { assembleOperationCalls } from "./assemble-operation-calls.js";
5
5
  import { calcBorrowedAmountPlusInterestAndFees } from "./borrowed-amount-plus-interest-and-fees.js";
6
- import { fetchCreditAccountSlice, toCreditAccountSlice } from "./credit-account-slice.js";
6
+ import { fetchCreditAccountSlice, toCreditAccountSlice, unopenedAccountSlice } from "./credit-account-slice.js";
7
7
  import { OperationLedger } from "./ledger.js";
8
8
  import { collectPriceImpact, lossRate, startProbe } from "./price-impact.js";
9
9
  import { clearedQuotas, getQuotasForUpdate, quotasAfterUpdate } from "./quotas-for-update.js";
10
10
  import { createOraclePaths, createRouterPaths } from "./router-path.js";
11
- export { OperationLedger, adjustStateToSnapshot, assembleOperationCalls, calcBorrowedAmountPlusInterestAndFees, clearedQuotas, collectPriceImpact, createOraclePaths, createRouterPaths, eq, fetchCreditAccountSlice, getQuotasForUpdate, isPhantomToken, isRedemptionPhantomToken, lossRate, pickFattestNonPhantomToken, quotasAfterUpdate, rankAccountTokens, startProbe, toCreditAccountSlice, toRouterCaSlice, toTargetDecimals };
11
+ export { OperationLedger, adjustStateToSnapshot, assembleOperationCalls, calcBorrowedAmountPlusInterestAndFees, clearedQuotas, collectPriceImpact, createOraclePaths, createRouterPaths, eq, fetchCreditAccountSlice, getQuotasForUpdate, isPhantomToken, isRedemptionPhantomToken, lossRate, pickFattestNonPhantomToken, quotasAfterUpdate, rankAccountTokens, resolveCreditManager, startProbe, toCreditAccountSlice, toRouterCaSlice, toTargetDecimals, unopenedAccountSlice };
@@ -3,8 +3,8 @@ import "../../utils/index.js";
3
3
  import { MIN_HEALTH_FACTOR_FACADE } from "../../validation/helpers/health-factor-limits.js";
4
4
  import "../../validation/index.js";
5
5
  import { eq } from "./utils/common.js";
6
- import { collateralMoney } from "./collateral-money.js";
7
6
  import { maxProportionalWithdrawal } from "./math.js";
7
+ import { collateralValuation } from "./collateral-valuation.js";
8
8
  import { accountView } from "./view.js";
9
9
  //#region src/onchain/accounts/intents/withdraw-limits.ts
10
10
  /**
@@ -38,7 +38,7 @@ function withdrawLimits(props) {
38
38
  * in underlying units.
39
39
  *
40
40
  * A withdrawal hands funds over, so the facade weighs the account it leaves
41
- * behind at safe prices rather than main ones — see {@link collateralMoney}.
41
+ * behind at safe prices rather than main ones — see {@link collateralValuation}.
42
42
  * That is a second limit on top of the facade's `debtLimits`, and the two are
43
43
  * independent: a caller wanting the amount a form may actually offer takes the
44
44
  * lesser of this and `maxProportionalWithdrawal`.
@@ -71,20 +71,20 @@ function maxSafeWithdrawal(props) {
71
71
  const view = accountView(creditAccount, sdk);
72
72
  if (view.collateral <= 0n) return 0n;
73
73
  if (view.debt === 0n) return view.collateral;
74
- const money = collateralMoney(creditAccount, sdk);
74
+ const valuation = collateralValuation(creditAccount, sdk);
75
75
  const source = props.sourceToken ?? view.fattest();
76
76
  const holding = source === void 0 ? void 0 : creditAccount.tokens.find((t) => eq(t.token, source));
77
77
  if (!holding) return view.collateral;
78
78
  let total = 0n;
79
- for (const t of creditAccount.tokens) if (money.counts(t)) total += money.weigh(t);
80
- const debtUsd = money.mainUsd(money.underlying, view.debt);
79
+ for (const t of creditAccount.tokens) if (valuation.counts(t)) total += valuation.weigh(t);
80
+ const debtUsd = valuation.mainUsd(valuation.underlying, view.debt);
81
81
  if (debtUsd === void 0 || debtUsd <= 0n) return view.collateral;
82
82
  const slack = total - debtUsd * targetHF;
83
83
  if (slack < 0n) return 0n;
84
- const sourceMainUsd = money.mainUsd(holding.token, holding.balance);
84
+ const sourceMainUsd = valuation.mainUsd(holding.token, holding.balance);
85
85
  if (sourceMainUsd === void 0 || sourceMainUsd <= 0n) return view.collateral;
86
- const sourceRate = money.lt(holding.token) * money.checkedUsd(holding);
87
- const tvlUsd = money.mainUsd(money.underlying, view.collateral + view.debt);
86
+ const sourceRate = valuation.lt(holding.token) * valuation.checkedUsd(holding);
87
+ const tvlUsd = valuation.mainUsd(valuation.underlying, view.collateral + view.debt);
88
88
  if (tvlUsd === void 0) return view.collateral;
89
89
  const drain = sourceRate * tvlUsd - targetHF * debtUsd * sourceMainUsd;
90
90
  if (drain <= 0n) return view.collateral;
@@ -35,7 +35,7 @@ function calcHealthFactor(props) {
35
35
  const scale = 10n ** BigInt(decimalsByToken.get(token) ?? 18);
36
36
  return amount * price / scale;
37
37
  };
38
- const assetMoney = snapshot.assets.reduce((acc, { token, balance }) => {
38
+ const assetValue = snapshot.assets.reduce((acc, { token, balance }) => {
39
39
  if (balance <= 10n) return acc;
40
40
  const lt = BigInt(lts.get(token) ?? 0);
41
41
  const tokenLtWeighted = (convertToUSD(token, balance, true) ?? 0n) * lt;
@@ -44,8 +44,8 @@ function calcHealthFactor(props) {
44
44
  const quotaWeighted = (convertToUSD(underlying, quotaBalance) ?? 0n) * PERCENTAGE_FACTOR;
45
45
  return acc + (quota ? BigIntMath.min(quotaWeighted, tokenLtWeighted) : tokenLtWeighted);
46
46
  }, 0n);
47
- const borrowedMoney = convertToUSD(underlying, snapshot.totalDebt) ?? 0n;
48
- const hf = borrowedMoney > 0n ? assetMoney / borrowedMoney : 0n;
47
+ const borrowedValue = convertToUSD(underlying, snapshot.totalDebt) ?? 0n;
48
+ const hf = borrowedValue > 0n ? assetValue / borrowedValue : 0n;
49
49
  return Number(hf);
50
50
  }
51
51
  //#endregion
@@ -17,7 +17,7 @@ function previewOpenStrategyPosition(sdk, input, operation, replay) {
17
17
  warning ??= priced.error;
18
18
  return priced.value;
19
19
  };
20
- const netValue = before.balances.sum(price) + after.collateralAdded.sum(price);
20
+ const netValue = before.balances.sum(price) + after.collateralAdded.sum(price) - after.collateralWithdrawn.sum(price);
21
21
  const unwrapped = unwrapNativeCollateral(after.collateralAdded.toAssets(), value, sdk.addressProvider.getAddress(AP_WETH_TOKEN, 0));
22
22
  if (!unwrapped.ok) return unwrapped;
23
23
  const collateral = unwrapped.data;
@@ -27,6 +27,7 @@ function previewOpenStrategyPosition(sdk, input, operation, replay) {
27
27
  ...asEstimated(sdk.positions.projection(snap, { availableLiquidityChange: -account.totalDebt })),
28
28
  targetCollateral: targetAsset ? oracle.toTokenAmount(targetAsset.token, targetAsset.balance) : void 0,
29
29
  collateralAdded: collateral.map((a) => oracle.toTokenAmount(a.token, a.balance)),
30
+ collateralWithdrawn: after.collateralWithdrawn.toAssets().map((a) => oracle.toTokenAmount(a.token, a.balance)),
30
31
  warning
31
32
  };
32
33
  return sdkOk({