@gearbox-protocol/sdk 16.4.0-next.5 → 16.4.0-next.7

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 (55) hide show
  1. package/dist/cjs/onchain/accounts/intents/borrow.js +139 -0
  2. package/dist/cjs/onchain/accounts/intents/{collateral-money.js → collateral-valuation.js} +7 -7
  3. package/dist/cjs/onchain/accounts/intents/index.js +82 -1
  4. package/dist/cjs/onchain/accounts/intents/leverage-band.js +2 -12
  5. package/dist/cjs/onchain/accounts/intents/maxBorrow.js +89 -0
  6. package/dist/cjs/onchain/accounts/intents/maxWithdrawCollateral.js +14 -14
  7. package/dist/cjs/onchain/accounts/intents/open-strategy.js +6 -45
  8. package/dist/cjs/onchain/accounts/intents/testing/sdk-mock.js +1 -0
  9. package/dist/cjs/onchain/accounts/intents/utils/common.js +19 -0
  10. package/dist/cjs/onchain/accounts/intents/utils/credit-account-slice.js +21 -0
  11. package/dist/cjs/onchain/accounts/intents/utils/index.js +2 -0
  12. package/dist/cjs/onchain/accounts/intents/withdraw-limits.js +8 -8
  13. package/dist/cjs/onchain/positions/calcHealthFactor.js +3 -3
  14. package/dist/cjs/onchain/preview/preview/previewOpenStrategyPosition.js +2 -1
  15. package/dist/cjs/sdk/execute/ExecuteApi.js +65 -5
  16. package/dist/cjs/sdk/prepare/PrepareApi.js +86 -12
  17. package/dist/esm/onchain/accounts/intents/borrow.js +137 -0
  18. package/dist/esm/onchain/accounts/intents/{collateral-money.js → collateral-valuation.js} +7 -7
  19. package/dist/esm/onchain/accounts/intents/index.js +82 -1
  20. package/dist/esm/onchain/accounts/intents/leverage-band.js +2 -12
  21. package/dist/esm/onchain/accounts/intents/maxBorrow.js +87 -0
  22. package/dist/esm/onchain/accounts/intents/maxWithdrawCollateral.js +14 -14
  23. package/dist/esm/onchain/accounts/intents/open-strategy.js +6 -45
  24. package/dist/esm/onchain/accounts/intents/testing/sdk-mock.js +1 -0
  25. package/dist/esm/onchain/accounts/intents/utils/common.js +19 -1
  26. package/dist/esm/onchain/accounts/intents/utils/credit-account-slice.js +21 -1
  27. package/dist/esm/onchain/accounts/intents/utils/index.js +3 -3
  28. package/dist/esm/onchain/accounts/intents/withdraw-limits.js +8 -8
  29. package/dist/esm/onchain/positions/calcHealthFactor.js +3 -3
  30. package/dist/esm/onchain/preview/preview/previewOpenStrategyPosition.js +2 -1
  31. package/dist/esm/sdk/execute/ExecuteApi.js +65 -5
  32. package/dist/esm/sdk/prepare/PrepareApi.js +86 -12
  33. package/dist/types/model/previews.d.ts +11 -2
  34. package/dist/types/onchain/accounts/index.d.ts +4 -3
  35. package/dist/types/onchain/accounts/intents/borrow.d.ts +141 -0
  36. package/dist/types/onchain/accounts/intents/{collateral-money.d.ts → collateral-valuation.d.ts} +8 -8
  37. package/dist/types/onchain/accounts/intents/index.d.ts +81 -2
  38. package/dist/types/onchain/accounts/intents/maxBorrow.d.ts +54 -0
  39. package/dist/types/onchain/accounts/intents/maxWithdrawCollateral.d.ts +3 -3
  40. package/dist/types/onchain/accounts/intents/open-strategy.d.ts +3 -17
  41. package/dist/types/onchain/accounts/intents/testing/sdk-mock.d.ts +2 -0
  42. package/dist/types/onchain/accounts/intents/tests/open-strategy.fixtures.d.ts +2 -2
  43. package/dist/types/onchain/accounts/intents/types.d.ts +6 -1
  44. package/dist/types/onchain/accounts/intents/utils/common.d.ts +15 -1
  45. package/dist/types/onchain/accounts/intents/utils/credit-account-slice.d.ts +14 -1
  46. package/dist/types/onchain/accounts/intents/utils/index.d.ts +3 -3
  47. package/dist/types/onchain/accounts/intents/withdraw-limits.d.ts +1 -1
  48. package/dist/types/onchain/index.d.ts +4 -3
  49. package/dist/types/sdk/execute/index.d.ts +2 -2
  50. package/dist/types/sdk/execute/types.d.ts +48 -6
  51. package/dist/types/sdk/index.d.ts +5 -4
  52. package/dist/types/sdk/prepare/PrepareApi.d.ts +15 -3
  53. package/dist/types/sdk/prepare/index.d.ts +4 -3
  54. package/dist/types/sdk/prepare/types.d.ts +176 -49
  55. package/package.json +1 -1
