@gearbox-protocol/sdk 15.1.0-next.21 → 15.1.0-next.23

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 (29) hide show
  1. package/dist/cjs/new-sdk/prepare/PrepareApi.js +4 -4
  2. package/dist/cjs/sdk/accounts/intents/index.js +57 -28
  3. package/dist/cjs/sdk/accounts/intents/plan.js +40 -2
  4. package/dist/cjs/sdk/accounts/intents/realize.js +22 -12
  5. package/dist/cjs/sdk/accounts/intents/tail.js +120 -0
  6. package/dist/cjs/sdk/accounts/intents/utils/index.js +1 -0
  7. package/dist/cjs/sdk/accounts/intents/utils/router-path.js +32 -0
  8. package/dist/cjs/sdk/market/oracle/PriceOracleBaseContract.js +11 -0
  9. package/dist/cjs/sdk/positions/PositionsService.js +22 -11
  10. package/dist/esm/new-sdk/prepare/PrepareApi.js +4 -4
  11. package/dist/esm/sdk/accounts/intents/index.js +58 -29
  12. package/dist/esm/sdk/accounts/intents/plan.js +40 -3
  13. package/dist/esm/sdk/accounts/intents/realize.js +22 -12
  14. package/dist/esm/sdk/accounts/intents/tail.js +118 -0
  15. package/dist/esm/sdk/accounts/intents/utils/index.js +2 -2
  16. package/dist/esm/sdk/accounts/intents/utils/router-path.js +32 -1
  17. package/dist/esm/sdk/market/oracle/PriceOracleBaseContract.js +11 -0
  18. package/dist/esm/sdk/positions/PositionsService.js +22 -11
  19. package/dist/types/new-sdk/prepare/types.d.ts +22 -8
  20. package/dist/types/sdk/accounts/intents/index.d.ts +13 -6
  21. package/dist/types/sdk/accounts/intents/plan.d.ts +18 -2
  22. package/dist/types/sdk/accounts/intents/realize.d.ts +7 -0
  23. package/dist/types/sdk/accounts/intents/tail.d.ts +52 -0
  24. package/dist/types/sdk/accounts/intents/types.d.ts +46 -10
  25. package/dist/types/sdk/accounts/intents/utils/index.d.ts +2 -2
  26. package/dist/types/sdk/accounts/intents/utils/router-path.d.ts +15 -1
  27. package/dist/types/sdk/market/oracle/PriceOracleBaseContract.d.ts +4 -0
  28. package/dist/types/sdk/market/oracle/types.d.ts +9 -0
  29. package/package.json +1 -1
@@ -307,12 +307,12 @@ function slice(sdk, creditAccount) {
307
307
  }
308
308
  /**
309
309
  * The operation a claim resumes, or `undefined` when there is none to resume:
310
- * a withdrawal requested without an intent, a compressor too old to report one,
311
- * or a full close, which the engine no longer previews.
310
+ * a withdrawal requested without an intent, or one read through a compressor
311
+ * too old to report it. Every intent the engine records can be finished,
312
+ * `CLOSE_ACCOUNT` included.
312
313
  **/
313
314
  function resumable(intent) {
314
- if (!intent || intent.type === "CLOSE_ACCOUNT") return;
315
- return intent;
315
+ return intent ?? void 0;
316
316
  }
