A decentralized exchange (DEX) built on Plasma testnet featuring both stable and volatile AMM pools, inspired by Solidly's design with Uniswap V2-like interface.
- Dual AMM Types: Support for both stable (low-slippage) and volatile (standard xy=k) pools
- Uniswap V2 Compatible: Standard interface for easy frontend integration
- Fee-on-Transfer Support: Works with tokens that charge fees on transfers
- Native Token Integration: Full XPL/WXPL support with automatic wrapping/unwrapping
- PairFactoryUpgradeable: 0x71a870D1c935C2146b87644DF3B5316e8756aE18
- RouterV2 (Main): 0xD70962bd7C6B3567a8c893b55a8aBC1E151759f3
- GlobalRouter: 0xC7E4BCC695a9788fd0f952250cA058273BE7F6A3
- TradeHelper: 0xf2e70f25a712B2FEE0B76d5728a620707AF5D42c
- WXPL: 0x6100e367285b01f48d07953803a2d8dca5d19873
- USDT0: 0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb
- USDe: 0x5d3a1ff2b6bab83b63cd9ad0787074081a52ef34
- USDai: 0x0a1a1a107e45b7ced86833863f482bc5f4ed82ef
- WETH: 0x9895d81bb462a195b4922ed7de0e3acd007c32cb
- weETH: 0xa3d68b74bf0528fdd07263c60d6488749044914b
- xUSD: 0x6eaf19b2fc24552925db245f9ff613157a7dbb4c
- tcUSDT0: 0xa9c251f8304b1b3fc2b9e8fcae78d94eff82ac66
Note: RouterV2 is the primary contract for frontend integration. It handles all liquidity and swap operations.
-
WXPL/USDT0 (Volatile)
-
USDe/USDT0 (Stable)
-
USDe/USDT0 (Volatile)
-
USDai/USDT0 (Stable)
-
WETH/weETH (Volatile)
-
WXPL/WETH (Volatile)
-
xUSD/tcUSDT0 (Stable)
The ve(3,3) system is live on Plasma mainnet. Key governance contracts (synced to deployments/mainnet/state.json):
- Lithos Token (LITH): 0xAbB48792A3161E81B47cA084c0b7A22a50324A44
- VotingEscrow: 0x2Eff716Caa7F9EB441861340998B0952AF056686
- Voter: 0x2AF460a511849A7aA37Ac964074475b0E6249c69
- GaugeFactory: 0xA0Ce83fd2003e7C7F06E01E917a3E57fceee41A0
- BribeFactory: 0x9CfC6d1C1309457160A4BcAB3F71A16a09336788
- PermissionsRegistry: 0x97A5AD8B3d1c16565d9EC94A95cBE2D61d0a4ac7
- RewardsDistributor: 0x0E68ac23a0aFEF4f018d647C3E58d59c55065308
- Minter (proxy): 0x3bE9e60902D5840306d3Eb45A29015B7EC3d10a6
- Minter Implementation: 0x469aCB68BeCd95EE43672C6FA0963aA4C8421f95
- VeArtProxy (proxy): 0xBd24Ee2688d1b564E2f6Afa51b44aBF692877193
- VeArtProxy Implementation: 0xdE68b70dCB2c5A8fd1802e18b46037666b9271dC
- Timelock: 0x16F5cE1EB8a0EE5816262f2c5bddF4dd55a9FCF4
- Algebra Factory: 0x10253594A832f967994b44f33411940533302ACb
- GaugeFactoryV2_CL: 0x60072e4939985737AF4fD75403D7cfBCf4468d99
- Pool Init Code Hash:
0x62441ebe4e4315cf3d49d5957f94d66b253dbabe7006f34ad7f70947e60bf15c
- SwapRouter (IRouterV3): 0x3012E9049d05B4B5369D690114D5A5861EbB85cb
- Quoter: 0x03f8B4b140249Dc7B2503C928E7258CCe1d91F1A
- QuoterV2: 0xa77aD9f635a3FB3bCCC5E6d1A87cB269746Aba17
- TickLens: 0x13fcE0acbe6Fb11641ab753212550574CaD31415
- NonfungiblePositionManager: 0x69D57B9D705eaD73a5d2f2476C30c55bD755cc2F
- Algebra Interface Multicall: 0xB4F9b6b019E75CBe51af4425b2Fc12797e2Ee2a1
- WXPL/USDT Hypervisor: 0x19F4eBc0a1744b93A355C2320899276aE7F79Ee5
- WETH/USDT Hypervisor: 0xca8759814695516C34168BBedd86290964D37adA
- PairAPI (proxy): 0x833Cb061EB4c176FC0Fc00196E9Fa4A5473D84cC
- PairAPI Implementation: 0x58f187b860A796F4da2BB777a065d5c557404008
- RewardAPI (proxy): 0x6BF5ad21FFeAf0f9B69f4A29B627801EbF8Fff37
- RewardAPI Implementation: 0xF90742f9EF92f7CB36c9D3F16AE3180Ec1dE1833
- veNFTAPI (proxy): 0x950830f2eB571cd65cb02f7aB8c50E1E95eFc89f
- veNFTAPI Implementation: 0x53FfBAD767def2021fEfC0B26D781Edb27fe5a13
- PairFactory: 0xa74848bAC41c4B1E6d1CFA6615Afb8893805075A
- RouterV2 (Main): 0xb7Be9aB86d1A18c0425C3f6ABbbD58d0Ef19f1a9
- GlobalRouter: 0x88C19a127aa22C7826546F34E63FE0e8995c88d0
- TradeHelper: 0xf30E5cD4E25603fd2262Aa00bf78D1A4b9AEDeEF
- VotingEscrow: 0x516C42d4BcF32531Cb7cf5Eb89Bb8870A4a60011
- VeArtProxyUpgradeable: 0xbc1e64DBdF71AC6A7Df0FD656E2D4F5A628faf7F
- RewardsDistributor: 0xEa132AE719aa6280a0f72AF3E4ee44Dd6888B1Ec
- PermissionsRegistry: 0xEcBb3aE0e0Cb7D5AdFa6F88c366Bb0D44Aba986A
- VoterV3: 0x5C1f4391ad20475D76f4738d3faAF3B170A06919
- GaugeFactoryV2: 0x4c7410dEd27c8DE462A288801F23ec08977f0F62
- BribeFactoryV3: 0x7f38E9a5cA4F8279eCEb0ab02eA5291F23e350b8
- MinterUpgradeable: 0x4f00b43CD851ac5d5599834f71434f245A92D973
- LITH: 0x3a6a2309Bc05b9798CF46699Bba9F6536039B72D
- WXPL: 0x3576E9157cF2e1dB071b3587dEbBFb67D9e0962d
- TEST: 0xb89cdFf170b45797BF93536773113861EBEABAfa (Test token for bribes contract)
Note: RouterV2 is the primary contract for frontend integration. It handles all liquidity and swap operations.
LITHOS/WXPL Pair
- Pair: 0xa9a6b6A0F249e90C999e96010554814907B8f9D7
- Gauge: 0xa5471946F66c8eaFFa101feF465B912C0255D1f8
- Internal Bribe: 0x17a7be2A7Ea0dcea799EA6f8d37FEE33Ff52636A
- External Bribe: 0x2E3a318e5289f5C6f94A822c779d91D58c907fb4
- Pool Type: Volatile
Create trading pairs through the PairFactory contract:
// Create a new trading pair
function createPair(
address tokenA,
address tokenB,
bool stable // true for stable pool, false for volatile pool
) external returns (address pair)JavaScript Example:
const pairFactory = new ethers.Contract(
PAIR_FACTORY_ADDRESS,
pairFactoryAbi,
signer
);
// Create volatile pool (standard AMM)
const volatilePair = await pairFactory.createPair(tokenA, tokenB, false);
// Create stable pool (low slippage for similar assets)
const stablePair = await pairFactory.createPair(USDC, USDT, true);All liquidity operations are handled through RouterV2:
function addLiquidity(
address tokenA,
address tokenB,
bool stable,
uint amountADesired,
uint amountBDesired,
uint amountAMin, // Slippage protection
uint amountBMin, // Slippage protection
address to, // LP token recipient
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity)function addLiquidityETH(
address token,
bool stable,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity)function removeLiquidity(
address tokenA,
address tokenB,
bool stable,
uint liquidity, // LP tokens to burn
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB)function removeLiquidityETH(
address token,
bool stable,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external returns (uint amountToken, uint amountETH)JavaScript Example:
const router = new ethers.Contract(ROUTER_V2_ADDRESS, routerV2Abi, signer);
// Add liquidity to USDC/USDT stable pool
await tokenA.approve(ROUTER_V2_ADDRESS, amountADesired);
await tokenB.approve(ROUTER_V2_ADDRESS, amountBDesired);
const tx = await router.addLiquidity(
USDC_ADDRESS,
USDT_ADDRESS,
true, // stable pool
amountADesired,
amountBDesired,
amountAMin,
amountBMin,
userAddress,
deadline
);
// Add liquidity with XPL
await token.approve(ROUTER_V2_ADDRESS, amountTokenDesired);
const tx2 = await router.addLiquidityETH(
TOKEN_ADDRESS,
false, // volatile pool
amountTokenDesired,
amountTokenMin,
amountETHMin,
userAddress,
deadline,
{ value: ethers.utils.parseEther("1.0") }
);// Multi-hop swap with custom routing
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
route[] calldata routes, // Custom routing path
address to,
uint deadline
) external returns (uint[] memory amounts)
// Simple single-hop swap
function swapExactTokensForTokensSimple(
uint amountIn,
uint amountOutMin,
address tokenFrom,
address tokenTo,
bool stable, // Which pool type to use
address to,
uint deadline
) external returns (uint[] memory amounts)// XPL -> Token
function swapExactETHForTokens(
uint amountOutMin,
route[] calldata routes,
address to,
uint deadline
) external payable returns (uint[] memory amounts)
// Token -> XPL
function swapExactTokensForETH(
uint amountIn,
uint amountOutMin,
route[] calldata routes,
address to,
uint deadline
) external returns (uint[] memory amounts)struct route {
address from; // Input token
address to; // Output token
bool stable; // Pool type (true = stable, false = volatile)
}JavaScript Example (GlobalRouter):
const globalRouter = new ethers.Contract(
GLOBAL_ROUTER_ADDRESS,
globalRouterAbi,
signer
);
// Simple swap: USDC -> USDT (stable pool)
await usdc.approve(GLOBAL_ROUTER_ADDRESS, amountIn);
const tx = await globalRouter.swapExactTokensForTokens(
amountIn,
amountOutMin,
[{ from: USDC_ADDRESS, to: USDT_ADDRESS, stable: true }],
userAddress,
deadline,
true // _type = true for V2 pools, false for V3 pools
);
// Multi-hop swap: TokenA -> TokenB -> TokenC
const routes = [
{ from: TOKEN_A, to: TOKEN_B, stable: false },
{ from: TOKEN_B, to: TOKEN_C, stable: true },
];
await tokenA.approve(GLOBAL_ROUTER_ADDRESS, amountIn);
const tx2 = await globalRouter.swapExactTokensForTokens(
amountIn,
amountOutMin,
routes,
userAddress,
deadline,
true // use V2 pools
);
// Get swap preview using GlobalRouter
const [amount, isStablePool] = await globalRouter.getAmountOut(
amountIn,
TOKEN_A,
TOKEN_B
);Use TradeHelper for price calculations without executing trades:
// Get best rate between stable and volatile pools
function getAmountOut(
uint amountIn,
address tokenIn,
address tokenOut
) external view returns (uint amount, bool stable)
// Calculate multi-hop swap output
function getAmountsOut(
uint amountIn,
route[] memory routes
) external view returns (uint[] memory amounts)
// Quote liquidity addition
function quoteAddLiquidity(
address tokenA,
address tokenB,
bool stable,
uint amountADesired,
uint amountBDesired
) external view returns (uint amountA, uint amountB, uint liquidity)For tokens that charge fees on transfers, use the SupportingFeeOnTransferTokens variants:
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
route[] calldata routes,
address to,
uint deadline
) external
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
route[] calldata routes,
address to,
uint deadline
) external payable- Use Case: Assets with similar values (USDC/USDT, ETH/stETH)
- Algorithm: Curve-like stable swap math
- Benefits: Lower slippage, better rates for similar assets
- Fees: 0.04% (4 basis points)
- Use Case: Standard trading pairs with different values
- Algorithm: Uniswap V2 xy=k formula
- Benefits: Standard AMM behavior, suitable for all assets
- Fees: 0.18% (18 basis points)
- Import RouterV2 ABI and connect to
0xb7Be9aB86d1A18c0425C3f6ABbbD58d0Ef19f1a9 - Implement token approval flows before liquidity/swap operations
- Add slippage tolerance settings (recommend 0.5% for stable, 2% for volatile)
- Implement deadline parameter (recommend current timestamp + 20 minutes)
- Handle both stable and volatile pool routing
- Add support for XPL (native token) operations via
*ETHfunctions - Implement price impact warnings for large trades
- Add liquidity preview using
quoteAddLiquidity
Lock LITHOS tokens to receive veNFTs with voting power and revenue sharing rights:
// Create a new lock position
function create_lock(uint256 _value, uint256 _lock_duration) external returns (uint256)
// Create lock for another address
function create_lock_for(uint256 _value, uint256 _lock_duration, address _to) external returns (uint256)// Add more tokens to existing lock
function increase_amount(uint256 _tokenId, uint256 _value) external
// Extend lock duration
function increase_unlock_time(uint256 _tokenId, uint256 _lock_duration) external
// Withdraw after lock expires
function withdraw(uint256 _tokenId) external// Get voting power of NFT
function balanceOfNFT(uint256 _tokenId) external view returns (uint256)
// Transfer veNFT (standard ERC-721)
function transferFrom(address _from, address _to, uint256 _tokenId) external
// Merge two NFTs into one
function merge(uint256 _from, uint256 _to) external
// Split NFT into multiple (specify percentages)
function split(uint256[] memory amounts, uint256 _tokenId) externalJavaScript Example:
const votingEscrow = new ethers.Contract(
VOTING_ESCROW_ADDRESS,
votingEscrowAbi,
signer
);
// Approve LITHOS tokens
await lithos.approve(VOTING_ESCROW_ADDRESS, lockAmount);
// Create 1 year lock
const lockDuration = 365 * 24 * 60 * 60; // 1 year in seconds
const tx = await votingEscrow.create_lock(lockAmount, lockDuration);
// Get veNFT ID from event
const receipt = await tx.wait();
const tokenId = receipt.events.find((e) => e.event === "Transfer").args.tokenId;
// Check voting power
const votingPower = await votingEscrow.balanceOfNFT(tokenId);
// Increase lock amount
await lithos.approve(VOTING_ESCROW_ADDRESS, additionalAmount);
await votingEscrow.increase_amount(tokenId, additionalAmount);
// Transfer veNFT
await votingEscrow.transferFrom(userAddress, recipientAddress, tokenId);Lock Parameters:
- Min Duration: 1 week
- Max Duration: 2 years (104 weeks)
- Voting Power: Linear decay over time
- Revenue Sharing: Proportional to voting power
- Import GlobalRouter ABI and connect to
0x88C19a127aa22C7826546F34E63FE0e8995c88d0 - Implement token approval flows before liquidity/swap operations
- Add slippage tolerance settings (recommend 0.5% for stable, 2% for volatile)
- Implement deadline parameter (recommend current timestamp + 20 minutes)
- Handle both stable and volatile pool routing
- Add support for XPL (native token) operations via
*ETHfunctions - Implement price impact warnings for large trades
- Add liquidity preview using
quoteAddLiquidity
- Import VotingEscrow ABI and connect to
0x516C42d4BcF32531Cb7cf5Eb89Bb8870A4a60011 - Import Lithos token ABI and connect to
0x3a6a2309Bc05b9798CF46699Bba9F6536039B72D - Implement LITHOS token approval for locking operations
- Add lock duration selector (1 week to 2 years)
- Display voting power decay over time
- Implement veNFT transfer functionality (ERC-721 standard)
- Add merge/split NFT features for advanced users
- Show lock expiration dates and withdrawal eligibility
Common revert reasons:
BaseV1Router: EXPIRED- Transaction deadline passedBaseV1Router: INSUFFICIENT_OUTPUT_AMOUNT- Slippage tolerance exceededBaseV1Router: INSUFFICIENT_A_AMOUNT- Minimum amount not metBaseV1Router: INVALID_PATH- Routing path invalid (check WXPL address)Pair: INSUFFICIENT_LIQUIDITY- Pool has no liquidity
Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
Foundry consists of:
- Forge: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- Cast: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- Anvil: Local Ethereum node, akin to Ganache, Hardhat Network.
- Chisel: Fast, utilitarian, and verbose solidity REPL.
$ forge build$ forge test$ forge fmt$ forge snapshot$ anvil$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>$ cast <subcommand>$ forge --help
$ anvil --help
$ cast --helpDeploy the subgraph to index blockchain data using Goldsky CLI:
-
Install Goldsky CLI (if not already installed):
npm install -g @goldsky/cli
-
Login to Goldsky (if not already logged in):
goldsky login
Use the shared API key provided by the team.
-
Navigate to subgraph directory:
cd subgraph -
Generate types from schema:
yarn codegen
-
Build the subgraph:
yarn build
-
Deploy to Goldsky:
goldsky subgraph deploy <subgraph-name>/<version> --path .
For mainnet:
cd subgraph
yarn codegen
yarn build
goldsky subgraph deploy lithos-subgraph-mainnet/v1.0.0 --path .List deployed subgraphs:
goldsky subgraph listGet subgraph info:
goldsky subgraph info <subgraph-name>/<version>Delete a subgraph:
goldsky subgraph delete <subgraph-name>/<version>Note: Ensure your
subgraph.yamlmanifest has the correct contract addresses from your deployment state file before deploying.