import { Bps, ChainId, Leverage, Timestamp, Token, TokenAmount, UnderlyingToken } from "./primitives.js"; import { Curator } from "./curators.js"; import { DelayedIntent } from "./delayed-intents.js"; import { RWAOperationArgs } from "./rwa.js"; import { UnpriceableTokenError } from "./errors/oracle-errors.js"; import "./errors/index.js"; import { BorrowRateBreakdown } from "./positions.js"; import { Address, Hex } from "viem"; //#region src/model/previews.d.ts /** * ERC4626 pool operation kind, as surfaced on a {@link PoolPositionOperationPreview}. **/ export type PoolOperationType = "Deposit" | "Mint" | "Withdraw" | "Redeem"; /** * Raw-calldata operation to preview: the chain and the transaction fields. **/ export interface PreviewOperationInput { /** * Chain the transaction is sent on. **/ chainId: ChainId; /** * Contract address that was called. **/ to: Address; /** * Raw calldata of the operation. **/ calldata: Hex; /** * Transaction sender. **/ sender: Address; /** * Transaction `msg.value`, required for native-token zapper deposits. **/ value?: bigint; } /** * Options for previewing a raw operation. **/ export interface PreviewOperationOptions { /** * Block to read/simulate at; defaults to latest. Only set for testnet * forks. **/ blockNumber?: bigint; } /** * What a pool transaction that already exists would do — the counterpart of * `prepare.deposit`, `prepare.withdraw` and `prepare.redeem`, read off calldata * rather than planned into it. **/ export interface PoolPositionOperationPreview { operation: PoolOperationType; /** * Pool address */ pool: Address; /** * Call target when the operation is zapper-routed. The wallet approves this * instead of the pool. Absent on a direct pool call. */ zapper?: Address; /** * Whose share balance {@link netValue} describes and whose shares burn on a * withdrawal or redeem (the calldata's `owner` there, its `receiver` on a * deposit or mint). */ holder: Address; /** * Human-readable pool name */ name: string; /** * Pool underlying token. * * For RWA markets this is the unwrapped asset, e.g. USDC rather than * dcUSDC (the pool's on-chain underlying). Same as {@link PoolPosition.underlyingToken}. */ underlyingToken: UnderlyingToken; /** * Token that goes from user to pool. * In case of deposit, underlying for direct deposit, zapper input for zapper-routed deposit. * In case of withdraw, pool shares (diesel token) for direct withdraw or zapper token out. * * On withdraw, this is the amount of shares burned that covers both the requested withdrawal * and the fee. * * On redeem this is the shares from calldata, fee-free. */ tokenIn: TokenAmount; /** * Token that goes from pool to user. * In case of deposit, pool shares (diesel token) for direct deposit or zapper token out. * In case of withdraw, underlying for direct withdraw or zapper token in. * * On withdraw this is the requested underlying. * On redeem this is the underlying after `withdrawFee`, so less than * the burned shares are worth. */ tokenOut: TokenAmount; /** * Diesel rate of the pool: underlying per 1 diesel share, RAY-scaled * (`1e27`). */ shareRate: bigint; /** * Curator of the market this pool belongs to */ curator: Curator; /** * Remaining LP after this transaction: the same quantity * {@link PoolPosition.netValue} reports for a live position, denominated in * the market's unwrapped underlying. For RWA markets this is USDC rather than * dcUSDC (the pool's on-chain underlying) or diesel shares. * * Does not account for withdraw fee: this is what the remaining shares are worth, not * what leaving with them would pay. The fee shows up on {@link tokenIn} / * {@link tokenOut} instead. */ netValue: TokenAmount; } /** * The market a credit operation acts on, reported the same way by every half of * the SDK: the calldata previews, the intents engine's projections, the * open-strategy walk and the liquidatable-account rows all carry it, so a screen * naming the market needs nothing beside the result it already holds. **/ export interface CreditOperationMarket { /** * Credit manager the account belongs to. Carried on the result itself so a * caller weighing one — `checkSimulation` among them — needs nothing beside * it to find the market. */ creditManager: Address; /** * Human-readable strategy name, e.g. `"wstETH / WETH"`. Same as * {@link StrategyPosition.name} */ name: string; /** * Pool underlying token. * * For RWA markets this is the unwrapped asset, e.g. USDC rather than * dcUSDC (the pool's on-chain underlying). Same as * {@link StrategyPosition.underlyingToken}. */ underlyingToken: UnderlyingToken; /** * Curator of the market {@link creditManager} belongs to, in the same shape * {@link StrategyOpportunity} reports it: the market configurator's address, * plus the display name and the page a screen labels it with when the * configurator is a known one. */ curator: Curator; /** * What a liquidation takes off the account, in basis points: the premium the * liquidator keeps plus the protocol's own fee. * * Not the credit manager's `liquidationDiscount`, which is the complement of * the premium alone (`100% - liquidationPremium`) and says what share of the * collateral repays the debt. This is the figure a position screen labels * "Liquidation Discount": `liquidationPremium + feeLiquidation`. */ liquidationDiscount: Bps; } /** * What an account is worth and what it is made of, once an operation has run. * * The measured half of a {@link AccountProjection}: read off the balances the * walk arrived at, without a formula between them and the answer. **/ export interface AccountHoldings { /** * Everything the account holds, denominated in the market's underlying. **/ totalValue: TokenAmount; /** * What it would take to settle the loan: principal plus accrued interest and * fees, in the market's underlying. **/ totalDebt: TokenAmount; /** * Own funds in the position: `totalValue` less `totalDebt`, in the market's * underlying. The read model leaves a strategy caller to subtract these two * itself; a projection reports the figure so an "own funds" row reads the * same on every screen that shows one. **/ netValue: TokenAmount; /** * What the account holds, token by token. **/ assets: TokenAmount[]; /** * Quota bought for each collateral, denominated in the market's underlying * rather than in the collateral token — the same convention as * {@link PositionCollateral.quota}. A token the account leaves unquoted is * absent rather than present at zero. **/ quotas: TokenAmount[]; } /** * The risk and cost of an account, derived from what it holds. * * The computed half of a {@link AccountProjection}, and the reason it is a type * of its own: every one of these is a formula over the same * {@link AccountSnapshot}, so both halves of the SDK get them from one place — * `sdk.positions.metrics` — and cannot drift into two answers for one account. **/ export interface AccountMetrics { /** * Health factor in basis points: below `10000` the account is liquidatable. * * @example `12500` for a health factor of 1.25 **/ healthFactor: Bps; /** * The same factor with collateral valued at safe prices — `min` of each * token's main and reserve feeds (0 when there is no reserve), which is * what the credit manager switches to for a call that hands funds over. * The underlying is always priced at the main feed. * * Always reported, whether or not the operation in question hands anything * over: which of the two factors decides a transaction is a property of the * call the caller ends up sending, and a screen showing the account is * entitled to both. * * @example `11800` where `healthFactor` is `12500` **/ safeHealthFactor: Bps; /** * Cost of the debt, broken down by source. * * Half of the breakdown rests on the debt and the quotas alone (`base`, * `totalOnDebt`) and half on the position's value (`total`, `quotas[].rate`), * which is why it counts as a {@link RoutedField}. **/ borrowRate: BorrowRateBreakdown; /** * Estimated milliseconds until the health factor decays to `10000` under * the current borrow rate, or `null` when the debt carries no rate (or the * account is already liquidatable). **/ timeToLiquidation: bigint | null; /** * Price of the single non-underlying collateral at which the account * becomes liquidatable, in the oracle's 8-decimal fixed point, or `null` * when the account holds zero or several non-underlying assets. **/ liquidationPrice: bigint | null; /** * Total-value leverage: `totalValue / (totalValue − totalDebt)`. `1` = * unleveraged; `0` if underwater. **/ leverage: Leverage; } /** * A credit account as an operation leaves it, answered by both halves of the * SDK: `prepare`, which walks a request forward into the calls that realise it, * and `preview`, which decodes calls that already exist and replays them back. * * Both answer in this same vocabulary, and from the same builder * (`sdk.positions.projection`), so the two descriptions of one operation can be * compared field by field — which is what * `previewMatchesPrepare.test.ts` does. * * This shape is the expected branch of a routed leg, which is what `prepare` * reports; `preview` sees only the floor and answers with an * {@link EstimatedProjection}, the same fields with the routed ones marked * `est`. Where nothing routes the two coincide exactly. * * `totalDebt`, `totalValue`, `leverage` and `healthFactor` mean here exactly * what they mean on a {@link StrategyPosition}, down to the token an amount * names — an RWA market reports USDC, not the dcUSDC wrapper the pool holds. **/ export interface AccountProjection extends CreditOperationMarket, AccountHoldings, AccountMetrics {} /** * The fields of an {@link AccountProjection} a routed leg's outcome decides. * * A swap is quoted twice: the amount the pathfinder expects to return, and the * floor it is willing to guarantee once slippage is allowed for. `prepare` has * both and reports the expected one — that is where the position lands. * `preview` reads a transaction that already exists, and calldata carries only * the floor, so its answer is the worst case the same operation can settle at. * * Everything not listed here is the same number on either branch: the debt and * the quotas are named by the calls themselves. * * The borrow rate is listed despite being half made of those two. `base` and * `totalOnDebt` are branch-independent — the pool's rate at the projected * utilization, and the quota rates over the debt — but `total` and the per-token * `quotas[].rate` normalize against `totalValue`, so a floor-branch breakdown * quotes the same cost against a smaller position and comes out higher. One * field cannot be half prefixed, and the half that moves is the half a screen * shows, so the whole breakdown carries the marker. **/ export type RoutedField = "totalValue" | "netValue" | "assets" | "healthFactor" | "safeHealthFactor" | "borrowRate" | "timeToLiquidation" | "liquidationPrice" | "leverage"; /** * `x` becomes `estX`, for a projection assembled from the guaranteed floor. * * The prefix is not decoration: an `estHealthFactor` and a `healthFactor` are * answers to different questions, and naming them alike would invite a screen * to show one as the other or a test to hold them equal. **/ export type Estimated = { [K in keyof T as `est${Capitalize}`]: T[K]; }; /** * A projection as `preview` can answer it: the branch-independent half under the * shared names, and everything a route decides marked `est`. * * Same builder, same formulas, same units as an {@link AccountProjection} — only * the snapshot underneath is the floor rather than the expected outcome. **/ export type EstimatedProjection = Omit & Estimated>; /** * Renames a projection's routed fields, for a caller that built one from floor * balances. * * Lives beside the type so the two cannot drift: a field added to * {@link RoutedField} fails to compile until it is renamed here too. **/ export declare function asEstimated(p: AccountProjection): EstimatedProjection; /** * What an operation moved, as opposed to where it left the account. * * Only `preview` reports these: it is handed both sides of the transaction and * diffs them, while `prepare` is asked to reach a state and answers with the * calls that get there. Split out so the two halves agree on the names for the * day prepare reports deltas too. **/ export interface AccountStateChange { /** * Debt after minus debt before. A repayment settles interest and fees * before principal, so this is the payment itself rather than the part of * it the principal happened to absorb. */ totalDebtChange: TokenAmount; /** * Quotas after minus quotas before. Denominated in the market's underlying * like {@link AccountHoldings.quotas}, so `token` names the collateral the * quota applies to rather than the amount's own unit. */ quotasChange: TokenAmount[]; /** * Assets after minus assets before. * * Unprefixed despite resting on the same floor balances as * {@link EstimatedProjection.estAssets}: `prepare` reports no deltas at all, * so there is no expected-branch figure of this name to be mistaken for. */ assetsChange: TokenAmount[]; } /** * Fields every account opening shares. **/ interface OpenStrategyPositionProjection extends EstimatedProjection { /** * Collateral token this position is a strategy in: the first quoted token, * with its balance taken from `estAssets` — so, like them, the floor the * route guarantees rather than what it expects to return. * Undefined when nothing is quoted. */ targetCollateral?: TokenAmount; /** * Tokens that were added as collateral during account opening. * * When the transaction has native value attached, it is represented as a * `NATIVE_ADDRESS` entry, with the wrapped native token amount reduced * accordingly (omitted entirely when it reaches zero). */ collateralAdded: TokenAmount[]; /** * Tokens the same transaction hands back to the wallet * (`withdrawCollateral` calls, with the MAX_UINT256 sentinel resolved * against replayed balances). * * Empty for an opening that keeps everything it bought; a borrow pays its * loan out here, and the values above are what is left once it has. */ collateralWithdrawn: TokenAmount[]; /** * The oracle could not price a token; it contributes nothing to the values. */ warning?: UnpriceableTokenError; } /** * What an account-opening transaction that already exists would do — the * counterpart of `prepare.openNewStrategy` and `prepare.borrow`, read off * calldata rather than planned into it. **/ export interface OpenStrategyPositionPreview extends OpenStrategyPositionProjection { operation: "OpenCreditAccount"; /** * Existing zero-debt, zero-quota account this operation reopens. */ creditAccount?: Address; /** * Registration args the factory received (Securitize: `tokensToRegister`, * `signaturesToCache`). Present on RWA-factory accounts only. */ rwaArgs?: RWAOperationArgs; /** * Whether the multicall grants the Midas greenlisted role to the credit * account via `receiveGreenlist()`. Needed for the Midas empty-account * flow. */ midasGreenlistsAccount?: boolean; } /** * What a transaction on an existing account would do — the counterpart of the * `prepare` flows that adjust one (`depositStrategy`, `withdrawStrategy`, * `addCollateral`, `withdrawCollateral`, `adjustLeverage`), read off calldata * rather than planned into it. **/ export interface AdjustStrategyPositionPreview extends EstimatedProjection, AccountStateChange { operation: "AdjustCreditAccount"; /** * Credit account that is being adjusted */ creditAccount: Address; /** * Collateral token this position is a strategy in. Same as * {@link StrategyPosition.targetCollateral} */ targetCollateral: Token | null; /** * Tokens that were added as collateral during account opening. * * When the transaction has native value attached, it is represented as a * `NATIVE_ADDRESS` entry, with the wrapped native token amount reduced * accordingly (omitted entirely when it reaches zero). */ collateralAdded: TokenAmount[]; /** * Tokens that were withdrawn as collateral during account adjustment. */ collateralWithdrawn: TokenAmount[]; /** * Intent of the delayed withdrawal this transaction claims; set when the * multicall claims a delayed withdrawal */ intent?: DelayedIntent; /** * The oracle could not price a token; it contributes nothing to the values. */ warning?: UnpriceableTokenError; } /** * What an exit transaction that already exists would do — the counterpart of * `prepare.withdrawStrategy` asked for everything, read off calldata rather * than planned into it. **/ export interface ExitStrategyPositionPreview extends EstimatedProjection { operation: "CloseCreditAccount"; /** * True when the account is closed permanently (facade `closeCreditAccount` * entry point), false when the debt is zeroed but the account stays open * (plain multicall). */ permanent: boolean; /** * Credit account that is being closed */ creditAccount: Address; /** * Collateral token this position is a strategy in. Same as * {@link StrategyPosition.targetCollateral} */ targetCollateral: Token | null; /** * Token withdrawn to the user and its minimal guaranteed amount, from the * multicall replay (all collateral is swapped into the received token * before withdrawal). The token is the market underlying or, on RWA * markets, the unwrapped RWA underlying (the ERC4626 vault asset the * underlying share converts 1:1 into) */ receivedAmount: TokenAmount; /** * Intent of the delayed withdrawal this transaction claims; set when the * multicall claims a delayed withdrawal */ intent?: DelayedIntent; /** * The oracle could not price a token; it contributes nothing to the values. */ warning?: UnpriceableTokenError; } /** * What a settling repayment that already exists would do — the counterpart of * `prepare.repayStrategy` asked for the whole debt, read off calldata rather * than planned into it. **/ export interface RepayStrategyPositionPreview extends EstimatedProjection { operation: "RepayCreditAccount"; /** * True when the account is closed permanently (facade `closeCreditAccount` * entry point), false when the debt is zeroed but the account stays open * (plain multicall). */ permanent: boolean; /** * Credit account that is being repaid */ creditAccount: Address; /** * Collateral token this position is a strategy in. Same as * {@link StrategyPosition.targetCollateral} */ targetCollateral: Token | null; /** * Tokens added from the wallet to cover the debt (`addCollateral` calls). * * When the transaction has native value attached, it is represented as a * `NATIVE_ADDRESS` entry, with the wrapped native token amount reduced * accordingly (omitted entirely when it reaches zero). */ collateralAdded: TokenAmount[]; /** * Tokens returned to the user in-kind (`withdrawCollateral` calls, with the * MAX_UINT256 sentinel resolved against replayed balances) */ collateralWithdrawn: TokenAmount[]; /** * Total debt repaid: principal + accrued interest + fees, in underlying. * * The same quantity a {@link AdjustStrategyPositionPreview} reports as * `totalDebtChange`, with the sign a repayment screen reads: positive for * what the wallet parted with. */ debtRepaid: TokenAmount; /** * Intent of the delayed withdrawal this transaction claims; set when the * multicall claims a delayed withdrawal */ intent?: DelayedIntent; /** * The oracle could not price a token; it contributes nothing to the values. */ warning?: UnpriceableTokenError; } /** * Preview of the "instant" part of an operation on an existing credit * account: what the transaction does in the same block, before any delayed * withdrawal is claimed. */ export type InstantStrategyPositionOperationPreview = AdjustStrategyPositionPreview | ExitStrategyPositionPreview | RepayStrategyPositionPreview; /** * Preview of a multicall that requests a delayed withdrawal (e.g. Securitize * redemption): the source token is spent now and a withdrawal phantom token is received; * the actual claim token materializes later, when the withdrawal is claimed and * the recorded (if any) is resumed */ export interface DelayedStrategyPositionOperationPreview extends CreditOperationMarket { operation: "DelayedCreditAccountOperation"; /** * Credit account the operation is performed on */ creditAccount: Address; /** * Collateral token this position is a strategy in. Same as * {@link StrategyPosition.targetCollateral} */ targetCollateral: Token | null; /** * Decoded from the withdrawal request's extraData; undefined when the * request carries no intent (e.g. Mellow) */ intent?: DelayedIntent; /** * Estimated unix timestamp (seconds) when the delayed outputs become * claimable: `now + withdrawalLength` of phantom token. * Undefined when the compressor has no information about the asset. */ estClaimableAt?: Timestamp; /** * What this transaction does right now: the delayed withdrawal is * represented by the phantom token among the account's assets */ instantPreview: InstantStrategyPositionOperationPreview; /** * Best-effort state after the withdrawal is claimed and the intent is * resumed; claim-only (phantom burned, claim token received) when * `intent` is undefined */ delayedPreview: InstantStrategyPositionOperationPreview; } /** * Result of previewing a raw operation calldata: currently pool operations and * credit account opening, adjustment, closure, repayment and delayed * withdrawal operations are supported. */ export type OperationPreview = PoolPositionOperationPreview | OpenStrategyPositionPreview | AdjustStrategyPositionPreview | ExitStrategyPositionPreview | RepayStrategyPositionPreview | DelayedStrategyPositionOperationPreview; //#endregion export {};