317
317
  /**
318
318
  * Picks the route the operation takes out of `tokenIn`, as a value rather than
@@ -5,10 +5,10 @@ const require_sdk_accounts_intents_guards = require("./guards.js");
5
5
  const require_sdk_accounts_intents_math = require("./math.js");
6
6
  const require_sdk_accounts_intents_utils_credit_account_slice = require("./utils/credit-account-slice.js");
7
7
  const require_sdk_accounts_intents_open_strategy = require("./open-strategy.js");
8
- const require_sdk_accounts_intents_operations = require("./operations.js");
9
8
  const require_sdk_accounts_intents_plan = require("./plan.js");
10
9
  const require_sdk_accounts_intents_realize = require("./realize.js");
11
10
  const require_sdk_accounts_intents_view = require("./view.js");
11
+ const require_sdk_accounts_intents_tail = require("./tail.js");
12
12
  //#region src/sdk/accounts/intents/index.ts
13
13
  /**
14
14
  * Previews of everything a wallet can do to an existing credit account.
@@ -81,10 +81,16 @@ var CreditAccountOperationsService = class extends require_sdk_base_SDKConstruct
81
81
  * {@link intentRoutes}; this is the one to call when the delayed route is the
82
82
  * only one of interest.
83
83
  *
84
+ * `preview` is where the intent ends — the account once the redemption has
85
+ * matured, been claimed and the tail has run — because that is what the
86
+ * caller asked for; the half-way state the request itself lands in is
87
+ * `delayed.afterRequest`. Both are validated, so a request whose tail could
88
+ * not be completed is refused instead of started.
89
+ *
84
90
  * @param props - Intent plus account slice, quota reserve and slippage
85
- * @returns The request transaction and what it recorded for the tail, or
86
- * `{ ok: false, reason }` — `noDelayedRoute` when this route does not exist
87
- * for the account at all
91
+ * @returns The request transaction, the state it ends in and what it recorded
92
+ * for the tail, or `{ ok: false, reason }` — `noDelayedRoute` when this route
93
+ * does not exist for the account at all
88
94
  */
89
95
  async startDelayedIntent(props) {
90
96
  const { intent } = props;
@@ -97,11 +103,30 @@ var CreditAccountOperationsService = class extends require_sdk_base_SDKConstruct
97
103
  }
98
104
  });
99
105
  if (!result.ok) return result;
100
- if (!result.delayed) throw new Error("startDelayedIntent: plan started no withdrawal");
101
- return {
106
+ const { delayed } = result;
107
+ if (!delayed) throw new Error("startDelayedIntent: plan started no withdrawal");
108
+ if (delayed.settlement === "instant") return {
102
109
  ...result,
103
- delayed: result.delayed
110
+ delayed
104
111
  };
112
+ const request = result.operations.find((op) => op.type === "startDelayedWithdrawal");
113
+ if (!request) throw new Error("startDelayedIntent: no request among the operations");
114
+ try {
115
+ const tail = await require_sdk_accounts_intents_tail.projectTail({
116
+ request,
117
+ delayed,
118
+ creditAccount: props.creditAccount,
119
+ sdk: props.sdk,
120
+ quotaReserve: props.quotaReserve
121
+ });
122
+ return {
123
+ ...result,
124
+ preview: tail.state,
125
+ delayed
126
+ };
127
+ } catch (e) {
128
+ return asFailure(e);
129
+ }
105
130
  }
106
131
  /**
107
132
  * Previews a delayable intent both ways at once: settled by the router now,
@@ -150,9 +175,10 @@ var CreditAccountOperationsService = class extends require_sdk_base_SDKConstruct
150
175
  * Previews the tail of a delayed intent, once the withdrawal it started has
151
176
  * matured: the claim, then whatever the intent still owes.
152
177
  *
153
- * Serves the two operations that can genuinely be interrupted by a delay — a
154
- * withdrawal and a deleveraging. For the rest, the claim is the whole tail:
155
- * the tokens land on the account and only their quota has to catch up.
178
+ * Serves the three operations that can genuinely be interrupted by a delay —
179
+ * a withdrawal, a deleveraging and an exit. For the rest, the claim is the
180
+ * whole tail: the tokens land on the account and only their quota has to
181
+ * catch up.
156
182
  *
157
183
  * @param props - The recorded intent, the account slice as it stands now, and
158
184
  * the matured claimable
@@ -160,24 +186,11 @@ var CreditAccountOperationsService = class extends require_sdk_base_SDKConstruct
160
186
  * an operation are consumed the same way
161
187
  */
