Skip to content

Latest commit

 

History

History
554 lines (431 loc) · 16.5 KB

File metadata and controls

554 lines (431 loc) · 16.5 KB

LOAN Protocol on XPR Network

This guide covers integration with the LOAN lending protocol on XPR Network.

Overview

LOAN is the native lending protocol on XPR Network enabling:

  • Supply assets to earn interest - Deposit tokens, receive L-tokens (shares)
  • Collateralized loans - Borrow against supplied collateral
  • Variable interest rates - Supply/demand-based APY
  • Liquidations - Automated health factor monitoring
  • LOAN token rewards - Earn LOAN governance tokens on supply/borrow
  • Governance - LOAN token holders govern protocol

Key Contracts

Contract Description
lending.loan Main lending/borrowing/redeem logic
shares.loan L-token share receipts (LXPR, LUSDC, etc.)
loan.token LOAN governance token
oracles Price feeds for collateral

⚠️ Important: The main contract is lending.loan, NOT lending. Using the wrong contract will cause transactions to fail.


Core Concepts

L-Tokens (Share Tokens)

When you supply assets, you receive L-tokens representing your share of the pool:

Supplied L-Token Received Precision Token Contract
XPR LXPR 4 shares.loan
XUSDC LUSDC 6 shares.loan
XBTC LBTC 8 shares.loan
XETH LETH 8 shares.loan
XMD LXMD 6 shares.loan
XUSDT LUSDT 6 shares.loan
XMT (MTL — Metal DAO) LXMT 8 shares.loan
XXRP LXRP 6 shares.loan
XDOGE LDOGE 6 shares.loan
XHBAR LHBAR 6 shares.loan
XADA LADA 6 shares.loan
XXLM LXLM 6 shares.loan
XLTC LLTC 8 shares.loan
XSOL LSOL 6 shares.loan

L-token values increase over time as interest accrues, so you get back more than you deposited.

Health Factor

Health factor measures loan safety:

Health Factor = (Collateral Value × Liquidation Threshold) / Debt Value
Health Factor Status
> 1.5 Safe
1.0 - 1.5 Warning
< 1.0 Liquidatable

Collateral Factor

Maximum borrowing power as percentage of collateral:

Asset Collateral Factor
XPR 40%
XUSDC 80%
XBTC 70%
XETH 70%
XMD 90%
XMT (MTL — Metal DAO) 50%
XDOGE 60%
XADA 60%
XLTC 60%
XSOL 60%
XXLM 60%
XXRP 60%
XHBAR 60%

Supply Assets (Mint L-Tokens)

Step 1: Enter Markets

Before supplying, you must enter the markets for the assets you want to use:

# Enter XPR and XUSDC markets
proton action lending.loan entermarkets '{"payer":"myaccount","user":"myaccount","markets":["LXPR","LUSDC"]}' myaccount

Step 2: Deposit (Mint)

Transfer tokens to lending.loan with memo mint:

# Supply 40000 XPR
proton action eosio.token transfer '{"from":"myaccount","to":"lending.loan","quantity":"40000.0000 XPR","memo":"mint"}' myaccount

# Supply 100 XUSDC
proton action xtokens transfer '{"from":"myaccount","to":"lending.loan","quantity":"100.000000 XUSDC","memo":"mint"}' myaccount
async function supply(
  session: any,
  quantity: string,
  tokenContract: string  // 'eosio.token' for XPR, 'xtokens' for wrapped tokens
): Promise<any> {
  return session.transact({
    actions: [{
      account: tokenContract,
      name: 'transfer',
      authorization: [session.auth],
      data: {
        from: session.auth.actor,
        to: 'lending.loan',
        quantity: quantity,
        memo: 'mint'
      }
    }]
  }, { broadcast: true });
}

// Example: Supply 1000 XUSDC
await supply(session, '1000.000000 XUSDC', 'xtokens');

// Example: Supply 10000 XPR
await supply(session, '10000.0000 XPR', 'eosio.token');

Note: The mint memo triggers L-token minting. Your L-token shares appear in the lending.loan shares table scoped to lending.loan.

Verify Shares

