Skip to main content
Withdraw collateral out of an account and repay its debt, both through the controller. Read first: Supply and borrow, Health factor. Both calls go to the controller. Withdraw requires the account owner or an active delegate and re-checks risk. Repay is permissionless: anyone can repay any account. Neither is pause-gated — a paused protocol still lets solvent users exit and lets anyone reduce debt. Confirm state from controller and pool events.

Withdraw

withdraw removes one or more supplied assets from an account and sends them to to when provided, else to caller. It returns the actual amount paid per deduped asset.
The controller batches the legs into one pool call, withdraw(receiver, is_liquidation = false, entries), then re-checks the LTV gate and health factor on cached indexes. The account owner (or delegate) stays the auth subject regardless of to; only the token destination changes.
Full-position sentinel. Pass amount = 0 for an asset to withdraw its entire post-accrual position. The controller forwards i128::MAX to the pool, which clamps it to the current actual supply; over-asking with any amount at or above the position value clamps the same way. The zero is sticky: once an asset’s running total across the batch is zero, further legs for that asset stay at “all”.The return value is the resolved figure — the only way a caller learns what a withdraw-all actually paid. There is no max_withdraw view; simulate the call instead.

Preconditions

  • caller is the account owner or an active delegate.
  • Each asset has a live supply position in the account (else #121).
  • After the withdrawal, if any debt remains, the health factor stays at or above 1e18, the LTV gate holds, and LTV-weighted collateral stays above the minimum-borrow floor.
  • The pool holds enough cash for the requested amount (else #112).
  • Every priced asset resolves a valid price. A frozen listing blocks entry but not exit, so a frozen asset stays withdrawable; a paused listing blocks both.

Inputs

Address
required
Account owner. Authorizes the withdrawal; receives the tokens when to is absent.
u64
required
The account to withdraw from. Must already exist.
Vec<(HubAssetKey, i128)>
required
Each entry is ({ hub_id, asset }, amount) in asset-native units. amount = 0 withdraws the full position for that asset. Duplicate legs for the same asset are summed, preserving first-appearance order.
Option<Address>
Optional recipient override. When set, the pool pays the withdrawn tokens directly to this address — useful for contracts (vaults, routers) forwarding funds to an end user without an extra hop.

Build the call

Authorize

caller.require_auth() runs inside withdraw; the caller must be the account owner (the position-NFT holder) or an active delegate. The controller transfers the withdrawn assets from the pool to the recipient (to or caller) — pool-internal, no extra signature. The transaction’s only authorization is caller ‘s top-level call. Simulate first: an account with debt can revert at the post-withdrawal health-factor gate.

Verify

On success the controller emits UpdatePositionBatchEvent (["position", "batch_update"], legs tagged Withdraw) and the pool emits PoolMarketStateBatchEvent (["market", "batch_state_update"]). The call returns the actual amounts paid per asset (a full close pays the floor-valued balance). Confirm with get_collateral_amount(account_id, hub_asset) (zero after a full close), get_total_collateral_usd(account_id), and get_health_factor(account_id). If the withdrawal cleared the last supply and debt position, the account is removed and its NFT burned — get_account_attributes(account_id) then raises #24 AccountNotFound, and a later supply must pass account_id = 0 to open a new account.

Failure modes

Repay

repay reduces an account’s debt. It is permissionless: any caller may repay any account_id, and overpayment is refunded to caller.
The controller pre-transfers each leg from caller into the pool, then calls the pool’s batched repay(payer, actions). If a payment exceeds the outstanding debt, the pool refunds the excess to caller. Credit is measured: a token that delivers less than it is sent retires only the delivered amount.

Preconditions

  • caller holds at least amount of every asset in payments. No account ownership is required.
  • Each asset has a live debt position in the account (else #120).
  • repay loads and persists the debt side only, and is not pause-gated at the protocol level. A paused listing on that asset still blocks it (#315); a frozen listing does not.

Inputs

Address
required
Funds the repayment and authorizes the transfers. Need not own the account. Receives any overpayment refund.
u64
required
The account whose debt is repaid. Must already exist.
Vec<(HubAssetKey, i128)>
required
Each entry is ({ hub_id, asset }, amount) in asset-native units, strictly positive. Overpayment is refunded to caller.

Build the call

Authorize

caller.require_auth() runs inside repay. The transaction authorizes one token transfer per asset from caller to the pool. Because repay is permissionless, the only signature required is the payer’s — never the account owner’s.

Verify

On success the controller emits UpdatePositionBatchEvent (["position", "batch_update"]) and PoolMarketStateBatchEvent (["market", "batch_state_update"]). Confirm with get_borrow_amount(account_id, hub_asset), get_total_borrow_usd(account_id), and get_health_factor(account_id) (rises as debt falls; i128::MAX once debt-free).

Account close behavior

Clearing all positions cleans up the account. Repaying the last debt while collateral remains does not remove the account — the supply positions still exist. The account’s storage is removed only when both its supply and debt maps are empty, which happens when an owner withdrawal (or a close-position strategy) takes out the final position. To fully close, repay all debt, then withdraw all collateral with the full-position sentinel.

Failure modes

Next

Liquidations

What happens when health factor drops below 1, and the liquidator’s recipe.

Accounts and risk

Accounts, positions, and how account close-out works.