162
188
  async finishIntent(props) {
163
- const { intent, claimable } = props;
164
- return plain(await this.#preview(props, () => {
165
- const view = require_sdk_accounts_intents_view.accountView(props.creditAccount, props.sdk);
166
- const claimed = () => {
167
- const output = require_sdk_accounts_intents_operations.instantOutput(claimable.outputs);
168
- if (!output) throw new require_sdk_accounts_intents_types.IntentPreviewError("insufficientSourceBalance", "finishIntent: the claim credits nothing to spend");
169
- return output;
170
- };
171
- switch (intent.type) {
172
- case "WITHDRAW_COLLATERAL": return require_sdk_accounts_intents_plan.planFinishWithdraw(intent, claimable, claimed(), view);
173
- case "DECREASE_LEVERAGE": return require_sdk_accounts_intents_plan.planFinishDecreaseLeverage(claimable, claimed(), view);
174
- case "ADD_COLLATERAL":
175
- case "INCREASE_LEVERAGE":
176
- case "DEPOSIT":
177
- case "DEPOSIT_AND_INCREASE_LEVERAGE": return require_sdk_accounts_intents_plan.planFinishClaimOnly(claimable);
178
- default: throw new Error(`${intent.type} - not implemented`);
179
- }
180
- }));
189
+ return plain(await this.#preview(props, () => require_sdk_accounts_intents_tail.planTail({
190
+ intent: props.intent,
191
+ claimable: props.claimable,
192
+ view: require_sdk_accounts_intents_view.accountView(props.creditAccount, props.sdk)
193
+ })));
181
194
  }
182
195
  /**
183
196
  * Previews opening a brand-new leveraged position.
@@ -240,8 +253,24 @@ function asFailure(e) {
240
253
  ok: false,
241
254
  reason: e.reason
242
255
  };
256
+ if (isUnroutable(e)) return {
257
+ ok: false,
258
+ reason: "unsupportedTokenPair"
259
+ };
243
260
  throw e;
244
261
  }
262
+ /**
263
+ * How the pathfinder says there is no route: it reverts instead of answering
264
+ * with an empty path, so viem raises a contract error where the rest of the
265
+ * engine raises an {@link IntentPreviewError}. Nothing is wrong — the trade
266
+ * asked for cannot be made, which is a refusal the caller can act on, and one
267
+ * `intentRoutes` in particular must keep as a value so the other route can
268
+ * still be offered.
269
+ */
270
+ function isUnroutable(e) {
271
+ for (let cause = e; cause instanceof Error; cause = cause.cause) if (cause.message.includes("no optimal edge found")) return true;
272
+ return false;
273
+ }
245
274
  //#endregion
246
275
  exports.CreditAccountOperationsService = CreditAccountOperationsService;
247
276
  exports.IntentPreviewError = require_sdk_accounts_intents_types.IntentPreviewError;
