@gearbox-protocol/sdk 16.0.0-next.8 → 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 (56) 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 +23 -6
  5. package/dist/cjs/onchain/accounts/intents/open-strategy.js +5 -6
  6. package/dist/cjs/onchain/accounts/intents/plan.js +43 -21
  7. package/dist/cjs/onchain/accounts/intents/realize.js +23 -8
  8. package/dist/cjs/onchain/accounts/intents/refusal.js +28 -0
  9. package/dist/cjs/onchain/accounts/intents/tail.js +2 -2
  10. package/dist/cjs/onchain/accounts/intents/types.js +0 -16
  11. package/dist/cjs/onchain/index.js +3 -2
  12. package/dist/cjs/sdk/prepare/PrepareApi.js +19 -20
  13. package/dist/esm/dev/AccountOpener.js +1 -1
  14. package/dist/esm/dev/withdrawalUtils.js +1 -1
  15. package/dist/esm/onchain/accounts/CreditAccountsServiceV310.js +2 -2
  16. package/dist/esm/onchain/accounts/index.js +2 -2
  17. package/dist/esm/onchain/accounts/intents/guards.js +71 -14
  18. package/dist/esm/onchain/accounts/intents/index.js +11 -16
  19. package/dist/esm/onchain/accounts/intents/math.js +23 -6
  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/accounts/liquidations/LiquidationsService.js +1 -1
  27. package/dist/esm/onchain/accounts/withdrawal-compressor/RedemptionLoggerV310Contract.js +1 -1
  28. package/dist/esm/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV310Contract.js +1 -1
  29. package/dist/esm/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV311Contract.js +1 -1
  30. package/dist/esm/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV313Contract.js +1 -1
  31. package/dist/esm/onchain/base/TokensMeta.js +3 -3
  32. package/dist/esm/onchain/chain/detectNetwork.js +1 -1
  33. package/dist/esm/onchain/core/createAddressProvider.js +1 -1
  34. package/dist/esm/onchain/index.js +2 -2
  35. package/dist/esm/onchain/market/adapters/contracts/AccountMigratorAdapterContract.js +1 -1
  36. package/dist/esm/onchain/market/adapters/contracts/ERC4626AdapterContract.js +1 -1
  37. package/dist/esm/onchain/market/credit/CreditFacadeV310BaseContract.js +1 -1
  38. package/dist/esm/onchain/market/pool/PoolV310Contract.js +1 -1
  39. package/dist/esm/onchain/market/zapper/IETHZapperContract.js +1 -1
  40. package/dist/esm/onchain/market/zapper/ZapperContract.js +1 -1
  41. package/dist/esm/onchain/pools/PoolService.js +1 -1
  42. package/dist/esm/onchain/utils/viem/simulateWithPriceUpdates.js +1 -1
  43. package/dist/esm/preview/simulate/simulatePoolOperation.js +1 -1
  44. package/dist/esm/preview/trace/extractTransfers.js +1 -1
  45. package/dist/esm/sdk/prepare/PrepareApi.js +19 -20
  46. package/dist/types/onchain/accounts/index.d.ts +3 -2
  47. package/dist/types/onchain/accounts/intents/guards.d.ts +23 -5
  48. package/dist/types/onchain/accounts/intents/index.d.ts +4 -6
  49. package/dist/types/onchain/accounts/intents/math.d.ts +2 -1
  50. package/dist/types/onchain/accounts/intents/refusal.d.ts +175 -0
  51. package/dist/types/onchain/accounts/intents/types.d.ts +7 -71
  52. package/dist/types/onchain/index.d.ts +3 -2
  53. package/dist/types/sdk/index.d.ts +3 -1
  54. package/dist/types/sdk/prepare/index.d.ts +3 -1
  55. package/dist/types/sdk/prepare/types.d.ts +14 -26
  56. package/package.json +1 -1
@@ -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;
@@ -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.
@@ -1,9 +1,9 @@
1
- import { ierc20Abi } from "../abi/iERC20.js";
2
1
  import { iCreditFacadeV310Abi } from "../abi/310/generated.js";
