@gearbox-protocol/sdk 17.1.0-next.1 → 17.1.0-next.3
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/MultichainSDK.js +2 -27
- package/dist/cjs/onchain/OnchainSDK.js +5 -6
- package/dist/cjs/onchain/accounts/intents/borrow.js +47 -20
- package/dist/cjs/onchain/accounts/intents/guards.js +6 -4
- package/dist/cjs/onchain/accounts/intents/index.js +40 -9
- package/dist/cjs/onchain/accounts/intents/math.js +4 -10
- package/dist/cjs/onchain/accounts/intents/open-strategy.js +49 -17
- package/dist/cjs/onchain/accounts/intents/plan.js +8 -1
- package/dist/cjs/onchain/accounts/intents/realize.js +55 -24
- package/dist/cjs/onchain/index.js +0 -5
- package/dist/cjs/onchain/market/credit/CreditSuite.js +6 -5
- package/dist/cjs/onchain/market/index.js +0 -5
- package/dist/cjs/onchain/market/pricefeeds/feeds/RedstonePriceFeed.js +1 -7
- package/dist/cjs/onchain/market/pricefeeds/index.js +0 -5
- package/dist/cjs/onchain/market/pricefeeds/updates/UpdatablePriceFeedRegistry.js +5 -5
- package/dist/cjs/onchain/market/pricefeeds/updates/index.js +0 -5
- package/dist/cjs/onchain/options.js +1 -7
- package/dist/cjs/permissionless/index.js +1 -5
- package/dist/cjs/permissionless/utils/index.js +1 -5
- package/dist/cjs/permissionless/utils/price-update/get-price-update-tx.js +5 -0
- package/dist/cjs/permissionless/utils/price-update/get-updatable-feeds.js +6 -4
- package/dist/cjs/permissionless/utils/price-update/index.js +1 -5
- package/dist/cjs/sdk/prepare/PrepareApi.js +83 -32
- package/dist/esm/onchain/MultichainSDK.js +2 -27
- package/dist/esm/onchain/OnchainSDK.js +5 -6
- package/dist/esm/onchain/accounts/intents/borrow.js +47 -20
- package/dist/esm/onchain/accounts/intents/guards.js +6 -4
- package/dist/esm/onchain/accounts/intents/index.js +40 -9
- package/dist/esm/onchain/accounts/intents/math.js +5 -10
- package/dist/esm/onchain/accounts/intents/open-strategy.js +49 -17
- package/dist/esm/onchain/accounts/intents/plan.js +8 -1
- package/dist/esm/onchain/accounts/intents/realize.js +55 -24
- package/dist/esm/onchain/index.js +1 -3
- package/dist/esm/onchain/market/credit/CreditSuite.js +6 -5
- package/dist/esm/onchain/market/index.js +1 -3
- package/dist/esm/onchain/market/pricefeeds/feeds/RedstonePriceFeed.js +1 -7
- package/dist/esm/onchain/market/pricefeeds/index.js +1 -3
- package/dist/esm/onchain/market/pricefeeds/updates/UpdatablePriceFeedRegistry.js +5 -5
- package/dist/esm/onchain/market/pricefeeds/updates/index.js +1 -3
- package/dist/esm/onchain/options.js +1 -7
- package/dist/esm/permissionless/index.js +2 -3
- package/dist/esm/permissionless/utils/index.js +2 -3
- package/dist/esm/permissionless/utils/price-update/get-price-update-tx.js +5 -0
- package/dist/esm/permissionless/utils/price-update/get-updatable-feeds.js +6 -4
- package/dist/esm/permissionless/utils/price-update/index.js +2 -3
- package/dist/esm/sdk/prepare/PrepareApi.js +83 -32
- package/dist/types/onchain/MultichainSDK.d.ts +0 -10
- package/dist/types/onchain/OnchainSDK.d.ts +0 -10
- package/dist/types/onchain/accounts/index.d.ts +2 -2
- package/dist/types/onchain/accounts/intents/borrow.d.ts +5 -0
- package/dist/types/onchain/accounts/intents/guards.d.ts +6 -4
- package/dist/types/onchain/accounts/intents/index.d.ts +4 -4
- package/dist/types/onchain/accounts/intents/math.d.ts +2 -9
- package/dist/types/onchain/accounts/intents/open-strategy.d.ts +5 -0
- package/dist/types/onchain/accounts/intents/realize.d.ts +5 -0
- package/dist/types/onchain/accounts/intents/types.d.ts +49 -6
- package/dist/types/onchain/index.d.ts +4 -6
- package/dist/types/onchain/market/credit/CreditSuite.d.ts +10 -0
- package/dist/types/onchain/market/index.d.ts +3 -5
- package/dist/types/onchain/market/pricefeeds/feeds/RedstonePriceFeed.d.ts +1 -4
- package/dist/types/onchain/market/pricefeeds/index.d.ts +3 -5
- package/dist/types/onchain/market/pricefeeds/updates/UpdatablePriceFeedRegistry.d.ts +4 -4
- package/dist/types/onchain/market/pricefeeds/updates/index.d.ts +3 -5
- package/dist/types/onchain/market/pricefeeds/updates/types.d.ts +2 -16
- package/dist/types/onchain/options.d.ts +0 -7
- package/dist/types/permissionless/index.d.ts +2 -3
- package/dist/types/permissionless/utils/index.d.ts +2 -3
- package/dist/types/permissionless/utils/price-update/get-price-update-tx.d.ts +5 -0
- package/dist/types/permissionless/utils/price-update/get-updatable-feeds.d.ts +9 -3
- package/dist/types/permissionless/utils/price-update/index.d.ts +2 -3
- package/dist/types/sdk/index.d.ts +2 -2
- package/dist/types/sdk/prepare/PrepareApi.d.ts +16 -14
- package/dist/types/sdk/prepare/index.d.ts +2 -2
- package/dist/types/sdk/prepare/types.d.ts +21 -14
- package/package.json +1 -5
- package/dist/cjs/onchain/market/pricefeeds/updates/RedstoneUpdater.js +0 -193
- package/dist/cjs/onchain/market/pricefeeds/updates/fetchRedstonePayloads.js +0 -95
- package/dist/cjs/permissionless/utils/price-update/get-price-feeds.js +0 -60
- package/dist/esm/onchain/market/pricefeeds/updates/RedstoneUpdater.js +0 -191
- package/dist/esm/onchain/market/pricefeeds/updates/fetchRedstonePayloads.js +0 -94
- package/dist/esm/permissionless/utils/price-update/get-price-feeds.js +0 -57
- package/dist/types/onchain/market/pricefeeds/updates/RedstoneUpdater.d.ts +0 -37
- package/dist/types/onchain/market/pricefeeds/updates/fetchRedstonePayloads.d.ts +0 -62
- package/dist/types/permissionless/utils/price-update/get-price-feeds.d.ts +0 -22
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { getNetworkType } from "./chain/chains.js";
|
|
2
2
|
import { ChainNotConfiguredError, SdkMissingChainStateError, SdkStateVersionMismatchError, SdkSyncFailedError } from "./core/errors.js";
|
|
3
|
-
import { PriceUpdatesCache } from "./market/pricefeeds/updates/PriceUpdatesCache.js";
|
|
4
|
-
import "./market/pricefeeds/updates/index.js";
|
|
5
3
|
import { MultichainLiquidationsService } from "./accounts/liquidations/MultichainLiquidationsService.js";
|
|
6
4
|
import "./accounts/index.js";
|
|
7
5
|
import "./core/index.js";
|
|
@@ -18,7 +16,6 @@ import { OnchainSDK } from "./OnchainSDK.js";
|
|
|
18
16
|
**/
|
|
19
17
|
var MultichainSDK = class {
|
|
20
18
|
#chains;
|
|
21
|
-
#redstoneCache;
|
|
22
19
|
#logger;
|
|
23
20
|
/**
|
|
24
21
|
* Namespace for liquidatable credit accounts discovery across all
|
|
@@ -60,20 +57,9 @@ var MultichainSDK = class {
|
|
|
60
57
|
* @param options - Shared and per-chain attach options.
|
|
61
58
|
*/
|
|
62
59
|
async attach(options) {
|
|
63
|
-
if (options?.redstone) this.#redstoneCache = new PriceUpdatesCache({
|
|
64
|
-
ttl: options.redstone.cacheTTL ?? 225e3,
|
|
65
|
-
historical: !!options.redstone.historicTimestamp
|
|
66
|
-
});
|
|
67
60
|
await Promise.all([...this.#chains.entries()].map(([network, sdk]) => {
|
|
68
61
|
const perChainOpts = options?.perChain?.[network] ?? {};
|
|
69
|
-
return sdk.attach(
|
|
70
|
-
...perChainOpts,
|
|
71
|
-
redstone: {
|
|
72
|
-
...options?.redstone,
|
|
73
|
-
cache: this.#redstoneCache,
|
|
74
|
-
...perChainOpts.redstone
|
|
75
|
-
}
|
|
76
|
-
});
|
|
62
|
+
return sdk.attach(perChainOpts);
|
|
77
63
|
}));
|
|
78
64
|
this.#logger?.info("Attached all chains");
|
|
79
65
|
}
|
|
@@ -88,10 +74,6 @@ var MultichainSDK = class {
|
|
|
88
74
|
*/
|
|
89
75
|
hydrate(state, options) {
|
|
90
76
|
if (state.version !== 2) throw new SdkStateVersionMismatchError(2, state.version);
|
|
91
|
-
if (options?.redstone) this.#redstoneCache = new PriceUpdatesCache({
|
|
92
|
-
ttl: options.redstone.cacheTTL ?? 225e3,
|
|
93
|
-
historical: !!options.redstone.historicTimestamp
|
|
94
|
-
});
|
|
95
77
|
const stateByNetwork = new Map(state.chains.map((cs) => [cs.network, cs]));
|
|
96
78
|
for (const [network, sdk] of this.#chains) {
|
|
97
79
|
const chainState = stateByNetwork.get(network);
|
|
@@ -100,14 +82,7 @@ var MultichainSDK = class {
|
|
|
100
82
|
throw new SdkMissingChainStateError(network);
|
|
101
83
|
}
|
|
102
84
|
const perChainOpts = options?.perChain?.[network] ?? {};
|
|
103
|
-
sdk.hydrate(chainState,
|
|
104
|
-
...perChainOpts,
|
|
105
|
-
redstone: {
|
|
106
|
-
...options?.redstone,
|
|
107
|
-
cache: this.#redstoneCache,
|
|
108
|
-
...perChainOpts.redstone
|
|
109
|
-
}
|
|
110
|
-
});
|
|
85
|
+
sdk.hydrate(chainState, perChainOpts);
|
|
111
86
|
}
|
|
112
87
|
this.#logger?.info("Hydrated all chains");
|
|
113
88
|
}
|
|
@@ -151,7 +151,7 @@ var OnchainSDK = class extends ChainContractsRegister {
|
|
|
151
151
|
*/
|
|
152
152
|
async attach(options) {
|
|
153
153
|
if (this.#attached) throw new SdkAlreadyAttachedError();
|
|
154
|
-
const { addressProvider = ADDRESS_PROVIDER_V310, blockNumber, ignoreUpdateablePrices, ignoreMarkets, marketConfigurators: mcs
|
|
154
|
+
const { addressProvider = ADDRESS_PROVIDER_V310, blockNumber, ignoreUpdateablePrices, ignoreMarkets, marketConfigurators: mcs } = options ?? {};
|
|
155
155
|
const marketConfigurators = mcs ?? this.client.chain.defaultMarketConfigurators.keys();
|
|
156
156
|
const rwaFactories = options?.rwaFactories ?? this.client.chain.rwaFactories;
|
|
157
157
|
this.logger?.info({
|
|
@@ -166,8 +166,7 @@ var OnchainSDK = class extends ChainContractsRegister {
|
|
|
166
166
|
const block = await this.client.getBlock(blockNumber ? { blockNumber: BigInt(blockNumber) } : { blockTag: "latest" });
|
|
167
167
|
this.#currentBlock = block.number;
|
|
168
168
|
this.#timestamp = block.timestamp;
|
|
169
|
-
|
|
170
|
-
this.#priceFeeds = new UpdatablePriceFeedRegistry(this, { redstone });
|
|
169
|
+
this.#priceFeeds = new UpdatablePriceFeedRegistry(this);
|
|
171
170
|
this.logger?.debug(`attach block number ${this.currentBlock} timestamp ${this.timestamp}`);
|
|
172
171
|
this.#addressProvider = await createAddressProvider(this, addressProvider);
|
|
173
172
|
this.logger?.debug(`address provider version: ${this.#addressProvider.version}`);
|
|
@@ -258,11 +257,11 @@ var OnchainSDK = class extends ChainContractsRegister {
|
|
|
258
257
|
if (this.#attached) throw new SdkAlreadyAttachedError();
|
|
259
258
|
if (state.version !== 2) throw new SdkStateVersionMismatchError(2, state.version);
|
|
260
259
|
if (state.network !== this.networkType) throw new SdkChainMismatchError(this.networkType, state.network);
|
|
261
|
-
const { ignoreMarkets
|
|
260
|
+
const { ignoreMarkets } = options ?? {};
|
|
262
261
|
this.logger?.info({ networkType: this.networkType }, "hydrating sdk state");
|
|
263
262
|
this.#currentBlock = state.currentBlock;
|
|
264
263
|
this.#timestamp = state.timestamp;
|
|
265
|
-
this.#priceFeeds = new UpdatablePriceFeedRegistry(this
|
|
264
|
+
this.#priceFeeds = new UpdatablePriceFeedRegistry(this);
|
|
266
265
|
this.#addressProvider = hydrateAddressProvider(this, state.addressProvider);
|
|
267
266
|
this.logger?.debug(`address provider version: ${this.#addressProvider.version}`);
|
|
268
267
|
this.#marketRegister = new MarketRegister(this, ignoreMarkets);
|
|
@@ -345,7 +344,7 @@ var OnchainSDK = class extends ChainContractsRegister {
|
|
|
345
344
|
*/
|
|
346
345
|
async syncState(opts) {
|
|
347
346
|
let { blockNumber, timestamp, ignoreUpdateablePrices } = opts ?? {};
|
|
348
|
-
if (this.priceFeeds.historical && !ignoreUpdateablePrices) this.logger?.warn("syncState is not supported with
|
|
347
|
+
if (this.priceFeeds.historical && !ignoreUpdateablePrices) this.logger?.warn("syncState is not supported with historical price updates");
|
|
349
348
|
if (!blockNumber || !timestamp) {
|
|
350
349
|
const block = await this.client.getBlock({ blockTag: "latest" });
|
|
351
350
|
blockNumber = block.number;
|
|
@@ -29,7 +29,10 @@ async function buildBorrowState(props) {
|
|
|
29
29
|
const { sdk, creditManager, collateralAmount, borrowAmount, slippage = 0, quotaReserve, creditAccount: existing } = props;
|
|
30
30
|
const suite = sdk.marketRegister.findCreditManager(creditManager);
|
|
31
31
|
const market = sdk.marketRegister.findByCreditManager(creditManager);
|
|
32
|
+
const draft = props.draft ?? {};
|
|
33
|
+
Object.assign(draft, suite.creditOperationMarket(), { slippage });
|
|
32
34
|
assertMarketOperable(suite);
|
|
35
|
+
const priced = (token, balance) => market.priceOracle.toTokenAmount(token, balance);
|
|
33
36
|
const underlying = market.pool.underlying.toLowerCase();
|
|
34
37
|
const collateralToken = props.collateralToken.toLowerCase();
|
|
35
38
|
const borrowToken = props.borrowToken.toLowerCase();
|
|
@@ -41,8 +44,16 @@ async function buildBorrowState(props) {
|
|
|
41
44
|
if (collateralAmount <= 0n) throw new IntentPreviewError(insufficientBalance(), "borrow: no collateral supplied");
|
|
42
45
|
if (borrowAmount <= 0n) throw new IntentPreviewError(insufficientBalance(), "borrow: nothing to borrow");
|
|
43
46
|
const margin = convert(collateralToken, underlying, collateralAmount);
|
|
47
|
+
Object.assign(draft, {
|
|
48
|
+
collateral: priced(collateralToken, collateralAmount),
|
|
49
|
+
totalValue: market.toUnderlyingAmount(margin)
|
|
50
|
+
});
|
|
44
51
|
if (margin <= 0n) throw new IntentPreviewError(insufficientBalance(), "borrow: collateral is worth nothing in underlying");
|
|
45
52
|
const debt = eq(borrowToken, underlying) ? borrowAmount : unwrapsPayout ? toTargetDecimals(borrowAmount, borrowToken, underlying, sdk) : convert(borrowToken, underlying, borrowAmount);
|
|
53
|
+
Object.assign(draft, {
|
|
54
|
+
totalDebt: market.toUnderlyingAmount(debt),
|
|
55
|
+
netValue: market.toUnderlyingAmount(margin - debt)
|
|
56
|
+
});
|
|
46
57
|
assertDebtLimits(sdk, debt, suite.creditFacade, underlying);
|
|
47
58
|
assertCanBorrow(sdk, suite, debt);
|
|
48
59
|
const account = existing ?? unopenedAccountSlice({
|
|
@@ -72,6 +83,37 @@ async function buildBorrowState(props) {
|
|
|
72
83
|
assets,
|
|
73
84
|
quotaReserve
|
|
74
85
|
});
|
|
86
|
+
const payout = unwrapsPayout ? toTargetDecimals(debt, underlying, borrowToken, sdk) : leg ? leg.amount : debt;
|
|
87
|
+
/** The two sides of the loan, which the guards below weigh but do not move. */
|
|
88
|
+
const loan = {
|
|
89
|
+
collateral: priced(collateralToken, collateralAmount),
|
|
90
|
+
borrowed: priced(borrowToken, payout),
|
|
91
|
+
minBorrowed: priced(borrowToken, leg ? leg.minAmount : payout),
|
|
92
|
+
slippage,
|
|
93
|
+
quotaIncrease,
|
|
94
|
+
calls: leg ? [...leg.calls] : unwrap ?? [],
|
|
95
|
+
creditAccount: existing?.creditAccount
|
|
96
|
+
};
|
|
97
|
+
Object.assign(draft, loan);
|
|
98
|
+
const snapshot = {
|
|
99
|
+
creditManager,
|
|
100
|
+
assets,
|
|
101
|
+
quotas: quotaIncrease,
|
|
102
|
+
totalDebt: debt,
|
|
103
|
+
totalValue: margin
|
|
104
|
+
};
|
|
105
|
+
const settle = () => ({
|
|
106
|
+
...sdk.positions.projection(snapshot, { availableLiquidityChange: -debt }),
|
|
107
|
+
currentPrice: sdk.positions.currentPrice(snapshot),
|
|
108
|
+
executionCost: void 0
|
|
109
|
+
});
|
|
110
|
+
let reached;
|
|
111
|
+
try {
|
|
112
|
+
reached = settle();
|
|
113
|
+
Object.assign(draft, reached);
|
|
114
|
+
} catch {
|
|
115
|
+
reached = void 0;
|
|
116
|
+
}
|
|
75
117
|
assertGrowthAllowed({
|
|
76
118
|
sdk,
|
|
77
119
|
suite,
|
|
@@ -80,35 +122,20 @@ async function buildBorrowState(props) {
|
|
|
80
122
|
after: assets
|
|
81
123
|
});
|
|
82
124
|
assertQuotaAvailable(sdk, market, quotaIncrease);
|
|
83
|
-
const
|
|
84
|
-
creditManager,
|
|
85
|
-
assets,
|
|
86
|
-
quotas: quotaIncrease,
|
|
87
|
-
totalDebt: debt,
|
|
88
|
-
totalValue: margin
|
|
89
|
-
};
|
|
90
|
-
const projection = sdk.positions.projection(snapshot, { availableLiquidityChange: -debt });
|
|
125
|
+
const projection = reached ?? settle();
|
|
91
126
|
assertCollateralised(projection.safeHealthFactor, true);
|
|
92
|
-
const
|
|
93
|
-
const payout = unwrapsPayout ? toTargetDecimals(debt, underlying, borrowToken, sdk) : leg ? leg.amount : debt;
|
|
94
|
-
return {
|
|
127
|
+
const state = {
|
|
95
128
|
...projection,
|
|
96
|
-
currentPrice: sdk.positions.currentPrice(snapshot),
|
|
97
|
-
executionCost: void 0,
|
|
98
129
|
priceImpact: await collectPriceImpact(leg?.probe ? [leg.probe] : [], {
|
|
99
130
|
totalValue: margin,
|
|
100
131
|
netValue: margin - debt,
|
|
101
132
|
toUnderlying: (from, amount) => convert(from, underlying, amount),
|
|
102
133
|
toUnderlyingAmount: market.toUnderlyingAmount
|
|
103
134
|
}),
|
|
104
|
-
|
|
105
|
-
borrowed: priced(borrowToken, payout),
|
|
106
|
-
minBorrowed: priced(borrowToken, leg ? leg.minAmount : payout),
|
|
107
|
-
slippage,
|
|
108
|
-
quotaIncrease,
|
|
109
|
-
calls: leg ? [...leg.calls] : unwrap ?? [],
|
|
110
|
-
creditAccount: existing?.creditAccount
|
|
135
|
+
...loan
|
|
111
136
|
};
|
|
137
|
+
Object.assign(draft, state);
|
|
138
|
+
return state;
|
|
112
139
|
}
|
|
113
140
|
/**
|
|
114
141
|
* Quota a borrow buys for its collateral on an account that holds none yet.
|
|
@@ -13,7 +13,8 @@ import { eq } from "./utils/common.js";
|
|
|
13
13
|
import { isPhantomToken } from "./utils/pick-token.js";
|
|
14
14
|
//#region src/onchain/accounts/intents/guards.ts
|
|
15
15
|
/**
|
|
16
|
-
* What the market itself refuses,
|
|
16
|
+
* What the market itself refuses, read from its loaded state rather than
|
|
17
|
+
* learned from a revert.
|
|
17
18
|
*
|
|
18
19
|
* The planners answer for the arithmetic of an intent and the ledger for the
|
|
19
20
|
* balances it moves, but neither can see that the facade is paused or that the
|
|
@@ -22,9 +23,10 @@ import { isPhantomToken } from "./utils/pick-token.js";
|
|
|
22
23
|
* loaded market and reported as refusals instead.
|
|
23
24
|
*
|
|
24
25
|
* Every guard here refuses something the market decides rather than something
|
|
25
|
-
* the arithmetic cannot do,
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* the arithmetic cannot do, so where the account would have landed stays a
|
|
27
|
+
* question the walk can answer. The three that judge that end state — growth,
|
|
28
|
+
* quota limits, collateral — therefore run only once the projection exists,
|
|
29
|
+
* which is what lets them hand it back as `state` when they refuse.
|
|
28
30
|
*/
|
|
29
31
|
/**
|
|
30
32
|
* The facade takes no multicall while it is paused or past its expiration, so
|
|
@@ -219,7 +219,7 @@ var CreditAccountOperationsService = class extends SDKConstruct {
|
|
|
219
219
|
delayed
|
|
220
220
|
};
|
|
221
221
|
} catch (e) {
|
|
222
|
-
return
|
|
222
|
+
return asRefused(e, result.state);
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
/**
|
|
@@ -242,7 +242,13 @@ var CreditAccountOperationsService = class extends SDKConstruct {
|
|
|
242
242
|
* @returns Whichever routes are viable, or `{ ok: false, error }` when none is
|
|
243
243
|
*/
|
|
244
244
|
async intentRoutes(props) {
|
|
245
|
-
const [instant, delayed] = await Promise.allSettled([this.startIntent(
|
|
245
|
+
const [instant, delayed] = await Promise.allSettled([this.startIntent({
|
|
246
|
+
...props,
|
|
247
|
+
draft: props.draft ?? {}
|
|
248
|
+
}), this.startDelayedIntent({
|
|
249
|
+
...props,
|
|
250
|
+
draft: {}
|
|
251
|
+
})]);
|
|
246
252
|
const instantRoute = instant.status === "fulfilled" && instant.value.ok ? instant.value : void 0;
|
|
247
253
|
const delayedRoute = delayed.status === "fulfilled" && delayed.value.ok ? delayed.value : void 0;
|
|
248
254
|
const instantError = instant.status === "fulfilled" && !instant.value.ok ? instant.value.error : void 0;
|
|
@@ -339,13 +345,17 @@ var CreditAccountOperationsService = class extends SDKConstruct {
|
|
|
339
345
|
* leverage or the resulting debt is not viable
|
|
340
346
|
*/
|
|
341
347
|
async openStrategyIntent(props) {
|
|
348
|
+
const draft = props.draft ?? {};
|
|
342
349
|
try {
|
|
343
350
|
return {
|
|
344
351
|
ok: true,
|
|
345
|
-
state: await buildOpenStrategyState(
|
|
352
|
+
state: await buildOpenStrategyState({
|
|
353
|
+
...props,
|
|
354
|
+
draft
|
|
355
|
+
})
|
|
346
356
|
};
|
|
347
357
|
} catch (e) {
|
|
348
|
-
return
|
|
358
|
+
return asRefused(e, draft);
|
|
349
359
|
}
|
|
350
360
|
}
|
|
351
361
|
/**
|
|
@@ -370,24 +380,32 @@ var CreditAccountOperationsService = class extends SDKConstruct {
|
|
|
370
380
|
* router has no path to
|
|
371
381
|
*/
|
|
372
382
|
async borrowIntent(props) {
|
|
383
|
+
const draft = props.draft ?? {};
|
|
373
384
|
try {
|
|
374
385
|
return {
|
|
375
386
|
ok: true,
|
|
376
|
-
state: await buildBorrowState(
|
|
387
|
+
state: await buildBorrowState({
|
|
388
|
+
...props,
|
|
389
|
+
draft
|
|
390
|
+
})
|
|
377
391
|
};
|
|
378
392
|
} catch (e) {
|
|
379
|
-
return
|
|
393
|
+
return asRefused(e, draft);
|
|
380
394
|
}
|
|
381
395
|
}
|
|
382
396
|
/** Plan → realise → wrap. Unviable requests become `{ ok: false }`. */
|
|
383
397
|
async #preview(props, plan) {
|
|
398
|
+
const draft = props.draft ?? {};
|
|
384
399
|
try {
|
|
385
|
-
|
|
400
|
+
const suite = props.sdk.marketRegister.findCreditManager(props.creditAccount.creditManager);
|
|
401
|
+
Object.assign(draft, suite.creditOperationMarket());
|
|
402
|
+
assertMarketOperable(suite);
|
|
386
403
|
const { operations, state, calls, delayed } = await realize(plan(), {
|
|
387
404
|
creditAccount: props.creditAccount,
|
|
388
405
|
sdk: props.sdk,
|
|
389
406
|
slippage: props.slippage ?? 0,
|
|
390
|
-
quotaReserve: props.quotaReserve
|
|
407
|
+
quotaReserve: props.quotaReserve,
|
|
408
|
+
draft
|
|
391
409
|
});
|
|
392
410
|
return {
|
|
393
411
|
ok: true,
|
|
@@ -397,7 +415,7 @@ var CreditAccountOperationsService = class extends SDKConstruct {
|
|
|
397
415
|
delayed
|
|
398
416
|
};
|
|
399
417
|
} catch (e) {
|
|
400
|
-
return
|
|
418
|
+
return asRefused(e, draft);
|
|
401
419
|
}
|
|
402
420
|
}
|
|
403
421
|
};
|
|
@@ -419,6 +437,19 @@ function asSDKError(e) {
|
|
|
419
437
|
throw e;
|
|
420
438
|
}
|
|
421
439
|
/**
|
|
440
|
+
* {@inheritDoc asSDKError}
|
|
441
|
+
*
|
|
442
|
+
* The same verdict with the walk's draft attached, which is what every flow
|
|
443
|
+
* that projects a state answers with — see {@link WithPartialState} for what
|
|
444
|
+
* a caller can expect to find on it.
|
|
445
|
+
*/
|
|
446
|
+
function asRefused(e, state) {
|
|
447
|
+
return sdkErr({
|
|
448
|
+
...asSDKError(e).error,
|
|
449
|
+
state
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
422
453
|
* How the pathfinder says there is no route: it reverts instead of answering
|
|
423
454
|
* with an empty path, so viem raises a contract error where the rest of the
|
|
424
455
|
* engine raises an {@link IntentPreviewError}. Nothing is wrong — the trade
|
|
@@ -23,17 +23,12 @@ function proportionalDebt(position, collateralDelta) {
|
|
|
23
23
|
if (position.collateral <= 0n) throw new IntentPreviewError(insufficientBalance(), "cannot preserve leverage on an account with no collateral");
|
|
24
24
|
return position.debt * collateralDelta / position.collateral;
|
|
25
25
|
}
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
* the transaction to sit in the mempool for hours at any sane borrow rate,
|
|
29
|
-
* small enough not to matter to the wallet that fronts it.
|
|
30
|
-
*/
|
|
31
|
-
const SETTLE_MARGIN = 10n;
|
|
26
|
+
/** Share of the debt kept above `minDebt` for interest accrued before sending. */
|
|
27
|
+
const WITHDRAW_DEBT_MARGIN = 1n;
|
|
32
28
|
/**
|
|
33
29
|
* Largest withdrawal (in underlying) that {@link proportionalDebt} can still
|
|
34
30
|
* pay for: the repayment it implies leaves debt at or above `minDebt` plus
|
|
35
|
-
* {@link
|
|
36
|
-
* blocks before it is sent, and
|
|
31
|
+
* {@link WITHDRAW_DEBT_MARGIN} of the debt, and
|
|
37
32
|
* strictly less than the collateral goes — the last unit closes the account
|
|
38
33
|
* rather than shrinks it. `0n` when the debt already sits below `minDebt`.
|
|
39
34
|
*
|
|
@@ -44,7 +39,7 @@ function maxProportionalWithdrawal(position, debtLimits) {
|
|
|
44
39
|
if (collateral <= 0n) return 0n;
|
|
45
40
|
const allButLast = collateral - 1n;
|
|
46
41
|
if (debt === 0n) return allButLast;
|
|
47
|
-
const repayable = debt - debtLimits.minDebt - debt *
|
|
42
|
+
const repayable = debt - debtLimits.minDebt - debt * WITHDRAW_DEBT_MARGIN / PERCENTAGE_FACTOR;
|
|
48
43
|
if (repayable < 0n) return 0n;
|
|
49
44
|
const bound = BigIntMath.ceilDiv(collateral * (repayable + 1n), debt) - 1n;
|
|
50
45
|
return bound < allButLast ? bound : allButLast;
|
|
@@ -70,4 +65,4 @@ function assertDebtLimits(sdk, debt, debtLimits, underlying) {
|
|
|
70
65
|
}), debt > debtLimits.maxDebt ? `debt ${debt} exceeds maxDebt ${debtLimits.maxDebt}` : `debt ${debt} is below minDebt ${debtLimits.minDebt}`);
|
|
71
66
|
}
|
|
72
67
|
//#endregion
|
|
73
|
-
export {
|
|
68
|
+
export { assertDebtLimits, assertLeverageAtLeastOne, debtForLeverage, maxProportionalWithdrawal, proportionalDebt };
|
|
@@ -26,12 +26,19 @@ async function buildOpenStrategyState(props) {
|
|
|
26
26
|
assertLeverageAtLeastOne(leverage);
|
|
27
27
|
const suite = sdk.marketRegister.findCreditManager(creditManager);
|
|
28
28
|
const market = sdk.marketRegister.findByCreditManager(creditManager);
|
|
29
|
+
const draft = props.draft ?? {};
|
|
30
|
+
Object.assign(draft, suite.creditOperationMarket());
|
|
29
31
|
assertMarketOperable(suite);
|
|
30
32
|
const underlying = market.pool.underlying.toLowerCase();
|
|
31
33
|
const convert = (from, to, amount) => market.priceOracle.safeConvert(from, to, amount).value;
|
|
32
34
|
const margin = collateral.reduce((acc, a) => acc + convert(a.token, underlying, a.balance), 0n);
|
|
33
|
-
if (margin <= 0n) throw new IntentPreviewError(insufficientBalance(), "openStrategy: collateral is worth nothing in underlying");
|
|
34
35
|
const debt = debtForLeverage(margin, leverage);
|
|
36
|
+
Object.assign(draft, {
|
|
37
|
+
totalValue: market.toUnderlyingAmount(margin + debt),
|
|
38
|
+
totalDebt: market.toUnderlyingAmount(debt),
|
|
39
|
+
netValue: market.toUnderlyingAmount(margin)
|
|
40
|
+
});
|
|
41
|
+
if (margin <= 0n) throw new IntentPreviewError(insufficientBalance(), "openStrategy: collateral is worth nothing in underlying");
|
|
35
42
|
const account = existing ?? unopenedAccountSlice({
|
|
36
43
|
creditManager,
|
|
37
44
|
creditFacade: suite.creditFacade.address,
|
|
@@ -66,6 +73,42 @@ async function buildOpenStrategyState(props) {
|
|
|
66
73
|
}).quotaIncrease;
|
|
67
74
|
const averageQuota = quotasFor(averageAssets);
|
|
68
75
|
const minQuota = quotasFor(minAssets);
|
|
76
|
+
/** What the route came back with, which the guards below weigh but do not move. */
|
|
77
|
+
const routed = {
|
|
78
|
+
averageAssets: averageAssets.map(priced),
|
|
79
|
+
minAssets: minAssets.map(priced),
|
|
80
|
+
averageQuota,
|
|
81
|
+
minQuota,
|
|
82
|
+
calls: [...leg.calls],
|
|
83
|
+
creditAccount: existing?.creditAccount
|
|
84
|
+
};
|
|
85
|
+
Object.assign(draft, routed);
|
|
86
|
+
const snapshot = {
|
|
87
|
+
creditManager,
|
|
88
|
+
assets: averageAssets,
|
|
89
|
+
quotas: averageQuota,
|
|
90
|
+
totalDebt: debt,
|
|
91
|
+
totalValue: margin + debt
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* The shared builder, as everywhere else — the two branches are what this
|
|
95
|
+
* flow reports instead of `assets` and `quotas`, so those are dropped.
|
|
96
|
+
* Opening borrows the whole debt from the pool.
|
|
97
|
+
*/
|
|
98
|
+
const settle = () => {
|
|
99
|
+
const { assets: _assets, quotas: _quotas, ...projection } = sdk.positions.projection(snapshot, { availableLiquidityChange: -debt });
|
|
100
|
+
return {
|
|
101
|
+
...projection,
|
|
102
|
+
currentPrice: sdk.positions.currentPrice(snapshot)
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
let reached;
|
|
106
|
+
try {
|
|
107
|
+
reached = settle();
|
|
108
|
+
Object.assign(draft, reached);
|
|
109
|
+
} catch {
|
|
110
|
+
reached = void 0;
|
|
111
|
+
}
|
|
69
112
|
assertGrowthAllowed({
|
|
70
113
|
sdk,
|
|
71
114
|
suite,
|
|
@@ -74,14 +117,7 @@ async function buildOpenStrategyState(props) {
|
|
|
74
117
|
after: averageAssets
|
|
75
118
|
});
|
|
76
119
|
assertQuotaAvailable(sdk, market, averageQuota);
|
|
77
|
-
const
|
|
78
|
-
creditManager,
|
|
79
|
-
assets: averageAssets,
|
|
80
|
-
quotas: averageQuota,
|
|
81
|
-
totalDebt: debt,
|
|
82
|
-
totalValue: margin + debt
|
|
83
|
-
};
|
|
84
|
-
const { assets: _assets, quotas: _quotas, ...projection } = sdk.positions.projection(snapshot, { availableLiquidityChange: -debt });
|
|
120
|
+
const projection = reached ?? settle();
|
|
85
121
|
assertCollateralised(projection.healthFactor, false);
|
|
86
122
|
const priceImpact = await collectPriceImpact(leg.probe ? [leg.probe] : [], {
|
|
87
123
|
totalValue: margin + debt,
|
|
@@ -89,17 +125,13 @@ async function buildOpenStrategyState(props) {
|
|
|
89
125
|
toUnderlying: (from, amount) => convert(from, underlying, amount),
|
|
90
126
|
toUnderlyingAmount: market.toUnderlyingAmount
|
|
91
127
|
});
|
|
92
|
-
|
|
128
|
+
const state = {
|
|
93
129
|
...projection,
|
|
94
|
-
currentPrice: sdk.positions.currentPrice(snapshot),
|
|
95
130
|
priceImpact,
|
|
96
|
-
|
|
97
|
-
minAssets: minAssets.map(priced),
|
|
98
|
-
averageQuota,
|
|
99
|
-
minQuota,
|
|
100
|
-
calls: [...leg.calls],
|
|
101
|
-
creditAccount: existing?.creditAccount
|
|
131
|
+
...routed
|
|
102
132
|
};
|
|
133
|
+
Object.assign(draft, state);
|
|
134
|
+
return state;
|
|
103
135
|
}
|
|
104
136
|
/** Collateral plus the borrowed underlying, folded into one balance per token. */
|
|
105
137
|
function mergeExpectedBalances(collateral, underlying, debt) {
|
|
@@ -6,7 +6,7 @@ import "../../../model/index.js";
|
|
|
6
6
|
import { toToken, toTokenAmount } from "../../validation/helpers/token.js";
|
|
7
7
|
import { IntentPreviewError } from "../../validation/raise.js";
|
|
8
8
|
import { eq } from "./utils/common.js";
|
|
9
|
-
import {
|
|
9
|
+
import { assertDebtLimits, assertLeverageAtLeastOne, debtForLeverage, proportionalDebt } from "./math.js";
|
|
10
10
|
//#region src/onchain/accounts/intents/plan.ts
|
|
11
11
|
/** Whatever the previous convert produced. */
|
|
12
12
|
const RAISED = { raised: true };
|
|
@@ -360,6 +360,13 @@ const claim = (claimable) => ({
|
|
|
360
360
|
claimable
|
|
361
361
|
});
|
|
362
362
|
const clearQuotas = () => ({ kind: "clearQuotas" });
|
|
363
|
+
/**
|
|
364
|
+
* Headroom a settlement raises on top of the debt it read, in
|
|
365
|
+
* `PERCENTAGE_FACTOR`: enough interest for the transaction to sit in the
|
|
366
|
+
* mempool for hours at any sane borrow rate, small enough not to matter to the
|
|
367
|
+
* wallet that fronts it.
|
|
368
|
+
*/
|
|
369
|
+
const SETTLE_MARGIN = 10n;
|
|
363
370
|
const withMargin = (debt) => debt + debt * SETTLE_MARGIN / PERCENTAGE_FACTOR;
|
|
364
371
|
/** The amount that asks for all of it, whatever "all" turns out to be. */
|
|
365
372
|
const everything = (amount) => amount >= MAX_UINT256;
|
|
@@ -36,6 +36,8 @@ async function realize(steps, props) {
|
|
|
36
36
|
const market = sdk.marketRegister.findByCreditManager(creditAccount.creditManager);
|
|
37
37
|
const price = (from, to, amount) => market.priceOracle.safeConvert(from, to, amount).value;
|
|
38
38
|
const suite = sdk.marketRegister.findCreditManager(creditAccount.creditManager);
|
|
39
|
+
const draft = props.draft ?? {};
|
|
40
|
+
Object.assign(draft, suite.creditOperationMarket());
|
|
39
41
|
const start = {
|
|
40
42
|
initialAssets: creditAccount.tokens,
|
|
41
43
|
underlying,
|
|
@@ -327,13 +329,6 @@ async function realize(steps, props) {
|
|
|
327
329
|
const floor = ledger.snapshot();
|
|
328
330
|
const { assets, debt } = floor;
|
|
329
331
|
const projected = expected.snapshot();
|
|
330
|
-
assertGrowthAllowed({
|
|
331
|
-
sdk,
|
|
332
|
-
suite,
|
|
333
|
-
market,
|
|
334
|
-
before: creditAccount.tokens,
|
|
335
|
-
after: projected.assets
|
|
336
|
-
});
|
|
337
332
|
const quotas = cleared ?? getQuotasForUpdate({
|
|
338
333
|
assetsBefore: creditAccount.tokens,
|
|
339
334
|
assetsAfter: assets,
|
|
@@ -345,14 +340,6 @@ async function realize(steps, props) {
|
|
|
345
340
|
maxDebt: suite.creditFacade.maxDebt,
|
|
346
341
|
convert: price
|
|
347
342
|
});
|
|
348
|
-
if (!cleared && quotas.quotaIncrease.length + quotas.quotaDecrease.length > 0) {
|
|
349
|
-
assertQuotaAvailable(sdk, market, quotas.quotaIncrease);
|
|
350
|
-
push(buildQuotaUpdateOperation({
|
|
351
|
-
update: quotas,
|
|
352
|
-
creditAccount,
|
|
353
|
-
sdk
|
|
354
|
-
}));
|
|
355
|
-
}
|
|
356
343
|
const quotasAfter = quotasAfterUpdate(creditAccount.tokens, quotas.desiredQuota);
|
|
357
344
|
const quoted = Object.values(quotasAfter);
|
|
358
345
|
const snapshot = {
|
|
@@ -362,7 +349,56 @@ async function realize(steps, props) {
|
|
|
362
349
|
totalDebt: debt,
|
|
363
350
|
totalValue: projected.totalValue
|
|
364
351
|
};
|
|
365
|
-
const
|
|
352
|
+
const executionCost = traded.priced && traded.spentUnd > 0n ? {
|
|
353
|
+
amount: market.toUnderlyingAmount(traded.returnedUnd - traded.spentUnd),
|
|
354
|
+
rate: PERCENTAGE_FACTOR_1KK * (traded.returnedUnd - traded.spentUnd) / traded.spentUnd
|
|
355
|
+
} : void 0;
|
|
356
|
+
/**
|
|
357
|
+
* Everything about where the account lands except how much depth the routes
|
|
358
|
+
* cost, which is a quote of its own and waits until the guards have spoken.
|
|
359
|
+
*
|
|
360
|
+
* The same builder the preview module fills its answers from, so a state
|
|
361
|
+
* this walk plans and the state read back out of the calls it produced are
|
|
362
|
+
* described by one piece of code. Debt taken on leaves the pool, debt repaid
|
|
363
|
+
* returns to it.
|
|
364
|
+
*/
|
|
365
|
+
const settle = () => ({
|
|
366
|
+
...sdk.positions.projection(snapshot, { availableLiquidityChange: creditAccount.totalDebt - debt }),
|
|
367
|
+
executionCost,
|
|
368
|
+
currentPrice: sdk.positions.currentPrice(snapshot)
|
|
369
|
+
});
|
|
370
|
+
/**
|
|
371
|
+
* Read before the guards below rather than after them, so a plan they turn
|
|
372
|
+
* down is still described by the state they turned it down on — which is the
|
|
373
|
+
* question a form asks next, and the reason a guard's verdict is worth more
|
|
374
|
+
* than its code alone.
|
|
375
|
+
*
|
|
376
|
+
* A snapshot the oracle cannot value goes unanswered here and is raised by
|
|
377
|
+
* the success path instead: a guard's verdict is the better answer when
|
|
378
|
+
* there is one.
|
|
379
|
+
*/
|
|
380
|
+
let reached;
|
|
381
|
+
try {
|
|
382
|
+
reached = settle();
|
|
383
|
+
Object.assign(draft, reached);
|
|
384
|
+
} catch {
|
|
385
|
+
reached = void 0;
|
|
386
|
+
}
|
|
387
|
+
assertGrowthAllowed({
|
|
388
|
+
sdk,
|
|
389
|
+
suite,
|
|
390
|
+
market,
|
|
391
|
+
before: creditAccount.tokens,
|
|
392
|
+
after: projected.assets
|
|
393
|
+
});
|
|
394
|
+
if (!cleared && quotas.quotaIncrease.length + quotas.quotaDecrease.length > 0) {
|
|
395
|
+
assertQuotaAvailable(sdk, market, quotas.quotaIncrease);
|
|
396
|
+
push(buildQuotaUpdateOperation({
|
|
397
|
+
update: quotas,
|
|
398
|
+
creditAccount,
|
|
399
|
+
sdk
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
366
402
|
const settled = {
|
|
367
403
|
...snapshot,
|
|
368
404
|
assets,
|
|
@@ -381,16 +417,11 @@ async function realize(steps, props) {
|
|
|
381
417
|
toUnderlying: (from, amount) => price(from, underlying, amount),
|
|
382
418
|
toUnderlyingAmount: market.toUnderlyingAmount
|
|
383
419
|
});
|
|
384
|
-
const executionCost = traded.priced && traded.spentUnd > 0n ? {
|
|
385
|
-
amount: market.toUnderlyingAmount(traded.returnedUnd - traded.spentUnd),
|
|
386
|
-
rate: PERCENTAGE_FACTOR_1KK * (traded.returnedUnd - traded.spentUnd) / traded.spentUnd
|
|
387
|
-
} : void 0;
|
|
388
420
|
const state = {
|
|
389
|
-
...
|
|
390
|
-
priceImpact
|
|
391
|
-
executionCost,
|
|
392
|
-
currentPrice: sdk.positions.currentPrice(snapshot)
|
|
421
|
+
...reached ?? settle(),
|
|
422
|
+
priceImpact
|
|
393
423
|
};
|
|
424
|
+
Object.assign(draft, state);
|
|
394
425
|
return {
|
|
395
426
|
operations,
|
|
396
427
|
state,
|