@@ -4,8 +4,8 @@ require("../../utils/index.js");
4
4
  const require_onchain_validation_helpers_health_factor_limits = require("../../validation/helpers/health-factor-limits.js");
5
5
  require("../../validation/index.js");
6
6
  const require_onchain_accounts_intents_utils_common = require("./utils/common.js");
7
- const require_onchain_accounts_intents_collateral_money = require("./collateral-money.js");
8
7
  const require_onchain_accounts_intents_math = require("./math.js");
8
+ const require_onchain_accounts_intents_collateral_valuation = require("./collateral-valuation.js");
9
9
  const require_onchain_accounts_intents_view = require("./view.js");
10
10
  //#region src/onchain/accounts/intents/withdraw-limits.ts
11
11
  /**
@@ -39,7 +39,7 @@ function withdrawLimits(props) {
39
39
  * in underlying units.
40
40
  *
41
41
  * A withdrawal hands funds over, so the facade weighs the account it leaves
42
- * behind at safe prices rather than main ones — see {@link collateralMoney}.
42
+ * behind at safe prices rather than main ones — see {@link collateralValuation}.
43
43
  * That is a second limit on top of the facade's `debtLimits`, and the two are
44
44
  * independent: a caller wanting the amount a form may actually offer takes the
45
45
  * lesser of this and `maxProportionalWithdrawal`.
@@ -72,20 +72,20 @@ function maxSafeWithdrawal(props) {
72
72
  const view = require_onchain_accounts_intents_view.accountView(creditAccount, sdk);
73
73
  if (view.collateral <= 0n) return 0n;
74
74
  if (view.debt === 0n) return view.collateral;
75
- const money = require_onchain_accounts_intents_collateral_money.collateralMoney(creditAccount, sdk);
75
+ const valuation = require_onchain_accounts_intents_collateral_valuation.collateralValuation(creditAccount, sdk);
76
76
  const source = props.sourceToken ?? view.fattest();
77
77
  const holding = source === void 0 ? void 0 : creditAccount.tokens.find((t) => require_onchain_accounts_intents_utils_common.eq(t.token, source));
78
78
  if (!holding) return view.collateral;
79
79
  let total = 0n;
80
- for (const t of creditAccount.tokens) if (money.counts(t)) total += money.weigh(t);
81
- const debtUsd = money.mainUsd(money.underlying, view.debt);
80
+ for (const t of creditAccount.tokens) if (valuation.counts(t)) total += valuation.weigh(t);
81
+ const debtUsd = valuation.mainUsd(valuation.underlying, view.debt);
82
82
  if (debtUsd === void 0 || debtUsd <= 0n) return view.collateral;
83
83
  const slack = total - debtUsd * targetHF;
84
84
  if (slack < 0n) return 0n;
85
- const sourceMainUsd = money.mainUsd(holding.token, holding.balance);
85
+ const sourceMainUsd = valuation.mainUsd(holding.token, holding.balance);
86
86
  if (sourceMainUsd === void 0 || sourceMainUsd <= 0n) return view.collateral;
87
- const sourceRate = money.lt(holding.token) * money.checkedUsd(holding);
88
- const tvlUsd = money.mainUsd(money.underlying, view.collateral + view.debt);
87
+ const sourceRate = valuation.lt(holding.token) * valuation.checkedUsd(holding);
88
+ const tvlUsd = valuation.mainUsd(valuation.underlying, view.collateral + view.debt);
89
89
  if (tvlUsd === void 0) return view.collateral;
90
90
  const drain = sourceRate * tvlUsd - targetHF * debtUsd * sourceMainUsd;
91
91
  if (drain <= 0n) return view.collateral;
@@ -36,7 +36,7 @@ function calcHealthFactor(props) {
36
36
  const scale = 10n ** BigInt(decimalsByToken.get(token) ?? 18);
37
37
  return amount * price / scale;
38
38
  };
39
- const assetMoney = snapshot.assets.reduce((acc, { token, balance }) => {
39
+ const assetValue = snapshot.assets.reduce((acc, { token, balance }) => {
40
40
  if (balance <= 10n) return acc;
41
41
  const lt = BigInt(lts.get(token) ?? 0);
42
42
  const tokenLtWeighted = (convertToUSD(token, balance, true) ?? 0n) * lt;
@@ -45,8 +45,8 @@ function calcHealthFactor(props) {
45
45
  const quotaWeighted = (convertToUSD(underlying, quotaBalance) ?? 0n) * require_onchain_constants_math.PERCENTAGE_FACTOR;
46
46
  return acc + (quota ? require_onchain_utils_bigint_math.BigIntMath.min(quotaWeighted, tokenLtWeighted) : tokenLtWeighted);
47
47
  }, 0n);
48
- const borrowedMoney = convertToUSD(underlying, snapshot.totalDebt) ?? 0n;
49
- const hf = borrowedMoney > 0n ? assetMoney / borrowedMoney : 0n;
48
+ const borrowedValue = convertToUSD(underlying, snapshot.totalDebt) ?? 0n;
49
+ const hf = borrowedValue > 0n ? assetValue / borrowedValue : 0n;
50
50
  return Number(hf);
51
51
  }
52
52
  //#endregion
@@ -18,7 +18,7 @@ function previewOpenStrategyPosition(sdk, input, operation, replay) {
18
18
  warning ??= priced.error;
19
19
  return priced.value;
20
20
  };
21
- const netValue = before.balances.sum(price) + after.collateralAdded.sum(price);
21
+ const netValue = before.balances.sum(price) + after.collateralAdded.sum(price) - after.collateralWithdrawn.sum(price);
22
22
  const unwrapped = require_onchain_preview_preview_unwrapNativeCollateral.unwrapNativeCollateral(after.collateralAdded.toAssets(), value, sdk.addressProvider.getAddress(require_onchain_constants_address_provider.AP_WETH_TOKEN, 0));
23
23
  if (!unwrapped.ok) return unwrapped;
24
24
  const collateral = unwrapped.data;
@@ -28,6 +28,7 @@ function previewOpenStrategyPosition(sdk, input, operation, replay) {
28
28
  ...require_model_previews.asEstimated(sdk.positions.projection(snap, { availableLiquidityChange: -account.totalDebt })),
29
29
  targetCollateral: targetAsset ? oracle.toTokenAmount(targetAsset.token, targetAsset.balance) : void 0,
30
30
  collateralAdded: collateral.map((a) => oracle.toTokenAmount(a.token, a.balance)),
31
+ collateralWithdrawn: after.collateralWithdrawn.toAssets().map((a) => oracle.toTokenAmount(a.token, a.balance)),
31
32
  warning
32
33
  };
33
34
  return require_model_result.sdkOk({
@@ -17,6 +17,8 @@ var ExecuteApi = class {
17
17
  switch (request.kind) {
18
18
  case "pool": return poolTx(sdk, request);
19
19
  case "open": return openTx(sdk, request);
20
+ case "openEmpty": return openEmptyTx(sdk, request);
21
+ case "borrow": return borrowTx(sdk, request);
20
22
  case "account": return accountTx(sdk, request);
21
23
  }
22
24
  }
@@ -63,17 +65,75 @@ async function openTx(sdk, request) {
63
65
  reopenCreditAccount: state.creditAccount,
64
66
  permits: {},
65
67
  referralCode: 0n,
66
- rwaOptions: await openRwaOptions(sdk, request)
68
+ rwaOptions: await openRwaOptions(sdk, request, request.targetToken)
69
+ });
70
+ }
71
+ /**
72
+ * The opening with nothing in it: no debt to draw, no collateral to take, no
73
+ * path to run and no quota to buy, so `openCA` is left with the price updates
74
+ * the market demands and the facade's own `openCreditAccount`.
75
+ *
76
+ * Nothing is read off the preparation because there is nothing on it. The
77
+ * account holds no token, so an RWA market has none to gate on either.
78
+ **/
79
+ function openEmptyTx(sdk, request) {
80
+ return sdk.accounts.openCA({
81
+ creditManager: request.creditManager,
82
+ to: request.wallet,
83
+ collateral: [],
84
+ ethAmount: 0n,
85
+ debt: 0n,
86
+ calls: [],
87
+ averageQuota: [],
88
+ minQuota: [],
89
+ permits: {},
90
+ referralCode: 0n
91
+ });
92
+ }
93
+ /**
94
+ * A loan is an opening whose debt leaves again, so it is the same `openCA`
95
+ * with `withdrawToken` set: the facade draws the debt, takes the collateral,
96
+ * runs whatever path buys the payout, and sweeps that payout to the wallet.
97
+ *
98
+ * Both quota branches are the one the state carries. An opening has two
99
+ * because the balances it lands on are a router quote; here the account is
100
+ * left holding the collateral the caller named, and a named amount has no
101
+ * floor to differ from.
102
+ **/
103
+ async function borrowTx(sdk, request) {
104
+ const { creditManager, wallet, ethAmount, sim } = request;
105
+ const { state } = sim.data;
106
+ const collateral = {
107
+ token: state.collateral.token.address,
108
+ balance: state.collateral.value
109
+ };
110
+ return sdk.accounts.openCA({
111
+ creditManager,
112
+ to: wallet,
113
+ collateral: [collateral],
114
+ ethAmount,
115
+ debt: state.totalDebt.value,
116
+ calls: state.calls,
117
+ withdrawToken: state.borrowed.token.address,
118
+ averageQuota: state.quotaIncrease,
119
+ minQuota: state.quotaIncrease,
120
+ reopenCreditAccount: state.creditAccount,
121
+ permits: {},
122
+ referralCode: 0n,
123
+ rwaOptions: await openRwaOptions(sdk, request, collateral.token)
67
124
  });
68
125
  }
