@gearbox-protocol/sdk 16.0.0-next.7 → 16.0.0-next.9

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 (41) hide show
  1. package/dist/cjs/onchain/accounts/index.js +3 -2
  2. package/dist/cjs/onchain/accounts/intents/guards.js +71 -14
  3. package/dist/cjs/onchain/accounts/intents/index.js +12 -16
  4. package/dist/cjs/onchain/accounts/intents/math.js +25 -8
  5. package/dist/cjs/onchain/accounts/intents/maxWithdrawCollateral.js +2 -5
  6. package/dist/cjs/onchain/accounts/intents/open-strategy.js +5 -6
  7. package/dist/cjs/onchain/accounts/intents/plan.js +43 -21
  8. package/dist/cjs/onchain/accounts/intents/realize.js +23 -8
  9. package/dist/cjs/onchain/accounts/intents/refusal.js +28 -0
  10. package/dist/cjs/onchain/accounts/intents/tail.js +2 -2
  11. package/dist/cjs/onchain/accounts/intents/types.js +0 -16
  12. package/dist/cjs/onchain/index.js +3 -2
  13. package/dist/cjs/onchain/utils/bigint-math.js +9 -0
  14. package/dist/cjs/sdk/prepare/PrepareApi.js +19 -20
  15. package/dist/esm/onchain/accounts/index.js +2 -2
  16. package/dist/esm/onchain/accounts/intents/guards.js +71 -14
  17. package/dist/esm/onchain/accounts/intents/index.js +11 -16
  18. package/dist/esm/onchain/accounts/intents/math.js +25 -8
  19. package/dist/esm/onchain/accounts/intents/maxWithdrawCollateral.js +2 -5
  20. package/dist/esm/onchain/accounts/intents/open-strategy.js +6 -7
  21. package/dist/esm/onchain/accounts/intents/plan.js +43 -21
  22. package/dist/esm/onchain/accounts/intents/realize.js +23 -8
  23. package/dist/esm/onchain/accounts/intents/refusal.js +26 -0
  24. package/dist/esm/onchain/accounts/intents/tail.js +2 -2
  25. package/dist/esm/onchain/accounts/intents/types.js +1 -16
  26. package/dist/esm/onchain/index.js +2 -2
  27. package/dist/esm/onchain/utils/bigint-math.js +9 -0
  28. package/dist/esm/sdk/GearboxSDK.js +2 -2
  29. package/dist/esm/sdk/prepare/PrepareApi.js +19 -20
  30. package/dist/types/onchain/accounts/index.d.ts +3 -2
  31. package/dist/types/onchain/accounts/intents/guards.d.ts +23 -5
  32. package/dist/types/onchain/accounts/intents/index.d.ts +4 -6
  33. package/dist/types/onchain/accounts/intents/math.d.ts +2 -1
  34. package/dist/types/onchain/accounts/intents/refusal.d.ts +175 -0
  35. package/dist/types/onchain/accounts/intents/types.d.ts +7 -71
  36. package/dist/types/onchain/index.d.ts +3 -2
  37. package/dist/types/onchain/utils/bigint-math.d.ts +9 -0
  38. package/dist/types/sdk/index.d.ts +3 -1
  39. package/dist/types/sdk/prepare/index.d.ts +3 -1
  40. package/dist/types/sdk/prepare/types.d.ts +14 -26
  41. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_onchain_accounts_intents_types = require("./types.js");
2
+ const require_onchain_accounts_intents_refusal = require("./refusal.js");
3
3
  const require_onchain_accounts_intents_utils_router_path = require("./utils/router-path.js");
4
4
  const require_onchain_accounts_intents_plan = require("./plan.js");
5
5
  const require_onchain_accounts_intents_operations = require("./operations.js");
