Nolus Docs

Repaying and closing

Repay a Spot Margin position, close it in full or in part, and set take profit and stop loss, with the order in which payments and liquidations apply.

An open position ends when the user repays the debt and receives the position asset, when the user closes it and receives the proceeds in the pool currency, or when the protocol liquidates it. Partial repayments, partial closes and partial liquidations reduce the debt and leave the position open. Repayments and closes are messages to the position's Lease contract, and no outcome needs a follow-up claim, because the user is paid automatically.

Repayment order

Every payment into a position is applied in the same order, whether it comes from a repayment, from the proceeds of a close or from a liquidation:

  1. Overdue loan interest
  2. Overdue margin interest
  3. Due loan interest
  4. Due margin interest
  5. Principal
  6. Change, returned to the user

Loan interest goes to the lending pool, while margin interest goes to the protocol's Profit contract. Change comes last, because it exists only once the principal is fully paid.

Interest falls due once every due period, and interest left unpaid past the period becomes overdue and is collected by a partial liquidation, at the time that the overdue_collect_in field of the state query shows. The position parameters give an example due period and the query that returns the live one.

Repay a position

A repayment is the repay message, sent to the position's Lease:

{ "repay": [] }

Attach the payment as funds in its bank denom, which you resolve as described in Opening a position. The payment can be in the pool currency, in a position asset of the protocol or in NLS, and anyone may repay any position. From the command line, a repayment in USDC looks like this:

nolusd tx wasm execute <lease> '{"repay":[]}' \
  --amount 50000000$USDC_DENOM \
  --from <key> --gas auto --gas-adjustment 1.3 --gas-prices 0.025unls

Replace <lease> with the address of the position's Lease contract and <key> with the name of your key in nolusd. $USDC_DENOM holds the bank denom of USDC, and the number in front of it is the payment in the smallest unit of that currency.

How the Lease applies the payment depends on its currency. A payment in the pool currency is applied on Nolus at once, without involving Solana, whereas a payment in another currency is first redeemed into the position's account on Solana and swapped there to the pool currency through Metis. That swap fills at no less than the oracle price minus the repay slippage limit, and it is retried until it fills. The proceeds then return to the escrow of Solray, the Nolus program on Solana, and the Lease applies the pool-currency receipts credited to it.

A payment that leaves debt keeps the position opened with a lower LTV, while a payment that clears the debt settles the position as described in Full repayment. To repay in full, query the state with due_projection_secs, as described in Querying a position, and pay the principal plus all interest, after which any excess comes back as change.

Full repayment

When the debt reaches zero, the loan is closed in the pool and the position settles on its own in four steps:

  1. The state becomes paid.
  2. The position asset returns to Solray's escrow on Solana, and a receipt for it, together with any surplus in the pool currency, is credited to the user's Nolus account.
  3. The state becomes closing while the account on Solana is closed.
  4. The state becomes closed.

The user then holds a receipt for the position asset in their Nolus account and can redeem it for the asset on Solana.

Close a position in full

A full close sells the whole position and pays the debt from the proceeds, so the user adds no funds. The position owner sends the close, and a take profit or stop loss trigger runs the same close. The message is the full_close variant of close_position:

{ "close_position": { "full_close": {} } }

From the command line, the owner sends it to the position's Lease as follows:

nolusd tx wasm execute <lease> '{"close_position":{"full_close":{}}}' \
  --from <key> --gas auto --gas-adjustment 1.3 --gas-prices 0.025unls

As in a repayment, <lease> is the address of the position's Lease contract and <key> is the name of the owner's key in nolusd. The Lease then settles the close in four steps:

  1. The position asset is swapped to the pool currency on Solana at no less than the oracle price minus the close slippage limit, and the swap is retried until it fills.
  2. The proceeds return to Solray's escrow, and the receipts credited to the Lease are applied in the repayment order.
  3. What is left is credited to the user's Nolus account as pool-currency receipts. If the proceeds do not cover the debt, the reserve covers the shortfall.
  4. The state becomes closing, then closed.

Close part of a position

A partial close sells part of the position and applies the proceeds to the debt. Its message names the amount to sell:

{
  "close_position": {
    "partial_close": { "amount": { "amount": "1000000000", "ticker": "SOL" } }
  }
}

The amount is in the position asset and must be at least the minimum transaction, and the position left after the close must be at least the minimum position. A close that breaks either limit is rejected with PositionCloseAmountTooSmall or PositionCloseAmountTooBig, so when the remainder would fall below the minimum, close the position in full instead.

The proceeds are applied in the repayment order, and if they leave debt, the position stays opened with a smaller size and lower debt. If they clear the debt, the rest settles as a full repayment, and receipts for the remaining position asset and the surplus are credited to the user's Nolus account.

Set take profit and stop loss

A position can carry a take profit and a stop loss, both expressed as LTV in permille, and when either one triggers, the Lease runs a full close. To set both levels, send change_close_policy to the Lease:

{ "change_close_policy": { "stop_loss": { "set": 870 }, "take_profit": { "set": 650 } } }

Either key is optional, and "reset" removes a level, as in this message, which removes the take profit:

{ "change_close_policy": { "take_profit": "reset" } }

The levels follow these rules:

  • Only the position owner can set them, and only once the position is opened.
  • The levels must satisfy 0 < take profit <= current LTV < stop loss < liquidation LTV, and a change that would trigger at once is rejected.
  • Stop loss triggers when LTV rises to or above it, and take profit triggers when LTV falls below it.
  • A trigger is used once, because the position closes after it fires.
  • Take profit is reset if a payment or a partial liquidation brings LTV below it, since those are not price moves.
  • If the stop loss and the liquidation LTV are crossed together, the liquidation takes precedence.

Liquidation

Besides the user's own repayments and closes, the protocol can reduce or close a position through liquidation. The Lease re-checks its LTV on price alarms from the oracle and on a periodic time alarm, and it liquidates the position when the LTV reaches the liquidation LTV or interest is overdue. A partial liquidation sells enough of the position asset to bring LTV back to the healthy level and leaves the position opened, whereas a full liquidation sells the whole position, after which the state becomes closing and then liquidated.

Unlike repayments and closes, which retry within their own slippage limit, a liquidation swap that would fill below the oracle price minus the liquidation slippage limit does not execute, and the position waits with status slippage_protection_activated until it is re-assessed.

Liquidations and risk covers the rest of the risk framework, from the thresholds and the choice between partial and full liquidation to slippage protection, the reserve and the limits set per asset. The position parameters give example values for every limit, with the query for the live ones.

On this page