69
126
  /**
70
127
  * The documented `openCA` contract: ask the market for its open requirements
71
128
  * and hand them back as operation args, with the caller's cached signatures
72
- * attached. `undefined` on non-RWA markets and when no target token is named.
129
+ * attached. `undefined` on non-RWA markets and when no token is named.
130
+ *
131
+ * `token` is the one the account ends up holding, which is what an RWA market
132
+ * gates on: the opening's target, and the borrow's collateral.
73
133
  **/
74
- async function openRwaOptions(sdk, request) {
75
- if (!request.targetToken) return;
76
- const requirements = await sdk.accounts.getOpenAccountRequirements(request.wallet, request.creditManager, { tokenOutAddress: request.targetToken });
134
+ async function openRwaOptions(sdk, request, token) {
135
+ if (!token) return;
136
+ const requirements = await sdk.accounts.getOpenAccountRequirements(request.wallet, request.creditManager, { tokenOutAddress: token });
77
137
  if (requirements?.protocol !== "securitize") return;
78
138
  return {
79
139
  protocol: "securitize",
@@ -196,26 +196,33 @@ var PrepareApi = class extends require_onchain_base_MultichainConstruct.Multicha
196
196
  }
197
197
  }
198
198
  /**
199
+ * {@inheritDoc IOpportunitiesPrepare.openEmptyCreditAccount}
200
+ **/
201
+ async openEmptyCreditAccount(strategy) {
202
+ try {
203
+ const sdk = await this.#chain(strategy.chainId);
204
+ const at = stateBlock(sdk);
205
+ const result = await service(sdk).openEmptyAccountIntent({
206
+ sdk,
207
+ creditManager: strategy.creditManager
208
+ });
209
+ return result.ok ? require_model_result.sdkOk(at) : methodError(result);
210
+ } catch (e) {
211
+ return require_model_result.sdkErr(require_model_errors_prepare_errors.unexpectedFailure(e));
212
+ }
213
+ }
214
+ /**
199
215
  * {@inheritDoc IOpportunitiesPrepare.openNewStrategy}
200
216
  **/
201
217
  async openNewStrategy(strategy, params) {
202
218
  try {
203
219
  const sdk = await this.#chain(strategy.chainId);
204
220
  const at = stateBlock(sdk);
205
- if (params.empty) return opened(await service(sdk).openStrategyIntent({
206
- sdk,
207
- creditManager: strategy.creditManager,
208
- empty: true
209
- }), at);
210
221
  const targetToken = params.targetToken ?? sdk.marketRegister.findCreditManager(strategy.creditManager).strategyTargetCollateral;
211
222
  if (!targetToken) return require_model_result.sdkErr(require_model_errors_prepare_errors.noStrategyTargetCollateral(strategy.creditManager));
212
- let creditAccount;
213
- if (params.creditAccount) {
214
- const reused = await slice(sdk, params.creditAccount);
215
- if (!reused || !(0, viem.isAddressEqual)(reused.creditManager, strategy.creditManager)) return require_model_result.sdkErr(require_model_errors_prepare_errors.creditAccountNotFound(params.creditAccount));
216
- if (reused.totalDebt > 0n || reused.tokens.some((t) => t.quota > 0n)) return require_model_result.sdkErr(require_model_errors_prepare_errors.creditAccountNotEmpty(params.creditAccount));
217
- creditAccount = reused;
218
- }
223
+ const reused = await reusable(sdk, strategy, params.creditAccount);
224
+ if (reused && "error" in reused) return reused;
225
+ const creditAccount = reused?.account;
219
226
  return opened(await service(sdk).openStrategyIntent({
220
227
  sdk,
221
228
  creditManager: strategy.creditManager,
@@ -232,6 +239,30 @@ var PrepareApi = class extends require_onchain_base_MultichainConstruct.Multicha
232
239
  }
233
240
  }
234
241
  /**
242
+ * {@inheritDoc IOpportunitiesPrepare.borrow}
243
+ **/
244
+ async borrow(strategy, params) {
245
+ try {
246
+ const sdk = await this.#chain(strategy.chainId);
247
+ const at = stateBlock(sdk);
248
+ const reused = await reusable(sdk, strategy, params.creditAccount);
249
+ if (reused && "error" in reused) return reused;
250
+ return borrowed(await service(sdk).borrowIntent({
251
+ sdk,
252
+ creditManager: strategy.creditManager,
253
+ collateralToken: params.collateralToken,
254
+ collateralAmount: params.collateralAmount,
255
+ borrowToken: params.borrowToken,
256
+ borrowAmount: params.borrowAmount,
257
+ slippage: params.slippage,
258
+ quotaReserve: params.quotaReserve,
259
+ creditAccount: reused?.account
260
+ }), at);
261
+ } catch (e) {
262
+ return require_model_result.sdkErr(require_model_errors_prepare_errors.unexpectedFailure(e));
263
+ }
264
+ }
265
+ /**
235
266
  * {@inheritDoc IOpportunitiesPrepare.depositStrategy}
236
267
  **/
237
268
  async depositStrategy(position, params) {
@@ -354,6 +385,21 @@ var PrepareApi = class extends require_onchain_base_MultichainConstruct.Multicha
354
385
  });
355
386
  }
356
387
  /**
388
+ * {@inheritDoc IOpportunitiesPrepare.maxBorrow}
389
+ **/
390
+ maxBorrow(strategy, params) {
391
+ const sdk = this.sdk.chain(strategy.chainId);
392
+ return service(sdk).maxBorrow({
393
+ sdk,
394
+ creditManager: strategy.creditManager,
395
+ collateralToken: params.collateralToken,
396
+ collateralAmount: params.collateralAmount,
397
+ borrowToken: params.borrowToken,
398
+ targetHF: params.targetHF,
399
+ quotaReserve: params.quotaReserve
400
+ });
401
+ }
402
+ /**
357
403
  * The account a bare `max*` read weighs. These reads answer a number, not
358
404
  * an envelope, so an account the markets do not hold is thrown rather than
359
405
  * described, see {@link IOpportunitiesPrepare.maxWithdraw}.
@@ -439,6 +485,25 @@ async function slice(sdk, creditAccount) {
439
485
  return data && require_onchain_accounts_intents_utils_credit_account_slice.toCreditAccountSlice(data);
440
486
  }
441
487
  /**
488
+ * The pre-opened account a request asks to be run on, held to what "pre-opened"
489
+ * means: this manager's, owing nothing and holding no quota.
490
+ *
491
+ * Shared by the two flows that put something on a fresh account — an opening
492
+ * and a borrow — so both hold a reused one to the same terms. Whatever
493
+ * balances sit on it are left to the flow: an opening routes them, a borrow
494
+ * leaves them where they are.
495
+ *
496
+ * @returns Nothing when the request named no account, the refusal to answer
497
+ * with when it named one that does not qualify, and the slice otherwise
498
+ **/
499
+ async function reusable(sdk, strategy, creditAccount) {
500
+ if (!creditAccount) return;
501
+ const account = await slice(sdk, creditAccount);
502
+ if (!account || !(0, viem.isAddressEqual)(account.creditManager, strategy.creditManager)) return require_model_result.sdkErr(require_model_errors_prepare_errors.creditAccountNotFound(creditAccount));
503
+ if (account.totalDebt > 0n || account.tokens.some((t) => t.quota > 0n)) return require_model_result.sdkErr(require_model_errors_prepare_errors.creditAccountNotEmpty(creditAccount));
504
+ return { account };
505
+ }
506
+ /**
442
507
  * The operation a claim resumes, or `undefined` when there is none to resume:
443
508
  * a withdrawal requested without an intent, or one read through a compressor
444
509
  * too old to report it. Every intent the engine records can be finished,
@@ -538,6 +603,15 @@ function opened(result, at) {
538
603
  }
539
604
  /**
540
605
  * {@inheritDoc planned}
606
+ **/
607
+ function borrowed(result, at) {
608
+ return result.ok ? require_model_result.sdkOk({
609
+ state: result.state,
610
+ ...at
611
+ }) : methodError(result);
612
+ }
613
+ /**
614
+ * {@inheritDoc planned}
541
615
  *
542
616
  * Both routes are payload, error and all: `errors` says why a missing one is
543
617
  * missing, and it stays on the error when neither route answered, since that is
@@ -0,0 +1,137 @@
1
+ import { insufficientBalance } from "../../../model/errors/operation-errors.js";
2
+ import { unsupportedCollateralToken } from "../../../model/errors/prepare-errors.js";
3
+ import "../../../model/index.js";
4
+ import { toToken } from "../../validation/helpers/token.js";
5
+ import { IntentPreviewError } from "../../validation/raise.js";
6
+ import "../../validation/index.js";
7
+ import { eq, toTargetDecimals } from "./utils/common.js";
8
+ import { assertCanBorrow, assertCollateralised, assertGrowthAllowed, assertMarketOperable, assertQuotaAvailable } from "./guards.js";
9
+ import { assertDebtLimits } from "./math.js";
10
+ import { unopenedAccountSlice } from "./utils/credit-account-slice.js";
11
+ import { collectPriceImpact } from "./utils/price-impact.js";
12
+ import { getQuotasForUpdate } from "./utils/quotas-for-update.js";
13
+ import { createRouterPaths } from "./utils/router-path.js";
14
+ import "./utils/index.js";
15
+ //#region src/onchain/accounts/intents/borrow.ts
16
+ /**
17
+ * Builds the state a borrow would leave behind.
18
+ *
19
+ * The debt is named rather than derived: a borrow asks for an amount, where an
20
+ * opening asks for a leverage and lets the collateral decide. Everything the
21
+ * loan pays out leaves the account, so the collateral alone backs it — which
22
+ * is what makes the health factor here a straight function of the two amounts.
23
+ *
24
+ * Produces no operation list, as opening never does: the caller hands the
25
+ * numbers and the calls to `sdk.accounts.openCA`, with `withdrawToken` set to
26
+ * the payout.
27
+ */
28
+ async function buildBorrowState(props) {
29
+ const { sdk, creditManager, collateralAmount, borrowAmount, slippage = 0, quotaReserve, creditAccount: existing } = props;
30
+ const suite = sdk.marketRegister.findCreditManager(creditManager);
31
+ const market = sdk.marketRegister.findByCreditManager(creditManager);
32
+ assertMarketOperable(suite);
33
+ const underlying = market.pool.underlying.toLowerCase();
34
+ const collateralToken = props.collateralToken.toLowerCase();
35
+ const borrowToken = props.borrowToken.toLowerCase();
36
+ const convert = (from, to, amount) => market.priceOracle.safeConvert(from, to, amount).value;
37
+ if (eq(collateralToken, borrowToken)) throw new IntentPreviewError(unsupportedCollateralToken(toToken(sdk, collateralToken)), "borrow: the payout token cannot also be the collateral");
38
+ const rwaAsset = sdk.tokensMeta.rwaUnderlyings.get(underlying)?.asset?.toLowerCase();
39
+ const unwrapsPayout = !!rwaAsset && eq(borrowToken, rwaAsset);
40
+ if (rwaAsset && eq(borrowToken, underlying)) throw new IntentPreviewError(unsupportedCollateralToken(toToken(sdk, borrowToken)), `borrow: ${underlying} cannot leave the account, ask for the payout in ${rwaAsset}`);
41
+ if (collateralAmount <= 0n) throw new IntentPreviewError(insufficientBalance(), "borrow: no collateral supplied");
42
+ if (borrowAmount <= 0n) throw new IntentPreviewError(insufficientBalance(), "borrow: nothing to borrow");
43
+ const margin = convert(collateralToken, underlying, collateralAmount);
44
+ if (margin <= 0n) throw new IntentPreviewError(insufficientBalance(), "borrow: collateral is worth nothing in underlying");
45
+ const debt = eq(borrowToken, underlying) ? borrowAmount : unwrapsPayout ? toTargetDecimals(borrowAmount, borrowToken, underlying, sdk) : convert(borrowToken, underlying, borrowAmount);
46
+ assertDebtLimits(sdk, debt, suite.creditFacade, underlying);
47
+ assertCanBorrow(sdk, suite, debt);
48
+ const account = existing ?? unopenedAccountSlice({
49
+ creditManager,
50
+ creditFacade: suite.creditFacade.address,
51
+ underlying
52
+ });
53
+ const leg = eq(borrowToken, underlying) || unwrapsPayout ? void 0 : await createRouterPaths({
54
+ sdk,
55
+ creditAccount: account,
56
+ slippage
57
+ }).swap({
58
+ tokenIn: underlying,
59
+ tokenOut: borrowToken,
60
+ amount: debt,
61
+ keep: eq(collateralToken, underlying) ? collateralAmount : 0n
62
+ });
63
+ const unwrap = unwrapsPayout ? await sdk.accounts.assembleRWAUnwrapCalls(debt, creditManager) : void 0;
64
+ if (unwrapsPayout && !unwrap) throw new Error(`borrow: no unwrap calls found for ${borrowToken}`);
65
+ const assets = [{
66
+ token: collateralToken,
67
+ balance: collateralAmount
68
+ }];
69
+ const quotaIncrease = borrowCollateralQuota({
70
+ sdk,
71
+ creditManager,
72
+ assets,
73
+ quotaReserve
74
+ });
75
+ assertGrowthAllowed({
76
+ sdk,
77
+ suite,
78
+ market,
79
+ before: [],
80
+ after: assets
81
+ });
82
+ assertQuotaAvailable(sdk, market, quotaIncrease);
83
+ const snapshot = {
84
+ creditManager,
85
+ assets,
86
+ quotas: quotaIncrease,
87
+ totalDebt: debt,
88
+ totalValue: margin
89
+ };
90
+ const projection = sdk.positions.projection(snapshot, { availableLiquidityChange: -debt });
91
+ assertCollateralised(projection.safeHealthFactor, true);
92
+ const priced = (token, balance) => market.priceOracle.toTokenAmount(token, balance);
93
+ const payout = unwrapsPayout ? toTargetDecimals(debt, underlying, borrowToken, sdk) : leg ? leg.amount : debt;
94
+ return {
95
+ ...projection,
96
+ currentPrice: sdk.positions.currentPrice(snapshot),
97
+ executionCost: void 0,
98
+ priceImpact: await collectPriceImpact(leg?.probe ? [leg.probe] : [], {
99
+ totalValue: margin,
100
+ netValue: margin - debt,
101
+ toUnderlying: (from, amount) => convert(from, underlying, amount)
102
+ }),
103
+ collateral: priced(collateralToken, collateralAmount),
104
+ borrowed: priced(borrowToken, payout),
105
+ minBorrowed: priced(borrowToken, leg ? leg.minAmount : payout),
106
+ slippage,
107
+ quotaIncrease,
108
+ calls: leg ? [...leg.calls] : unwrap ?? [],
109
+ creditAccount: existing?.creditAccount
110
+ };
111
+ }
112
+ /**
113
+ * Quota a borrow buys for its collateral on an account that holds none yet.
114
+ *
115
+ * Shared with `maxBorrow`, because a quota short of the collateral's weighted
116
+ * value is what caps the loan: the two have to read the same number or the
117
+ * ceiling one offers is one the other refuses.
118
+ *
119
+ * @param props - {@link BorrowCollateralQuotaProps}
120
+ **/
121
+ function borrowCollateralQuota({ sdk, creditManager, assets, quotaReserve }) {
122
+ const suite = sdk.marketRegister.findCreditManager(creditManager);
123
+ const market = sdk.marketRegister.findByCreditManager(creditManager);
124
+ return getQuotasForUpdate({
125
+ assetsBefore: [],
126
+ assetsAfter: assets,
127
+ initialQuotas: [],
128
+ quotaReserve,
129
+ underlyingToken: market.pool.underlying,
130
+ liquidationThresholds: suite.creditManager.liquidationThresholds,
131
+ quotas: market.pool.pqk.quotas,
132
+ maxDebt: suite.creditFacade.maxDebt,
133
+ convert: (from, to, amount) => market.priceOracle.safeConvert(from, to, amount).value
134
+ }).quotaIncrease;
135
+ }
136
+ //#endregion
137
+ export { borrowCollateralQuota, buildBorrowState };
@@ -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,34 @@ 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.
139
+ *
140
+ * Reads no account, like {@link leverageBand}: the borrow opens one. The
141
+ * collateral is valued the way the transaction will be judged, at safe
142
+ * prices and under the quota the borrow buys, and the answer is then held to
143
+ * what the market will lend.
144
+ *
145
+ * A ceiling, not a verdict: the facade's `minDebt` is a floor and is not
146
+ * applied here, so collateral too small for this market still answers with
147
+ * what it carries and {@link borrowIntent} is the one that refuses the loan.
148
+ *
149
+ * The default is {@link MIN_HF_LIMITED}, the threshold a form holds an
150
+ * account to.
151
+ *
152
+ * @param props - The manager, the SDK holding its market, the collateral put
153
+ * up, the token to be paid in, and optionally the health factor to land at
154
+ * @returns Amount in the payout token's units; `0n` where no loan of this
155
+ * shape can be funded at any size
156
+ */
157
+ maxBorrow(props) {
158
+ const { targetHF = MIN_HF_LIMITED, ...rest } = props;
159
+ return maxBorrow({
160
+ ...rest,
161
+ targetHF: targetHF + 2n
162
+ });
163
+ }
164
+ /**
135
165
  * Previews the same operation when its source only redeems through its
136
166
  * issuer: a Securitize dsToken, a Mellow share.
137
167
  *
@@ -277,6 +307,26 @@ var CreditAccountOperationsService = class extends SDKConstruct {
277
307
  };
278
308
  }
279
309
  /**
310
+ * Previews opening an account that holds nothing.
311
+ *
312
+ * Nothing is put up, drawn or routed, so there is no state to build and no
313
+ * guard to run beyond the market's own: a paused or expired facade takes no
314
+ * multicall, and an opening is a multicall like any other. Answers the same
315
+ * envelope its two neighbours do so a caller branches on `ok` throughout.
316
+ *
317
+ * @param props - The SDK holding the market, and the manager to open in
318
+ * @returns `{ ok: true }`, or `{ ok: false, error }` when the market takes
319
+ * no transaction right now
320
+ */
321
+ async openEmptyAccountIntent(props) {
322
+ try {
323
+ assertMarketOperable(props.sdk.marketRegister.findCreditManager(props.creditManager));
324
+ return { ok: true };
325
+ } catch (e) {
326
+ return asSDKError(e);
327
+ }
328
+ }
329
+ /**
280
330
  * Previews opening a brand-new leveraged position.
281
331
  *
282
332
  * Sits apart from {@link startIntent} because there is no account yet: nothing
@@ -298,6 +348,37 @@ var CreditAccountOperationsService = class extends SDKConstruct {
298
348
  return asSDKError(e);
299
349
  }
300
350
  }
351
+ /**
352
+ * Previews taking a loan against collateral, on an account this same
353
+ * transaction opens.
354
+ *
355
+ * Sits beside {@link openStrategyIntent} rather than under
356
+ * {@link startIntent} for the same reason: there is no account yet, and the
357
+ * output feeds `sdk.accounts.openCA`. What sets it apart from an opening is
358
+ * where the loan goes — out to the wallet rather than into a position — so
359
+ * the debt is named outright instead of following from a leverage, and the
360
+ * collateral is the only thing the account is left holding.
361
+ *
362
+ * `creditAccount` draws the loan on one the wallet already holds instead of
363
+ * opening another, as an opening takes one.
364
+ *
365
+ * @param props - Credit manager, the collateral the wallet puts up and the
366
+ * payout it asks for
367
+ * @returns Debt, the payout's two branches and the projection the account
368
+ * lands in, or `{ ok: false, error }` when the loan is not viable — a debt
369
+ * outside the facade's limits, collateral that cannot carry it, a payout the
370
+ * router has no path to
371
+ */
372
+ async borrowIntent(props) {
373
+ try {
374
+ return {
375
+ ok: true,
376
+ state: await buildBorrowState(props)
377
+ };
378
+ } catch (e) {
379
+ return asSDKError(e);
380
+ }
381
+ }
301
382
  /** Plan → realise → wrap. Unviable requests become `{ ok: false }`. */
302
383
  async #preview(props, plan) {
303
384
  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 };