3
2
  import { AddressMap } from "../onchain/utils/AddressMap.js";
4
3
  import { AddressSet } from "../onchain/utils/AddressSet.js";
5
4
  import { AssetsMap } from "../onchain/utils/AssetsMap.js";
6
5
  import { childLogger } from "../onchain/utils/childLogger.js";
6
+ import { ierc20Abi } from "../abi/iERC20.js";
7
7
  import "../onchain/constants/addresses.js";
8
8
  import { MAX_UINT256, PERCENTAGE_FACTOR } from "../onchain/constants/math.js";
9
9
  import { SDKConstruct } from "../onchain/base/SDKConstruct.js";
@@ -1,6 +1,6 @@
1
- import { iWithdrawalCompressorV313Abi } from "../abi/IWithdrawalCompressorV313.js";
2
1
  import { getNetworkType } from "../onchain/chain/chains.js";
3
2
  import { getWithdrawalCompressorAddress } from "../onchain/accounts/withdrawal-compressor/addresses.js";
3
+ import { iWithdrawalCompressorV313Abi } from "../abi/IWithdrawalCompressorV313.js";
4
4
  import "../onchain/index.js";
5
5
  import { iMidasDataFeedAbi, iMidasRedemptionVaultAbi, midasGatewayAbi, midasRedeemerAbi, midasRedemptionVaultPhantomTokenAbi, securitizeRedeemerAbi, securitizeRedemptionGatewayAbi, securitizeRedemptionPhantomTokenAbi } from "./withdrawalAbi.js";
6
6
  import { erc20Abi, hexToString, parseAbi, parseEther } from "viem";
@@ -1,5 +1,3 @@
1
- import { iBaseRewardPoolAbi } from "../../abi/iBaseRewardPool.js";
2
- import { ierc4626AdapterAbi } from "../../abi/ierc4626Adapter.js";
3
1
  import { AP_REWARDS_COMPRESSOR } from "../constants/address-provider.js";
4
2
  import { ADDRESS_0X0 } from "../constants/addresses.js";
5
3
  import { MAX_UINT256 } from "../constants/math.js";
@@ -10,6 +8,8 @@ import "../base/index.js";
10
8
  import { AccountBotsService } from "./bots/AccountBotsService.js";
11
9
  import "./bots/index.js";
12
10
  import { rewardsCompressorAbi } from "../../abi/compressors/rewardsCompressor.js";
11
+ import { iBaseRewardPoolAbi } from "../../abi/iBaseRewardPool.js";
12
+ import { ierc4626AdapterAbi } from "../../abi/ierc4626Adapter.js";
13
13
  import { expectedBalanceDeltas } from "../market/credit/expectedBalanceDeltas.js";
14
14
  import "../market/index.js";
15
15
  import { CreditAccountCompressor } from "./credit-account-compressor/CreditAccountCompressor.js";
@@ -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,6 +1,6 @@
1
1
  import { BigIntMath } from "../../utils/bigint-math.js";
2
2
  import { LEVERAGE_DECIMALS } from "../../constants/math.js";
3
- import { IntentPreviewError } from "./types.js";
3
+ import { IntentPreviewError } from "./refusal.js";
4
4
  //#region src/onchain/accounts/intents/math.ts
5
5
  /** Debt that realises `leverage` on `collateral`: `D = C · (L − 1)`. */
