Simulation & Benchmark environment for Solana's Proprietary AMMs. The setup relies on Litesvm for local, consistent and expedited execution.
Supported Prop AMMs:
- HumidiFi (swap v1/v2/v3)
- SolFiV2
- ObricV2
- ZeroFi
- TesseraV
- GoonFi
- BisonFi
Swaps and benchmarks can be done through direct offchain calls, or alternatively through CPI calls from an onchain router - magnus-router. Additionally, Router CPI calls can be spoofed as one of the following aggregators: Jupiter, Okx, Dflow or Titan. Some Prop AMMs tend to provide different — preferential — rates for whitelisted set of addresses. Depending on who's the source of CPI, you might get different rates.
The swaps and benchmarks can be done with the local static accounts/programs stored at ./cfg/ or with the current live ones by fetching them on-the-go. By default all swaps & benchmark simulations are done with real-time accounts/programs. The markets for each Prop AMM are specified in ./cfg/setup.toml.
Figure 1: Exchange rates for WSOL -> USDC at discrete slot 401101387, with spoofed CPI call as Jupiter.
Possible modes of execution include:
- single - Run a single swap route across one or more Prop AMMs with specified weights. The route can go through an arbitrary combination of Prop AMMs.
- multi - Execute swaps across nested Prop AMM routes. Each inner list represents a single route, each route possibly going through multiple Prop AMMs.
- direct - Execute a direct, non-cpi swap with some of the Prop AMMs.
- benchmark - Benchmark swaps for any of the implemented Prop AMMs by specifying, optionally, spoofing, the src/dst tokens and range size. Benchmark data can be visualised with plot.py.
- fetch-accounts - Fetch accounts for specified PMMs via RPC and save them locally (presumably for later usage).
- fetch-programs - Fetch programs for specified PMMs via RPC and save them locally (presumably for later usage).
Example benchmarked exchange rate & CU usage:
| Prop AMM | Exchange Rate | Compute Units |
|---|---|---|
| HumidiFi | ![]() |
![]() |
| HumidiFi Swap V2 | ![]() |
![]() |
| HumidiFi Swap V3 | ![]() |
![]() |
| SolFi V2 | ![]() |
![]() |
| BisonFi | ![]() |
![]() |
| Tessera | ![]() |
![]() |
| Obric V2 | ![]() |
![]() |
| GoonFi | ![]() |
![]() |
To explicitly specify the market the operation should execute against, for a particular prop AMM, suffix the Prop AMM's name with a substring of the address of the market. If no market is specified explicitly, we'll default to the first one defined in ./cfg/setup.toml.
Build the project
cargo build --release
./target/release/pmm-sim single --amount-in=15000 --pmms=humidifi --weights=100 \
--src-token=USDC --dst-token=WSOL
./target/release/pmm-sim single --amount-in=15345 --pmms=goonfi --weights=100 --src-token=usdc --dst-token=wsol --jit-accounts=false --spoof=jupiter
Swap 69K USDC for WSOL using HumidiFi (swap-v2 instruction on the Fk market) and BisonFi, in one route, split 25%,75% accordingly.
./target/release/pmm-sim single --amount-in=69000 --pmms=humidifi-swap-v2_Fk,bisonfi --weights=25,75 \
--src-token=USDC --dst-token=WSOL
Swap 375 WSOL for USDC using Tessera and SolFiV2's 65Z market, in one route, split evenly - 187,5 WSOL per Prop AMM, spoofed as DFlow.
./target/release/pmm-sim single --spoof=dflow --amount-in=375 --pmms=tessera,solfi-v2_65Z \
--weights=50,50 --src-token=WSOL --dst-token=USDC
Swap 100 WSOL for USDC using SolFiV2, HumidiFi, and Tessera, in one route, split 33,33,34 WSOL per Prop AMM.
./target/release/pmm-sim single --amount-in=100 --pmms=solfi-v2,humidifi,tessera \
--weights=33,33,34 \
--src-token=WSOL --dst-token=USDC \
--jit-accounts=false --jit-programs=false
./target/release/pmm-sim single --amount-in=10000 --pmms=obric-v2 --weights=100 \
--src-token=USDC --dst-token=USDT
Swap 103 WSOL for USDC in a multi-route swap, 100 WSOL via HumidiFi and SolFiV2 (split 92%/8%) in one route, and 3 WSOL via Tessera in another route.
./target/release/pmm-sim multi --amount-in=100,3 --pmms="[[humidifi,solfi-v2],[tessera]]" \
--weights="[[92,8],[100]]"
Execute two routes, the first swapping 150,000 USDC for WSOL using HumidiFi and SolFiV2 (split 25%/75%), the second swapping 1000 USDC for WSOL using BisonFi.
RUST_LOG=debug ./target/release/pmm-sim multi --amount-in=150000,1000 --pmms="[[humidifi,solfi-v2],[bisonfi]]" --weights="[[25,75],[100]]" --src-token=USDC --dst-token=WSOL
RUST_LOG=debug ./target/release/pmm-sim direct --pmm=humidifi-swap-v3 --amount-in=350 --src-token=WSOL --dst-token=USDC
Execute a direct offchain call towards BisonFi's FC9 market, swapping 35K USDT for WSOL.
./target/release/pmm-sim direct --pmm=bisonfi_FC9 --amount-in=35000 --src-token=USDT --dst-token=WSOL
Benchmark swaps on HumidiFi,Tessera,SolFiV2 and GoonFi, from 1 to 4000 WSOL to USDC, in increments of 1 WSOL, using the accounts & programs from ./cfg. The results are saved at ./datasets.
./target/release/pmm-sim benchmark --pmms=humidifi,tessera,solfi-v2,goonfi \
--range=1.0,4000.0,1.0 --src-token=wsol --dst-token=usdc \
--jit-accounts=false --jit-programs=false
Benchmark swaps on Tessera and SolFiV2, from 1 to 250 WSOL, in increments of 0.01 WSOL. The results are saved at ./datasets.
./target/release/pmm-sim benchmark --pmms=tessera,solfi-v2 \
--range=1.0,250.0,0.01 --src-token=wsol --dst-token=usdc
Benchmark swaps (USDC->WSOL) on HumidiFi and SolFiV2, from 10K to 100K USDC, in increments of 100 USDC. The results are saved at ./datasets.
./target/release/pmm-sim benchmark --pmms=humidifi,solfi-v2 \
--range=10000,100000,100 --src-token=usdc --dst-token=wsol
Generated benchmark data can be plotted through ./scripts/plot.py, like so:
./scripts/plot.py ./datasets/389141713*
./target/release/pmm-sim fetch-accounts --pmms=bisonfi,humidifi-swap-v3
./target/release/pmm-sim fetch-accounts --pmms=tessera,solfi-v2
./target/release/pmm-sim fetch-programs
./target/release/pmm-sim fetch-programs --pmms=bisonfi,tessera
All datasets are saved as parquet and available at datasets. To peek at the data through cli:
duckdb -csv \
-c "SELECT * FROM 'datasets/datasets/401101387_magnus_bisonfi_51FQwjrvo8J8zXUaKyAznJ5NYpoiTCuqAqCu3HAMB9NZ_20260218-205826.parquet'" \
| column -t -s ,Accounts are by default loaded (saved) from (at) cfg/accounts. Tweaking the source/destination is possible via --accounts-path or ACCOUNTS_PATH env variable.
Programs are by default loaded (saved) from (at) cfg/programs. Tweaking the source/destination is possible via --programs-path or PROGRAMS_PATH env variable.
Datasets are by default loaded (saved) from (at) datasets. Tweaking the source/destination is possible via --datasets-path or DATASETS_PATH env variable.
The supported tokens are defined in cfg/setup.toml under [[tokens]] entries. The markets are defined in cfg/setup.toml under the corresponding PMM.
Check out the CLI subcommands for additional clues (i.e pmm-sim single --help)
$ pmm-sim --help
Simulation environment for Solana's Proprietary AMMs.
Simulate swaps and Benchmark performance across *any* of the major Solana Prop AMMs.
Usage: pmm-sim <COMMAND>
Commands:
direct Initialize an environment for a single PMM and execute a direct swap.
router-single Run a single swap route across one or more Prop AMMs with specified weights.
router-multi Execute multiple swap routes across nested Prop AMM routes. Each inner list represents a single route, each route possibly going through multiple Prop AMMs.
benchmark Benchmark swaps for any one of the implemented Prop AMMs by specifying, optionally, the accounts, src/dst tokens and step size
fetch-accounts Fetch accounts from the specified Pmms via RPC and save them locally (presumably for later usage).
fetch-programs Fetch programs from the specified Pmms via RPC and save them locally (presumably for later usage).
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
License: MIT















