diff --git a/skill/defi-trading.md b/skill/defi-trading.md index 3f63a01..fdf9b13 100644 --- a/skill/defi-trading.md +++ b/skill/defi-trading.md @@ -794,9 +794,145 @@ placeOrder(...): void { --- +## Proton Swaps (AMM Liquidity Pools) + +`proton.swaps` provides automated market maker (AMM) swap pools — an alternative to the DEX order book for instant trades. + +### Key Contract + +| Contract | Purpose | +|----------|---------| +| `proton.swaps` | AMM swap pools, liquidity provision | + +### Query All Pools + +```bash +curl -s -X POST https://proton.greymass.com/v1/chain/get_table_rows \ + -H 'Content-Type: application/json' \ + -d '{"code":"proton.swaps","scope":"proton.swaps","table":"pools","limit":100,"json":true}' +``` + +Pool row structure: + +| Field | Description | +|-------|-------------| +| `lt_symbol` | LP token symbol (e.g., `XPRUSDC`) | +| `creator` | Pool creator account | +| `memo` | Pool identifier string | +| `pool1` | Reserve of token A (extended_asset) | +| `pool2` | Reserve of token B (extended_asset) | +| `hash` | Pool hash | +| `fee` | Exchange fee object | + +### Exchange Fees + +The `fee` field contains `exchange_fee` as an integer where: +- `20` = 0.20% fee (most pools) +- `5` = 0.05% fee (stablecoin pools like XUSDT/XUSDC) + +### Active Pools (as of Feb 2026) + +| Pool | Fee | Notes | +|------|-----|-------| +| XPR/XUSDC | 0.20% | Highest volume, primary XPR trading pair | +| XPR/LOAN | 0.20% | LOAN governance token pair | +| METAL/XPR | 0.20% | Metal token pair | +| XPR/XMT | 0.20% | Metal X token pair | +| SNIPS/XPR | 0.20% | Community token | +| XUSDT/XUSDC | 0.05% | Stablecoin pool | +| XPR/XBTC | 0.20% | Bitcoin pair | +| XPR/XETH | 0.20% | Ethereum pair | + +### Execute a Swap + +Swaps are done via token transfer to `proton.swaps` with a memo specifying the output token: + +```bash +# Swap 1000 XPR → XUSDC (minimum 1 XUSDC out) +proton action eosio.token transfer \ + '{"from":"myaccount","to":"proton.swaps","quantity":"1000.0000 XPR","memo":"XPRUSDC,1"}' \ + myaccount + +# Swap 10 XUSDC → XPR (minimum 1 XPR out) +proton action xtokens transfer \ + '{"from":"myaccount","to":"proton.swaps","quantity":"10.000000 XUSDC","memo":"XPRUSDC,1"}' \ + myaccount +``` + +**Memo format:** `,` + +- `POOL_LT_SYMBOL`: The LP token symbol (e.g., `XPRUSDC`) +- `MIN_OUTPUT`: Minimum amount to receive (slippage protection, use `1` for no minimum) + +The contract automatically determines direction based on which token you send. + +### Calculate Expected Output + +For a constant-product AMM (x × y = k): + +``` +output = (input_amount × (10000 - exchange_fee) × output_reserve) / (input_reserve × 10000 + input_amount × (10000 - exchange_fee)) +``` + +```typescript +function calculateSwapOutput( + inputAmount: number, + inputReserve: number, + outputReserve: number, + exchangeFee: number // e.g., 20 for 0.2% +): number { + const inputWithFee = inputAmount * (10000 - exchangeFee); + return (inputWithFee * outputReserve) / (inputReserve * 10000 + inputWithFee); +} +``` + +### Add Liquidity + +```bash +# Add liquidity to XPR/XUSDC pool (must add both sides proportionally) +proton action proton.swaps addliquidity \ + '{"owner":"myaccount","pool1":{"quantity":"1000.0000 XPR","contract":"eosio.token"},"pool2":{"quantity":"2.200000 XUSDC","contract":"xtokens"},"lt_symbol":"8,XPRUSDC","max_slippage":100}' \ + myaccount +``` + +### Remove Liquidity + +```bash +# Remove liquidity +proton action proton.swaps liquidityrmv \ + '{"owner":"myaccount","lt_symbol":"8,XPRUSDC","lt_amount":"1000.00000000 XPRUSDC"}' \ + myaccount + +# Withdraw returned tokens +proton action proton.swaps withdrawall '{"owner":"myaccount"}' myaccount +``` + +> ⚠️ Always call `withdrawall` after removing liquidity to receive your tokens back. + +### Multi-Hop Swaps + +For tokens without a direct pool (e.g., METAL → XUSDC), you need multiple swaps: + +``` +METAL → XPR (via METAL/XPR pool) → XUSDC (via XPR/XUSDC pool) +``` + +Each hop incurs the pool's exchange fee, making multi-hop trades more expensive. + +### Arbitrage Considerations + +- **Fees eat spread:** With 0.2% per hop, a round-trip (buy + sell) costs ~0.4%. Arbitrage only works if price discrepancy exceeds this. +- **Triangular routes:** 3-hop routes cost ~0.6% minimum in fees. In practice, pools on XPR Network are efficient enough that profitable cycles are rare. +- **Pool imbalances:** Large swaps can temporarily move pool prices. Watch for whale trades creating imbalances that revert over time. +- **DEX vs Swap divergence:** The order book (MetalX DEX) and AMM pools can diverge in price. Check both before trading. + +--- + ## Resources - **MetalX DEX**: https://metalx.com - **XPR DEX Bot**: https://github.com/XPRNetwork/dex-bot - **Oracle Feeds**: `oracles` contract on XPR Network - **Perpetual Protocol Concepts**: https://docs.perp.com (reference for perps design) +- **Proton Swaps**: AMM pools at `proton.swaps` contract +- **RPC Endpoints**: `proton.greymass.com` (primary), `proton.eosusa.io`, `proton.protonuk.io` (fallbacks) diff --git a/skill/loan-protocol.md b/skill/loan-protocol.md index cf06e7d..e312ddb 100644 --- a/skill/loan-protocol.md +++ b/skill/loan-protocol.md @@ -5,23 +5,51 @@ This guide covers integration with the LOAN lending protocol on XPR Network. ## Overview LOAN is the native lending protocol on XPR Network enabling: -- **Collateralized loans** - Borrow against crypto collateral +- **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 | -| `lending` | Main lending/borrowing logic | -| `oracle` | Price feeds for collateral | +| `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` | +| METAL | LXMT | 8 | `shares.loan` | +| XRP | LXRP | 6 | `shares.loan` | +| DOGE | LDOGE | 6 | `shares.loan` | +| HBAR | LHBAR | 6 | `shares.loan` | +| ADA | LADA | 6 | `shares.loan` | +| XLM | LXLM | 6 | `shares.loan` | +| LTC | LLTC | 8 | `shares.loan` | +| SOL | 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: @@ -49,88 +77,34 @@ Maximum borrowing power as percentage of collateral: --- -## Query Protocol State - -### Get User Position +## Supply Assets (Mint L-Tokens) -```typescript -interface UserPosition { - account: string; - supplied: Asset[]; // Supplied collateral - borrowed: Asset[]; // Outstanding loans - health_factor: number; -} +### Step 1: Enter Markets -async function getUserPosition(account: string): Promise { - const { rows } = await rpc.get_table_rows({ - code: 'lending', - scope: 'lending', - table: 'positions', - lower_bound: account, - upper_bound: account, - limit: 1 - }); +Before supplying, you must enter the markets for the assets you want to use: - return rows[0] ?? null; -} +```bash +# Enter XPR and XUSDC markets +proton action lending.loan entermarkets '{"account":"myaccount","markets":["4,LXPR","6,LUSDC"]}' myaccount ``` -### Get Market Stats +### Step 2: Deposit (Mint) -```typescript -interface Market { - asset: string; - total_supplied: string; - total_borrowed: string; - supply_apy: number; - borrow_apy: number; - utilization: number; -} - -async function getMarkets(): Promise { - const { rows } = await rpc.get_table_rows({ - code: 'lending', - scope: 'lending', - table: 'markets', - limit: 100 - }); +Transfer tokens to `lending.loan` with memo `mint`: - return rows; -} -``` - -### Get Interest Rates - -```typescript -async function getInterestRates(symbol: string): Promise<{ - supplyAPY: number; - borrowAPY: number; -}> { - const markets = await getMarkets(); - const market = markets.find(m => m.asset.includes(symbol)); - - if (!market) { - throw new Error(`Market ${symbol} not found`); - } +```bash +# Supply 40000 XPR +proton action eosio.token transfer '{"from":"myaccount","to":"lending.loan","quantity":"40000.0000 XPR","memo":"mint"}' myaccount - return { - supplyAPY: market.supply_apy / 100, // Convert basis points - borrowAPY: market.borrow_apy / 100 - }; -} +# Supply 100 XUSDC +proton action xtokens transfer '{"from":"myaccount","to":"lending.loan","quantity":"100.000000 XUSDC","memo":"mint"}' myaccount ``` ---- - -## Supply Collateral - -### Deposit Assets - ```typescript async function supply( session: any, quantity: string, - tokenContract: string + tokenContract: string // 'eosio.token' for XPR, 'xtokens' for wrapped tokens ): Promise { return session.transact({ actions: [{ @@ -139,9 +113,9 @@ async function supply( authorization: [session.auth], data: { from: session.auth.actor, - to: 'lending', + to: 'lending.loan', quantity: quantity, - memo: 'supply' + memo: 'mint' } }] }, { broadcast: true }); @@ -149,29 +123,95 @@ async function supply( // Example: Supply 1000 XUSDC await supply(session, '1000.000000 XUSDC', 'xtokens'); + +// Example: Supply 10000 XPR +await supply(session, '10000.0000 XPR', 'eosio.token'); ``` -### Withdraw Collateral +> **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 + +```bash +# Check your L-token shares +curl -s -X POST https://proton.greymass.com/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: +```json +{ + "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` + +--- + +## 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: + +```bash +# 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 +``` ```typescript -async function withdraw( +async function redeem( session: any, - quantity: string + quantity: string, // e.g. "39033.4875 LXPR" ): Promise { return session.transact({ actions: [{ - account: 'lending', - name: 'withdraw', + account: 'lending.loan', + name: 'redeem', authorization: [session.auth], data: { - account: session.auth.actor, - quantity: quantity + 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 @@ -183,7 +223,6 @@ async function borrow( session: any, quantity: string ): Promise { - // Check health factor first const position = await getUserPosition(session.auth.actor); if (position && position.health_factor < 1.2) { @@ -192,7 +231,7 @@ async function borrow( return session.transact({ actions: [{ - account: 'lending', + account: 'lending.loan', name: 'borrow', authorization: [session.auth], data: { @@ -204,77 +243,80 @@ async function borrow( } ``` -### Calculate Max Borrow +```bash +# Borrow 500 XPR against collateral +proton action lending.loan borrow '{"account":"myaccount","quantity":"500.0000 XPR"}' myaccount +``` -```typescript -async function calculateMaxBorrow( - account: string, - borrowSymbol: string -): Promise { - const position = await getUserPosition(account); +--- - if (!position) return 0; +## Repay Loan - // Get prices from oracle - const collateralValue = await calculateCollateralValue(position.supplied); - const debtValue = await calculateDebtValue(position.borrowed); +Transfer tokens back with `repay` memo: - // Available = (Collateral × Factor) - Current Debt - const collateralFactor = 0.75; // Example: 75% - const availableToBorrow = (collateralValue * collateralFactor) - debtValue; +```bash +# Repay 500 XUSDC +proton action xtokens transfer '{"from":"myaccount","to":"lending.loan","quantity":"500.000000 XUSDC","memo":"repay"}' myaccount - const borrowPrice = await getAssetPrice(borrowSymbol); - return availableToBorrow / borrowPrice; -} +# Repay 1000 XPR +proton action eosio.token transfer '{"from":"myaccount","to":"lending.loan","quantity":"1000.0000 XPR","memo":"repay"}' myaccount ``` --- -## Repay Loan - -### Full Repayment +## Claim LOAN Rewards -```typescript -async function repay( - session: any, - quantity: string, - tokenContract: string -): Promise { - return session.transact({ - actions: [{ - account: tokenContract, - name: 'transfer', - authorization: [session.auth], - data: { - from: session.auth.actor, - to: 'lending', - quantity: quantity, - memo: 'repay' - } - }] - }, { broadcast: true }); -} +LOAN tokens are distributed as rewards for supplying and borrowing: -// Example: Repay 500 XUSDC -await repay(session, '500.000000 XUSDC', 'xtokens'); +```bash +proton action lending.loan claimrewards '{"claimer":"myaccount","markets":["4,LXPR","6,LUSDC"]}' myaccount ``` -### Repay Max (Full Outstanding) +> **Note:** Use the L-token symbol format (e.g., `4,LXPR` not `4,XPR`) for the markets parameter. You can only claim for markets you have active positions in. + +--- + +## Query Protocol State + +### Get User Shares (L-Token Balances) ```typescript -async function repayMax( - session: any, - symbol: string, - tokenContract: string -): Promise { - const position = await getUserPosition(session.auth.actor); - const debt = position?.borrowed.find(b => b.includes(symbol)); +async function getUserShares(account: string): Promise { + const response = await fetch('https://proton.greymass.com/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; +} +``` - if (!debt) { - throw new Error('No outstanding debt for ' + symbol); - } +### Get Market Stats - return repay(session, debt, tokenContract); +```typescript +async function getMarkets(): Promise { + const response = await fetch('https://proton.greymass.com/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; } ``` @@ -285,58 +327,32 @@ async function repayMax( ### Check Liquidatable Positions ```typescript -async function getLiquidatablePositions(): Promise { - const { rows } = await rpc.get_table_rows({ - code: 'lending', - scope: 'lending', - table: 'positions', - limit: 1000 +async function getLiquidatablePositions(): Promise { + const response = await fetch('https://proton.greymass.com/v1/chain/get_table_rows', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + code: 'lending.loan', + scope: 'lending.loan', + table: 'positions', + limit: 1000, + json: true + }) }); - - return rows.filter((p: UserPosition) => p.health_factor < 1.0); + const { rows } = await response.json(); + return rows.filter((p: any) => p.health_factor < 1.0); } ``` ### Execute Liquidation -```typescript -async function liquidate( - session: any, - targetAccount: string, - debtToCover: string, - collateralAsset: string, - tokenContract: string -): Promise { - // Verify position is liquidatable - const position = await getUserPosition(targetAccount); - - if (!position || position.health_factor >= 1.0) { - throw new Error('Position not liquidatable'); - } - - return session.transact({ - actions: [ - // Send debt tokens to cover - { - account: tokenContract, - name: 'transfer', - authorization: [session.auth], - data: { - from: session.auth.actor, - to: 'lending', - quantity: debtToCover, - memo: `liquidate:${targetAccount}:${collateralAsset}` - } - } - ] - }, { broadcast: true }); -} +```bash +# Liquidate an underwater position +proton action xtokens transfer '{"from":"myaccount","to":"lending.loan","quantity":"500.000000 XUSDC","memo":"liquidate:targetaccount:XPR"}' myaccount ``` ### Liquidation Bonus -Liquidators receive a bonus for helping maintain protocol health: - | Collateral | Liquidation Bonus | |------------|-------------------| | XPR | 10% | @@ -359,198 +375,52 @@ Liquidators receive a bonus for helping maintain protocol health: ### Staking LOAN -```typescript -async function stakeLoan( - session: any, - quantity: string -): Promise { - return session.transact({ - actions: [{ - account: 'loan.token', - name: 'transfer', - authorization: [session.auth], - data: { - from: session.auth.actor, - to: 'loan.staking', - quantity: quantity, - memo: 'stake' - } - }] - }, { broadcast: true }); -} +```bash +proton action loan.token transfer '{"from":"myaccount","to":"loan.staking","quantity":"1000.0000 LOAN","memo":"stake"}' myaccount ``` ### Query Staking Rewards -```typescript -async function getStakingRewards(account: string): Promise { - const { rows } = await rpc.get_table_rows({ - code: 'loan.staking', - scope: 'loan.staking', - table: 'stakers', - lower_bound: account, - upper_bound: account, - limit: 1 - }); - - return rows[0]?.pending_rewards ?? '0.0000 LOAN'; -} +```bash +proton table loan.staking stakers --lower myaccount --upper myaccount ``` --- -## Health Monitoring - -### React Hook for Position Monitoring - -```typescript -import { useState, useEffect } from 'react'; - -interface PositionStatus { - position: UserPosition | null; - healthStatus: 'safe' | 'warning' | 'danger'; - loading: boolean; -} - -export function usePositionHealth(account: string): PositionStatus { - const [state, setState] = useState({ - position: null, - healthStatus: 'safe', - loading: true - }); - - useEffect(() => { - let mounted = true; - - async function fetchPosition() { - try { - const position = await getUserPosition(account); - - if (!mounted) return; - - let healthStatus: 'safe' | 'warning' | 'danger' = 'safe'; - if (position) { - if (position.health_factor < 1.0) { - healthStatus = 'danger'; - } else if (position.health_factor < 1.5) { - healthStatus = 'warning'; - } - } - - setState({ position, healthStatus, loading: false }); - } catch (error) { - if (mounted) { - setState(prev => ({ ...prev, loading: false })); - } - } - } - - fetchPosition(); - - // Poll every 30 seconds - const interval = setInterval(fetchPosition, 30000); +## Price Oracle Integration - return () => { - mounted = false; - clearInterval(interval); - }; - }, [account]); +### Get Asset Prices - return state; -} +```bash +# XPR price (feed index 3) +curl -s -X POST https://proton.greymass.com/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}' ``` -### Health Warning Component - -```tsx -interface HealthWarningProps { - healthFactor: number; -} +Known oracle feed indices: -export function HealthWarning({ healthFactor }: HealthWarningProps) { - if (healthFactor >= 1.5) return null; - - const isDanger = healthFactor < 1.0; - - return ( -
- - {isDanger - ? '⚠️ Position at Risk of Liquidation!' - : '⚠ Health Factor Low'} - -

- Health Factor: {healthFactor.toFixed(2)} - {isDanger - ? ' - Add collateral or repay debt immediately' - : ' - Consider adding collateral'} -

-
- ); -} -``` +| Feed | Asset | +|------|-------| +| 1 | BTC | +| 2 | ETH | +| 3 | XPR | +| 4 | METAL | +| 5 | USDC | +| 6 | USDT | --- -## Price Oracle Integration - -### Get Asset Prices - -```typescript -const ORACLE_FEEDS: Record = { - 'XPR': 1, - 'BTC': 4, - 'ETH': 5, - 'USDC': 6 -}; - -async function getAssetPrice(symbol: string): Promise { - const feedIndex = ORACLE_FEEDS[symbol]; - - if (!feedIndex) { - throw new Error(`No oracle feed for ${symbol}`); - } +## Whitelist Note - const { rows } = await rpc.get_table_rows({ - code: 'oracles', - scope: 'oracles', - table: 'data', - lower_bound: feedIndex, - upper_bound: feedIndex, - limit: 1 - }); +The LOAN protocol has a whitelist (`updatewl` action) that controls which accounts can interact. However, as of February 2026, the `mint` (supply) action appears to work for accounts not on the whitelist. The whitelist may only restrict certain operations like borrowing. - if (rows.length === 0) { - throw new Error(`Oracle data not found for ${symbol}`); - } +To check the whitelist: - // Price is stored as u64 with 4 decimals - return rows[0].value / 10000; -} -``` - -### Calculate Collateral Value - -```typescript -async function calculateCollateralValue( - supplied: Asset[] -): Promise { - let totalValue = 0; - - for (const asset of supplied) { - const [amount, symbol] = asset.split(' '); - const price = await getAssetPrice(symbol); - totalValue += parseFloat(amount) * price; - } - - return totalValue; -} +```bash +curl -s -X POST https://proton.greymass.com/v1/chain/get_table_rows \ + -H 'Content-Type: application/json' \ + -d '{"code":"lending.loan","scope":"lending.loan","table":"whitelist","limit":100,"json":true}' ``` --- @@ -560,37 +430,61 @@ async function calculateCollateralValue( ### Common Actions ```bash -# Supply collateral -proton action xtokens transfer '{"from":"alice","to":"lending","quantity":"1000.000000 XUSDC","memo":"supply"}' alice +# Enter markets (required before first supply) +proton action lending.loan entermarkets '{"account":"alice","markets":["4,LXPR","6,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 borrow '{"account":"alice","quantity":"500.0000 XPR"}' alice +proton action lending.loan borrow '{"account":"alice","quantity":"500.0000 XPR"}' alice # Repay -proton action xtokens transfer '{"from":"alice","to":"lending","quantity":"500.000000 XUSDC","memo":"repay"}' alice +proton action xtokens transfer '{"from":"alice","to":"lending.loan","quantity":"500.000000 XUSDC","memo":"repay"}' alice -# Withdraw -proton action lending withdraw '{"account":"alice","quantity":"1000.000000 XUSDC"}' alice +# Claim LOAN rewards +proton action lending.loan claimrewards '{"claimer":"alice","markets":["4,LXPR","6,LUSDC"]}' alice -# Check position -proton table lending positions -l alice -u alice +# Check shares +# Use RPC: code=lending.loan, scope=lending.loan, table=shares, lower_bound=alice, upper_bound=alice ``` ### Key Tables -| Contract | Table | Description | -|----------|-------|-------------| -| `lending` | `positions` | User supply/borrow positions | -| `lending` | `markets` | Market stats, APYs | -| `lending` | `config` | Protocol parameters | -| `loan.staking` | `stakers` | LOAN staking positions | -| `oracles` | `data` | Price feeds | - -### Important Parameters - -| Parameter | Description | -|-----------|-------------| -| Liquidation Threshold | Health factor below 1.0 | -| Close Factor | Max % of debt repayable per liquidation | -| Liquidation Bonus | Reward for liquidators | -| Reserve Factor | % of interest going to reserves | +| 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` | `positions` | `lending.loan` | User borrow positions, health factors | +| `lending.loan` | `whitelist` | `lending.loan` | Whitelisted accounts | +| `loan.token` | (standard token tables) | | LOAN governance token | +| `loan.staking` | `stakers` | `loan.staking` | LOAN staking positions | +| `oracles` | `data` | `oracles` | Price feeds | + +### RPC Endpoints + +Use these endpoints for querying tables (in order of reliability): + +1. `https://proton.greymass.com` (recommended) +2. `https://proton.eosusa.io` +3. `https://proton.protonuk.io` +4. `https://proton.eoscommunity.org` (occasionally unreliable) + +### 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 diff --git a/skill/metalx-dex.md b/skill/metalx-dex.md index 45bf063..8fa1bac 100644 --- a/skill/metalx-dex.md +++ b/skill/metalx-dex.md @@ -18,6 +18,29 @@ MetalX is the primary decentralized exchange on XPR Network - a peer-to-peer mar | Environment | API Base URL | RPC URL | |-------------|--------------|---------| | **Mainnet** | `https://dex.api.mainnet.metalx.com` | `https://rpc.api.mainnet.metalx.com` | + +### RPC Fallback Endpoints + +If the primary RPC is unavailable, use these alternatives for chain queries: + +1. `https://proton.greymass.com` (recommended) +2. `https://proton.eosusa.io` +3. `https://proton.protonuk.io` +4. `https://proton.eoscommunity.org` (occasionally unreliable) + +> ⚠️ **CRITICAL: DEX Token Deposits** +> +> When transferring tokens to the `dex` contract for order placement, the memo **MUST be an empty string** (`""`). +> Using any other memo (e.g., `"deposit"`) will result in tokens being **permanently stuck** in the contract with no way to recover them. +> The `withdrawall` action will NOT return tokens deposited with a wrong memo. +> +> ```bash +> # ✅ CORRECT — empty memo +> proton action eosio.token transfer '{"from":"myaccount","to":"dex","quantity":"1000.0000 XPR","memo":""}' myaccount +> +> # ❌ WRONG — tokens will be lost forever +> proton action eosio.token transfer '{"from":"myaccount","to":"dex","quantity":"1000.0000 XPR","memo":"deposit"}' myaccount +> ``` | **Testnet** | `https://dex.api.testnet.metalx.com` | `https://rpc.api.testnet.metalx.com` | ---