# Check your L-token shares
curl -s -X POST https://proton.eosusa.io/v1/chain/get_table_rows \
  -H 'Content-Type: application/json' \
  -d '{"code":"lending.loan","scope":"lending.loan","table":"shares","lower_bound":"myaccount","upper_bound":"myaccount","limit":1,"json":true}'

Response format:

{
  "rows": [{
    "account": "myaccount",
    "tokens": [
      {
        "key": {"sym": "4,LXPR", "contract": "shares.loan"},
        "value": 390334875
      },
      {
        "key": {"sym": "6,LUSDC", "contract": "shares.loan"},
        "value": 71479562
      }
    ]
  }]
}

The value field is the raw integer. Divide by 10^precision to get the human-readable amount:

  • LXPR value 390334875 with precision 4 = 39033.4875 LXPR
  • LUSDC value 71479562 with precision 6 = 71.479562 LUSDC

Exit Markets

To remove assets from your entered markets (e.g., when you no longer want them used as collateral):

# Exit XPR and XUSDC markets
proton action lending.loan exitmarkets '{"user":"myaccount","markets":["LXPR","LUSDC"]}' myaccount
async function exitMarkets(
  session: any,
  markets: string[]  // e.g. ['LXPR', 'LUSDC']
): Promise<any> {
  return session.transact({
    actions: [{
      account: 'lending.loan',
      name: 'exitmarkets',
      authorization: [session.auth],
      data: {
        user: session.auth.actor,
        markets: markets
      }
    }]
  }, { broadcast: true });
}

Note: You cannot exit a market if you have outstanding borrows against that collateral.


Redeem (Withdraw Supplied Assets)

The redeem action burns your L-tokens and returns the underlying asset plus accrued interest.

Redeem Action

The action takes an extended_asset parameter:

# Redeem all LXPR shares back to XPR
proton action lending.loan redeem '{"redeemer":"myaccount","token":{"quantity":"39033.4875 LXPR","contract":"shares.loan"}}' myaccount

# Redeem all LUSDC shares back to XUSDC
proton action lending.loan redeem '{"redeemer":"myaccount","token":{"quantity":"71.479562 LUSDC","contract":"shares.loan"}}' myaccount
async function redeem(
  session: any,
  quantity: string,  // e.g. "39033.4875 LXPR"
): Promise<any> {
  return session.transact({
    actions: [{
      account: 'lending.loan',
      name: 'redeem',
      authorization: [session.auth],
      data: {
        redeemer: session.auth.actor,
        token: {
          quantity: quantity,
          contract: 'shares.loan'
        }
      }
    }]
  }, { broadcast: true });
}

// Example: Redeem all LXPR
await redeem(session, '39033.4875 LXPR');

⚠️ Precision matters! LXPR uses precision 4 (e.g., 39033.4875), LUSDC uses precision 6 (e.g., 71.479562). Using wrong precision will fail.

The redeem action will:

  1. Burn your L-tokens via shares.loan retire action
  2. Issue LOAN reward tokens
  3. Transfer the underlying asset back to you (e.g., XPR via eosio.token)

Borrow

Take Loan

async function borrow(
  session: any,
  quantity: string,
  tokenContract: string  // 'eosio.token' for XPR, 'xtokens' for wrapped tokens
): Promise<any> {
  // There is no health_factor column on-chain. Compute it yourself from
  // `shares` (L-token balances), `markets` (collateral_factor, exchange rate)
  // and oracle prices: sum(collateral value × collateral_factor) / sum(debt value).
  const healthFactor = await computeHealthFactor(session.auth.actor);

  if (healthFactor < 1.2) {
    throw new Error('Health factor too low to borrow');
  }

  return session.transact({
    actions: [{
      account: 'lending.loan',
      name: 'borrow',
      authorization: [session.auth],
      data: {
        borrower: session.auth.actor,
        underlying: {
          quantity: quantity,
          contract: tokenContract
        },
        use_stable_rate: false
      }
    }]
  }, { broadcast: true });
}
# Borrow 500 XPR against collateral
proton action lending.loan borrow '{"borrower":"myaccount","underlying":{"quantity":"500.0000 XPR","contract":"eosio.token"},"use_stable_rate":false}' myaccount

Repay Loan

Transfer tokens back with repay memo:

# Repay 500 XUSDC
proton action xtokens transfer '{"from":"myaccount","to":"lending.loan","quantity":"500.000000 XUSDC","memo":"repay"}' myaccount

# Repay 1000 XPR
proton action eosio.token transfer '{"from":"myaccount","to":"lending.loan","quantity":"1000.0000 XPR","memo":"repay"}' myaccount

Claim LOAN Rewards

LOAN tokens are distributed as rewards for supplying and borrowing:

proton action lending.loan claimrewards '{"claimer":"myaccount","markets":["LXPR","LUSDC"]}' myaccount

Note: Use the L-token symbol_code format (e.g., LXPR not 4,LXPR) for the markets parameter. You can only claim for markets you have active positions in.


Query Protocol State

Get User Shares (L-Token Balances)

async function getUserShares(account: string): Promise<any> {
  const response = await fetch('https://proton.eosusa.io/v1/chain/get_table_rows', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      code: 'lending.loan',
      scope: 'lending.loan',
      table: 'shares',
      lower_bound: account,
      upper_bound: account,
      limit: 1,
      json: true
    })
  });
  const { rows } = await response.json();
  return rows[0] ?? null;
}

Get Market Stats

async function getMarkets(): Promise<any[]> {
  const response = await fetch('https://proton.eosusa.io/v1/chain/get_table_rows', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      code: 'lending.loan',
      scope: 'lending.loan',
      table: 'markets',
      limit: 100,
      json: true
    })
  });
  const { rows } = await response.json();
  return rows;
}

Liquidations

Check Liquidatable Borrows

async function getLiquidatableBorrows(): Promise<any[]> {
  const response = await fetch('https://proton.eosusa.io/v1/chain/get_table_rows', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      code: 'lending.loan',
      scope: 'lending.loan',
      table: 'borrows',
      limit: 1000,
      json: true
    })
  });
  const { rows } = await response.json();
  // `borrows` rows hold principals per token only — there is no health_factor
  // column. Compute HF per account (collateral × collateral_factor vs. debt,
  // both at oracle prices) and keep the ones below 1.0.
  const scored = await Promise.all(
    rows.map(async (p: any) => ({ ...p, hf: await computeHealthFactor(p.account) }))
  );
  return scored.filter((p) => p.hf < 1.0);
}

Execute Liquidation

# Liquidate an underwater position: repay with the borrowed token, memo is
# comma-separated and names the L-TOKEN collateral to seize (verified from a
# live log.seize: memo "liquidate,<borrower>,LXRP")
proton action xtokens transfer '{"from":"myaccount","to":"lending.loan","quantity":"500.000000 XUSDC","memo":"liquidate,targetaccount,LXPR"}' myaccount

Liquidation Incentive

One global setting, not per-collateral: globals.cfg.liquidation_incentive = 0.10 (10%) and close_factor = 0.10 (at most 10% of the debt per liquidation). Observed seizures pay 1 / (1 − 0.10) ≈ 11.1% more collateral value than debt repaid. Read it live:

proton table lending.loan globals.cfg

LOAN Token

Token Details

Property Value
Contract loan.token
Symbol LOAN
Precision 4
Max Supply Unbounded (0.0000 LOAN on the token contract — i.e. no cap)
Circulating (September 2026) ~114.2 billion LOAN (114153323234.0560 LOAN)

Note: Earlier versions of this doc claimed a 100,000,000 LOAN max supply. That was wrong — eosio.token::stat shows max_supply: 0.0000 LOAN (unbounded) and supply already exceeds 113 billion. Verify the current supply with:

curl -s -X POST https://proton.eosusa.io/v1/chain/get_currency_stats \
  -H 'Content-Type: application/json' \
  -d '{"code":"loan.token","symbol":"LOAN"}'

Staking LOAN

Note: LOAN staking may not be active on mainnet. The loan.staking contract does not appear to exist as of March 2026. Check on-chain before attempting staking operations.


Price Oracle Integration

Get Asset Prices

# XPR price (feed index 3)
curl -s -X POST https://proton.eosusa.io/v1/chain/get_table_rows \
  -H 'Content-Type: application/json' \
  -d '{"code":"oracles","scope":"oracles","table":"data","lower_bound":3,"upper_bound":3,"limit":1,"json":true}'

