Opening a position
Send open_lease to open a Spot Margin position, see how the fixed loan rate is set, and follow the Lease as it builds its account on Solana.
Opening a position takes one transaction from the user, an open_lease
message to the protocol's Leaser with the down payment attached, and
everything after that runs between contracts. The Leaser creates a Lease
contract for the position, and the Lease borrows from the pool at a fixed rate
and opens an account for the position on Solana through
Solray, the Nolus program on Solana. The Lease then
redeems its receipts so that the tokens are released into that account and
buys the position asset through Metis, and the position is open once that
swap settles.
Send the open message
The open_lease message names the position asset and can cap the size of the
loan:
{ "open_lease": { "currency": "SOL", "max_ltd": 2000 } }The message has two fields, and the down payment travels with it as funds:
| Field | Meaning |
|---|---|
currency | Ticker of the position asset. For short markets this is USDC. |
max_ltd | Optional loan-to-down-payment cap in permille (2000 borrows at most 2x the down payment). Omit it for the maximum leverage. |
| funds | The down payment, attached as funds in its bank denom. It can be in the pool currency, in a position asset of the protocol, or in NLS. |
To send the message from the command line, resolve $LEASER and $ORACLE as
shown in
Find the contract addresses,
then look up the bank denom of the down payment currency and execute the
message:
# bank denom of the down payment currency
USDC_DENOM=$(nolusd q wasm contract-state smart $ORACLE '{"currencies":{}}' -o json \
| jq -r '.data[] | select(.ticker=="USDC") | .bank_symbol')
# open a SOL long with 100 USDC down at maximum leverage
nolusd tx wasm execute $LEASER '{"open_lease":{"currency":"SOL"}}' \
--amount 100000000$USDC_DENOM \
--from <key> --gas auto --gas-adjustment 1.3 --gas-prices 0.025unlsReplace <key> with the key that signs the transaction. The amount
100000000 is 100 USDC in its smallest unit, and because the message leaves
out max_ltd, the position opens at maximum leverage.
The app builds the same message for the user's Solana wallet to sign, and the app's REST API returns it as an unsigned transaction. Because users hold their tokens on Solana, they lock tokens in Solray's escrow before opening a position and receive receipts for them in their Nolus account, as User flow describes.
How much is borrowed
The size of the loan follows from the down payment and the opening LTV:
default loan-to-down-payment = initial_ltv / (1 - initial_ltv)
loan = down payment x min(max_ltd, default loan-to-down-payment)Here initial_ltv is the opening LTV from the Leaser's configuration and
max_ltd is the optional cap from the message. With an opening LTV of, for
example, 80%, the default ratio is 4, so a 100 USDC down payment borrows
400 USDC and opens a 5x position worth 500 USDC. With max_ltd: 2000, the
same down payment borrows 200 USDC.
Before it opens the position, the Leaser also checks the down payment and the loan against the protocol's minimum sizes, as Limits checked at opening describes.
The loan rate
The pool sets the loan rate from its utilization at the moment the position opens, using the curve below, and the rate then stays fixed for the life of the loan:
rate = base + addon / optimal x min(liability / idle, optimal / (1 - optimal))In the formula, liability / idle is the pool's outstanding loans divided by
its idle balance, which measures utilization. The base term is the minimum
rate, while optimal is the target utilization and addon sets the slope.
Because the min(...) caps the utilization term at the optimal utilization,
the rate stops rising once the pool is past it.
For example, with a base of 3%, an optimal utilization of 70% and an addon of 2%, the curve gives these rates:
| Utilization | liability / idle | Loan rate |
|---|---|---|
| 50% | 1.0 | 3% + 2% / 0.7 x 1.0 = 5.86% |
| 60% | 1.5 | 3% + 2% / 0.7 x 1.5 = 7.29% |
| 70% or more | capped at 0.7 / 0.3 = 2.33 | 3% + 2% / 0.7 x 2.33 = 9.67% |
Margin interest is added on top of the loan rate at the annual rate set in the position parameters. To check the live values, query the pool's curve and the Leaser's margin rate:
nolusd q wasm contract-state smart $LPP '{"config":[]}' -o json
# {"data":{"borrow_rate":{"base_interest_rate":30,"utilization_optimal":700,"addon_optimal_interest_rate":20},...}}
nolusd q wasm contract-state smart $LEASER '{"config":{}}' -o json \
| jq .data.config.lease_config.interest_rate_marginThe comment under the first query shows the shape of the pool's answer, in
which the curve values are in permille, so a base_interest_rate of 30 is
3%.
Where the position lives
A position's assets live on Solana, in a per-position account of the Remote Lease app inside Solray, and the Lease contract on Nolus is the only controller of that account. The Lease sends its instructions over IBC through the protocol's remote lease controller, so the account opens, swaps, returns tokens to the escrow and closes only when the Lease asks it to. Positions on Solana describes how receipts, swaps and the account work together on the Solana side.
The opening flow
The diagram and the steps below follow an open from the user's message to the opened position:
- The user sends
open_leasewith the down payment, and the Leaser checks the limits and creates a Lease contract for the position. Only the Leaser can create a Lease, and the pool lends only to Leases it recognizes. - The Lease borrows the loan from the LPP at the current rate, which is now fixed for this loan.
- Through its protocol's remote lease controller, the Lease sends an IBC packet to open the position's account on Solana. Solray's Remote Lease app creates the account, and the acknowledgement returns it to the Lease.
- The Lease redeems its receipts for the down payment and the loan, and Solray releases the tokens from escrow into that account.
- One swap through Metis buys the position asset. The swap accepts no less than the oracle price minus the open slippage limit, and it is retried until it fills.
- The Lease records the position and enters
opened, and from then on it tracks interest and LTV with a periodic re-check and price alarms.
While the open runs, the Lease's {"state":{}} query returns opening, and
its in_progress field moves from open_lease to transfer_out and then to
buy_asset. Querying a position explains how
to read the state.
If the account cannot be opened
If the request to open the account on Solana fails or times out, the Lease
has not yet redeemed any receipts, so it unwinds the open on Nolus. It refunds
the whole down payment to the user, closes the loan with the pool and ends in
the terminal state open_failed, which carries the reason. Funding the
account and the swap later in the flow never fail the open, because both are
retried.
Positions are fixed at opening
Each Lease stores the loan rate, the margin rate, the LTV levels, the minimums and the due period when it opens, so a later change to the Leaser's configuration applies only to positions opened after that change. The maximum slippage limits are the exception, since the Lease reads them from the Leaser each time it starts an open, repay, close or liquidation swap.
The next page, Querying a position, shows how to find a user's positions and read their state.
Quoting a position
Preview a Spot Margin position with the Leaser quote query, including position size, loan amount, loan rate and margin rate, before opening it.
Querying a position
Find a user's Spot Margin positions through the Leaser, then read a position's state, amounts due, interest rates and close policy from its Lease.