A novel DEX protocol implementing sharded liquidity pools with dynamic fee optimisation, TPS-driven auto-scaling, and an integrated arbitrage rebalancer. Live on RiseChain Testnet.
Traditional AMMs force every trade — regardless of size — through one enormous pool.
SAMM inverts this by sharding each token pair into multiple pools of increasing size:
| Tier | TVL Target | Best For |
|---|---|---|
| Small | $250 K | Trades < $1 K |
| Medium | $1 M | Trades $1 K – $5 K |
| Large | $5 M | Trades $5 K+ |
| Dynamic | auto-scaled | Spill-over during high TPS |
The CrossPoolRouter automatically selects the smallest shard that can handle your trade.
Smaller shards → lower fees → better rates (the c-smaller-better property from the SAMM litepaper).
| Parameter | Value | Meaning |
|---|---|---|
| β₁ | −250 000 | Steep fee curve slope |
| rₘᵢₙ | 100 (0.01%) | Floor fee rate |
| rₘₐₓ | 2 500 (0.25%) | Ceiling fee rate |
| c | 9 600 (0.96%) | Shard eligibility threshold |
┌──────────────────────┐
│ api-server.js (REST) │ ← port 3000
└──────┬───────────────┘
┌────────────┼────────────────┐
▼ ▼ ▼
arbitrage-bot.js dynamic-shard- tx-queue.js
(rebalancer) manager.js (nonce serialiser)
│ (TPS scaler) │
└────────────┼────────────────┘
▼
┌────────────────────────┐
│ RiseChain Testnet │
│ (Solidity contracts) │
└────────────────────────┘
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
CrossPoolRouter SAMMPoolFactory DynamicShardOrchestrator
│ │
▼ ▼
SAMMPool shards SAMMCurve / SAMMFees
(20 live pools) (math libraries)
| Contract | Purpose |
|---|---|
| SAMMPool | Individual liquidity shard with SAMM curve |
| SAMMPoolFactory | Creates & indexes shards per pair |
| CrossPoolRouter | Multi-hop swaps with auto shard selection |
| DynamicShardOrchestrator | On-chain shard creation (called by backend) |
| SAMMCurve / SAMMFees | Pure-math libraries for pricing |
| TokenFaucet | Testnet token dispenser |
| Module | Purpose |
|---|---|
| api-server.js | Express REST API — auto-discovers deployment, starts subsystems |
| arbitrage-bot.js | Monitors every shard for oracle deviation, rebalances with 50% gap closure |
| dynamic-shard-manager.js | Reads TPS, applies litepaper §6 formula: n = min(⌈TPS/50⌉, 10) |
| tx-queue.js | Serialises all wallet transactions to prevent nonce collisions |
The backend wallet sends transactions. The arb bot and shard manager use a single
PRIVATE_KEYto sign rebalancing swaps and create new shards. This is by design.
| Component | Sends Txs? | Why |
|---|---|---|
| Arb Bot | ✅ | Rebalances shard reserves toward oracle price |
| Shard Manager | ✅ | Creates new shards when TPS exceeds capacity |
POST /swap |
✅ | Executes user-requested swaps via the backend wallet |
GET /quote |
❌ | Read-only — calls calculateSwapSAMM() view function |
| All GET endpoints | ❌ | Read-only on-chain queries |
.envis gitignored — the private key never enters the repo.- Railway deployment injects
PRIVATE_KEYandRISECHAIN_RPC_URLas environment variables via the dashboard. - The
POST /swap,POST /arbitrage/*, andPOST /sharding/*endpoints require the wallet to be initialised (i.e.PRIVATE_KEYmust be set in the environment). Without it, the server runs in read-only mode — all GET and quote endpoints still work.
Since this repo is public:
- Never commit
.env— it is already in.gitignore. - The deployment data in
deployment-data/contains only contract addresses (public on-chain data). - Anyone can call the API, but the
POST /swapendpoint spends the server's own tokens (testnet faucet tokens with zero real-world value). - The arb bot and shard manager run server-side only — the server wallet holds only testnet tokens minted by the faucet.
Chain ID: 11155931
RPC: https://testnet.riselabs.xyz/http
| Contract | Address |
|---|---|
| SAMMPoolFactory | 0xc4c6ceABeBBfA1Bf9D219fE80F5b95982664fb94 |
| CrossPoolRouter | 0x6A45347a8DbC629000F725c544D695209b0c3d00 |
| DynamicShardOrchestrator | 0x93174f86F57A97827680c279e07704AbE2a0b0c0 |
| TokenFaucet | 0x42a930BF9259cE3D9e76bb1d8C61b52daf68dBE4 |
| Token | Address | Decimals |
|---|---|---|
| WETH | 0x0234367975aCbcBe49867dD36bf37C7d05C2E743 |
18 |
| USDC | 0x1B40c25A7cDF5b11c67dc956d6b63EEaE1C349B0 |
6 |
| USDT | 0xa95558713D7E6D3F41bC70E867323A84404586f9 |
6 |
| WBTC | 0xD35648Ad048e450aFd22f3421cE6A5EFFC40DC4D |
8 |
| DAI | 0x51A046A489da585eB5875845FdC7323c0f1F0606 |
18 |
| Pair | Shards | Combined TVL |
|---|---|---|
| WETH-USDC | Small, Medium, Large, Dynamic | ~$6.60M |
| USDC-USDT | Small, Medium, Large, Dynamic | ~$6.50M |
| WETH-USDT | Small, Medium, Large, Dynamic | ~$6.60M |
| WBTC-USDC | Small, Medium, Large, Dynamic | ~$6.56M |
| USDC-DAI | Small, Medium, Large, Dynamic | ~$6.50M |
- Node.js ≥ 18
- npm
git clone <repo-url>
cd samm-evm
npm installcp .env.example .env
# Edit .env — set PRIVATE_KEY and RISECHAIN_RPC_URLnpx hardhat compilenpx hardhat test # all Hardhat tests
npx hardhat test test/unit/ # unit tests only
npm run test:swap-matrix # on-chain swap matrix (requires RiseChain)npm run deploy:risechain # full production deploy
npm run deploy:faucet # token faucet
npm run deploy:router # router onlynpm startThe server auto-discovers the latest production-risechain-*.json file in deployment-data/, starts the arb bot and shard manager, and listens on the configured port.
npm run verify:apisRuns 42 read-only tests against every endpoint (no swaps executed).
Base URL: http://localhost:3000
| Method | Path | Description |
|---|---|---|
GET |
/health |
Server health + deployment info |
GET |
/tokens |
All tokens with CoinGecko prices |
GET |
/pools |
All pairs with shards and TVL |
GET |
/pools/:tokenA/:tokenB |
Shards for a specific pair |
GET |
/shards/:tokenA/:tokenB |
Shard details direct from chain |
GET |
/quote/:tokenIn/:tokenOut/:amount |
Single-hop quote (fee, slippage, shard) |
POST |
/quote |
Multi-hop quote (body: { route, amountOut }) |
GET |
/price/:tokenA/:tokenB |
Spot price + oracle deviation |
GET |
/balance/:address/:token |
Token balance |
GET |
/balances/:address |
All token balances for address |
GET |
/stats |
DEX-wide stats (TVL, pair count, shard names) |
GET |
/arbitrage/status |
Arb bot running status |
GET |
/arbitrage/history |
Recent arb swap log |
GET |
/sharding/status |
Shard manager status + TPS readings |
| Method | Path | Description |
|---|---|---|
POST |
/swap |
Execute swap (server wallet signs tx) |
POST |
/arbitrage/start |
Start arb bot |
POST |
/arbitrage/stop |
Stop arb bot |
POST |
/sharding/start |
Start shard manager |
POST |
/sharding/stop |
Stop shard manager |
POST |
/sharding/check |
Trigger immediate shard check |
# Quick quote — buy 100 USDC with WETH
curl http://localhost:3000/quote/WETH/USDC/100
# Multi-hop quote — WETH → USDC → USDT
curl -X POST http://localhost:3000/quote \
-H "Content-Type: application/json" \
-d '{"route":["WETH","USDC","USDT"],"amountOut":"500"}'
# Spot price
curl http://localhost:3000/price/WETH/USDC
# DEX stats
curl http://localhost:3000/statsThe repo includes railway.json and nixpacks.toml for one-click Railway deployment.
Set these environment variables in Railway's dashboard:
| Variable | Required | Description |
|---|---|---|
PRIVATE_KEY |
Yes | Wallet private key (no 0x prefix) |
RISECHAIN_RPC_URL |
Yes | RiseChain RPC endpoint |
PORT |
No | Defaults to 3000 |
ENABLE_ARBITRAGE |
No | true to auto-start arb bot |
ENABLE_DYNAMIC_SHARDING |
No | true to auto-start shard manager |
samm-evm/
├── contracts/ # Solidity source
│ ├── SAMMPool.sol # Liquidity pool shard
│ ├── SAMMPoolFactory.sol # Factory for creating shards
│ ├── CrossPoolRouter.sol # Multi-hop swap router
│ ├── DynamicShardOrchestrator.sol # On-chain shard creator
│ ├── TokenFaucet.sol # Testnet faucet
│ ├── interfaces/ # ISAMMPool, ISAMMPoolFactory, ICrossPoolRouter
│ └── libraries/ # SAMMCurve.sol, SAMMFees.sol
├── api-server.js # REST API server (Express)
├── arbitrage-bot.js # Oracle-deviation rebalancer
├── dynamic-shard-manager.js # TPS-driven shard scaler
├── tx-queue.js # Nonce-safe tx serialiser
├── hardhat.config.js # Hardhat configuration
├── package.json # Dependencies & npm scripts
├── railway.json # Railway deployment config
├── nixpacks.toml # Nixpacks build config
├── .env.example # Environment variable template
├── config/ # Chain configs (chains.json)
├── deployment-data/ # Contract addresses (auto-generated)
├── scripts/
│ ├── deploy-production-risechain.js # Full production deploy
│ ├── deploy-production-risechain-v2.js # V2 deploy variant
│ ├── deploy-crosspool-router-risechain.js
│ ├── deploy-faucet-risechain.js
│ ├── validate-risechain-swap-matrix.js # On-chain swap matrix test
│ ├── comprehensive-e2e-test-risechain.js
│ ├── comprehensive-swap-analysis.js # Detailed swap analysis
│ ├── verify-all-apis.js # 42-test API verification
│ ├── bench-batched.js # Batched RPC TPS benchmark
│ ├── bench-sustained-tps.js # Sustained TPS benchmark
│ ├── tps-load-test.js # TPS load generator
│ └── initialize-empty-pools.js # Pool init utility
├── test/ # Hardhat / Mocha tests
│ ├── unit/ # Unit tests
│ ├── offchain/ # Off-chain math verification
│ ├── *.property.test.js # Property-based tests (fast-check)
│ └── *.test.js # Integration tests
├── test-results/ # Benchmark outputs (gitignored)
└── Research.md # SAMM litepaper & research notes
The SAMM litepaper's core insight: for a given trade size, the smallest eligible shard always gives the best rate. The router enforces this — it iterates shards from smallest to largest and uses the first one where the trade-to-reserve ratio stays within the c-threshold.
When on-chain TPS exceeds a per-shard capacity, the shard manager creates additional shards:
n = min(⌈TPS / PER_SHARD_TPS⌉, MAX_SHARDS_PER_PAIR)
Default: 50 TPS per shard, max 10 shards per pair.
Monitors every shard's spot price against CoinGecko oracles. When deviation exceeds 0.3%, it executes a corrective swap sized at 50% of the gap. A 3-cycle cooldown per shard prevents oscillation.
MIT