Oracle feed indices for LOAN collateral assets (excerpt — the canonical full table with liveness notes is in oracles-randomness.md):

Index Asset
3 XPR/USD
4 BTC/USD
6 MTL/USD (XMT)
7 ETH/USD
8 DOGE/USD
9 USDT/USD
16 LTC/USD
18 XRP/USD
19 SOL/USD
21 HBAR/USD
22 ADA/USD
23 XLM/USD

Index 5 (USDC/USD) and 12 (XMD/USD) are dormant (no updates in 1+ years) — use 9 (USDT/USD) as the dollar reference.


Whitelist Note

The whitelist table (updatewl(account, hash, add)) lists contract accounts by code hash that are permitted to call LOAN from contract code. End-user operations are not whitelist-gated: mint, borrow, repay and liquidate all succeed for ordinary accounts that are not in the table (verified September 2026 on a live borrow and liquidation by non-whitelisted accounts).

To check the whitelist:

curl -s -X POST https://proton.eosusa.io/v1/chain/get_table_rows \
  -H 'Content-Type: application/json' \
  -d '{"code":"lending.loan","scope":"lending.loan","table":"whitelist","limit":100,"json":true}'

Quick Reference

Common Actions

# Enter markets (required before first supply)
proton action lending.loan entermarkets '{"payer":"alice","user":"alice","markets":["LXPR","LUSDC"]}' alice

# Exit markets (remove collateral eligibility)
proton action lending.loan exitmarkets '{"user":"alice","markets":["LXPR","LUSDC"]}' alice

# Supply XPR (mint L-tokens)
proton action eosio.token transfer '{"from":"alice","to":"lending.loan","quantity":"1000.0000 XPR","memo":"mint"}' alice

# Supply XUSDC (mint L-tokens)
proton action xtokens transfer '{"from":"alice","to":"lending.loan","quantity":"100.000000 XUSDC","memo":"mint"}' alice

# Redeem LXPR back to XPR
proton action lending.loan redeem '{"redeemer":"alice","token":{"quantity":"975.1234 LXPR","contract":"shares.loan"}}' alice

# Redeem LUSDC back to XUSDC
proton action lending.loan redeem '{"redeemer":"alice","token":{"quantity":"71.479562 LUSDC","contract":"shares.loan"}}' alice

# Borrow
proton action lending.loan borrow '{"borrower":"alice","underlying":{"quantity":"500.0000 XPR","contract":"eosio.token"},"use_stable_rate":false}' alice

# Repay
proton action xtokens transfer '{"from":"alice","to":"lending.loan","quantity":"500.000000 XUSDC","memo":"repay"}' alice

# Claim LOAN rewards
proton action lending.loan claimrewards '{"claimer":"alice","markets":["LXPR","LUSDC"]}' alice

# Check shares
# Use RPC: code=lending.loan, scope=lending.loan, table=shares, lower_bound=alice, upper_bound=alice

Key Tables

Contract Table Scope Description
lending.loan shares lending.loan User L-token share balances
lending.loan markets lending.loan Market stats, APYs, reserve ratios
lending.loan borrows lending.loan User borrow positions: {account, tokens:[{key:{sym,contract}, value:{variable_principal, variable_interest_index, stable_principal, ...}}]} — no health-factor column
lending.loan whitelist lending.loan Contract accounts (by code hash) allowed to call LOAN from contract code
loan.token (standard token tables) LOAN governance token
oracles data oracles Price feeds

RPC Endpoints

Use these endpoints for querying tables (in order of reliability):

  1. https://proton.eosusa.io (recommended)
  2. https://proton.protonuk.io
  3. https://proton.cryptolions.io

Important Notes

  • Contract is lending.loan — not lending
  • Share tokens live on shares.loan — not in your token balance
  • Memo mint for deposits, memo repay for loan repayment
  • Redeem uses extended_asset format: {"quantity":"X LXPR","contract":"shares.loan"}
  • Precision must match exactly — LXPR=4, LUSDC=6, LBTC=8, etc.
  • Enter markets first before supplying or borrowing
  • LOAN rewards are issued during redeem and can also be claimed separately