@@ -115,11 +115,25 @@ function planWithdraw(intent, view) {
115
115
  *
116
116
  * The tail is planned at claim time by {@link planFinishWithdraw}, from the
117
117
  * intent this request records — only then is the claimed amount, and the token
118
- * it arrived in, known.
118
+ * it arrived in, known. An exit records {@link planFinishCloseAccount}'s intent
119
+ * instead, and rebuilds itself from the account rather than from the request.
119
120
  */
120
121
  function planWithdrawDelayed(intent, view) {
121
122
  const { U, T, S, WU, dD, all } = withdrawShape(intent, view);
122
- if (all) throw new require_sdk_accounts_intents_types.IntentPreviewError("noDelayedRoute", "withdraw: taking the whole net value cannot be started as a redemption");
123
+ if (all) {
124
+ const held = view.balanceOf(S);
125
+ if (held <= 0n) throw new require_sdk_accounts_intents_types.IntentPreviewError("insufficientSourceBalance", `withdraw: account holds no ${S} to redeem`);
126
+ return [{
127
+ kind: "request",
128
+ token: S,
129
+ amount: held,
130
+ reserve: 0n,
131
+ record: {
132
+ type: "CLOSE_ACCOUNT",
133
+ to: intent.to
134
+ }
135
+ }];
136
+ }
123
137
  if (!require_sdk_accounts_intents_utils_common.eq(T, U) && !(view.rwaAsset && require_sdk_accounts_intents_utils_common.eq(T, view.rwaAsset))) throw new require_sdk_accounts_intents_types.IntentPreviewError("noDelayedRoute", `withdraw: a delayed route cannot pay out in ${T}`);
124
138
  const payoutIsSource = require_sdk_accounts_intents_utils_common.eq(T, S);
125
139
  return [{
@@ -218,6 +232,29 @@ function planFinishWithdraw(intent, claimable, claimed, view) {
218
232
  }, intent.to)
219
233
  ];
220
234
  }
235
+ /**
236
+ * The tail of an exit: the claim lands, everything the account holds is sold
237
+ * into the underlying, the loan is settled out of the proceeds and the rest
238
+ * goes to the wallet. The account survives it, empty and owing nothing.
239
+ *
240
+ * Nothing is quoted from the request — the same shape {@link planWithdraw}
241
+ * builds for an instant exit is rebuilt here against the account as it stands
242
+ * now, which is the only state that can name these amounts.
243
+ */
244
+ function planFinishCloseAccount(intent, claimable, claimed, view) {
245
+ const wrap = view.rwaAsset && require_sdk_accounts_intents_utils_common.eq(claimed.token, view.rwaAsset) ? [convert(claimed.token, view.underlying, view.balanceOf(claimed.token) + claimed.amount)] : [];
246
+ return [
247
+ claim(claimable),
248
+ ...wrap,
249
+ clearQuotas(),
250
+ { kind: "closeAll" },
251
+ ...view.debt > 0n ? [repay(view.debt)] : [],
252
+ {
253
+ kind: "sweep",
254
+ to: intent.to
255
+ }
256
+ ];
257
+ }
221
258
  /** Nothing is owed beyond the claim: the tokens land and quotas catch up. */
222
259
  function planFinishClaimOnly(claimable) {
223
260
  return [claim(claimable)];
@@ -365,6 +402,7 @@ exports.planAdjustLeverage = planAdjustLeverage;
365
402
  exports.planAdjustLeverageDelayed = planAdjustLeverageDelayed;
366
403
  exports.planDeposit = planDeposit;
367
404
  exports.planFinishClaimOnly = planFinishClaimOnly;
405
+ exports.planFinishCloseAccount = planFinishCloseAccount;
368
406
  exports.planFinishDecreaseLeverage = planFinishDecreaseLeverage;
369
407
  exports.planFinishWithdraw = planFinishWithdraw;
370
408
  exports.planRepay = planRepay;
@@ -25,7 +25,7 @@ async function realize(steps, props) {
25
25
  const { underlying } = creditAccount;
26
26
  const rwaAsset = sdk.tokensMeta.rwaUnderlyings.get(underlying)?.asset;
27
27
  const price = require_sdk_accounts_intents_utils_convert_amount.convertAmount(sdk, creditAccount.creditManager);
28
- const paths = require_sdk_accounts_intents_utils_router_path.createRouterPaths({
28
+ const paths = props.paths ?? require_sdk_accounts_intents_utils_router_path.createRouterPaths({
29
29
  sdk,
30
30
  creditAccount,
31
31
  slippage
@@ -45,6 +45,7 @@ async function realize(steps, props) {
45
45
  };
46
46
  /** Output of the last convert or claim, for `RAISED` amounts. */
47
47
  let raised = 0n;
48
+ /** The request, before the walk's end state can be attached to it. */
48
49
  let delayed;
49
50
  /**
50
51
  * Set by a `clearQuotas` step, which settles the quotas mid-walk instead of
@@ -146,7 +147,7 @@ async function realize(steps, props) {
146
147
  if (pending) throw new require_sdk_accounts_intents_types.IntentPreviewError("withdrawalInProgress", `closeAll: ${pending.token} is a pending withdrawal, claim it first`);
147
148
  if (balances.length > 0) {
148
149
  const leg = await paths.closeAll({ balances });
149
- if (leg.calls.length > 0) push(require_sdk_accounts_intents_operations.buildCloseSwapOperation({
150
+ if (leg.calls.length > 0 || leg.minAmount > 0n) push(require_sdk_accounts_intents_operations.buildCloseSwapOperation({
150
151
  from: balances,
151
152
  tokenOut: underlying,
152
153
  amountOut: leg.minAmount,
@@ -185,10 +186,15 @@ async function realize(steps, props) {
185
186
  creditAccount,
186
187
  sdk
187
188
  }));
189
+ const queued = preview.outputs.find((o) => o.isDelayed);
188
190
  delayed = {
189
191
  record: step.record,
190
192
  claimableAt: preview.claimableAt,
191
- settlement: preview.outputs.some((o) => o.isDelayed) ? "delayed" : "instant"
193
+ settlement: queued ? "delayed" : "instant",
194
+ claim: queued ? {
195
+ token: asset.underlying.toLowerCase(),
196
+ amount: require_sdk_accounts_intents_utils_common.toTargetDecimals(queued.amount, queued.token, asset.underlying, sdk)
197
+ } : void 0
192
198
  };
193
199
  raised = require_sdk_accounts_intents_operations.instantOutput(preview.outputs)?.amount ?? 0n;
194
200
  break;
@@ -266,18 +272,22 @@ async function realize(steps, props) {
266
272
  liquidationPrice: sdk.positions.liquidationPrice(snapshot)
267
273
  };
268
274
  require_sdk_accounts_intents_guards.assertCollateralised(paysOut ? sdk.positions.healthFactor(snapshot, { safePrices: true }) : metrics.healthFactor);
275
+ const state = {
276
+ totalValue,
277
+ accountDebt: debt,
278
+ leverage: require_sdk_market_math.calcPositionLeverage(totalValue, debt),
279
+ assets,
280
+ quotas: quotasAfter,
281
+ ...metrics
282
+ };
269
283
  return {
270
284
  operations,
271
- state: {
272
- totalValue,
273
- accountDebt: debt,
274
- leverage: require_sdk_market_math.calcPositionLeverage(totalValue, debt),
275
- assets,
276
- quotas: quotasAfter,
277
- ...metrics
278
- },
285
+ state,
279
286
  calls: callsOf(operations),
280
- delayed
287
+ delayed: delayed && {
288
+ ...delayed,
289
+ afterRequest: state
290
+ }
281
291
  };
282
292
  }
283
293
  /**
@@ -0,0 +1,120 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_sdk_accounts_intents_types = require("./types.js");
3
+ const require_sdk_accounts_intents_utils_router_path = require("./utils/router-path.js");
4
+ const require_sdk_accounts_intents_plan = require("./plan.js");
5
+ const require_sdk_accounts_intents_operations = require("./operations.js");
6
+ const require_sdk_accounts_intents_realize = require("./realize.js");
7
+ const require_sdk_accounts_intents_view = require("./view.js");
8
+ //#region src/sdk/accounts/intents/tail.ts
9
+ /**
10
+ * The second half of a delayed intent: the claim, then whatever the intent
11
+ * still owes.
12
+ *
13
+ * Shared by the two callers that need it and must not disagree — the tail as
14
+ * it is previewed days later against the account that really exists, and the
15
+ * tail as it is projected the moment the request is made.
16
+ */
17
+ function planTail(args) {
18
+ const { intent, claimable, view } = args;
19
+ const claimed = () => {
20
+ const output = require_sdk_accounts_intents_operations.instantOutput(claimable.outputs);
21
+ if (!output) throw new require_sdk_accounts_intents_types.IntentPreviewError("insufficientSourceBalance", "finishIntent: the claim credits nothing to spend");
22
+ return output;
23
+ };
24
+ switch (intent.type) {
25
+ case "WITHDRAW_COLLATERAL": return require_sdk_accounts_intents_plan.planFinishWithdraw(intent, claimable, claimed(), view);
26
+ case "DECREASE_LEVERAGE": return require_sdk_accounts_intents_plan.planFinishDecreaseLeverage(claimable, claimed(), view);
27
+ case "CLOSE_ACCOUNT": return require_sdk_accounts_intents_plan.planFinishCloseAccount(intent, claimable, claimed(), view);
28
+ case "ADD_COLLATERAL":
29
+ case "INCREASE_LEVERAGE":
30
+ case "DEPOSIT":
31
+ case "DEPOSIT_AND_INCREASE_LEVERAGE": return require_sdk_accounts_intents_plan.planFinishClaimOnly(claimable);
32
+ default: throw new Error(`${intent.type} - not implemented`);
33
+ }
34
+ }
35
+ /**
36
+ * Where a delayed intent ends up, worked out at the moment it is started.
37
+ *
38
+ * A request is only half a withdrawal, so the state it lands in is not the
39
+ * answer to "what does this do to my position": the debt is still there, the
40
+ * payout has not been made, and the position sits in a phantom token. What the
41
+ * caller means is the far side — and that side can be walked now, because the
42
+ * request already fixes the claim it will be finished from.
43
+ *
44
+ * So the same tail {@link planTail} builds at claim time is built here against
45
+ * the account as the request leaves it, with the claim it is expected to bring,
46
+ * and walked by the same realiser — with one substitution: routed legs are
47
+ * priced by the oracle rather than the pathfinder, since the funds they trade
48
+ * do not exist yet and no calldata is being produced. The result is an
49
+ * estimate that the engine's guards are nevertheless applied to, so a request
50
+ * that would strand the account is refused before it is sent rather than
51
+ * discovered days later.
52
+ */
53
+ async function projectTail(args) {
54
+ const { request, delayed, creditAccount, sdk, quotaReserve } = args;
55
+ const { claim } = delayed;
56
+ const queued = request.outputs.find((o) => o.isDelayed);
57
+ if (!queued || !claim) throw new Error("projectTail: the request queued nothing to claim");
58
+ const next = sliceAfter(creditAccount, delayed.afterRequest);
59
+ const steps = planTail({
60
+ intent: delayed.record,
61
+ claimable: projectedClaimable(request, queued.token, queued.amount, claim),
62
+ view: require_sdk_accounts_intents_view.accountView(next, sdk)
63
+ });
64
+ const { state, operations } = await require_sdk_accounts_intents_realize.realize(steps, {
65
+ creditAccount: next,
66
+ sdk,
67
+ slippage: 0,
68
+ quotaReserve,
69
+ paths: require_sdk_accounts_intents_utils_router_path.createOraclePaths({
70
+ sdk,
71
+ creditAccount: next
72
+ })
73
+ });
74
+ return {
75
+ state,
76
+ operations
77
+ };
78
+ }
79
+ /**
80
+ * The matured withdrawal the tail will be built from, as the request implies
81
+ * it: the phantom it created is burned and the venue's payout takes its place.
82
+ * It carries no claim calls — those are read from the chain when the claim is
83
+ * real, and nothing here is going to be sent.
84
+ */
85
+ function projectedClaimable(request, phantom, phantomAmount, claim) {
86
+ return {
87
+ token: request.token,
88
+ withdrawalPhantomToken: phantom,
89
+ withdrawalTokenSpent: phantomAmount,
90
+ outputs: [{
91
+ token: claim.token,
92
+ amount: claim.amount,
93
+ isDelayed: false
94
+ }],
95
+ claimCalls: []
96
+ };
97
+ }
98
+ /**
99
+ * The account as one walk left it, shaped as the slice the next walk reads.
100
+ * Masks are carried over from the tokens the account already held; one it
101
+ * picked up along the way has none to carry, which only the router slice
102
+ * would have used.
103
+ */
104
+ function sliceAfter(creditAccount, after) {
105
+ const masks = new Map(creditAccount.tokens.map((t) => [t.token.toLowerCase(), t.mask]));
106
+ return {
107
+ ...creditAccount,
108
+ accountDebt: after.accountDebt,
109
+ tokens: after.assets.map((a) => ({
110
+ token: a.token,
111
+ balance: a.balance,
112
+ quota: after.quotas[a.token]?.balance ?? 0n,
113
+ mask: masks.get(a.token) ?? 0n,
114
+ success: true
115
+ }))
116
+ };
117
+ }
118
+ //#endregion
119
+ exports.planTail = planTail;
120
+ exports.projectTail = projectTail;
@@ -15,6 +15,7 @@ exports.assembleOperationCalls = require_sdk_accounts_intents_utils_assemble_ope
15
15
  exports.calcBorrowedAmountPlusInterestAndFees = require_sdk_accounts_intents_utils_borrowed_amount_plus_interest_and_fees.calcBorrowedAmountPlusInterestAndFees;
16
16
  exports.clearedQuotas = require_sdk_accounts_intents_utils_quotas_for_update.clearedQuotas;
17
17
  exports.convertAmount = require_sdk_accounts_intents_utils_convert_amount.convertAmount;
18
+ exports.createOraclePaths = require_sdk_accounts_intents_utils_router_path.createOraclePaths;
18
19
  exports.createRouterPaths = require_sdk_accounts_intents_utils_router_path.createRouterPaths;
19
20
  exports.eq = require_sdk_accounts_intents_utils_common.eq;
20
21
  exports.fetchCreditAccountSlice = require_sdk_accounts_intents_utils_credit_account_slice.fetchCreditAccountSlice;
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_sdk_accounts_intents_utils_common = require("./common.js");
3
+ const require_sdk_accounts_intents_utils_convert_amount = require("./convert-amount.js");
3
4
  //#region src/sdk/accounts/intents/utils/router-path.ts
4
5
  /**
5
6
  * The engine's only door to the pathfinder.
@@ -82,5 +83,36 @@ function createRouterPaths(args) {
82
83
  }
83
84
  };
84
85
  }
86
+ /**
87
+ * The same door, priced by the oracle and opening onto no calldata.
88
+ *
89
+ * For a leg that cannot be quoted yet: the tail of a redemption trades funds
90
+ * that do not exist, along a route the pathfinder will only be able to build
91
+ * once they do. Asking it now would price a swap of nothing, so the amounts
92
+ * come from the oracle instead — an estimate with no slippage floor, which is
93
+ * all a projection days out can honestly be — and the walk yields a state
94
+ * rather than a transaction.
95
+ */
96
+ function createOraclePaths(args) {
97
+ const { sdk, creditAccount } = args;
98
+ const price = require_sdk_accounts_intents_utils_convert_amount.convertAmount(sdk, creditAccount.creditManager);
99
+ const estimate = (amount) => ({
100
+ amount,
101
+ minAmount: amount,
102
+ calls: []
103
+ });
104
+ return {
105
+ async swap({ tokenIn, tokenOut, amount }) {
106
+ return estimate(amount > 0n ? price(tokenIn, tokenOut, amount) : 0n);
107
+ },
108
+ async closeAll({ balances }) {
109
+ return estimate(balances.reduce((sum, b) => sum + price(b.token, creditAccount.underlying, b.balance), 0n));
110
+ },
111
+ async openStrategy() {
112
+ throw new Error("oracle paths: opening a position is never projected");
113
+ }
114
+ };
115
+ }
85
116
  //#endregion
117
+ exports.createOraclePaths = createOraclePaths;
86
118
  exports.createRouterPaths = createRouterPaths;
@@ -138,6 +138,17 @@ var PriceOracleBaseContract = class extends require_sdk_base_BaseContract.BaseCo
138
138
  return amount * 10n ** BigInt(this.tokensMeta.decimals(token)) / price;
139
139
  }
140
140
  /**
141
+ * {@inheritDoc IPriceOracleContract.safeConvert}
142
+ **/
143
+ safeConvert(from, to, amount) {
144
+ try {
145
+ return this.convert(from, to, amount);
146
+ } catch (e) {
147
+ this.logger?.debug(`cannot convert ${this.labelAddress(from)} to ${this.labelAddress(to)}: ${e}`);
148
+ return null;
149
+ }
150
+ }
151
+ /**
141
152
  * {@inheritDoc IPriceOracleContract.safeConvertToUSD}
142
153
  **/
143
154
  safeConvertToUSD(token, amount) {
@@ -154,6 +154,24 @@ var PositionsService = class extends require_sdk_base_SDKConstruct.SDKConstruct
154
154
  const borrowRate = this.borrowRate(snapshot);
155
155
  const timeToLiquidation = this.timeToLiquidation(snapshot);
156
156
  const liquidationPrice = this.liquidationPrice(snapshot);
157
+ const zeroDebt = ca.debt === 0n;
158
+ const collaterals = [];
159
+ let totalValue = zeroDebt ? 0n : ca.totalValue;
160
+ let totalValueUSD = zeroDebt ? 0n : ca.totalValueUSD;
161
+ for (const t of ca.tokens) {
162
+ if (t.balance <= 10n) continue;
163
+ collaterals.push({
164
+ collateral: priceOracle.toTokenAmount(t.token, t.balance),
165
+ quota: priceOracle.toTokenAmount(market.underlying, t.quota),
166
+ withdrawals: withdrawals.get(t.token) ?? []
167
+ });
168
+ if (zeroDebt) {
169
+ const value = priceOracle.safeConvert(t.token, market.underlying, t.balance) || 0n;
170
+ totalValue += value;
171
+ const usd = priceOracle.safeConvertToUSD(t.token, t.balance) || 0n;
172
+ totalValueUSD += usd;
173
+ }
174
+ }
157
175
  return {
158
176
  kind: "strategy",
159
177
  chainId: this.sdk.chainId,
@@ -161,7 +179,7 @@ var PositionsService = class extends require_sdk_base_SDKConstruct.SDKConstruct
161
179
  creditAccount: ca.creditAccount,
162
180
  name: target ? require_sdk_market_strategyName.strategyName(this.sdk.tokensMeta.mustGetToken(target), token, this.sdk.chainId) : token.symbol,
163
181
  targetCollateral: target ? this.sdk.tokensMeta.mustGetToken(target) : null,
164
- leverage: require_sdk_market_math.calcPositionLeverage(ca.totalValue, totalDebtValue),
182
+ leverage: require_sdk_market_math.calcPositionLeverage(totalValue, totalDebtValue),
165
183
  borrowApy: require_sdk_market_math.calcBorrowApy(pool.baseInterestRate, suite.creditManager.feeInterest),
166
184
  totalDebt: {
167
185
  token,
@@ -170,21 +188,14 @@ var PositionsService = class extends require_sdk_base_SDKConstruct.SDKConstruct
170
188
  },
171
189
  totalValue: {
172
190
  token,
173
- value: ca.totalValue,
174
- valueUsd: require_sdk_market_math.usdToNumber(ca.totalValueUSD)
191
+ value: totalValue,
192
+ valueUsd: require_sdk_market_math.usdToNumber(totalValueUSD)
175
193
  },
176
194
  healthFactor: require_sdk_market_math.healthFactorBps(ca.healthFactor),
177
195
  borrowRate,
178
196
  timeToLiquidation,
179
197
  liquidationPrice,
180
- collaterals: ca.tokens.flatMap((t) => {
181
- if (t.balance <= 10n) return [];
182
- return [{
183
- collateral: priceOracle.toTokenAmount(t.token, t.balance),
184
- quota: priceOracle.toTokenAmount(market.underlying, t.quota),
185
- withdrawals: withdrawals.get(t.token) ?? []
186
- }];
187
- })
198
+ collaterals
188
199
  };
189
200
  }
190
201
  /**
@@ -306,12 +306,12 @@ function slice(sdk, creditAccount) {
306
306
  }
307
307
  /**
308
308
  * The operation a claim resumes, or `undefined` when there is none to resume:
309
- * a withdrawal requested without an intent, a compressor too old to report one,
310
- * or a full close, which the engine no longer previews.
309
+ * a withdrawal requested without an intent, or one read through a compressor
310
+ * too old to report it. Every intent the engine records can be finished,
311
+ * `CLOSE_ACCOUNT` included.
311
312
  **/
312
313
  function resumable(intent) {
313
- if (!intent || intent.type === "CLOSE_ACCOUNT") return;
314
- return intent;
314
+ return intent ?? void 0;
315
315
  }
316
316
  /**
317
317
  * Picks the route the operation takes out of `tokenIn`, as a value rather than