6
6
  function debtForLeverage(collateral, leverage) {
@@ -14,7 +14,7 @@ function debtForLeverage(collateral, leverage) {
14
14
  * it, so no rounding is introduced on the way.
15
15
  */
16
16
  function proportionalDebt(position, collateralDelta) {
17
- 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");
18
18
  return position.debt * collateralDelta / position.collateral;
19
19
  }
20
20
  /**
@@ -37,15 +37,32 @@ function maxProportionalWithdrawal(position, band) {
37
37
  }
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 };
@@ -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,
@@ -1,6 +1,6 @@
1
1
  import { MAX_UINT256, 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 { assertDebtInBand, assertLeverageAtLeastOne, debtForLeverage, proportionalDebt } from "./math.js";
6
6
  //#region src/onchain/accounts/intents/plan.ts
@@ -23,18 +23,27 @@ function planAdjustLeverage(intent, view) {
23
23
  const T = intent.token ?? positionToken(view, "adjustLeverage");
24
24
  if (delta > 0n) return [borrow(delta), convert(U, T, delta)];
25
25
  const shortfall = -delta - view.balanceOf(U);
26
- if (shortfall > 0n && eq(T, U)) throw new IntentPreviewError("insufficientSourceBalance", `adjustLeverage: needs ${-delta} underlying, account holds ${view.balanceOf(U)}`);
26
+ if (shortfall > 0n && eq(T, U)) throw new IntentPreviewError("insufficientSourceBalance", {
27
+ required: {
28
+ token: U,
29
+ balance: -delta
30
+ },
31
+ held: {
32
+ token: U,
33
+ balance: view.balanceOf(U)
34
+ }
35
+ }, `adjustLeverage: needs ${-delta} underlying, account holds ${view.balanceOf(U)}`);
27
36
  return [...shortfall > 0n ? [convert(T, U, view.price(U, T, shortfall))] : [], repay(-delta)];
28
37
  }
29
38
  /** Intents 1.1 / 1.2: collateral grows by `a`, debt grows to match. */
30
39
  function planDeposit(intent, view) {
31
40
  assertPositive(intent.amount, "deposit");
32
41
  const U = view.underlying;
33
- if (!eq(intent.token, U) && !(view.rwaAsset && eq(intent.token, view.rwaAsset))) throw new IntentPreviewError("unsupportedCollateralToken", `deposit: only ${U}${view.rwaAsset ? ` or ${view.rwaAsset}` : ""} can be deposited, got ${intent.token}`);
42
+ if (!eq(intent.token, U) && !(view.rwaAsset && eq(intent.token, view.rwaAsset))) throw new IntentPreviewError("unsupportedCollateralToken", { token: intent.token }, `deposit: only ${U}${view.rwaAsset ? ` or ${view.rwaAsset}` : ""} can be deposited, got ${intent.token}`);
34
43
  const aU = view.price(intent.token, U, intent.amount);
35
44
  const debtDelta = intent.targetLeverage === void 0 ? proportionalDebt(view, aU) : debtForLeverage(view.collateral + aU, intent.targetLeverage) - view.debt;
36
- if (debtDelta < 0n) throw new IntentPreviewError("leverageOutOfRange", `deposit: target leverage ${intent.targetLeverage} would require repaying debt`);
37
- assertDebtInBand(view.debt + debtDelta, view.band);
45
+ if (debtDelta < 0n) throw new IntentPreviewError("leverageOutOfRange", void 0, `deposit: target leverage ${intent.targetLeverage} would require repaying debt`);
46
+ assertDebtInBand(view.debt + debtDelta, view.band, U);
38
47
  const T = intent.positionToken ?? positionToken(view, "deposit");
39
48
  const depositStays = eq(intent.token, T);
40
49
  return [
@@ -61,11 +70,24 @@ function planRepay(intent, view) {
61
70
  assertPositive(intent.amount, "repay");
62
71
  const U = view.underlying;
63
72
  const fundsInU = eq(intent.token, U);
64
- if (!fundsInU && !(view.rwaAsset && eq(intent.token, view.rwaAsset))) throw new IntentPreviewError("unsupportedCollateralToken", `repay: only ${U}${view.rwaAsset ? ` or ${view.rwaAsset}` : ""} can be repaid with, got ${intent.token}`);
65
- if (view.debt <= 0n) throw new IntentPreviewError("debtOutOfRange", "repay: the account owes nothing");
73
+ if (!fundsInU && !(view.rwaAsset && eq(intent.token, view.rwaAsset))) throw new IntentPreviewError("unsupportedCollateralToken", { token: intent.token }, `repay: only ${U}${view.rwaAsset ? ` or ${view.rwaAsset}` : ""} can be repaid with, got ${intent.token}`);
74
+ if (view.debt <= 0n) throw new IntentPreviewError("debtOutOfRange", {
75
+ requested: {
76
+ token: U,
77
+ balance: view.debt
78
+ },
79
+ minDebt: {
80
+ token: U,
81
+ balance: view.band.minDebt
82
+ },
83
+ maxDebt: {
84
+ token: U,
85
+ balance: view.band.maxDebt
86
+ }
87
+ }, "repay: the account owes nothing");
66
88
  const funding = everything(intent.amount) ? view.price(U, intent.token, withMargin(view.debt)) : intent.amount;
67
89
  const repaid = min(view.price(intent.token, U, funding), view.debt);
68
- assertDebtInBand(view.debt - repaid, view.band);
90
+ assertDebtInBand(view.debt - repaid, view.band, U);
69
91
  return [
70
92
  add(intent.token, funding, intent.value),
71
93
  ...fundsInU ? [] : [convert(intent.token, U, funding)],
@@ -121,7 +143,7 @@ function planWithdrawDelayed(intent, view) {
121
143
  const { U, T, S, WU, dD, all } = withdrawShape(intent, view);
122
144
  if (all) {
123
145
  const held = view.balanceOf(S);
124
- if (held <= 0n) throw new IntentPreviewError("insufficientSourceBalance", `withdraw: account holds no ${S} to redeem`);
146
+ if (held <= 0n) throw new IntentPreviewError("insufficientSourceBalance", void 0, `withdraw: account holds no ${S} to redeem`);
125
147
  return [{
126
148
  kind: "request",
127
149
  token: S,
@@ -133,7 +155,7 @@ function planWithdrawDelayed(intent, view) {
133
155
  }
134
156
  }];
135
157
  }
136
- if (!eq(T, U) && !(view.rwaAsset && eq(T, view.rwaAsset))) throw new IntentPreviewError("noDelayedRoute", `withdraw: a delayed route cannot pay out in ${T}`);
158
+ if (!eq(T, U) && !(view.rwaAsset && eq(T, view.rwaAsset))) throw new IntentPreviewError("noDelayedRoute", { token: T }, `withdraw: a delayed route cannot pay out in ${T}`);
137
159
  const payoutIsSource = eq(T, S);
138
160
  return [{
139
161
  kind: "request",
@@ -159,9 +181,9 @@ function planWithdrawDelayed(intent, view) {
159
181
  */
160
182
  function planAdjustLeverageDelayed(intent, view) {
161
183
  const { U, delta } = leverageShape(intent, view);
162
- if (delta >= 0n) throw new IntentPreviewError("noDelayedRoute", "adjustLeverage: only deleveraging can settle with a delay");
184
+ if (delta >= 0n) throw new IntentPreviewError("noDelayedRoute", void 0, "adjustLeverage: only deleveraging can settle with a delay");
163
185
  const shortfall = -delta - view.balanceOf(U);
164
- if (shortfall <= 0n) throw new IntentPreviewError("noDelayedRoute", "adjustLeverage: idle underlying covers the repayment, nothing to redeem");
186
+ if (shortfall <= 0n) throw new IntentPreviewError("noDelayedRoute", void 0, "adjustLeverage: idle underlying covers the repayment, nothing to redeem");
165
187
  const T = intent.token ?? positionToken(view, "adjustLeverage");
166
188
  return [{
167
189
  kind: "request",
@@ -189,7 +211,7 @@ function planFinishWithdraw(intent, claimable, claimed, view) {
189
211
  const U = view.underlying;
190
212
  const T = intent.withdrawToken;
191
213
  const W = intent.withdrawAmount;
192
- if (!eq(T, U) && !(view.rwaAsset && eq(T, view.rwaAsset))) throw new IntentPreviewError("noDelayedRoute", `finishWithdraw: cannot pay out in ${T}`);
214
+ if (!eq(T, U) && !(view.rwaAsset && eq(T, view.rwaAsset))) throw new IntentPreviewError("noDelayedRoute", { token: T }, `finishWithdraw: cannot pay out in ${T}`);
193
215
  if (intent.debtRepaid === 0n) return [
194
216
  claim(claimable),
195
217
  convert(claimed.token, T, claimed.amount),
@@ -281,7 +303,7 @@ function withdrawShape(intent, view) {
281
303
  };
282
304
  }
283
305
  const WU = view.price(T, U, intent.amount);
284
- if (WU <= 0n) throw new IntentPreviewError("insufficientSourceBalance", `withdraw: cannot price ${intent.amount} of ${T}`);
306
+ if (WU <= 0n) throw new IntentPreviewError("insufficientSourceBalance", void 0, `withdraw: cannot price ${intent.amount} of ${T}`);
285
307
  if (WU >= view.collateral) {
286
308
  assertHasValue(view);
287
309
  return {
@@ -294,7 +316,7 @@ function withdrawShape(intent, view) {
294
316
  };
295
317
  }
296
318
  const dD = proportionalDebt(view, WU);
297
- assertDebtInBand(view.debt - dD, view.band);
319
+ assertDebtInBand(view.debt - dD, view.band, U);
298
320
  return {
299
321
  U,
300
322
  T,
@@ -311,9 +333,9 @@ function withdrawShape(intent, view) {
311
333
  */
312
334
  function leverageShape(intent, view) {
313
335
  assertLeverageAtLeastOne(intent.targetLeverage);
314
- if (view.collateral <= 0n) throw new IntentPreviewError("insufficientSourceBalance", "adjustLeverage: account has no collateral to lever");
336
+ if (view.collateral <= 0n) throw new IntentPreviewError("insufficientSourceBalance", void 0, "adjustLeverage: account has no collateral to lever");
315
337
  const target = debtForLeverage(view.collateral, intent.targetLeverage);
316
- assertDebtInBand(target, view.band);
338
+ assertDebtInBand(target, view.band, view.underlying);
317
339
  return {
318
340
  U: view.underlying,
319
341
  delta: target - view.debt
@@ -379,20 +401,20 @@ function payout(view, token, amount, to) {
379
401
  }
380
402
  function positionToken(view, flow) {
381
403
  const pick = view.fattest([view.underlying]);
382
- if (!pick) throw new IntentPreviewError("insufficientSourceBalance", `${flow}: no position token on the account`);
404
+ if (!pick) throw new IntentPreviewError("insufficientSourceBalance", void 0, `${flow}: no position token on the account`);
383
405
  return pick;
384
406
  }
385
407
  function sourceToken(view) {
386
408
  const pick = view.fattest();
387
- if (!pick) throw new IntentPreviewError("insufficientSourceBalance", "withdraw: account has no spendable balance");
409
+ if (!pick) throw new IntentPreviewError("insufficientSourceBalance", void 0, "withdraw: account has no spendable balance");
388
410
  return pick;
389
411
  }
390
412
  /** An account whose debt has eaten its collateral has nothing to hand over. */
391
413
  function assertHasValue(view) {
392
- if (view.collateral <= 0n) throw new IntentPreviewError("insufficientSourceBalance", `withdraw: nothing to withdraw, net value is ${view.collateral}`);
414
+ if (view.collateral <= 0n) throw new IntentPreviewError("insufficientSourceBalance", void 0, `withdraw: nothing to withdraw, net value is ${view.collateral}`);
393
415
  }
394
416
  function assertPositive(amount, flow) {
395
- if (amount <= 0n) throw new IntentPreviewError("insufficientSourceBalance", `${flow}: amount must be positive`);
417
+ if (amount <= 0n) throw new IntentPreviewError("insufficientSourceBalance", void 0, `${flow}: amount must be positive`);
396
418
  }
397
419
  //#endregion
398
420
  export { RAISED, planAddCollateral, planAdjustLeverage, planAdjustLeverageDelayed, planDeposit, planFinishClaimOnly, planFinishCloseAccount, planFinishDecreaseLeverage, planFinishWithdraw, planRepay, planWithdraw, planWithdrawAsset, planWithdrawDelayed };