@@ -18,7 +18,7 @@ function planTail(args) {
18
18
  const { intent, claimable, view } = args;
19
19
  const claimed = () => {
20
20
  const output = require_onchain_accounts_intents_operations.instantOutput(claimable.outputs);
21
- if (!output) throw new require_onchain_accounts_intents_types.IntentPreviewError("insufficientSourceBalance", "finishIntent: the claim credits nothing to spend");
21
+ if (!output) throw new require_onchain_accounts_intents_refusal.IntentPreviewError("insufficientSourceBalance", void 0, "finishIntent: the claim credits nothing to spend");
22
22
  return output;
23
23
  };
24
24
  switch (intent.type) {
@@ -1,17 +1 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region src/onchain/accounts/intents/types.ts
3
- /**
4
- * Validation failure that maps onto {@link PreviewErrorReason} rather than
5
- * crashing the caller: thrown by builders, converted to `{ ok: false }` by
6
- * `CreditAccountOperationsService.startIntent`.
7
- */
8
- var IntentPreviewError = class extends Error {
9
- reason;
10
- constructor(reason, message) {
11
- super(message ?? reason);
12
- this.name = "IntentPreviewError";
13
- this.reason = reason;
14
- }
15
- };
16
- //#endregion
17
- exports.IntentPreviewError = IntentPreviewError;
@@ -193,7 +193,7 @@ require("./market/index.js");
193
193
  const require_onchain_accounts_credit_account_compressor_CreditAccountCompressorV310Contract = require("./accounts/credit-account-compressor/CreditAccountCompressorV310Contract.js");
194
194
  const require_onchain_accounts_credit_account_compressor_CreditAccountCompressor = require("./accounts/credit-account-compressor/CreditAccountCompressor.js");
195
195
  const require_onchain_accounts_CreditAccountsServiceV310 = require("./accounts/CreditAccountsServiceV310.js");
196
- const require_onchain_accounts_intents_types = require("./accounts/intents/types.js");
196
+ const require_onchain_accounts_intents_refusal = require("./accounts/intents/refusal.js");
197
197
  const require_onchain_accounts_intents_utils_credit_account_slice = require("./accounts/intents/utils/credit-account-slice.js");
198
198
  const require_onchain_accounts_intents_index = require("./accounts/intents/index.js");
199
199
  const require_onchain_accounts_liquidations_constants = require("./accounts/liquidations/constants.js");
@@ -337,7 +337,7 @@ exports.IERC20ZapperContract = require_onchain_market_zapper_IERC20ZapperContrac
337
337
  exports.IETHZapperContract = require_onchain_market_zapper_IETHZapperContract.IETHZapperContract;
338
338
  exports.InfinifiGatewayAdapterContract = require_onchain_market_adapters_contracts_InfinifiGatewayAdapterContract.InfinifiGatewayAdapterContract;
339
339
  exports.InfinifiUnwindingGatewayAdapterContract = require_onchain_market_adapters_contracts_InfinifiUnwindingGatewayAdapterContract.InfinifiUnwindingGatewayAdapterContract;
340
- exports.IntentPreviewError = require_onchain_accounts_intents_types.IntentPreviewError;
340
+ exports.IntentPreviewError = require_onchain_accounts_intents_refusal.IntentPreviewError;
341
341
  exports.InvalidDelayedIntentError = require_onchain_accounts_withdrawal_compressor_errors.InvalidDelayedIntentError;
342
342
  exports.KelpLRTDepositPoolAdapterContract = require_onchain_market_adapters_contracts_KelpLRTDepositPoolAdapterContract.KelpLRTDepositPoolAdapterContract;
343
343
  exports.KelpLRTWithdrawalManagerAdapterContract = require_onchain_market_adapters_contracts_KelpLRTWithdrawalManagerAdapterContract.KelpLRTWithdrawalManagerAdapterContract;
@@ -645,6 +645,7 @@ exports.percentFmt = require_onchain_utils_formatter.percentFmt;
645
645
  exports.pickStrategyTargetCollateral = require_onchain_market_credit_collateralUtils.pickStrategyTargetCollateral;
646
646
  exports.rayToBps = require_onchain_market_math.rayToBps;
647
647
  exports.rayToNumber = require_onchain_utils_formatter.rayToNumber;
648
+ exports.refuse = require_onchain_accounts_intents_refusal.refuse;
648
649
  exports.retry = require_onchain_utils_retry.retry;
649
650
  exports.rewardsFromTransfers = require_onchain_market_adapters_transferHelpers.rewardsFromTransfers;
650
651
  exports.sendRawTx = require_onchain_utils_viem_sendRawTx.sendRawTx;
@@ -42,6 +42,15 @@ var BigIntMath = class {
42
42
  * @returns A non-positive bigint representation of `a`.
43
43
  */
44
44
  static neg = (a) => a > 0 ? a * -1n : a;
45
+ /**
46
+ * Divides rounding toward positive infinity.
47
+ *
48
+ * @param a - Dividend; must not be negative.
49
+ * @param b - Divisor; must be positive — zero throws, negative returns
50
+ * nonsense rather than the ceiling.
51
+ * @returns The smallest integer that is at least `a / b`.
52
+ **/
53
+ static ceilDiv = (a, b) => (a + b - 1n) / b;
45
54
  };
46
55
  //#endregion
47
56
  exports.BigIntMath = BigIntMath;
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_onchain_utils_hex = require("../../onchain/utils/hex.js");
3
3
  const require_onchain_base_MultichainConstruct = require("../../onchain/base/MultichainConstruct.js");
4
+ const require_onchain_accounts_intents_refusal = require("../../onchain/accounts/intents/refusal.js");
4
5
  const require_onchain_accounts_intents_utils_credit_account_slice = require("../../onchain/accounts/intents/utils/credit-account-slice.js");
5
6
  const require_onchain_accounts_intents_index = require("../../onchain/accounts/intents/index.js");
6
7
  require("../../onchain/index.js");
@@ -36,10 +37,7 @@ var PrepareApi = class extends require_onchain_base_MultichainConstruct.Multicha
36
37
  network: position.chainId,
37
38
  run: async (sdk) => {
38
39
  const intent = resumable(params.intent ?? params.claimable.intent);
39
- if (!intent) return {
40
- ok: false,
41
- reason: "noRecordedIntent"
42
- };
40
+ if (!intent) return require_onchain_accounts_intents_refusal.refuse("noRecordedIntent", void 0);
43
41
  return service(sdk).finishIntent({
44
42
  intent,
45
43
  claimable: params.claimable,
@@ -58,10 +56,7 @@ var PrepareApi = class extends require_onchain_base_MultichainConstruct.Multicha
58
56
  const { marketRegister, pools } = this.sdk.chain(pool.chainId);
59
57
  const tokenIn = params.tokenIn ?? marketRegister.findByPool(pool.pool).pool.underlying;
60
58
  const tokenOut = lpRoute(params.tokenOut, () => pools.getDepositTokensOut(pool.pool, tokenIn));
61
- if (!tokenOut) return {
62
- ok: false,
63
- reason: "unsupportedTokenPair"
64
- };
59
+ if (!tokenOut) return unroutable(tokenIn, void 0);
65
60
  const preview = pools.simulateDeposit({
66
61
  pool: pool.pool,
67
62
  amount: params.amount,
@@ -74,10 +69,7 @@ var PrepareApi = class extends require_onchain_base_MultichainConstruct.Multicha
74
69
  wallet: params.wallet,
75
70
  meta: pools.getDepositMetadata(pool.pool, tokenIn, tokenOut)
76
71
  });
77
- if (!call) return {
78
- ok: false,
79
- reason: "unsupportedTokenPair"
80
- };
72
+ if (!call) return unroutable(tokenIn, tokenOut);
81
73
  return {
82
74
  ok: true,
83
75
  operations: [],
@@ -92,10 +84,7 @@ var PrepareApi = class extends require_onchain_base_MultichainConstruct.Multicha
92
84
  const { pools } = this.sdk.chain(pool.chainId);
93
85
  const tokenIn = params.tokenIn ?? pool.pool;
94
86
  const tokenOut = lpRoute(params.tokenOut, () => pools.getWithdrawalTokensOut(pool.pool, tokenIn));
95
- if (!tokenOut) return {
96
- ok: false,
97
- reason: "unsupportedTokenPair"
98
- };
87
+ if (!tokenOut) return unroutable(tokenIn, void 0);
99
88
  const preview = pools.simulateWithdraw({
100
89
  pool: pool.pool,
101
90
  amount: params.amount,
@@ -124,10 +113,7 @@ var PrepareApi = class extends require_onchain_base_MultichainConstruct.Multicha
124
113
  const { pools } = this.sdk.chain(pool.chainId);
125
114
  const tokenIn = params.tokenIn ?? pool.pool;
126
115
  const tokenOut = lpRoute(params.tokenOut, () => pools.getWithdrawalTokensOut(pool.pool, tokenIn));
127
- if (!tokenOut) return {
128
- ok: false,
129
- reason: "unsupportedTokenPair"
130
- };
116
+ if (!tokenOut) return unroutable(tokenIn, void 0);
131
117
  const preview = pools.simulateRedeem({
132
118
  pool: pool.pool,
133
119
  amount: params.amount,
@@ -328,6 +314,19 @@ function resumable(intent) {
328
314
  return intent ?? void 0;
329
315
  }
330
316
  /**
317
+ * A pool route the market does not offer, as the refusal a caller reads.
318
+ *
319
+ * `to` is absent where {@link lpRoute} found no output to name at all, which
320
+ * is the usual way of it; both are present where a pair exists but nothing of
321
+ * ours implements it.
322
+ **/
323
+ function unroutable(from, to) {
324
+ return require_onchain_accounts_intents_refusal.refuse("unsupportedTokenPair", {
325
+ from,
326
+ to
327
+ });
328
+ }
329
+ /**
331
330
  * Picks the route the operation takes out of `tokenIn`, as a value rather than
332
331
  * an exception: an unroutable or ambiguous pair is a request the caller can
333
332
  * fix, so it belongs in the `ok: false` half alongside the strategy refusals.
@@ -5,7 +5,7 @@ import { CreditAccountCompressorV310Contract } from "./credit-account-compressor
5
5
  import { CreditAccountCompressor } from "./credit-account-compressor/CreditAccountCompressor.js";
6
6
  import "./credit-account-compressor/index.js";
7
7
  import { CreditAccountsServiceV310 } from "./CreditAccountsServiceV310.js";
8
- import { IntentPreviewError } from "./intents/types.js";
8
+ import { IntentPreviewError, refuse } from "./intents/refusal.js";
9
9
  import { fetchCreditAccountSlice, toCreditAccountSlice } from "./intents/utils/credit-account-slice.js";
10
10
  import { CreditAccountOperationsService } from "./intents/index.js";
11
11
  import { LIQUIDATION_APPROVAL_BUFFER, LIQUIDATION_COMPRESSOR_V313_ADDRESS } from "./liquidations/constants.js";
@@ -25,4 +25,4 @@ import { toWithdrawalStatus } from "./withdrawal-compressor/types.js";
25
25
  import { WithdrawalCompressorV313Contract } from "./withdrawal-compressor/WithdrawalCompressorV313Contract.js";
26
26
  import { createWithdrawalCompressor } from "./withdrawal-compressor/createWithdrawalCompressor.js";
27
27
  import "./withdrawal-compressor/index.js";
28
- export { AbstractWithdrawalCompressorContract, AccountBotsService, CreditAccountCompressor, CreditAccountCompressorV310Contract, CreditAccountOperationsService, CreditAccountsServiceV310, DELAYED_INTENT_TYPES, DELAYED_INTENT_VERSION, IntentPreviewError, InvalidDelayedIntentError, LIQUIDATION_APPROVAL_BUFFER, LIQUIDATION_COMPRESSOR_V313_ADDRESS, LiquidationsService, MultichainLiquidationsService, PeripheryCompressorV310Contract, RedemptionLoggerV310Contract, WithdrawalCompressorV310Contract, WithdrawalCompressorV311Contract, WithdrawalCompressorV313Contract, createRedemptionLogger, createWithdrawalCompressor, decodeDelayedIntent, encodeDelayedIntent, fetchCreditAccountSlice, getWithdrawalCompressorAddress, iCreditAccountAbi, toClaimableWithdrawal, toCreditAccountSlice, toPendingWithdrawal, toRequestableWithdrawal, toWithdrawalStatus };
28
+ export { AbstractWithdrawalCompressorContract, AccountBotsService, CreditAccountCompressor, CreditAccountCompressorV310Contract, CreditAccountOperationsService, CreditAccountsServiceV310, DELAYED_INTENT_TYPES, DELAYED_INTENT_VERSION, IntentPreviewError, InvalidDelayedIntentError, LIQUIDATION_APPROVAL_BUFFER, LIQUIDATION_COMPRESSOR_V313_ADDRESS, LiquidationsService, MultichainLiquidationsService, PeripheryCompressorV310Contract, RedemptionLoggerV310Contract, WithdrawalCompressorV310Contract, WithdrawalCompressorV311Contract, WithdrawalCompressorV313Contract, createRedemptionLogger, createWithdrawalCompressor, decodeDelayedIntent, encodeDelayedIntent, fetchCreditAccountSlice, getWithdrawalCompressorAddress, iCreditAccountAbi, refuse, toClaimableWithdrawal, toCreditAccountSlice, toPendingWithdrawal, toRequestableWithdrawal, toWithdrawalStatus };
@@ -1,6 +1,6 @@
1
- import "../../constants/math.js";
1
+ import { PERCENTAGE_FACTOR } from "../../constants/math.js";
2
2
  import "../../constants/index.js";
3
- import { IntentPreviewError } from "./types.js";
3
+ import { IntentPreviewError } from "./refusal.js";
4
4
  import { eq } from "./utils/common.js";
5
5
  import { isPhantomToken } from "./utils/pick-token.js";
6
6
  //#region src/onchain/accounts/intents/guards.ts
@@ -12,14 +12,23 @@ import { isPhantomToken } from "./utils/pick-token.js";
12
12
  * pool has run out of what it lends. On-chain those come back as a reverted
13
13
  * multicall with a selector no form can explain, so they are read from the
14
14
  * loaded market and reported as refusals instead.
15
+ *
16
+ * Every guard here refuses something the market decides rather than something
17
+ * the arithmetic cannot do, which is why all six of their reasons are
18
+ * `blocking`: the walk that hit one still reached an end state, and a caller
19
+ * gets that state alongside the refusal.
15
20
  */
16
21
  /**
17
22
  * The facade takes no multicall while it is paused or past its expiration, so
18
23
  * every intent the engine previews would revert on arrival.
19
24
  */
20
25
  function assertMarketOperable(suite) {
21
- if (suite.isPaused) throw new IntentPreviewError("marketPaused", `${suite.creditManager.address} is paused`);
22
- if (suite.isExpired) throw new IntentPreviewError("marketExpired", `${suite.creditManager.address} expired at ${suite.creditFacade.expirationDate}`);
26
+ const creditManager = suite.creditManager.address;
27
+ if (suite.isPaused) throw new IntentPreviewError("marketPaused", { creditManager }, `${creditManager} is paused`);
28
+ if (suite.isExpired) throw new IntentPreviewError("marketExpired", {
29
+ creditManager,
30
+ expirationDate: suite.creditFacade.expirationDate
31
+ }, `${creditManager} expired at ${suite.creditFacade.expirationDate}`);
23
32
  }
24
33
  /**
25
34
  * What the pool will hand this manager in one transaction: the tightest of its
@@ -41,7 +50,19 @@ function borrowable(suite) {
41
50
  /** The pool has to be able to lend what the plan means to draw. */
42
51
  function assertCanBorrow(suite, amount) {
43
52
  const limit = borrowable(suite);
44
- if (amount > limit) throw new IntentPreviewError("insufficientPoolLiquidity", `borrow: ${amount} exceeds what the pool can lend now (${limit})`);
53
+ if (amount > limit) {
54
+ const token = suite.market.pool.underlying;
55
+ throw new IntentPreviewError("insufficientPoolLiquidity", {
56
+ requested: {
57
+ token,
58
+ balance: amount
59
+ },
60
+ available: {
61
+ token,
62
+ balance: limit
63
+ }
64
+ }, `borrow: ${amount} exceeds what the pool can lend now (${limit})`);
65
+ }
45
66
  }
46
67
  /**
47
68
  * What the account is allowed to end up holding more of than it started with.
@@ -61,9 +82,16 @@ function assertGrowthAllowed(args) {
61
82
  const underlying = market.pool.underlying;
62
83
  for (const { token, balance } of after) {
63
84
  if (balance <= (before.find((a) => eq(a.token, token))?.balance ?? 0n)) continue;
64
- if (forbidden.some((f) => eq(f, token))) throw new IntentPreviewError("forbiddenToken", `${token} is forbidden in this market and the plan buys more of it`);
85
+ if (forbidden.some((f) => eq(f, token))) throw new IntentPreviewError("forbiddenToken", { token }, `${token} is forbidden in this market and the plan buys more of it`);
65
86
  if (eq(token, underlying) || isPhantomToken(sdk, token)) continue;
66
- if (!market.pool.pqk.hasActiveQuota(token)) throw new IntentPreviewError("quotaLimitReached", `${token} takes no quota in this market, so it counts as no collateral`);
87
+ if (!market.pool.pqk.hasActiveQuota(token)) throw new IntentPreviewError("quotaLimitReached", {
88
+ token,
89
+ requested: void 0,
90
+ available: {
91
+ token: underlying,
92
+ balance: 0n
93
+ }
94
+ }, `${token} takes no quota in this market, so it counts as no collateral`);
67
95
  }
68
96
  }
69
97
  /**
@@ -71,10 +99,23 @@ function assertGrowthAllowed(args) {
71
99
  * and reverts if the collateral does not cover it, so a plan that lands the
72
100
  * account below water is refused here.
73
101
  *
74
- * The bar is the health factor of the state the plan projects: collateral under
75
- * liquidation thresholds, capped by quota, against the debt. Note that a
76
- * position already underwater cannot be nursed back one step at a time — the
77
- * check is on where the transaction ends, not on whether it improved things.
102
+ * The bar is the facade's own `1.0`, because this guard answers one question:
103
+ * would the transaction revert. It is deliberately not `MIN_HF_LIMITED`, and
104
+ * the three bars in this codebase are three different jobs:
105
+ *
106
+ * - here, `1.0` — what the facade enforces, so what a plan must clear to land;
107
+ * - `maxWithdrawCollateral` sizes at `MIN_HF_LIMITED + 2` — a *sizing* helper
108
+ * leaving headroom, which is not the same as a validity check;
109
+ * - `validateHF` refuses at or below `MIN_HF_LIMITED` — a form's own caution.
110
+ *
111
+ * Raising this one to `MIN_HF_LIMITED` was tried and reverted: it made
112
+ * `maxWithdraw` hand back a ceiling this guard then refused, and it blocked
113
+ * small top-ups of an account sitting in `[1.0, 1.01)` — the very operations
114
+ * that rescue it. A form wanting the stricter bar has `validateHF`.
115
+ *
116
+ * Note that a position already underwater cannot be nursed back one step at a
117
+ * time — the check is on where the transaction ends, not on whether it
118
+ * improved things.
78
119
  *
79
120
  * @remarks
80
121
  * The caller decides the pricing the factor was computed at: main prices, or
@@ -82,20 +123,36 @@ function assertGrowthAllowed(args) {
82
123
  * whose reserve feed the SDK cannot read keeps its main price, so a plan can
83
124
  * still be refused on-chain after passing here.
84
125
  */
85
- function assertCollateralised(healthFactorBps) {
86
- if (healthFactorBps < Number(10000n)) throw new IntentPreviewError("insufficientCollateral", `the account would end at a health factor of ${healthFactorBps}, below 1.0`);
126
+ function assertCollateralised(healthFactorBps, safePrices) {
127
+ const required = Number(PERCENTAGE_FACTOR);
128
+ if (healthFactorBps < required) throw new IntentPreviewError("insufficientCollateral", {
129
+ healthFactor: healthFactorBps,
130
+ required,
131
+ safePrices
132
+ }, `the account would end at a health factor of ${healthFactorBps}, below ${required}`);
87
133
  }
88
134
  /**
89
135
  * A quota can only be raised as far as the market still has room for: past the
90
136
  * token's limit the keeper takes nothing more, whoever is asking.
91
137
  */
92
138
  function assertQuotaHeadroom(market, increases) {
139
+ const underlying = market.pool.underlying;
93
140
  for (const { token, balance } of increases) {
94
141
  if (balance <= 0n) continue;
95
142
  const quota = market.pool.pqk.quotas.get(token);
96
143
  if (!quota) continue;
97
144
  const left = quota.limit - quota.totalQuoted;
98
- if (balance > left) throw new IntentPreviewError("quotaLimitReached", `${token} has ${left} of quota left, the plan needs ${balance}`);
145
+ if (balance > left) throw new IntentPreviewError("quotaLimitReached", {
146
+ token,
147
+ requested: {
148
+ token: underlying,
149
+ balance
150
+ },
151
+ available: {
152
+ token: underlying,
153
+ balance: left
154
+ }
155
+ }, `${token} has ${left} of quota left, the plan needs ${balance}`);
99
156
  }
100
157
  }
101
158
  //#endregion
@@ -1,6 +1,6 @@
1
1
  import { SDKConstruct } from "../../base/SDKConstruct.js";
2
2
  import { MIN_HF_LIMITED } from "../../../common-utils/utils/validation/validate-hf.js";
3
- import { IntentPreviewError } from "./types.js";
3
+ import { IntentPreviewError, refuse } from "./refusal.js";
4
4
  import { assertMarketOperable } from "./guards.js";
5
5
  import { maxProportionalWithdrawal } from "./math.js";
6
6
  import { maxWithdrawCollateral } from "./maxWithdrawCollateral.js";
@@ -169,9 +169,11 @@ var CreditAccountOperationsService = class extends SDKConstruct {
169
169
  const [instant, delayed] = await Promise.allSettled([this.startIntent(props), this.startDelayedIntent(props)]);
170
170
  const instantRoute = instant.status === "fulfilled" && instant.value.ok ? instant.value : void 0;
171
171
  const delayedRoute = delayed.status === "fulfilled" && delayed.value.ok ? delayed.value : void 0;
172
+ const instantRefusal = instant.status === "fulfilled" && !instant.value.ok ? instant.value : void 0;
173
+ const delayedRefusal = delayed.status === "fulfilled" && !delayed.value.ok ? delayed.value : void 0;
172
174
  const refused = {
173
- instant: instant.status === "fulfilled" && !instant.value.ok ? instant.value.reason : void 0,
174
- delayed: delayed.status === "fulfilled" && !delayed.value.ok ? delayed.value.reason : void 0
175
+ instant: instantRefusal?.reason,
176
+ delayed: delayedRefusal?.reason
175
177
  };
176
178
  if (instantRoute || delayedRoute) return {
177
179
  ok: true,
@@ -181,11 +183,10 @@ var CreditAccountOperationsService = class extends SDKConstruct {
181
183
  };
182
184
  const failed = [instant, delayed].find((s) => s.status === "rejected");
183
185
  if (failed?.status === "rejected") throw failed.reason;
184
- const reason = refused.instant ?? refused.delayed;
185
- if (reason === void 0) throw new Error("intentRoutes: a route neither answered nor refused");
186
+ const chosen = instantRefusal ?? delayedRefusal;
187
+ if (chosen === void 0) throw new Error("intentRoutes: a route neither answered nor refused");
186
188
  return {
187
- ok: false,
188
- reason,
189
+ ...chosen,
189
190
  refused
190
191
  };
191
192
  }
@@ -267,14 +268,8 @@ function plain(result) {
267
268
  }
268
269
  /** Unviable requests are values; anything else is a genuine failure. */
269
270
  function asFailure(e) {
270
- if (e instanceof IntentPreviewError) return {
271
- ok: false,
272
- reason: e.reason
273
- };
274
- if (isUnroutable(e)) return {
275
- ok: false,
276
- reason: "unsupportedTokenPair"
277
- };
271
+ if (e instanceof IntentPreviewError) return refuse(e.reason, e.detail);
272
+ if (isUnroutable(e)) return refuse("unsupportedTokenPair", void 0);
278
273
  throw e;
279
274
  }
280
275
  /**
@@ -290,4 +285,4 @@ function isUnroutable(e) {
290
285
  return false;
291
286
  }
292
287
  //#endregion
293
- export { CreditAccountOperationsService, IntentPreviewError, fetchCreditAccountSlice, toCreditAccountSlice };
288
+ export { CreditAccountOperationsService, IntentPreviewError, fetchCreditAccountSlice, refuse, toCreditAccountSlice };
@@ -1,5 +1,6 @@
1
+ import { BigIntMath } from "../../utils/bigint-math.js";
1
2
  import { LEVERAGE_DECIMALS } from "../../constants/math.js";
2
- import { IntentPreviewError } from "./types.js";
3
+ import { IntentPreviewError } from "./refusal.js";
3
4
  //#region src/onchain/accounts/intents/math.ts
4
5
  /** Debt that realises `leverage` on `collateral`: `D = C · (L − 1)`. */
5
6
  function debtForLeverage(collateral, leverage) {
@@ -13,7 +14,7 @@ function debtForLeverage(collateral, leverage) {
13
14
  * it, so no rounding is introduced on the way.
14
15
  */
15
16
  function proportionalDebt(position, collateralDelta) {
16
- if (position.collateral <= 0n) throw new IntentPreviewError("insufficientSourceBalance", "cannot preserve leverage on an account with no collateral");
17
+ if (position.collateral <= 0n) throw new IntentPreviewError("insufficientSourceBalance", void 0, "cannot preserve leverage on an account with no collateral");
17
18
  return position.debt * collateralDelta / position.collateral;
18
19
  }
19
20
  /**
@@ -31,21 +32,37 @@ function maxProportionalWithdrawal(position, band) {
31
32
  if (debt === 0n) return allButLast;
32
33
  const repayable = debt - band.minDebt;
33
34
  if (repayable < 0n) return 0n;
34
- const bound = ceilDiv(collateral * (repayable + 1n), debt) - 1n;
35
+ const bound = BigIntMath.ceilDiv(collateral * (repayable + 1n), debt) - 1n;
35
36
  return bound < allButLast ? bound : allButLast;
36
37
  }
37
- const ceilDiv = (a, b) => (a + b - 1n) / b;
38
38
  /** Total leverage cannot drop below 1x — that would be negative debt. */
39
39
  function assertLeverageAtLeastOne(leverage) {
40
- if (leverage < 100n) throw new IntentPreviewError("leverageOutOfRange", `target leverage ${leverage} is below 1x`);
40
+ if (leverage < 100n) throw new IntentPreviewError("leverageOutOfRange", {
41
+ requested: leverage,
42
+ min: LEVERAGE_DECIMALS
43
+ }, `target leverage ${leverage} is below 1x`);
41
44
  }
42
45
  /**
43
46
  * Rejects a debt the facade would revert on: zero is always fine (no loan at
44
47
  * all), anything else has to sit inside `[minDebt, maxDebt]`.
45
48
  */
46
- function assertDebtInBand(debt, band) {
47
- if (debt > band.maxDebt) throw new IntentPreviewError("debtOutOfRange", `debt ${debt} exceeds maxDebt ${band.maxDebt}`);
48
- if (debt > 0n && debt < band.minDebt) throw new IntentPreviewError("debtOutOfRange", `debt ${debt} is below minDebt ${band.minDebt}`);
49
+ function assertDebtInBand(debt, band, underlying) {
50
+ const detail = {
51
+ requested: {
52
+ token: underlying,
53
+ balance: debt
54
+ },
55
+ minDebt: {
56
+ token: underlying,
57
+ balance: band.minDebt
58
+ },
59
+ maxDebt: {
60
+ token: underlying,
61
+ balance: band.maxDebt
62
+ }
63
+ };
64
+ if (debt > band.maxDebt) throw new IntentPreviewError("debtOutOfRange", detail, `debt ${debt} exceeds maxDebt ${band.maxDebt}`);
65
+ if (debt > 0n && debt < band.minDebt) throw new IntentPreviewError("debtOutOfRange", detail, `debt ${debt} is below minDebt ${band.minDebt}`);
49
66
  }
50
67
  //#endregion
51
68
  export { assertDebtInBand, assertLeverageAtLeastOne, debtForLeverage, maxProportionalWithdrawal, proportionalDebt };
@@ -56,8 +56,8 @@ function maxWithdrawCollateral(props) {
56
56
  const targetLt = BigInt(creditManager.liquidationThresholds.get(target.token) ?? 0);
57
57
  const targetUsd = safeUsd(priceOracle, target.token, target.balance);
58
58
  if (targetLt === 0n || !targetUsd) return 0n;
59
- const keptUsd = ceilDiv(shortfall, targetLt);
60
- const kept = ceilDiv(target.balance * keptUsd, targetUsd);
59
+ const keptUsd = BigIntMath.ceilDiv(shortfall, targetLt);
60
+ const kept = BigIntMath.ceilDiv(target.balance * keptUsd, targetUsd);
61
61
  return kept >= target.balance ? 0n : target.balance - kept;
62
62
  }
63
63
  /** USD value at the main feed, or `undefined` when the token has no price. */
@@ -81,8 +81,5 @@ function safeUsd(oracle, token, amount) {
81
81
  return main;
82
82
  }
83
83
  }
84
- function ceilDiv(a, b) {
85
- return (a + b - 1n) / b;
86
- }
87
84
  //#endregion
88
85
  export { maxWithdrawCollateral };
@@ -1,8 +1,7 @@
1
- import "../../constants/math.js";
2
- import { IntentPreviewError } from "./types.js";
1
+ import { IntentPreviewError } from "./refusal.js";
3
2
  import { convertAmount } from "./utils/convert-amount.js";
4
3
  import { assertCanBorrow, assertCollateralised, assertGrowthAllowed, assertMarketOperable, assertQuotaHeadroom } from "./guards.js";
5
- import { assertDebtInBand, debtForLeverage } from "./math.js";
4
+ import { assertDebtInBand, assertLeverageAtLeastOne, debtForLeverage } from "./math.js";
6
5
  import { getQuotasForUpdate } from "./utils/quotas-for-update.js";
7
6
  import { createRouterPaths } from "./utils/router-path.js";
8
7
  import "./utils/index.js";
@@ -22,14 +21,14 @@ const NO_ACCOUNT = "0x0000000000000000000000000000000000000000";
22
21
  */
23
22
  async function previewOpenStrategy(props) {
24
23
  const { sdk, creditManager, collateral, targetToken, leverage, slippage = 0, quotaReserve, leftoverBalances = [] } = props;
25
- if (leverage < 100n) throw new IntentPreviewError("leverageOutOfRange", `openStrategy: leverage ${leverage} is below 1x`);
24
+ assertLeverageAtLeastOne(leverage);
26
25
  const suite = sdk.marketRegister.findCreditManager(creditManager);
27
26
  const market = sdk.marketRegister.findByCreditManager(creditManager);
28
27
  assertMarketOperable(suite);
29
28
  const underlying = market.pool.underlying.toLowerCase();
30
29
  const convert = convertAmount(sdk, creditManager);
31
30
  const margin = collateral.reduce((acc, a) => acc + convert(a.token, underlying, a.balance), 0n);
32
- if (margin <= 0n) throw new IntentPreviewError("insufficientSourceBalance", "openStrategy: collateral is worth nothing in underlying");
31
+ if (margin <= 0n) throw new IntentPreviewError("insufficientSourceBalance", void 0, "openStrategy: collateral is worth nothing in underlying");
33
32
  const debt = debtForLeverage(margin, leverage);
34
33
  const account = {
35
34
  creditAccount: NO_ACCOUNT,
@@ -41,7 +40,7 @@ async function previewOpenStrategy(props) {
41
40
  accountDebt: 0n,
42
41
  tokens: []
43
42
  };
44
- assertDebtInBand(debt, suite.creditFacade);
43
+ assertDebtInBand(debt, suite.creditFacade, underlying);
45
44
  assertCanBorrow(suite, debt);
46
45
  const leg = await createRouterPaths({
47
46
  sdk,
@@ -90,7 +89,7 @@ async function previewOpenStrategy(props) {
90
89
  timeToLiquidation: sdk.positions.timeToLiquidation(snapshot, projectedPool),
91
90
  liquidationPrice: sdk.positions.liquidationPrice(snapshot)
92
91
  };
93
- assertCollateralised(metrics.healthFactor);
92
+ assertCollateralised(metrics.healthFactor, false);
94
93
  return {
95
94
  debt,
96
95
  collateral: margin,