# Glossary (/docs/glossary) | Term | Meaning | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Lease** | A position on Nolus. Implemented as a per-position CosmWasm contract. | | **Lease asset** | The asset purchased at open with down payment + LPP loan and held until close. The position holds this asset for its lifetime - it's what the borrower has long or short exposure to. | | **Down payment** | The borrower's own collateral contribution to a position. Provided in any supported currency; swapped together with the borrowed amount into the lease asset at open. | | **LPP** | Liquidity Provider Pool - the single-sided pool from which leases borrow. | | **nLPP** | The receipt token minted to LPP depositors. Accrues value as the pool earns interest. | | **Margin interest** | Interest accrued by an active lease against the LPP loan. | | **Feeder** | A whitelisted off-chain operator that submits signed spot prices to the on-chain Oracle. | | **EMA** | Exponential Moving Average - the price form the on-chain Oracle exposes to consumers. | | **ICA** | Interchain Account - Nolus uses ICA on counterparty DEXs to execute swaps for leases. | | **Profit** | The contract that routes protocol revenue. | | **Reserve** | The protocol-owned backstop pool that covers shortfalls when a lease closes underwater. | | **NLS** | The native staking and governance token of the Nolus chain. | # Welcome (/docs) **Nolus** is a spot margin protocol live on **Solana** - leveraged positions at a fixed rate, backed by the asset itself. Solray, the protocol's native Solana program, executes every position on Solana, with swaps routed through the Metis aggregator; users connect with Phantom or Solflare. This site is the developer reference for everything in and around the protocol. ## Where to start [#where-to-start] # Public APIs (/docs/public-apis) Beyond the chain itself, several services expose public APIs that applications integrate with. | Service | Surface | Repo | | -------------- | ----------------------------------------- | ------------------------------------------------------------ | | Tendermint RPC | Block/tx queries, websocket subscriptions | [`nolus-core`](https://github.com/nolus-protocol/nolus-core) | | Cosmos SDK LCD | REST gateway over chain queries | [`nolus-core`](https://github.com/nolus-protocol/nolus-core) | | gRPC | Typed Cosmos SDK + CosmWasm queries | [`nolus-core`](https://github.com/nolus-protocol/nolus-core) | | NLS Supply API | NLS supply data over HTTP | [`nls-supply`](https://github.com/nolus-protocol/nls-supply) | Stub page - endpoint URLs, OpenAPI references, and rate-limit notes will be added as services stabilize their public contracts. # Overview (/docs/agentic-workflows) Nolus exposes three machine-callable surfaces. They're complementary rather than alternatives, and you pick the one that matches where your agent runs and what it needs to do. | Surface | Where it runs | Scope | | ------------------------------------------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | [stdio MCP server](/docs/agentic-workflows/mcp-server) | Local on the user's machine, via Cursor / Claude Desktop / any MCP client | Full read surface + transaction builders (open lease, repay, deposit LPP, withdraw, stake, vote) | | [In-page WebMCP](/docs/agentic-workflows/webmcp) | Inside the `app.nolus.io` browser tab | Wallet-scoped reads + connect + navigate. No signing | | REST + WebSocket API | Anywhere that speaks HTTP | General-purpose queries, pre-built unsigned tx payloads | ## Picking a surface [#picking-a-surface] * *The agent runs on the user's desktop* (Cursor, Claude Desktop) and you want trading actions: use the **stdio MCP** in `@nolus/nolusjs`. It builds unsigned transactions; the user's wallet signs them. * *The agent lives in the browser tab* alongside the connected wallet, and you want it to inspect the live session or drive the UI: use the in-page **WebMCP**. Tools auto-bind to the connected wallet - no address argument needed. WebMCP intentionally does not sign; tx flows hand off to the UI or the stdio MCP. * *The agent runs anywhere and just needs HTTP*: hit the **REST API** at `https://app.nolus.io/api/`. The OpenAPI 3.1 spec lives at [`/api/openapi.json`](https://app.nolus.io/api/openapi.json) with 85 documented paths. The agent-card at [`/.well-known/agent-card.json`](https://app.nolus.io/.well-known/agent-card.json) describes the surface for agent autodiscovery. ## Signing boundary [#signing-boundary] Across all three surfaces, the protocol's signing boundary is the same: **Nolus never holds or signs with the user's private key.** Transaction tools return unsigned payloads; the wallet popup (Keplr, Phantom, Solflare, Ledger) is the single consent surface. WebMCP enforces this by design - it deliberately exposes no signing tool. The stdio MCP returns CLI-ready unsigned transactions that the user runs through `nolusd` or their wallet. # MCP server (/docs/agentic-workflows/mcp-server) # MCP Server [#mcp-server] The Nolus MCP server exposes the `@nolus/nolusjs` library as **tools that AI assistants can use** to interact with the Nolus Protocol - through Cursor, Claude Desktop, or any client that speaks the [Model Context Protocol](https://modelcontextprotocol.io/). The server runs locally on your machine and bridges the assistant to the chain via the SDK. ## Tools [#tools] ### Query tools (read-only) [#query-tools-read-only] Fetch state from the chain. Safe to call without any user confirmation. | Tool | What it returns | | ---------------------- | ------------------------------------------------ | | `get_protocols` | List of all registered protocols. | | `get_protocol` | Protocol details and contract addresses. | | `get_platform` | Platform-level contracts (treasury, timealarms). | | `get_lease_quote` | Quote for opening a leveraged position. | | `get_open_leases` | All active leases for a wallet. | | `get_leaser_config` | Current Leaser configuration. | | `get_lease_status` | Status of a specific lease. | | `get_lpp_balance` | Liquidity pool balance. | | `get_lpp_config` | Pool configuration. | | `get_lpp_price` | nLPN receipt token price. | | `get_lender_deposit` | Lender's deposit balance. | | `get_lender_rewards` | Lender's pending rewards. | | `get_deposit_capacity` | Remaining deposit capacity. | | `get_lpn` | Pool's native asset ticker. | | `get_oracle_prices` | All asset prices. | | `get_asset_price` | Price of a specific asset. | | `get_currencies` | All supported currencies. | | `get_oracle_config` | Oracle configuration. | | `calculate_rewards` | NLS rewards distribution. | | `get_wallet_balance` | Token balance for an address. | | `get_block_height` | Current block height. | | `get_chain_id` | Chain ID. | ### Prepare tools (build unsigned transactions) [#prepare-tools-build-unsigned-transactions] These tools **do not sign or broadcast**. They return a transaction message and a ready-to-use `nolusd tx ...` CLI command, which the user reviews and runs themselves. | Tool | Builds | | ----------------------------- | ------------------------------- | | `prepare_open_lease` | Lease opening transaction. | | `prepare_repay_lease` | Lease repayment transaction. | | `prepare_close_lease` | Lease close (full or partial). | | `prepare_change_close_policy` | Stop-loss / take-profit update. | | `prepare_deposit_lpp` | Deposit into liquidity pool. | | `prepare_withdraw_lpp` | Withdrawal from liquidity pool. | | `prepare_claim_lpp_rewards` | NLS rewards claim. | | `prepare_transfer_tokens` | Bank send transaction. | ## Network configuration [#network-configuration] The server ships with built-in configs for both networks: * **Mainnet (Pirin)** - `network: "pirin"` (default). * **Testnet (Rila)** - `network: "rila"`. Most tools accept an optional `network` parameter so you can target a specific chain per request. Omit it to use Pirin. You can override the defaults via environment variables: | Variable | Description | Network | | --------------------------- | ----------------------------------------------- | ------- | | `NOLUS_PIRIN_RPC_URL` | Pirin RPC endpoint | mainnet | | `NOLUS_PIRIN_ADMIN_ADDRESS` | Pirin Admin contract address | mainnet | | `NOLUS_PIRIN_CHAIN_ID` | Pirin chain ID (used in generated CLI commands) | mainnet | | `NOLUS_RILA_RPC_URL` | Rila RPC endpoint | testnet | | `NOLUS_RILA_ADMIN_ADDRESS` | Rila Admin contract address | testnet | | `NOLUS_RILA_CHAIN_ID` | Rila chain ID | testnet | ## Connecting from Cursor or Claude Desktop [#connecting-from-cursor-or-claude-desktop] Add the server to your MCP client's configuration (adjust the absolute path to the cloned `nolus.js` repo): ```json { "mcpServers": { "nolus": { "command": "npx", "args": ["tsx", "/absolute/path/to/nolus.js/src/mcp/server.ts"], "env": {} } } } ``` Restart your editor or Claude Desktop. The server will appear under MCP settings, and the assistant will surface the query and prepare tools listed above. ## Running it manually [#running-it-manually] ```sh npm run mcp ``` To override the built-in network settings, export the env vars before running: ```sh export NOLUS_PIRIN_RPC_URL=https://rpc.nolus.network export NOLUS_PIRIN_ADMIN_ADDRESS=nolus1gurgpv8savnfw66lckwzn4zk7fp394lpe667dhu7aw48u40lj6jsqxf8nd export NOLUS_PIRIN_CHAIN_ID=pirin-1 npm run mcp ``` ## Reference prompt for AI agents [#reference-prompt-for-ai-agents] Use this prompt as a system instruction for an agent that needs to interact with Nolus end-to-end (set up the server, install `nolusd`, configure the chain client): ```text You are a Nolus Protocol agent. Before doing anything else, ensure the environment is ready: ## Step 1: Set up the Nolus MCP server - Clone the nolusjs library: `git clone https://github.com/nolus-protocol/nolus.js` - Install dependencies: `cd nolus.js && npm install` - Register the following MCP server in your client's configuration: - Command: `npx tsx /src/mcp/server.ts` - Environment variables: - NOLUS_PIRIN_RPC_URL=https://rpc.nolus.network - NOLUS_PIRIN_ADMIN_ADDRESS=nolus1gurgpv8savnfw66lckwzn4zk7fp394lpe667dhu7aw48u40lj6jsqxf8nd - NOLUS_PIRIN_CHAIN_ID=pirin-1 - Reload your MCP tools so the server is available ## Step 2: Install nolusd if not already available - Detect OS and architecture - Download the appropriate binary from the latest release at https://github.com/nolus-protocol/nolus-core/releases/latest - Install it to a directory on your PATH and make it executable - Verify with `nolusd version` ## Step 3: Configure nolusd - `nolusd config set client chain-id pirin-1` - `nolusd config set client node https://rpc.nolus.network:443` Confirm setup is complete and list any existing keys with `nolusd keys list`. You are now ready to interact with Nolus Protocol. ``` # In-page WebMCP (/docs/agentic-workflows/webmcp) The Nolus webapp ships with a [WebMCP](https://webmachinelearning.github.io/webmcp/) integration. Tools are registered via `navigator.modelContext.registerTool` inside the `app.nolus.io` browser tab, so an agent running alongside the page (a browser extension, an embedded model, Chrome's WebMCP EPP) can inspect the user's live session and drive the UI without going through the HTTP API. ## When to use it [#when-to-use-it] Reach for WebMCP when the agent runs **in the same browser tab** as the dApp and the user wants it to: * See what wallet is connected, what balances and positions that wallet holds, and what oracle is reporting right now. * Trigger the wallet-connect flow when no wallet is active. * Navigate the user to a specific page on the dApp (lease, earn, staking, governance). For everything else, fall back to the [stdio MCP](/docs/agentic-workflows/mcp-server) (desktop agents, full trading instrument) or the REST API (general-purpose HTTP). ## Scope boundary [#scope-boundary] WebMCP intentionally does **not** expose any transaction-signing tool. No open-lease, no deposit, no stake, no vote. Signing stays inside the explicit user-driven UI flow with the wallet popup as the single consent surface. If an agent needs to act on the user's behalf, it hands the intent off to the dApp UI or to the stdio MCP - it never bypasses the wallet popup. ## Tools [#tools] All tools are wallet-scoped: when a wallet is connected, they auto-bind to it, so there's no address parameter to pass. | Tool | What it returns / does | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `get_connected_wallet` | The currently connected Nolus wallet (bech32 address) and connection status. | | `get_balances` | The connected wallet's token balances across all supported assets. | | `get_open_leases` | The connected wallet's open Nolus leases (margin positions) with full lifecycle detail. | | `get_earn_positions` | The connected wallet's positions in the Nolus earn pools, plus the pool-level state. | | `get_staking_delegations` | NLS staking delegations, unbonding entries, and claimable rewards. | | `get_prices` | Current oracle prices for all supported assets, keyed by `ticker@protocol`. | | `connect_wallet` | Opens the user's wallet extension to connect. Optional `mechanism` argument picks the extension (keplr, phantom, solflare, ledger). | | `navigate` | Routes the user to one of the dApp's main pages by route name. | The implementation lives in [`src/common/webmcp/`](https://github.com/nolus-protocol/webapp/tree/main/src/common/webmcp) in the webapp repo. Each tool is a thin adapter over the dApp's Pinia stores, so calls cost no extra backend hits. ## Detecting WebMCP from an agent [#detecting-webmcp-from-an-agent] The dApp gates tool registration behind a feature detection of `navigator.modelContext`. A browser without WebMCP support simply doesn't register the tools. From the agent side: 1. Open `https://app.nolus.io/` in the tab where the agent runs. 2. After page load, the dApp registers the eight tools above through `navigator.modelContext.registerTool`. 3. The agent enumerates the registered tool surface and calls into it the same way it would for any other WebMCP-enabled page. The dApp also publishes its agent card at [`/.well-known/agent-card.json`](https://app.nolus.io/.well-known/agent-card.json) and an agent-skills index at [`/.well-known/agent-skills/index.json`](https://app.nolus.io/.well-known/agent-skills/index.json) for autodiscovery alongside WebMCP. # Overview (/docs/etl/overview) The Nolus ETL (extract-transform-load) is a Rust service that ingests Nolus chain state in real time, transforms it into normalized records, and serves a read-only HTTP API. The webapp and other Nolus services consume it to surface indexed protocol data - prices, positions, lender state, treasury flows - without each having to run its own node + indexer. ## What's in there [#whats-in-there] The ETL aggregates blockchain events, smart-contract state, and oracle prices into a PostgreSQL store and exposes the result as 70+ REST endpoints. Below is a tour of the data surface grouped by concern. ### Configuration [#configuration] Which protocols and currencies the chain currently understands. | Path | What it returns | | ------------------------------ | -------------------------------------- | | `GET /api/protocols` | All protocols, active and deprecated. | | `GET /api/protocols/active` | Active protocols only. | | `GET /api/protocols/{name}` | A single protocol by name. | | `GET /api/currencies` | All currencies, active and deprecated. | | `GET /api/currencies/active` | Active currencies only. | | `GET /api/currencies/{ticker}` | A single currency by ticker. | ### Treasury and incentives [#treasury-and-incentives] Protocol-revenue side: where NLS flows in and out. | Path | What it returns | | ---------------------------- | --------------------------------------------- | | `GET /api/revenue` | Total protocol revenue to date. | | `GET /api/distributed` | Total distributed rewards. | | `GET /api/buyback` | Buyback history (`?period=3m\|6m\|12m\|all`). | | `GET /api/buyback-total` | Total buyback amount. | | `GET /api/incentives-pool` | Current incentives-pool balance. | | `GET /api/earnings?address=` | Earnings for one address. | ### Platform-wide metrics [#platform-wide-metrics] The numbers you'd put on a dashboard. | Path | What it returns | | ------------------------------------ | --------------------------------------- | | `GET /api/total-value-locked` | Platform TVL. | | `GET /api/supplied-funds` | Total supplied funds. | | `GET /api/borrowed` | Total borrowed (optional `?protocol=`). | | `GET /api/open-interest` | Open-interest value. | | `GET /api/supplied-borrowed-history` | Historical series for both. | ### Positions and leases [#positions-and-leases] Per-position state, scoped by address or pulled in aggregate. | Path | What it returns | | ------------------------------ | -------------------------------- | | `GET /api/positions` | All currently open positions. | | `GET /api/leases?address=` | Leases owned by a given address. | | `GET /api/liquidations` | Liquidation history. | | `GET /api/historically-opened` | Historical position openings. | ### Liquidity pools [#liquidity-pools] LPP utilisation and lender state. | Path | What it returns | | -------------------------------------- | ----------------------------------- | | `GET /api/pools` | All pools with utilisation and APR. | | `GET /api/utilization-level?protocol=` | Pool utilisation history. | | `GET /api/current-lenders` | Active lenders. | | `GET /api/historical-lenders` | Lender history. | ### Pricing, blocks, subscriptions [#pricing-blocks-subscriptions] Cross-cutting endpoints for oracle prices, chain head, and push-notification subscriptions used by the webapp. | Path | What it returns | | ---------------------------- | --------------------------------------- | | `GET /api/prices` | Current oracle prices. | | `GET /api/blocks/...` | Indexed blocks. | | `GET /api/subscriptions/...` | Webapp push-notification subscriptions. | ## Filtering and exports [#filtering-and-exports] Most list endpoints share a few query-string conventions: * `?format=csv` - return CSV instead of JSON. * `?period=3m|6m|12m|all` - time-window filter on history endpoints. * `?from=` - incremental sync filter; only rows updated since the given timestamp. * `?export=true` - streaming CSV export of the full result set, for bulk pulls that would otherwise paginate. ## Caching [#caching] Responses are cached at the API layer with per-endpoint TTLs. Treat the ETL as a near-real-time view of chain state - subsecond for live positions and prices, slightly stale for aggregates that don't need real-time updates. If you need strictly authoritative chain state, go to RPC/gRPC directly; the ETL is optimised for analytics-style queries that an RPC node would answer slowly or not at all. # Overview (/docs/ibc/overview) Nolus reaches its host networks over IBC to execute swaps as part of opening, repaying, and closing margin positions. The primary market is **Solana**, served natively by [Solray](/docs/ibc/solray), the protocol's own IBC program there. On Cosmos counterparty chains the same role is played by **Interchain Accounts (ICA)**. ## How a packet flows [#how-a-packet-flows] Two chains don't talk to each other directly. A relayer process watches both ends, picks up packets emitted on one chain, builds the corresponding message, and submits it to the other - then carries the acknowledgment back. Running a Hermes relayer for Nolus is documented under [Network > IBC relayer](/docs/network/relayer). Anyone can run a relayer - see [Network > IBC relayer](/docs/network/relayer) for the operator side. ## Counterparty integration [#counterparty-integration] Each supported DEX is wired up as a *protocol* in the money-market contract workspace, with the channel/connection configuration baked into the network's genesis or a governance upgrade. On Solana the integrated DEX is Metis, a swap aggregator, reached through Solray's Remote Lease channel. # Solray (/docs/ibc/solray) Solray is the IBC implementation Nolus runs **natively on Solana** - a single on-chain program that hosts the IBC core state (client, connection, channels) and two IBC applications on top of it: * **ICS-20 token transfer** - moves tokens between Nolus and Solana in both directions: Solana-native tokens are locked in per-token escrow accounts, and tokens arriving from Nolus (such as NLS) are minted as "mirror" SPL tokens. * **Remote Lease** - executes lease operations that a Nolus lease contract requests on Solana: opening a position, swapping via a DEX aggregator (currently Jupiter), transferring out, and closing. A *lease* is a [margin position](/docs/protocol/borrowing) - the same Lease described under Protocol > Spot Margin; "Remote" because the position's assets live on Solana while the contract governing it runs on Nolus. As on every IBC connection, off-chain relayer processes carry the packets: they pick up what one chain emits, submit it to the other, and return the acknowledgement. This page is a reference for the **production deployment on Solana mainnet**: the accounts the program controls, what each does, and its concrete address. ## Production deployment [#production-deployment] | What | Address | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | Program ID | [`9ATDq9dpDcM517hncjHnbWsBXzsNxKvqdfJ4L5qHJYy2`](https://solscan.io/account/9ATDq9dpDcM517hncjHnbWsBXzsNxKvqdfJ4L5qHJYy2) | | ProgramData (upgradeable loader) | `BNQ6uyEmDmEUTG2wrxePFKC9w6z7GQZVFzPP5Zo1A3k9` | | NLS token (mirror mint) | [`B9Vhg6XrNswZvLdKgpKhZ9Fj9yV7j2RxQKUaq5fmuphv`](https://solscan.io/token/B9Vhg6XrNswZvLdKgpKhZ9Fj9yV7j2RxQKUaq5fmuphv) | | Statics address lookup table | `75nJQPCfAwcrxHJG2kbcbozNEBKJtUZF8L22KNXxnRtJ` | The program is deployed through Solana's upgradeable BPF loader; upgrades are controlled by the upgrade authority recorded in the ProgramData account. ## How addresses are derived [#how-addresses-are-derived] Every account the program owns is a Program Derived Address (PDA) under the program ID. Seeds are short, fixed two-byte designators chained from parent to child, with 2-byte big-endian ordinals for numbered entities. The production instance uses **instance ordinal 0**, so every path starts with `["pr", 0u16]`. | Entity | Seed path | | --------------------------------------- | ------------------------------------------------------------------- | | Program (instance) | `["pr", ordinal]` | | Vault authority | `["pr", 0, "va"]` | | Statics table pointer | `["pr", 0, "st"]` | | Client N | `["pr", 0, "cl", N]` | | Client N's connection list | `["pr", 0, "cl", N, "cc"]` | | Connection N | `["pr", 0, "cn", N]` | | Connection N's channel list | `["pr", 0, "cn", N, "co"]` | | Channel N | `["pr", 0, "ch", N]` | | Per-channel stores | channel seeds + `"ns"` / `"pq"` / `"rc"` / `"pa"` / `"tc"` / `"le"` | | Escrow (per token) | channel seeds + `["es", mint pubkey]` | | Mirror mint (per token) | channel seeds + `["mt", sha256(symbol)]` | | Lease (per position) | channel seeds + `["ls", lease address]` | | In-flight transfer counter (per sender) | `["pr", 0, "in", sender pubkey]` | Anyone can re-derive and verify every address below with the standard `find_program_address` over these seeds, or query them with the `solray` CLI's `derive` and `query` commands. ## Program-wide accounts [#program-wide-accounts] | Account | Address | What it does | | --------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Program state | `6SJgbqjAFxvv3KnVjEjD54H8JEG74Y7ANksrXh1cj1Q7` | Records the instance configuration and the next client / connection / channel ordinals. | | Vault authority | [`6xDAvqaePki3Re9NN8YHLhrN2e1cZ4a2nzL2VXeDawWp`](https://solscan.io/account/6xDAvqaePki3Re9NN8YHLhrN2e1cZ4a2nzL2VXeDawWp) | The program's signing identity - a PDA with no private key that only the program can sign for. It is the mint authority of every mirror mint (including NLS), the owner of every escrow account, the authority of the address lookup tables, and the SOL source that funds rent for accounts the program creates. | | Statics table pointer | `78Wx6LowbsBdvECQGYBuoFrfZD3boQhbi36kS2ia9Svf` | Holds the address of the deployment-wide statics lookup table (a lookup table's address depends on the slot it was created in, so it cannot be derived - this PDA is where it is discovered). | | Statics lookup table | `75nJQPCfAwcrxHJG2kbcbozNEBKJtUZF8L22KNXxnRtJ` | A Solana address lookup table holding the accounts every transaction needs (program state, channels, packet stores, token programs) plus each registered token's mint and escrow. Lets packet-delivery transactions fit Solana's transaction size limit. Owned by the lookup-table program; its authority is the vault authority. | ## IBC entities [#ibc-entities] One IBC client, one connection, and two channels are live: | Entity | Address | | -------------------------- | ---------------------------------------------- | | `07-tendermint-0` (client) | `H66HFVTja92BRvYHPaFAB99JGzTLuhRoPSXU7SxdjdRG` | | Client's connection list | `3Nhy9pmkdKcYK6Xi5La5nCE779V76HnW41aY5iF9Dnp6` | | `connection-0` | `2GDasyhvUAbHmSC18Xxcdpkxnz71GEYBzVnMUfcNvpvq` | | Connection's channel list | `AVRfVWARk7K6xMbTHhmbn43BeG7Vzj6A16hXUViuudvv` | ### channel-0 - ICS-20 transfer [#channel-0---ics-20-transfer] The token-transfer channel: port `transfer`, version `ics20-1`, connected to Nolus `channel-61957`. | Account | Address | What it does | | ----------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Channel end | `9PVMGoqDKSZYCPyCoA6ZWusbme78eZkQ1tTxXLqMQCuF` | Channel state and counterparty identity | | Next send sequence | `B1sHvbsuSUBzN3Anma2vdhgddkNY9yWyRmqAS9nCtjvN` | Ordinal for the next outgoing packet | | Packet commitments | `Aomi2CxYwtbkR71B9yJPyVZBh1GoQEGupfTEKpeipBb4` | Commitments of sent, not-yet-acknowledged packets | | Packet receipts | `ugqGrE1HqQBfo1dmXwfAq546qK3k3bDdVuSxtULj9Lh` | Receipts of received packets (replay protection) | | Packet acknowledgements | `2tprvXyrbe4EMgJMs4dMKN9U5ggKbXWAVirtmv6qvPB9` | Acknowledgements written for received packets | | Token census | `91gUYsWAiCbnMJLzacBaXaFLhFcTMuaUZuHpMXQxhw8b` | Counts the channel's live escrows, mirror mints, and bound lease channels - the teardown gates that keep the channel from closing while tokens are still keyed under it | ### channel-1 - Remote Lease [#channel-1---remote-lease] The lease channel: version `nls-remote-lease.v1+transfer=channel-0`, connected to Nolus `channel-61961` on the Nolus protocol's contract port (`wasm.nolus1xd9sgdu3lvwm0pzrxg3c3lxwvuwkc6jgzk43zkgzjnndrdp5shvsfv3uzp`). The version string names the transfer channel it is paired with: lease operations move funds over `channel-0`. | Account | Address | What it does | | ----------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | Channel end | `8swVa22QfN6rX5Tb5DmTV234KMyR9esKgHKkKkba9s21` | Channel state and counterparty identity | | Lease channel binding | `GGyDtSVRtJ7harZmwd2z4Y12e4ULVZYiE2Cp8sLJmR7N` | Binds the lease channel to its transfer channel (`channel-0`) and records the swap engine the channel is served by (`jupiter`) | | Next send sequence | `DvLjrrvAYSXh8tEhhNpbPhYLAamnZUkDJ4cWwnJ1nr1k` | Ordinal for the next outgoing packet | | Packet commitments | `CFsMZSMTadBjUdF3yj8GB6caDEgqaKT85sFuneyJvuEE` | Commitments of sent, not-yet-acknowledged packets | | Packet receipts | `8o28mjzqXce4GJXyLtYHUjJBZf1QRYKtDwhEut4V7V1T` | Receipts of received packets (replay protection) | | Packet acknowledgements | `7hzirijfq5JJCYvYGh7mq4tRGy4xxnpsjR3hL3jsEE7W` | Acknowledgements written for received packets | | Token census | `F3Eb5eAFtvd7HdNMVsrRum8Fug9S2WXQJLjkW2LoSRiZ` | Counts the channel's live leases - the channel cannot be finalized while any lease is still open | Each open lease (margin position) additionally owns a small set of dynamic accounts keyed by the Nolus lease contract address: the Lease PDA itself, a per-lease authority PDA whose associated token accounts hold the position's funds, and a per-lease address lookup table. These are created when a lease opens and closed (rent reclaimed) when it closes. While a sender has ICS-20 transfers awaiting settlement, the program additionally keeps a per-sender in-flight transfer counter PDA (seed designator `in`), keyed by the packet sender's address - the lease authority for lease-flow sends, the funding wallet for wallet sends. It is created at the sender's first in-flight send and closed once the count returns to zero; a lease cannot be closed while its authority's counter still exists. ## Tokens [#tokens] ### NLS on Solana [#nls-on-solana] NLS arriving from Nolus over `channel-0` is minted as a mirror SPL token: | What | Value | | -------------- | ----------------------------------------------------------------------------------------------------------------------- | | Mint | [`B9Vhg6XrNswZvLdKgpKhZ9Fj9yV7j2RxQKUaq5fmuphv`](https://solscan.io/token/B9Vhg6XrNswZvLdKgpKhZ9Fj9yV7j2RxQKUaq5fmuphv) | | Decimals | 6 (same as `unls` on Nolus) | | Mint authority | `6xDAvqaePki3Re9NN8YHLhrN2e1cZ4a2nzL2VXeDawWp` (the vault authority) | | Derivation | `channel-0` seeds + `["mt", sha256("unls")]` | The supply on Solana is exactly the amount transferred over IBC and not yet returned: the program mints on each inbound transfer and burns on each outbound one. No wallet holds minting rights - only the program, via the vault authority PDA. ### Registered Solana-native tokens [#registered-solana-native-tokens] Solana-native tokens transfer to Nolus by being locked in a per-token escrow account - an SPL token account owned by the vault authority. Registered on `channel-0`: | Token | Mint | Escrow | | -------------- | ---------------------------------------------- | ---------------------------------------------- | | USDC | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | `A57C37YLZz412hQs6vPNYBUucRGkX5fe9bbKnwQDeRhs` | | Wrapped SOL | `So11111111111111111111111111111111111111112` | `9GUpG4SwCjEjZfHxkXbB5HchmWhvNLnij5VNbrGv8S7u` | | cbBTC | `cbbtcf3aa214zXHbiAZQwf4122FBYbraNdFqgw4iMij` | `DXvMe3wow8H5y2w4gPgEQBX19CpMQp7h3tK7u6nWKvzE` | | ETH (Wormhole) | `7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs` | `DpCN5TnWgSmdV5YQh97ThKa4fC1vA5ZVK697JYgEEQXP` | | HYPE | `98sMhvDwXj1RQi5c5Mndm3vPe9cBqPrbLaufMXFNMh5g` | `EkBvfVYmpfYMPThZygDu61QH2VVNqJKeKm2QiMaLXyib` | ## Program IDL [#program-idl] A machine-readable IDL for the program is published at [`/idl/solray.json`](/idl/solray.json) in the Anchor 0.30 JSON format. It describes all 36 instructions - discriminators, argument layouts, ordered account lists with signer/writable flags - plus the decodable account-state layouts, the PDA seed constants, and the program's error codes, so explorers and indexers can decode Solray transactions and accounts. The file's own top-level `docs` array carries the decoding rules an integrator needs (wire framing, the engine account tails, the lease packet envelope, error-log parsing). Every address on this page is either directly readable on-chain or re-derivable from the seed table above with the standard `find_program_address` - the seed constants in the IDL carry the exact designator bytes. # Chain overview (/docs/network/chain-overview) Nolus is a Cosmos SDK chain built with CometBFT. The chain binary is `nolusd`, produced from the [`nolus-core`](https://github.com/nolus-protocol/nolus-core) repository. ## Prerequisites [#prerequisites] * [Go](https://golang.org/) (matches the version pinned in `go.mod`) * [tomlq](https://tomlq.readthedocs.io/en/latest/installation.html) and [jq](https://stedolan.github.io/jq/) for the helper scripts ## Build [#build] ```sh make install ``` Compiles and installs `nolusd` locally. ## Initialize and run a local single-node chain [#initialize-and-run-a-local-single-node-chain] `scripts/init-local-network.sh` generates a local network setup, deploys the platform contracts, and configures Hermes. First, generate the mnemonic Hermes will use: ```sh nolusd keys mnemonic ``` Then initialize and start (run with `--help` for all options): ```sh ./scripts/init-local-network.sh \ --reserve-tokens \ --hermes-mnemonic \ --dex-admin-mnemonic \ --store-code-privileged-account-mnemonic ``` Notes: * Make sure [`nolus-money-market`](https://github.com/nolus-protocol/nolus-money-market) is checked out as a sibling to `nolus-core`. * Kill any running `nolusd` and `hermes` processes before re-running the init script. * `nolusd` logs land in `~/.nolus`. ## Run an already-configured node [#run-an-already-configured-node] ```sh nolusd start --home "networks/nolus/local-validator-1" ``` ## Run a full node with Docker [#run-a-full-node-with-docker] The [`nolus-networks`](https://github.com/nolus-protocol/nolus-networks) repository carries genesis files, seed/peer lists, and a docker-based runner for each public network. ## Reproducible binary [#reproducible-binary] The CI builder image is publicly available: ```sh docker run --rm -it -v "$(pwd)":/code \ public.ecr.aws/nolus/builder: \ make build -C /code ``` Use the tag matching the release you're reproducing. # RPC, LCD, and gRPC endpoints (/docs/network/rpc-endpoints) The Nolus team operates a public set of endpoints for the Pirin mainnet. They are rate-limited but free to use; if you need production-grade throughput, run your own node (see [Run a full node](/docs/network/run-a-node)) or use a community provider listed in [`nolus-networks`](https://github.com/nolus-protocol/nolus-networks). ## Pirin (mainnet) [#pirin-mainnet] | Kind | URL | | -------------- | ---------------------------- | | Tendermint RPC | `https://rpc.nolus.network` | | LCD (REST) | `https://lcd.nolus.network` | | gRPC | `https://grpc.nolus.network` | ## Endpoint kinds [#endpoint-kinds] * *Tendermint RPC* - block/tx queries, websocket subscriptions; used by the SDK to talk to the chain. * *LCD (REST)* - gateway over the Cosmos SDK queries; useful for plain HTTP clients without a Cosmos SDK dependency. * *gRPC* - typed queries against Cosmos SDK and CosmWasm modules. * *JSON-RPC (EVM)* - not applicable; Nolus is a CosmWasm chain. # Validators (/docs/network/validators) ## Create a Validator [#create-a-validator] ### 1. Create (Or Restore) A Local Key Pair [#1-create-or-restore-a-local-key-pair] Prior to creating your validator, you must first create your application key. Note that this is not your consensus key and will not be used for signing consensus votes. Instead, it is used to sign transactions. Create a new key pair ``` nolusd keys add ``` Restore an existing Nolus wallet by providing a BIP39 mnemonic ``` nolusd keys add --recover ``` Get your public address from the keystore ``` nolusd keys show -a ``` Feel free to replace `` with a name of your choice. After creating a new key, its information will be shown alongside the seed phrase. Make sure to write it down, as it is the only way to restore your keys. ### 2. Upgrade to a Validator [#2-upgrade-to-a-validator] Do not attempt to upgrade your node to a validator until the node is fully in sync after you have started the nolusd binary, as shown in the previous section. Since you need to send a transaction to be part of the validator set, you need to have a small amount of NLS tokens in the wallet address you are using on your keyring. Once you have a positive balance, you can send the create-validator transaction. For this, you would need to specify validator-related details, such as self-stake and commission, in a JSON file (named validator\_details.json in the example below) that is to be passed as a parameter in the command: ``` Contents of validator_details.json: { "pubkey": , "amount": "1000000unls", "moniker": "", "identity": "", "website": "", "security": "", "details": "", "commission-rate": "0.05", "commission-max-rate": "0.1", "commission-max-change-rate": "0.01", "min-self-delegation": "1" } ``` ``` nolusd tx staking create-validator validator_details.json --chain-id "pirin-1" --fees 1000unls --from ``` If you need further explanation for each of these validator-related parameters: * the amount is the amount you will place in your own validator in unls as self-stake (in the example, 1000000unls is 1 NLS) * the commission rate is the rate you will charge your delegates (in the example above, 5 percent) * the commission-max-rate is the most you are allowed to charge your delegates (in the example above, 10 percent) * the commission-max-change-rate is how much you can increase your commission rate in a 24-hour period (in the example above, 1 percent per day until reaching the max rate) * min-self-delegation is a strictly positive integer that represents the minimum amount of self-delegated voting power your validator must always have. In the example above, this is equal to 1 NLS or 1000000unls * the pubkey is the validator public key that could be obtained via running "nolusd tendermint show-validator" * the chain-id is whatever chain-id you are working with (in the Nolus mainnet case, it is pirin-1) * the fees is the gas fee used to send this create-validator transaction * the from flag is the KEY\_NAME you created when initializing the key on your keyring ### 3. Confirm That Your Validator Is Active [#3-confirm-that-your-validator-is-active] If running the following command returns something, your validator is active: ``` nolusd query tendermint-validator-set | grep "$(nolusd tendermint show-address)" ``` You are looking for the bech32 encoded address in the `~/.nolusd/config/priv_validator.json` file with a nolusvalcons prefix. ### 4. Secure Your Keys [#4-secure-your-keys] In general, a Nolus validator needs to do two things: * Sign and commit blocks (using the Tendermint Consensus key) * Conduct on-chain operations such as voting on Governance proposals (using an Application Operator Key) Take good care of those two keys to mitigate hardware or software failures. ### Restore a Validator [#restore-a-validator] A validator can be completely restored on a new Nolus node with the following set of keys: * The Consensus key, stored in `~/.nolus/config/priv_validator.json` * The mnemonic to the validator wallet (application key) **Danger!** Before proceeding, ensure that the existing validator is not active. Double voting has severe slashing consequences. To restore a validator: * Set up a full Nolus node synced up to the latest block. Check the previous section. * Replace the `~/.nolus/config/priv_validator.json` file of the new node with the associated file from the old node, then restart your node. ### Unjail a Validator [#unjail-a-validator] If your validator fails to sign at least 5% of the last 10000 blocks, it will get jailed. To unjail it so that it can continue to sign blocks and respectively earn staking rewards, you can submit the following transaction: ``` nolusd tx slashing unjail --from --chain-id pirin-1 --fees 500unls ``` # Architecture (/docs/protocol/architecture) Nolus runs on three layers: the **chain** (Go, Cosmos SDK + CometBFT), the **contracts** (Rust, CosmWasm) on top of it, and the **off-chain services** (price feeders, SDKs, frontends) that drive both. ## Layers [#layers] 1. **Chain layer** - [`nolus-core`](https://github.com/nolus-protocol/nolus-core) (Go, Cosmos SDK + CometBFT). Custom modules include vesting, mint, and tax handlers. 2. **Contract layer** - [`nolus-money-market`](https://github.com/nolus-protocol/nolus-money-market) (Rust, CosmWasm). The workspace splits into `platform/` (network- and protocol-agnostic) and `protocol/` (per-network/DEX). See the [components table](/docs/protocol/overview#the-moving-parts) for a per-contract index. 3. **Off-chain services** - the price feeder daemon, the in-page WebMCP and REST surfaces, and frontends like [app.nolus.io](https://app.nolus.io). ## Upgrades and the Admin contract [#upgrades-and-the-admin-contract] The [Admin](https://github.com/nolus-protocol/nolus-money-market/tree/main/platform/contracts/admin) contract is the migration coordinator for the rest of the system. It holds the registry of every platform and per-protocol contract address and is the only place that can roll out a new code ID across them as a single atomic operation. The contract is governance-driven, not key-controlled: * Upgrades arrive as `SudoMsg::MigrateContracts` (or `ChangeDexAdmin`, `RegisterProtocol`) messages, which CosmWasm sudo restricts to the chain itself - so they only land via on-chain governance proposals that pass. * A separate `dex_admin` address holds permission for operational registry edits (`Instantiate`, `RegisterProtocol`, `DeregisterProtocol`) but can never move user funds: the Admin contract is the registry, not a wallet. NLS lives in Treasury, LPN in LPP, lease assets in their Lease contract. If you ever query Nolus for "what are the current contract addresses on chain X?", the Admin contract is the answer source. ## Cross-chain interaction [#cross-chain-interaction] Nolus connects to counterparty chains over IBC and uses **Interchain Accounts (ICA)** on the host chain to execute swaps as part of opening, repaying, and closing positions. The Lease contract on Nolus is the controller; the ICA on the host chain holds the funds during a swap and returns them via the established IBC channel. See [IBC overview](/docs/ibc/overview) for the channel inventory and the packet-flow walkthrough. # Oracle (/docs/protocol/oracle) The Nolus oracle is a two-layer system: 1. **On-chain Oracle contract** - accepts signed spot prices from a whitelisted set of feeders, aggregates them into an EMA, and serves the EMA to consumers. Source: [`nolus-money-market/protocol/contracts/oracle`](https://github.com/nolus-protocol/nolus-money-market/tree/main/protocol/contracts/oracle). 2. **Off-chain price feeder** - a Rust daemon that pulls spot prices from configured sources (DEXs, CEX APIs), signs them, and feeds them to the Oracle contract. Source: [`oracle-price-feeder`](https://github.com/nolus-protocol/oracle-price-feeder). The on-chain Oracle is the real-time price source for the rest of the protocol. Consumers (Lease contracts, SDK clients, frontends) read EMA prices from it directly; they don't talk to feeders. ## Market Data Price Feeder [#market-data-price-feeder] The Market Data Price Feeder collects denomination pairs from the Market Data Price Oracle and fetches prices from the integrated DEXes for the supported pairs. It averages the retrieved prices and updates the Oracle with the latest data feed at intervals, typically every few seconds. Only governance-approved feeders are permitted to actively contribute price data to the Oracle. Supported currency pairs are configurable through governance, with the feeder's base currency set at initialization and unchangeable afterward. Pairs can be requested in reverse (e.g., A/B data is valid for a B/A request) and can span a resolution path (e.g., \[A/C, B/C], or \[A/C, C/B]). ## Price Calculation Algorithm [#price-calculation-algorithm] Price feeders use an Exponential Moving Average (EMA) algorithm to determine cryptocurrency pair prices, adding a security layer by prioritizing recent data. A discounting factor between 0 and 1 is applied, with higher values discounting older observations more quickly, unlike the equal-weight approach of a Standard Moving Average (SMA). ## Feeder responsibilities [#feeder-responsibilities] * Pull spot prices from configured providers. * Apply per-source weights and outlier filtering. * Sign and submit spot prices within each oracle feed window. * Recover gracefully from RPC errors and rate limits. # Overview (/docs/protocol/overview) ## What Nolus is [#what-nolus-is] Nolus is a sovereign blockchain with a CosmWasm money market focused on **fixed-rate, asset-backed spot margin**. Instead of issuing a generic collateralized loan that can be margin-called and liquidated, Nolus opens a *lease* - a position that holds the asset the borrower takes exposure to, until the borrower closes it. The same primitive supports both longs and shorts. Opening a position combines two pieces of capital: * a **down payment** the borrower puts up as collateral, in whichever currency they choose * a **borrowed amount** drawn from the Liquidity Provider Pool (LPP) The down payment and the borrowed amount are swapped together into the **lease currency** - the asset the position holds for its lifetime. The lease is its own on-chain CosmWasm contract instance. It tracks debt, margin interest, and the lifecycle (open → active → close) without ever forcing a liquidation in the traditional sense. ## Markets [#markets] The primary market runs on **Solana**: positions are executed there natively by [Solray](/docs/ibc/solray), the protocol's on-chain program, with swaps routed through **Metis**, a swap aggregator on Solana. Users connect with **Phantom** or **Solflare**. The assets currently available to trade are listed under [Spot Margin](/docs/protocol/borrowing#supported-assets). ## The moving parts [#the-moving-parts] The protocol surfaces as a small set of CosmWasm contracts and a few chain-level pieces: | Component | Source | Role | | --------------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Leaser** | `nolus-money-market/protocol/contracts/leaser` | Factory that instantiates new Lease contracts and tracks the open set. | | **Lease** | `nolus-money-market/protocol/contracts/lease` | One contract per position. Holds the lease asset (post-swap), accrues interest, handles repayment, close, and liquidations. | | **LPP** (Liquidity Provider Pool) | `nolus-money-market/protocol/contracts/lpp` | Single-sided lending pool - lenders deposit, the pool extends loans to leases. | | **Oracle** | `nolus-money-market/protocol/contracts/oracle` | On-chain price oracle. Receives spot prices from feeders and produces an EMA used by leases. | | **Profit** | `nolus-money-market/protocol/contracts/profit` | Routes protocol revenue. | | **Reserve** | `nolus-money-market/protocol/contracts/reserve` | Backstop pool, used when a lease closes underwater. | | **Treasury** | `nolus-money-market/platform/contracts/treasury` | Holds protocol-owned NLS, drives long-term economic flows. | | **TimeAlarms** | `nolus-money-market/platform/contracts/timealarms` | Generic on-chain scheduler - contracts subscribe to time-based callbacks. | | **Admin** | `nolus-money-market/platform/contracts/admin` | Registry of every platform and per-protocol contract; coordinates atomic CosmWasm migrations across them. Driven by chain governance (`SudoMsg`), holds no user funds. | | **Oracle Price Feeder** | `oracle-price-feeder` | Off-chain daemon that publishes signed spot prices to the on-chain Oracle. | | **Nolus Core** | `nolus-core` | The chain itself - Cosmos SDK + CometBFT plus custom modules. | ## Why this design [#why-this-design] Two practical consequences: * **Partial liquidations first.** When a position's LTV crosses the max threshold, the protocol sells just enough of the lease asset to bring the ratio back to the healthy band. Full liquidation only kicks in when a partial sale can't restore the invariant. See [Risk](/docs/protocol/risk) for the full mechanism. * **Self-contained accounting.** Each Lease is its own contract, so debt, interest, and lifecycle state stay isolated per position. The Leaser tracks the active set; everything else lives in the Lease. ## Where to go next [#where-to-go-next] # Liquidations & risk (/docs/protocol/risk) A position is healthy as long as its liability stays below the factory's configured threshold. As price moves against the borrower, the position climbs through three warning levels before reaching the liquidation trigger. The percentages below are the defaults; each factory sets its own via governance. ## Liquidations [#liquidations] If the borrower has open liabilities that do not meet the desired conditions set within the protocol, the system needs to sell (partially or fully) the borrower's collateral so that it remains solvent. These conditions are defined in two invariants: ### 1. Interest covered [#1-interest-covered] If the borrower has an outstanding margin or loan interest left after the defined interest payment period has passed, the invariant is broken, and the repayment algorithm is conducted using the leased amount. ### 2. Liability covered [#2-liability-covered] If the current liability of the position is greater than or equal to the max liability, then the invariant is broken, and a market price alarm notification message is sent. Given that the healthy liability is less than the max liability of the Lease, the liquidation amount is either the lease amount itself or the amount that is enough to guarantee a healthy position after the liquidation event has occurred. This liquidation amount is then swapped to LPN and is used as an input to the repayment algorithm. If an excess amount is returned, it is transferred to the Profit contract. **Note**: If the lease is fully liquidated, then it is closed. ## Market Anomaly Guard (MAG) [#market-anomaly-guard-mag] To protect users from unfair liquidations during volatile market conditions, the protocol implements Market Anomaly Guard (MAG) - a price-protective algorithm that evaluates liquidation risk before executing swaps. ### The Challenge [#the-challenge] During periods of high volatility, asset prices can temporarily diverge across exchanges and liquidity pools. While arbitrage mechanisms typically correct these discrepancies, a brief window may exist where a DEX displays prices significantly below the market average. This can result in: * Over-liquidation of user positions * Unnecessary losses for users * Potential bad debt for the protocol ### MAG Implementation [#mag-implementation] The Market Anomaly Guard operates through the following process: ### 1. Liquidation Trigger [#1-liquidation-trigger] When a position crosses the liquidation threshold (determined by an Exponential Moving Average to avoid reacting to momentary price dips), a liquidation attempt is initiated. ### 2. Minimum Output Validation [#2-minimum-output-validation] Before executing the swap, the protocol simulates the trade. If the expected output falls below a defined safety threshold relative to the oracle-based EMA price, the liquidation is paused. ### 3. Dynamic Response [#3-dynamic-response] MAG monitors market conditions during the pause: * If prices recover and the swap output improves, the liquidation resumes and finalizes * If the asset price rises back above the liquidation trigger threshold, the liquidation is canceled entirely This mechanism provides several key benefits: * Protects users from liquidations at temporarily depressed prices * Reduces unnecessary liquidation events during volatile market conditions * Prevents protocol losses from poorly-priced swaps * Allows positions to recover when market anomalies are temporary ## Asset-Specific Risk Framework [#asset-specific-risk-framework] To maintain the integrity of the protocol and protect users, Nolus enforces a dynamic risk management framework designed to prevent situations where open margin positions could outstrip the available liquidity necessary for their liquidation. This is especially critical for **long positions**, where declining asset prices often coincide with diminishing stablecoin liquidity in decentralized exchange (DEX) pools, making it less likely that liquidity providers will inject fresh capital. ## Liquidity and Exposure Limits [#liquidity-and-exposure-limits] To mitigate this risk, Nolus actively monitors the ratio of active loans to available liquidity in the integrated DEX pools. A core principle of this framework is that no asset should have outstanding loans that exceed a prudent percentage of the pool's available liquidity. While a general benchmark is that active loans should not exceed 20% of the liquidity in a given pool, this threshold is adjusted on a per-asset basis depending on the asset's broader liquidity profile. Key factors that influence this assessment include: * **High Market Capitalization**: A higher market cap typically indicates deeper, more resilient markets. * **Exchange Diversity**: Assets listed across multiple exchanges benefit from greater liquidity and arbitrage activity. * **Deep Liquidity**: Widely traded assets require more capital to move the price, reducing manipulation risks. Thus, an asset with broad exchange coverage and deep global liquidity may be allowed a more relaxed exposure threshold, compared to a niche asset with liquidity concentrated in a single DEX pool. ## Temporary Delisting Mechanism [#temporary-delisting-mechanism] When an asset reaches its predefined risk threshold: * It is temporarily delisted for opening new long or short positions. * Existing positions remain fully manageable by users and are not forcibly closed. * Nolus continues to monitor the asset's loan-to-liquidity ratio. If this ratio improves - i.e., loans decrease or liquidity increases - the asset can be reinstated for new margin operations. # Security (/docs/protocol/security) Nolus Protocol prioritises security and is transparent about it: every contract address and balance is publicly verifiable on chain, the codebases are open-source, and the money-market and chain core have been audited by Oak Security and Halborn (reports below). On top of the audits the team runs a bug bounty for community researchers. ## Audits [#audits] | Repository | Auditor | Report | | ------------------ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Nolus Core | Oak Security | [Audit report](https://github.com/oak-security/audit-reports/blob/master/Nolus/2022-12-12%20Audit%20Report%20-%20Nolus%20Core%20v1.1.pdf) | | Nolus Money Market | Oak Security | [Audit report](https://github.com/oak-security/audit-reports/blob/master/Nolus/2023-01-27%20Audit%20Report%20-%20Nolus%20Money%20Market%20v1.1.pdf) | | Nolus Money Market | Halborn | [Audit report](https://github.com/HalbornSecurity/PublicReports/blob/master/CosmWasm%20Smart%20Contract%20Audits/Nolus_Money_Market_CosmWasm_Smart_Contract_Security_Assessment_Report_Halborn_Final.pdf) | ## Reporting a vulnerability [#reporting-a-vulnerability] Email **[security@nolus.io](mailto:security@nolus.io)**. Do not open a public issue or PR for security matters - that's true for every Nolus repository. The canonical policy lives at [`nolus-protocol/.github/SECURITY.md`](https://github.com/nolus-protocol/.github/blob/main/SECURITY.md); the same text is mirrored below for convenience. ## Scope [#scope] The bug bounty covers the Nolus blockchain, the smart contracts, the Solana program (ibc-solray), and the webapp. It focuses on: * Theft or freezing of principal funds * Theft or freezing of unclaimed yields * Disruptions to webapp uptime * Unauthorized access to restricted pages * Deletion or tampering with user data ## What to include in your report [#what-to-include-in-your-report] * A clear description of the vulnerability and its impact. * A **reproducible proof-of-concept** - code, transaction hashes, or a recorded video. Theoretical findings without a working reproducer will be closed. * Affected versions, commits, or deployed addresses. * A suggested fix, if you have one. ## Response [#response] Legitimate submissions get acknowledged and triaged within a reasonable timeframe. We reserve the right to close, without response, any report that appears to be spam, fabricated, speculative, LLM-generated without human verification, or otherwise not a good-faith disclosure. ## Out of scope [#out-of-scope] * Social engineering of staff or users * Physical attacks * Volumetric denial-of-service against public infrastructure * Vulnerabilities requiring privileged access you were never granted * Findings in third-party dependencies without a working exploit against Nolus components ## AI-assisted reports [#ai-assisted-reports] AI tools are welcome as part of your research, but: * Reports generated by LLMs or automated scanners **without human verification will be closed without triage**. If you used AI, confirm in writing that a human has reproduced and understood the finding. * Reports that are paraphrased CVEs, raw scanner output, or speculative code analysis without a concrete Nolus impact will be closed. * Submitting AI-generated spam reports may result in your account being blocked from the organization. ## Safe harbour [#safe-harbour] Good-faith research that follows this policy will not result in legal action. ## Contributing [#contributing] Most Nolus-Protocol repositories are limited to organization members and explicit collaborators; external pull requests and issues on those repos are blocked at the platform level. Drive-by typo PRs, documentation tweaks, and trivial formatting changes are not accepted - they're typically airdrop-farming attempts. LLM-generated PRs or issues without human review are closed without triage. If you want to contribute regularly, reach the team on [Discord](https://discord.com/invite/nolus-protocol). The full policy lives at [`nolus-protocol/.github/CONTRIBUTING.md`](https://github.com/nolus-protocol/.github/blob/main/CONTRIBUTING.md). # IBC transfer channels (/docs/network/relayer/channels) | Source | Source chain ID | Source channel | Destination | Destination chain ID | Destination channel | | ------------------- | --------------- | -------------- | --------------- | -------------------- | ------------------- | | Nolus Pirin Mainnet | pirin-1 | channel-0 | Osmosis Mainnet | osmosis-1 | channel-783 | In addition, Nolus opens new ICA channels for each margin position opened - those are created on demand and not tracked here. # Hermes setup (/docs/network/relayer/hermes) ## 1. Install Rust dependencies [#1-install-rust-dependencies] ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source "$HOME/.cargo/env" sudo apt-get install pkg-config libssl-dev sudo apt install librust-openssl-dev build-essential git ``` ## 2. Build and configure Hermes [#2-build-and-configure-hermes] Install the Hermes CLI, prepare the working directory, and pin to a known-good release: ``` cargo install ibc-relayer-cli --bin hermes --locked mkdir -p $HOME/hermes git clone https://github.com/informalsystems/ibc-rs.git hermes cd hermes git checkout v1.7.1 ``` Create the Hermes home + keys directories and copy the bundled example config in: ``` mkdir -p $HOME/.hermes $HOME/.hermes/keys cp config.toml $HOME/.hermes ``` Verify the install: ``` hermes version ``` ### Hermes config.toml [#hermes-configtoml] Edit `$HOME/.hermes/config.toml`. Remove the default example `[[chains]]` block and replace it with the Pirin and Osmosis entries below. The example assumes you're running both a Pirin and an Osmosis full node on the same host - adjust the RPC/gRPC endpoints if not. ```toml [mode.clients] enabled = true refresh = true misbehaviour = false [mode.connections] enabled = true [mode.channels] enabled = true [mode.packets] enabled = true [[chains]] id = 'pirin-1' rpc_addr = 'http://127.0.0.1:26657' event_source = { mode = 'push', url = 'ws://127.0.0.1:26657/websocket', batch_delay = '500ms' } grpc_addr = 'http://127.0.0.1:9090' rpc_timeout = '10s' account_prefix = 'nolus' key_name = 'hermes-nolus' store_prefix = 'ibc' default_gas = 5000000 max_gas = 15000000 gas_multiplier = 1.1 max_msg_num = 30 max_tx_size = 2097152 clock_drift = '5s' max_block_time = '30s' trusting_period = '14days' memo_prefix = '' [chains.trust_threshold] numerator = '1' denominator = '3' [chains.gas_price] price = 0.025 denom = 'unls' [chains.packet_filter] policy = 'allow' list = [ ['transfer', 'channel-0'], ['ica*', '*'], ] [chains.address_type] derivation = 'cosmos' [[chains]] id = 'osmosis-1' rpc_addr = 'http://127.0.0.1:26557' event_source = { mode = 'push', url = 'ws://127.0.0.1:26557/websocket', batch_delay = '500ms' } grpc_addr = 'http://127.0.0.1:9091' rpc_timeout = '10s' account_prefix = 'osmo' key_name = 'hermes-osmosis' store_prefix = 'ibc' default_gas = 5000000 max_gas = 15000000 gas_multiplier = 1.1 max_msg_num = 20 max_tx_size = 209715 clock_drift = '20s' max_block_time = '10s' trusting_period = '10days' memo_prefix = '' [chains.trust_threshold] numerator = '1' denominator = '3' [chains.gas_price] price = 0.025 denom = 'uosmo' [chains.packet_filter] policy = 'allow' list = [ ['transfer', 'channel-783'], ['ica*', '*'], ] [chains.address_type] derivation = 'cosmos' ``` ### Relayer wallet keys [#relayer-wallet-keys] Add your relayer wallet to Hermes' keyring (stored under `$HOME/.hermes/keys`). The wallet needs a positive balance on every chain because the relayer pays gas to submit IBC transactions. Use the same mnemonic across all networks, and don't use those addresses for anything else - account sequence errors will follow. ``` hermes keys add --chain pirin-1 --mnemonic-file hermes keys add --chain osmosis-1 --mnemonic-file ``` ## 3. Validate the configuration [#3-validate-the-configuration] ``` hermes config validate hermes health-check ``` A healthy output looks like: ``` INFO ThreadId(01) [pirin-1] chain is healthy INFO ThreadId(01) [osmosis-1] chain is healthy ``` ## 4. Run Hermes [#4-run-hermes] Once the underlying nodes are fully synced, start the daemon: ``` hermes start ``` # IBC relayer (/docs/network/relayer) An IBC (Inter-Blockchain Communication) relayer is an off-chain process that ferries messages between two chains. Blockchains don't talk to each other directly; they emit data that a relayer collects, packages into IBC messages, and submits to the destination chain over a dedicated channel. The relayer is a mandatory actor in the IBC architecture. This section walks through running [Hermes](https://hermes.informal.systems/), a Rust-based relayer. TypeScript and Go implementations exist but are less preferred for production use. ## Hardware requirements [#hardware-requirements] * 8-core (4 physical core) x86\_64 CPU * 32 GB RAM (or equivalent swap) * 1 TB+ NVMe storage The relayer process itself is lightweight; the full nodes it relays between are what need the resources. Before you start, make sure you have a Nolus full node running (see [Run a full node](/docs/network/run-a-node)). You can relay on the same machine or connect to the Osmosis and Nolus full nodes over the network. Install `build-essential` and `git` on the relayer host. Download the latest Osmosis binary - the [Osmosis docs](https://docs.osmosis.zone/) have the current setup steps. ## Walk through the setup [#walk-through-the-setup] # Build nolusd (/docs/network/run-a-node/build) ## 1. Clone the repository [#1-clone-the-repository] ``` git clone https://github.com/Nolus-Protocol/nolus-core cd nolus-core ``` ## 2. Check out the latest stable release [#2-check-out-the-latest-stable-release] To sync from block 1, check out the initial mainnet version (`v0.3.0`) and upgrade the binary at each upgrade height. To sync from a recent snapshot, check out the latest tagged release. The current version is listed at the bottom of the [Ping.pub explorer page](https://ping.pub/nolus). ``` git checkout [latest version] ``` ## 3. Compile and install [#3-compile-and-install] ``` make install ``` ## 4. Verify the install [#4-verify-the-install] ``` nolusd version --long ``` You should see output similar to: ``` name: nolus server_name: nolusd version: [version] commit: [hash] build_tags: netgo ledger go: go version go1.21.5 linux/amd64 ``` If `nolusd: command not found`, the Go binary path is not on your `PATH`: ``` export PATH=$PATH:$(go env GOPATH)/bin ``` # Configure (/docs/network/run-a-node/configure) ## 1. Initialize the node [#1-initialize-the-node] ``` nolusd init [custom_moniker] ``` Example: `nolusd init "My First Nolus Node"`. This creates a `.nolus` directory under `$HOME` with configuration files (`config.toml`, `app.toml`) you'll edit in the next steps. Use ASCII characters for the moniker, not Unicode. The moniker is visible to other participants if you later become a validator. You can update it by editing the `moniker` field in `~/.nolus/config/config.toml`. ## 2. Fetch genesis and set persistent peers [#2-fetch-genesis-and-set-persistent-peers] Get the genesis state file from the Nolus Networks repository: ``` wget https://raw.githubusercontent.com/nolus-protocol/nolus-networks/main/mainnet/pirin-1/genesis.json mv ./genesis.json ~/.nolus/config/genesis.json ``` Live peers are published at [`nolus-networks/.../peers.txt`](https://github.com/nolus-protocol/nolus-networks/blob/main/mainnet/pirin-1/peers.txt). Polkachu also maintains an [alternate live peers list](https://polkachu.com/live_peers/nolus). Connect to at least one. Peer entries are of the form `@:`. Export them in a variable: ``` PEERS="@:,@:,..." ``` Then write them into the config: ``` sed -i -e "s|^persistent_peers *=.*|persistent_peers = \"$PEERS\"|" $HOME/.nolus/config/config.toml ``` ## 3. Edit configuration files [#3-edit-configuration-files] ### Minimum gas price [#minimum-gas-price] Open `~/.nolus/config/app.toml` and set the minimum gas price threshold your node accepts (anti-spam): ``` minimum-gas-prices = "0.025unls" ``` A portion of the fees paid by users gets diverted to the protocol as tax (40% at genesis). Factor this into your fee setting. ### Pruning (optional) [#pruning-optional] Default pruning keeps application data for the unbonding period (21 days in blocks) and prunes the rest. To prune everything (smallest disk): ``` pruning = "everything" ``` To keep all history (archive node): ``` pruning = "nothing" ``` # Cosmovisor (/docs/network/run-a-node/cosmovisor) Using Cosmovisor is optional. It monitors the governance module and swaps in the upgraded binary automatically, cutting downtime around chain upgrades. Without it, you'd manually download the new binary, stop the current process, switch binaries, and restart. ## 1. Install [#1-install] Install the latest Cosmovisor from the Cosmos SDK: ``` go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest ``` ## 2. Environment variables [#2-environment-variables] Append to `~/.profile`: ``` export DAEMON_NAME=nolusd export DAEMON_HOME=$HOME/.nolus export MONIKER_NAME= ``` Reload: ``` source ~/.profile ``` ## 3. Folder layout [#3-folder-layout] `$DAEMON_HOME/cosmovisor` is fully managed by Cosmovisor. The `current` subdirectory is a symlink to the active version (genesis or upgrades). Create the base directories: ``` mkdir -p $DAEMON_HOME/cosmovisor/genesis/bin mkdir -p $DAEMON_HOME/cosmovisor/upgrades ``` ## 4. Genesis binary [#4-genesis-binary] Copy your compiled `nolusd` into the place Cosmovisor expects: ``` cp /home//go/bin/nolusd $DAEMON_HOME/cosmovisor/genesis/bin ``` ## 5. systemd service [#5-systemd-service] Create `/etc/systemd/system/cosmovisor.service` (substitute your user account where shown): ``` [Unit] Description=cosmovisor After=network-online.target [Service] User= ExecStart=/home//go/bin/cosmovisor run start Restart=always RestartSec=3 LimitNOFILE=4096 Environment="DAEMON_NAME=nolusd" Environment="DAEMON_HOME=/home//.nolus" Environment="DAEMON_ALLOW_DOWNLOAD_BINARIES=false" Environment="DAEMON_RESTART_AFTER_UPGRADE=true" Environment="DAEMON_LOG_BUFFER_SIZE=512" [Install] WantedBy=multi-user.target ``` # Run a full node (/docs/network/run-a-node) ## System configuration [#system-configuration] You should be able to run a node on most modern Linux distributions and recent versions of macOS. This guide uses Ubuntu LTS commands - if you're on something else, adjust accordingly. Running a full node is resource-intensive: it stores all chain state and stays in sync with the network. It's also a prerequisite for running a validator. ## Hardware requirements [#hardware-requirements] Recommended minimums for a regular full node: * 4+ vCPU * 16+ GB RAM * 240+ GB SSD Archive nodes (full historical state from genesis) need substantially more disk. Prerequisites: Go 1.23.0 (Linux amd64) and a build toolchain: ``` sudo apt-get install -y build-essential ``` `nolusd` (the Nolus Daemon) is the CLI and daemon you use to interact with the chain. The official Go reference implementation lives in [`nolus-core`](https://github.com/nolus-protocol/nolus-core). ## Walk through the setup [#walk-through-the-setup] # Run the node (/docs/network/run-a-node/run) ## Without Cosmovisor [#without-cosmovisor] ``` nolusd start ``` ## With Cosmovisor [#with-cosmovisor] Enable and start the service you created earlier: ``` sudo systemctl daemon-reload sudo systemctl enable cosmovisor sudo systemctl start cosmovisor ``` Check status: ``` sudo systemctl status cosmovisor ``` Tail logs: ``` journalctl -u cosmovisor -f ``` ## Check sync status [#check-sync-status] Once `nolusd` is running, the chain starts syncing. Sync time varies with your setup and the current chain size and can be very long without a snapshot. ``` curl http://localhost:26657/status | jq .result.sync_info.catching_up ``` * `true` - still syncing. * `false` - caught up. Your node is now ready, and you can promote it to a validator. # Bootstrap from a snapshot (/docs/network/run-a-node/snapshot) A snapshot contains a compressed copy of the Nolus chain data and wasm directories. It lets a new node join quickly by restoring application state instead of replaying every block from genesis. Recommended for new participants. Always double-check the source. The Nolus-provided snapshot is pruned. It's suitable for a validator node but not for an RPC/archive node. The snapshot index lives at [`snapshots.nolus.network/backup/snapshots/`](https://snapshots.nolus.network/backup/snapshots/). ## Steps [#steps] 1. Stop the `nolusd` process or service: ``` nolusd stop ``` 2. Download the latest snapshot: ``` wget -O nolus_snapshot.tar.gz https://snapshots.nolus.network/backup/snapshots/pirin-1_XXXX-XX-XX_XX-XX-XX.tar.gz --inet4-only ``` 3. If you're validating, back up `priv_validator_state.json` first (otherwise skip): ``` cp ~/.nolus/data/priv_validator_state.json ~/.nolus/priv_validator_state.json ``` 4. Reset node state: ``` nolusd tendermint unsafe-reset-all --home $HOME/.nolus --keep-addr-book ``` 5. Remove the current data and wasm directories: ``` rm -r ~/.nolus/data rm -r ~/.nolus/wasm ``` 6. Unpack the snapshot into the node directory: ``` tar -xzf nolus_snapshot.tar.gz --strip-components=1 -C $HOME/.nolus/ ``` 7. If you backed up `priv_validator_state.json`, restore it (otherwise skip): ``` cp ~/.nolus/priv_validator_state.json ~/.nolus/data/priv_validator_state.json ``` 8. Restart `nolusd`. Make sure persistent peers and `genesis.json` are still in place: ``` nolusd start ``` # Spot Margin (/docs/protocol/borrowing) A spot margin position is opened with borrowed capital: the protocol borrows the loan amount from the [lending market](/docs/protocol/lending) on the borrower's behalf and repays it, with fixed interest, as the position is serviced and closed. The pools that lenders fund are the same pools every position draws from. Each supported denomination has a dedicated Liquidity Providers' Pool (LPP) instance that handles all borrow requests and repayments in that currency. The LPP calculates a fixed interest rate for each loan, tracks total principal and interest due, and maintains records of all active loans. An LPP operates exclusively in its native currency, called the Liquidity Pool's Native (LPN) denomination, and does not interact with other assets. A factory contract creates all margin positions, with one factory deployed per integrated market. Each factory is connected to a single LPP - and by extension, to a single LPN. The LPN can be a stablecoin or a volatile token. For instance, if a user wants to borrow USDC to go long, they submit a request to the factory linked to the LPP whose LPN is USDC. ## Supported assets [#supported-assets] Assets currently available to long on Solana, the primary market: | Asset | Direction | | ----- | --------- | | SOL | Long | | cbBTC | Long | | HYPE | Long | | WETH | Long | More assets are on the way. ## Factory configuration [#factory-configuration] The factory smart contract is configured with parameters that define the behavior and constraints of every position it creates. | Parameter | Definition | | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Code ID | The identifier of the margin-position contract used to initialize new positions. | | LPP Address | The LPP smart contract tied to this factory instance. | | Protocol Interest Rate | The fixed interest portion collected by the protocol (e.g., 3%); 100% is used for buybacks. | | Max Liability | Maximum allowed liability ratio. Crossing this triggers liquidation eligibility (e.g., 90%). | | Healthy Liability | Target liability after a liquidation restores the position (e.g., 83%). | | Initial Liability | Maximum LTV at position creation. Must be lower than the healthy liability (e.g., 60%). | | Minimum Position Size | Minimum value, in LPN, that a position must maintain to stay alive (e.g., 15 USDC). | | Minimum Transaction Amount | Smallest allowable amount for any transaction within the position (e.g., 0.01 USDC). | | Interest Due Period | Window in which outstanding interest must be repaid. Past the deadline, owed interest is deducted via partial liquidation. | | Liability Reevaluation Interval | How often (in seconds) the "liability covered" invariant is checked (e.g., every 2 seconds). | | First / Second / Third Liquidation Warning Threshold | Three warning levels that signal escalating liquidation risk (e.g., 83.5% / 85% / 87.5%). Each must be strictly lower than the next, and all below the max liability. | The "liability covered" invariant a position must satisfy at every check: ``` Current Liability < Max Liability × Position Value (in LPN) ``` ### Worked example [#worked-example] * Borrowed amount (loan): 150 USDC * Initial position value: 250 USDC * Liability: 150 / 250 = 0.60 * Max allowed liability: 0.90 If the position value drops to 200 USDC, liability becomes 150 / 200 = 0.75 - still within the limit. If it drops further to 165 USDC, liability becomes 150 / 165 ≈ 0.91, exceeding the 0.90 cap and breaking the invariant. Once breached, the position is flagged for liquidation. ## Walk through the flow [#walk-through-the-flow] # Opening a position (/docs/protocol/borrowing/opening) To open a new margin position, the user interacts with the appropriate factory contract and provides collateral. The maximum borrowable amount is determined by the collateral and the configured initial liability ratio (e.g., 60%). ## 1. Collateral deposit [#1-collateral-deposit] The user initiates a position by submitting collateral to the factory. There are three ways to provide it: * *Directly in the target asset.* If the collateral matches the desired final asset of the position (e.g., a volatile token for longs, a stablecoin for shorts), no swap is required. * *In the LPN currency.* The collateral is swapped via an integrated DEX (e.g., Metis, a swap aggregator on Solana) into the target asset. * *In a different volatile asset.* The collateral is also swapped via the DEX into the target asset. The resulting asset determines the final size of the position. For long positions, the target asset is typically a volatile token; for short positions, it's a stablecoin. ## 2. Position creation [#2-position-creation] Upon receiving the collateral, the factory instantiates a new position contract. Only the factory has permission to do so, and this is verified during contract creation. Each position contract stores a fixed set of parameters at creation: the borrower's address, the loan currency (LPN), the initial interest period, the max and healthy liability ratios, the initial LTV, the liability reevaluation interval, the annual protocol interest, the LPP address, and the next interest due date. These values come from the factory configuration and remain unchanged for the lifetime of the position. Governance changes to a factory's config only affect new positions opened after the change. The maximum borrowable amount is computed from the initial liability ratio and the provided collateral: ``` loan = initial_liability × (loan + collateral) loan = (initial_liability × collateral) / (1 - initial_liability) ``` The total position size is the swapped sum of the borrowed amount and the collateral. ## 3. Loan request [#3-loan-request] Once the margin position contract is created, it requests a loan from the corresponding LPP. As a security measure, the LPP first verifies that the caller is a valid position contract created by the factory. The LPP determines a fixed loan interest rate based on current pool utilization and the size of the requested loan: ``` Quoted Loan Interest Rate = Base Rate + (Utilization Factor / Optimal Utilization) × Add-on Rate ``` Where the utilization factor is `Utilization / Idle Assets Portion`, the base rate is the LPP-defined minimum, and the add-on rate is a slope parameter that adjusts sensitivity beyond the base. Worked example with utilization 60%, base rate 8%, add-on rate 2%, optimal utilization 70%: ``` Quoted Loan Interest Rate = 8% + (1.5 / 0.7) × 2% ≈ 12.29% ``` The protocol interest rate (e.g., 4%) is added on top: ``` Effective Fixed Interest Rate = 12.29% + 4% = 16.29% ``` Nolus caps the rate above the optimal utilization threshold - in this example, the maximum utilization factor becomes \~2.33, capping the borrower's effective rate at \~18.67%. Once the rate is calculated, the LPP transfers the borrowed amount directly to the position contract. The LPP records the loan with the position address, principal due in LPN, the fixed annual interest rate, and a timestamp updated on every interest payment. ## 4. Cross-chain setup [#4-cross-chain-setup] Once the borrowed funds are deposited into the position contract, a remote account is established on the target host network. On Solana - the primary market - this is a set of per-lease accounts owned by the [Solray](/docs/ibc/solray) program, controlled via IBC by the position contract on Nolus. On Cosmos host chains the same role is played by an Interchain Account (ICA): a standard account on the host chain controlled via IBC from a designated controller account on Nolus, in this case the position contract. Remote-account registration requires an IBC acknowledgment to confirm the message landed. If it times out, the protocol automatically retries the next block. After registration, a "transfer out" moves both the collateral and the loan amount from the position contract to the host chain via the established IBC channel. Like registration, this step requires acknowledgment before proceeding. Once the funds arrive on the host chain, the swap into the target asset runs. When that completes, the position is officially open. Remote-account channels are unordered by default. If an action times out, the channel stays open - this significantly reduces the load on relayer providers managing cross-chain operations between Nolus and the supported networks. ## 5. Active [#5-active] Once activated, the position is in the `Opened` state. From here on, two things can happen: the borrower repays the debt, or the position undergoes liquidation. If the user repays with the same asset as the loan's LPN (e.g., USDC for a USDC-denominated loan), the outstanding amount is reduced immediately. Repayment in a different asset goes through a transfer to the remote account, a host-chain swap into LPN, and an IBC bridge back to Nolus, with acknowledgments at each step. Liquidation can be partial or full. In both cases, position assets are swapped to the owed LPN on the host chain and bridged back. A partial liquidation leaves the position active; a full liquidation marks the debt as repaid. ## 6. Settlement [#6-settlement] Once the total debt - principal and interest - is fully repaid or liquidated, the position status changes from `Opened` to `Paid`. The user then closes the position by transferring the remaining assets to their wallet. Once that transfer is acknowledged, the position transitions to `Closed`. # Querying a position (/docs/protocol/borrowing/querying) A position's status can be queried at any time. There are four observable states: * `Opened` - active, with outstanding interest and/or principal. Liquidation can occur. Call `Repay` to reduce or settle. * `Paid` - fully repaid, principal and interest, but the remaining margin hasn't been withdrawn yet. Call `Close` to finalize. * `Closed` - fully repaid and all assets withdrawn. Contract is inactive. * `Liquidated` - fully liquidated. Contract is inactive. ## Returned fields [#returned-fields] | Field | Explanation | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | Position Amount | Current asset balance held in the position contract. Sum of collateral and borrowed assets post-swap, minus any liquidations or partial closings. | | Loan Interest Rate | Interest rate paid to lenders, expressed in permille (‰). | | Protocol Interest Rate | Protocol's interest rate (used for buybacks), expressed in permille (‰). | | Principal Due (in LPN) | Remaining principal that must be repaid, excluding accumulated interest. | | Protocol Interest Overdue | Unpaid protocol interest past the due period. Deducted via partial liquidation if left unpaid. | | Loan Interest Overdue | Unpaid loan interest past the due period. Same treatment. | | Protocol Interest Due | Total protocol interest accrued to the current point in time. | | Loan Interest Due | Total loan interest accrued to the current point in time. | | Take Profit Threshold | LTV at which the position automatically closes to realize profit. Empty by default. | | Stop Loss Threshold | LTV at which the position automatically closes to preserve capital. Empty by default. | # Quoting a position (/docs/protocol/borrowing/quoting) Users can query the factory contract to estimate loan terms based on a prospective collateral amount. The quote previews what a margin position would look like under current conditions. Quoted parameters may change by the time the position is opened because they depend on real-time LPP utilization, which affects the interest rate. The returned quote includes: * *Total* - the full size of the position (collateral + borrowed amount). * *Borrowed* - the amount the protocol will lend, in LPN. * *Interest rate* - the combined fixed rate, composed of the loan interest rate (to lenders) and the protocol interest rate (for buybacks). # Repaying and closing (/docs/protocol/borrowing/repaying-closing) The protocol applies any repayment amount in a strict order: 1. Overdue protocol interest 2. Overdue loan interest 3. Current-period protocol interest 4. Current-period loan interest 5. Principal repayment to the LPP A partial repayment or partial close that uses position-internal funds settles outstanding liabilities; the borrower must call `Claim` afterward to withdraw leftover margin. A full close - via full repayment or full liquidation - settles automatically and returns the remaining funds without a manual claim. ## Manual repayment [#manual-repayment] 1. *Initiation.* The borrower calls `Repay` on the position contract. The payment must currently be in LPN. 2. *Query outstanding debt.* The contract fetches principal due and total loan interest due from the LPP, and initializes two working variables: `change` (remaining repayment after each step) and `loan payment` (amount destined for the LPP). 3. *Interest due period enforcement.* Each borrower has a window in which outstanding interest must be paid. Paying both protocol and loan interest in full extends the next due date by the full period; a partial payment extends it proportionally (e.g., a 30-day period extends by 15 days if half the interest is paid). Missing the deadline triggers a partial liquidation to cover the unpaid amount. 4. *Protocol interest settlement.* If `change` covers the protocol interest, it's paid in full. Otherwise a portion is paid and the rest stays due. 5. *Loan interest settlement.* If `change` remains after protocol interest, the position queries the LPP for the loan interest owed and pays as much as possible. 6. *Update working variables.* If loan interest is fully covered, the interest due period is renewed. `loan payment` is updated. 7. *Principal repayment.* Any remaining `change` is applied to the outstanding principal. 8. *Refund and cleanup.* Any surplus is returned to the user, the loan record is deleted from the LPP, and the borrower can withdraw the remaining margin from the position contract via `Claim`. ## Market close (full) [#market-close-full] In a market close, the position is fully settled using only the funds already held in the contract - no external capital from the borrower. 1. *Initiation.* The user or the protocol triggers the full closure. 2. *Asset conversion.* All position assets are swapped to LPN on the host DEX and bridged back via IBC, with acknowledgments at each step. 3. *Debt assessment.* The contract computes protocol interest due, loan interest due, and principal due. 4. *Repayment from internal funds.* Debt is settled in the standard order (overdue protocol interest, overdue loan interest, current protocol interest, current loan interest, principal) - same as manual repayment, but sourced entirely from the position's internal balance. 5. *Loan finalization.* If internal funds cover all debts, the position is `Paid`; the loan entry is deleted from the LPP. No follow-up `Repay` call is needed. 6. *Automatic asset distribution.* The remaining LPN is returned to the borrower automatically - no manual claim required. 7. *Status update.* Position transitions directly from `Opened` to `Closed`. ## Partial close [#partial-close] In a partial close, the borrower uses a portion of the position's internal funds to settle part or all of the debt. Unlike a full close, this does not automatically close the position. 1. *Initiation.* The user triggers a partial close, specifying the portion of the position to apply to repayment. No external funds involved. 2. *Internal asset conversion.* The specified portion is swapped to LPN on the host DEX (if not already LPN) and bridged back via IBC. 3. *Debt assessment.* The contract computes protocol interest due, loan interest due, and principal due. 4. *Repayment execution.* The converted LPN is applied in order: protocol interest, loan interest, principal. If the full debt is covered, the position transitions from `Opened` to `Paid`. Otherwise it stays `Opened` with reduced liability. 5. *Remaining balance.* If the converted amount exceeded the debt (e.g., $120 to cover $100), the surplus stays in the position contract. The unused portion of the position (e.g., the remaining $80 in volatile or collateral assets) also stays. 6. *Manual claim.* Since the position isn't auto-closed, the borrower calls `Claim` to retrieve any excess LPN and the remaining unutilized assets. Until claimed, those funds stay locked and the position remains `Paid`. Once claimed, status moves to `Closed`. # Deposit and withdraw (/docs/protocol/lending/deposit-withdraw) ## Deposit LPN (mint nLPN) [#deposit-lpn-mint-nlpn] To participate as a lender in an LPP, users must hold the LPP's LPN currency (for example, USDC) in their wallet. Deposits are submitted as a transaction whose input message is processed by the Cosmos SDK bank module, which transfers the funds into the LPP smart contract. Upon deposit, lenders receive a receipt token - a CW20 derivative called nLPN - representing their share in the pool. These tokens serve both as proof of deposit and as the mechanism to withdraw the underlying assets later. The LPP contract tracks a price index representing the current exchange rate between nLPN and LPN. When the pool is empty (e.g., at genesis), the initial exchange rate is `1 nLPN = 1 LPN`. Once deposits and borrowing activity begin, the price of nLPN evolves based on: * Newly deposited LPN tokens * Interest payments made by borrowers As time progresses and interest accrues, the value of each nLPN token increases relative to LPN; the lender's share of the pool grows. The lender's current balance (in LPN terms): ``` Lender LPN Balance = Lender nLPN Balance × Price per nLPN ``` The number of nLPN tokens minted for each deposit is determined by the LPP smart contract using the current price index, tracked under the lender's wallet address. ## Withdraw LPN (burn nLPN) [#withdraw-lpn-burn-nlpn] On withdrawal, the LPP first computes the LPN the user is entitled to based on the current nLPN-to-LPN exchange rate: ``` Claimable LPN = Provided nLPN × Price per nLPN ``` Once the claimable amount is computed: * The user's nLPN balance is reduced accordingly. * The LPP's LPN balance is updated to reflect the outflow. If the user withdraws their entire nLPN balance, the contract automatically includes any accrued interest - effectively closing out their lending position. They no longer hold any nLPN, and they're no longer a participant in the pool. If the user opts for a partial withdrawal (e.g., 50% of their position), only the corresponding portion of nLPN is burned and the remainder continues to accrue interest. # Lending (/docs/protocol/lending) For each supported denomination there's a dedicated Liquidity Providers' Pool (LPP). Each LPP operates with a single LPN (Liquidity Pool's Native currency) and serves all lenders who deposit liquidity in that currency. When a user deposits assets into an LPP, they receive interest-bearing CW20 receipt tokens called nLPN (e.g., nUSDC). These represent the lender's share in the pool and accrue interest over time through an index-based accrual: the price of nLPN rises proportionally as borrowers repay interest into the pool. Each nLPN reflects a fraction of the total pool value, including both the original liquidity and accrued interest from active loans. On redemption, the lender gets back their share of the pool (principal plus accrued interest) at the current nLPN/LPN exchange rate. The LPP is a hub: lenders deposit LPN and mint nLPN (a receipt token whose price relative to LPN drifts up as borrowers pay interest into the pool); Lease contracts draw loans against that liquidity and return principal + margin interest; the Treasury periodically tops up NLS incentives that lenders claim alongside the underlying yield. ## Walk through the flow [#walk-through-the-flow] # NLS rewards (/docs/protocol/lending/rewards) Beyond the borrower interest that flows into the pool, lenders are eligible for additional NLS rewards. The Treasury smart contract distributes these incentives to each LPP, tracked internally in uNLS (the smallest unit of NLS). Reward distribution is based on: * The total value locked (TVL) in a specific LPP. * The aggregate TVL across all LPPs. * TVL threshold brackets, configured via governance in the Treasury contract. Each time the Treasury transfers uNLS to an LPP, a global reward index is updated to reflect the new incentive distribution across all nLPN holders. Rewards per nLPN (denominated in uNLS) are tracked as part of the global deposit state managed by the LPP contract. The actual uNLS balances are maintained by the Cosmos SDK bank module. Each lender is represented by an internal record with their wallet address, current nLPN balance, last recorded reward-per-nLPN value, and any pending uNLS rewards accumulated since their last interaction. ## Distribute incentives [#distribute-incentives] The global reward per nLPN, denominated in uNLS, increases each time the Treasury transfers new uNLS rewards to the LPP. This value accumulates over time, proportionally raising the value of each nLPN held by lenders. ## Update and claim [#update-and-claim] Whenever a lender deposits or withdraws, the contract calculates the rewards accrued up to that point based on the difference between the global reward per nLPN and the lender's last recorded reward per nLPN. The result is added to the lender's pending rewards balance. This update is triggered automatically on deposit, withdrawal, or claim. On claim, the pending uNLS is transferred to the lender's address by default (or to a specified recipient). The lender's reward index is then synced with the current global reward per nLPN. If the lender withdraws their full LPN balance, any remaining rewards are claimed automatically and they cease to hold any nLPN. ## Treasury [#treasury] The Treasury is a single-instance contract responsible for calculating uNLS rewards across all LPPs based on their individual TVLs and the network-wide aggregate. Each LPP reports its TVL directly to the Treasury, which uses governance-defined threshold intervals to determine the applicable APR. Thresholds start at a minimum and step up as higher tiers are reached. Reward distribution runs periodically, with duration measured in hours.