@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.
- package/dist/cjs/onchain/accounts/index.js +3 -2
- package/dist/cjs/onchain/accounts/intents/guards.js +71 -14
- package/dist/cjs/onchain/accounts/intents/index.js +12 -16
- package/dist/cjs/onchain/accounts/intents/math.js +25 -8
- package/dist/cjs/onchain/accounts/intents/maxWithdrawCollateral.js +2 -5
- package/dist/cjs/onchain/accounts/intents/open-strategy.js +5 -6
- package/dist/cjs/onchain/accounts/intents/plan.js +43 -21
- package/dist/cjs/onchain/accounts/intents/realize.js +23 -8
- package/dist/cjs/onchain/accounts/intents/refusal.js +28 -0
- package/dist/cjs/onchain/accounts/intents/tail.js +2 -2
- package/dist/cjs/onchain/accounts/intents/types.js +0 -16
- package/dist/cjs/onchain/index.js +3 -2
- package/dist/cjs/onchain/utils/bigint-math.js +9 -0
- package/dist/cjs/sdk/prepare/PrepareApi.js +19 -20
- package/dist/esm/onchain/accounts/index.js +2 -2
- package/dist/esm/onchain/accounts/intents/guards.js +71 -14
- package/dist/esm/onchain/accounts/intents/index.js +11 -16
- package/dist/esm/onchain/accounts/intents/math.js +25 -8
- package/dist/esm/onchain/accounts/intents/maxWithdrawCollateral.js +2 -5
- package/dist/esm/onchain/accounts/intents/open-strategy.js +6 -7
- package/dist/esm/onchain/accounts/intents/plan.js +43 -21
- package/dist/esm/onchain/accounts/intents/realize.js +23 -8
- package/dist/esm/onchain/accounts/intents/refusal.js +26 -0
- package/dist/esm/onchain/accounts/intents/tail.js +2 -2
- package/dist/esm/onchain/accounts/intents/types.js +1 -16
- package/dist/esm/onchain/index.js +2 -2
- package/dist/esm/onchain/utils/bigint-math.js +9 -0
- package/dist/esm/sdk/GearboxSDK.js +2 -2
- package/dist/esm/sdk/prepare/PrepareApi.js +19 -20
- package/dist/types/onchain/accounts/index.d.ts +3 -2
- package/dist/types/onchain/accounts/intents/guards.d.ts +23 -5
- package/dist/types/onchain/accounts/intents/index.d.ts +4 -6
- package/dist/types/onchain/accounts/intents/math.d.ts +2 -1
- package/dist/types/onchain/accounts/intents/refusal.d.ts +175 -0
- package/dist/types/onchain/accounts/intents/types.d.ts +7 -71
- package/dist/types/onchain/index.d.ts +3 -2
- package/dist/types/onchain/utils/bigint-math.d.ts +9 -0
- package/dist/types/sdk/index.d.ts +3 -1
- package/dist/types/sdk/prepare/index.d.ts +3 -1
- package/dist/types/sdk/prepare/types.d.ts +14 -26
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
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
|
|
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
|
|
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 =
|
|
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/
|
|
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 "./
|
|
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
|
-
|
|
22
|
-
if (suite.
|
|
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)
|
|
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",
|
|
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
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
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
|
-
|
|
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",
|
|
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 "./
|
|
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:
|
|
174
|
-
delayed:
|
|
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
|
|
185
|
-
if (
|
|
186
|
+
const chosen = instantRefusal ?? delayedRefusal;
|
|
187
|
+
if (chosen === void 0) throw new Error("intentRoutes: a route neither answered nor refused");
|
|
186
188
|
return {
|
|
187
|
-
|
|
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
|
-
|
|
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 "./
|
|
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",
|
|
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
|
-
|
|
48
|
-
|
|
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 "
|
|
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
|
-
|
|
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,
|