Pools
Create public liquidity pools on Elysium and run dynamic swap fees.
Use mlab pool to create public liquidity pools with dynamic swap fees on Elysium. Anyone can create a pool; its creator becomes the manager. Other users can add liquidity after the manager's first deposit.
Want to sell a token allocation without supplying a second asset? Use a bonding market instead.
Two parts work together:
| Part | What it does |
|---|---|
| Pool contract on Elysium | Holds the tokens, executes swaps and tracks LP shares. Swap fees stay in the pool for LPs. |
Fee controller in mlabd | Observes the pool's price movement and submits fee changes within the manager's fixed bounds. |
The pool uses constant-product pricing (x * y = k), the model used by Uniswap v2. It is a custom Market Lab contract, not a deployment of Uniswap v4 or its dynamic-fee hooks.
Creating a pool does not start dynamic fees. You authorize an operator and start mlab pool run separately. If that job stops, swaps and withdrawals still work. Swaps use the last applied fee; the manager can also change it directly.
First-Time Setup
Don't have a token yet? Create a fixed-supply test token to try the full workflow. Existing projects can use their own token instead.
- Choose and fund your pair. Hold both ERC-20 tokens on Elysium, plus native HYPE for gas. Wrap HYPE if your pair needs it. Creating a pool does not mint these tokens.
- Create the pool. Prepare the transaction, sign it with your wallet and confirm the new pool address.
- Add liquidity. Sign the token approvals and initial deposit. The pool can now serve swaps at its starting fee.
- Authorize an operator. Create its key, sign its approval and fund it with native HYPE.
- Start dynamic fees. Preview the policy, then start the daemon job.
To only provide liquidity, use an existing pool and follow step 3. You do not need to create an operator or run someone else's fee controller.
Create a Pool
Choose Your Tokens
For example, create a USDC/WHYPE pool on Elysium testnet (99801) using these deployed contracts:
| Token | Elysium contract address | Decimals |
|---|---|---|
| Bridged test USDC | 0x7D29d8047B905000459c0E80c34a26CeedcB47b2 | 6 |
| Example WHYPE wrapper | 0xA5b72796E5bf80443DC134F3c8a1cc97dcCC39B9 | 18 |
These are examples, not required assets. You can pair your own standard ERC-20 token with WHYPE, or use two other tokens deployed on Elysium. Creating a pool does not create its tokens. Copy token contract addresses from the project's deployment or Elysium explorer, not their symbols or addresses on another chain.
The USDC above is the bridge representation of Circle's HyperEVM testnet USDC, not native Circle-issued USDC on Elysium. It has no real dollar value. To obtain it, use the Elysium bridge from HyperEVM testnet; the source token there is 0x2B3370eE501B4a559b57D449569354196457D8Ab. See token bridging for the address mapping.
Prepare the Transaction
Connect your wallet to Elysium testnet and keep native HYPE for gas. The faucet provides test HYPE.
Replace the example --account address below with your wallet's public address. Keep the two token addresses to use the example pair, or replace them with your own Elysium token contracts.
mlab pool create \
--account 0x0361c685F6bA3Cd66e9e8fE6095C91Ab0b4e5b8E \
--token-a 0x7D29d8047B905000459c0E80c34a26CeedcB47b2 \
--token-b 0xA5b72796E5bf80443DC134F3c8a1cc97dcCC39B9 \
--fee-bps 30 --min-fee-bps 5 --max-fee-bps 100 \
--output jsonStarting fee: 0.30%. Fixed bounds: 0.05% to 1%.
Wallet actions prepare unsigned transactions. Submit them with your wallet or signing tool. They do not sign, broadcast or fund the pool for you.
Sign and Submit
The creation result contains transaction.to, data, value, from and chainId. Sign with the wallet matching from on Elysium testnet.
For example, with Foundry, use your local keystore name in place of elysium. If needed, import your wallet locally with cast wallet import elysium --interactive. Replace the three TRANSACTION_... fields with the returned JSON values:
cast send TRANSACTION_TO --data TRANSACTION_DATA --value TRANSACTION_VALUE \
--rpc-url https://testnet-rpc.elysium.kinetiq.xyz --account elysiumThis command signs and broadcasts. Wait for a successful receipt. For a deposit or swap that returns multiple transactions, submit each entry's transaction in order and wait for success before continuing.
After signing and submitting, replace CREATION_TX_HASH with your transaction hash. Use the same wallet address:
mlab pool created CREATION_TX_HASH \
--account 0x0361c685F6bA3Cd66e9e8fE6095C91Ab0b4e5b8E --output jsonReturns pending, then confirmed with the pool address after two successor blocks. This is not guaranteed finality. Resume with the same hash if interrupted; another creation transaction creates another pool.
Add Liquidity
Replace POOL_ADDRESS with the pool returned by your creation transaction. Check mlab pool inspect POOL_ADDRESS before depositing: amounts follow token0/token1 address order, not the order you typed during creation.
For the exact example pair above, token0 is USDC and token1 is WHYPE. This prepares 1 test USDC + 0.01 WHYPE; replace the example wallet with your own:
mlab pool deposit POOL_ADDRESS \
--account 0x0361c685F6bA3Cd66e9e8fE6095C91Ab0b4e5b8E \
--amount0 1000000 --amount1 10000000000000000 \
--slippage-bps 50 --output jsonAmounts use integer base units: 1 USDC with 6 decimals is 1000000; 0.01 WHYPE with 18 decimals is 10000000000000000. Choose your own deposit sizes. Changing either token contract can change the token order and decimals.
Use Sign and Submit for the returned approvals and deposit. Wait for each successful receipt; do not continue if a transaction fails. See Pool JSON for the output fields.
Only the manager can make the first deposit, which sets the starting price. The example ratio sets 1 WHYPE = 100 test USDC; it is an illustration, not a market-price quote. LP shares go to the depositing wallet; swap fees stay in the reserves for LPs.
Default slippage: 0.50%. Expiry: 20 minutes. Preparing again skips sufficient approvals. Check any already-sent deposit's receipt before retrying.
Authorize a Fee Operator
Create a dedicated operator on the machine running mlabd:
mlab pool operator create
mlab pool operator addressThe key is saved in $MLAB_HOME/credentials/pool-operator.key (default ~/.market-lab), with 0600 permissions, not encryption. Only the public address is printed.
Replace OPERATOR_ADDRESS with the public address printed by mlab pool operator address:
mlab pool authorize POOL_ADDRESS \
--account 0x0361c685F6bA3Cd66e9e8fE6095C91Ab0b4e5b8E \
--operator OPERATOR_ADDRESS --output jsonSign and submit the returned transaction with the pool manager wallet. Then send native HYPE to the operator address for fee-update gas. The operator can change swap fees, not withdraw other users' LP assets. No fee job has started yet.
Start Dynamic Fees
Once the pool has liquidity and the operator is approved and funded, preview and start:
mlab pool run POOL_ADDRESS --base-fee-bps 30 --dry-run
mlab pool run POOL_ADDRESS --base-fee-bps 30--dry-run reads and previews only. The second command asks for confirmation and starts the job in mlabd; use --yes for unattended execution. Your daemon must keep running to update fees.
Check its status and recent fee decisions, or stop it using the returned job ID:
mlab pool jobs
mlab pool logs "JOB_ID" --limit 20
mlab pool stop "JOB_ID"How the Fee Changes
The controller samples the pool's reserve ratio, measures recent price movement and calculates:
fee = base fee + sensitivity × measured volatilityWith a 30 bps base and sensitivity 1, a measured volatility of 20 bps gives a 50 bps (0.50%) fee, if the pool's bounds allow it. As volatility falls, the target returns toward the base. This is periodic adjustment, not a new calculation on every swap, and does not guarantee higher LP returns.
Volatility is the RMS log return of the pool's reserve ratio, in bps per observation, not an external price feed. The target is rounded and clamped to the contract's fixed bounds. The controller waits for the cooldown and minimum-change threshold before submitting another update.
| Flag | Default |
|---|---|
--base-fee-bps | 30 |
--sensitivity | 1 |
--window | 20 observations |
--interval | 30 seconds |
--cooldown | 300 seconds |
--min-change-bps | 2 |
--max-tx-gas-hype | 0.0001 HYPE per transaction |
Jobs continue after the CLI exits and resume after daemon restarts. One running fee job is allowed per pool.
Stopping prevents new fee decisions; a transaction already prepared may still settle. It does not close the pool or block swaps and withdrawals. The last applied swap fee stays in effect until the manager or an authorized operator changes it.
To revoke permission, prepare pool authorize with --operator 0x0000000000000000000000000000000000000000 and sign it with the manager wallet.
Use --output json or --output jsonl for structured results. Operator commands print an address only.
Find and Inspect Pools
mlab pool list --limit 10 --offset 0
mlab pool inspect POOL_ADDRESSThe list shows factory pools newest first, up to 50 per page. Replace POOL_ADDRESS with an address from it. Inspection shows the token pair, reserves, decimals, LP supply, fee bounds and operator permissions. No wallet is required.
LP tokens share the name MarketLab Pool Share (MLP). Identify pools by address and token pair; custom names are not supported.
Swap or Withdraw
Read your LP shares, withdrawable amounts and wallet balances:
mlab pool position POOL_ADDRESS \
--account 0x0361c685F6bA3Cd66e9e8fE6095C91Ab0b4e5b8E --output jsonFor the example pair, swap 0.1 test USDC. For withdrawals, replace LP_SHARES_RAW with the shares.raw amount from your position result. Use your own pool and wallet:
mlab pool swap POOL_ADDRESS \
--account 0x0361c685F6bA3Cd66e9e8fE6095C91Ab0b4e5b8E \
--token-in 0x7D29d8047B905000459c0E80c34a26CeedcB47b2 \
--amount-in 100000 --output json
mlab pool withdraw POOL_ADDRESS \
--account 0x0361c685F6bA3Cd66e9e8fE6095C91Ab0b4e5b8E \
--shares LP_SHARES_RAW --output jsonBoth return a quote and unsigned transactions for your wallet. Swaps include approvals when needed; withdrawals burn your LP shares and return both assets. Use --slippage-bps to change the default 50 bps; plans expire after 20 minutes.
Get Wrapped HYPE
Skip this section if you already hold your chosen wrapper's tokens. Native HYPE pays gas; WHYPE is the ERC-20 asset deposited into a pool. Get test HYPE from the Elysium faucet.
There is no mlab pool wrap command. The commands below use Foundry and a local keystore named elysium. Use an existing keystore, or import a wallet locally with cast wallet import elysium --interactive.
Wrap 0.01 HYPE in the example contract:
cast send 0xA5b72796E5bf80443DC134F3c8a1cc97dcCC39B9 "deposit()" \
--value 10000000000000000 \
--rpc-url https://testnet-rpc.elysium.kinetiq.xyz \
--account elysiumThis command signs and broadcasts, returning 0.01 WHYPE to that wallet. Keep some native HYPE unwrapped for gas.
Deploy Your Own Wrapper
The listed WHYPE address is Market Lab's example deployment, not a required or canonical Elysium wrapper. You can deploy the same WrappedHype contract yourself. From contracts/pools in the Market Lab repository, with Foundry installed:
make deps
forge create src/WrappedHype.sol:WrappedHype \
--rpc-url https://testnet-rpc.elysium.kinetiq.xyz \
--account elysium --broadcastUse the printed Deployed to address instead of the example WHYPE address when wrapping and creating your pool. Each wrapper has separate balances; they are not interchangeable just because both use the symbol WHYPE.
Create a Test Token
mlab token create prepares a fixed-supply ERC-20 deployment on Elysium testnet. You choose the name, symbol and supply. All tokens go to the wallet passed as --account; there is no later minting. Supply is entered in whole-token units, not base units. Tokens use 18 decimals.
mlab token create \
--name "Example Token" \
--symbol EXAMPLE \
--supply 1000000 \
--account 0x0361c685F6bA3Cd66e9e8fE6095C91Ab0b4e5b8E \
--output jsonReplace the example account with your wallet's public address. Hold native HYPE for deployment gas. The result contains an unsigned contract-creation transaction: your wallet signs and sends it. There is no to address because this deploys a new contract. The CLI never asks for a private key or broadcasts this plan.
After signing, use mlab token created <transaction-hash> --account <your-wallet-address> --output json. It reports pending until three confirmations, then returns the verified token address, metadata and supply. Receipt checks reject deployments from another wallet or contracts with different runtime bytecode. Both commands accept --rpc-url for another endpoint on the same chain.
Use the returned token address with mlab pool create. For example, from a supply of 1,000,000 tokens you could deposit 200,000 tokens alongside 10 WHYPE, keeping 800,000 tokens in your wallet. This transfers existing tokens into the pool and mints LP shares; it does not create more of your token. Then authorize the fee operator and start dynamic fees as above.
Cloud's temporary Launch a test token entry provides the same steps through your browser wallet, including pool creation, initial liquidity and automatic-fee setup. Tokens and existing pools remain onchain if the launcher is later disabled.