DEX
Mo Chain ships Uniswap V2, Uniswap V3, the Universal Router (v1.6.0), and Permit2 — all deployed from the official audited bytecode. The pair/pool init code hashes are byte-for-byte identical to Ethereum mainnet, so any Uniswap interface fork, SDK, router, or aggregator works by configuring the chain ID and addresses — no SDK hash patching required.
The Universal Router is the canonical swap entrypoint: it aggregates V2 + V3 liquidity in a single execute call and supports gasless ERC20 approvals via Permit2 (sign once, no per-swap approve transaction).
Contracts
| Contract | Address | Explorer |
|---|---|---|
| v2Factory | 0xc1f728e16D83F822599C810155731AC5302DeFd4 | view ↗ |
| v2Router02 | 0x5965f93551411D3ba621593470Dd08f951441b7a | view ↗ |
| v3Factory | 0x0177F39C814Fec1462A169e9689EFB904874efF0 | view ↗ |
| v3PositionManager | 0x87A7D23217d0A633e51B2a97dec648D25B793996 | view ↗ |
| v3Router | 0x35201E3B4Ce12448fF4EC3d4107D28Ace10acde3 | view ↗ |
| v3Quoter | 0x985f113bbFA7953F5Abc37F95d5E577c971F7F4a | view ↗ |
| universalRouter | 0x78E2FF97feeeFfE7D167F46a2cDBba9fb9551720 | view ↗ |
| permit2 | 0xDCFeC1d498D46E836c65e2Cd27dD1E8292bD8d95 | view ↗ |
The Universal Router needs two init-code hashes to compute pair/pool addresses on the fly. They are part of the deployment record (not addresses, so they are not in the table above):
| Field | Value |
|---|---|
pairInitCodeHash (V2) | 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f |
poolInitCodeHash (V3) | 0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54 |
Tokens:
| Contract | Address | Explorer |
|---|---|---|
| WMO | 0x1266239D40aB0b73C2C79448c30C02a153822c08 | view ↗ |
| tUSDT | 0x02165C25e94A5087c76249459c2E756Aa5D2B80d | view ↗ |
| tUSDC | 0x260F2dB4Af20e13556AD00de69a3bd47D82cCB3F | view ↗ |
| tBUSD | 0x5A40F0056cC652CFb681e483FB3b1cF8b455a6AA | view ↗ |
Swap with the SDK (Universal Router + Permit2)
@mochain/sdk wraps the whole flow — best-route quoting (single hop or two hops via WMO), the one-time Permit2 approval, the EIP-712 signature, and the execute call:
import { MoClient } from '@mochain/sdk';
import { parseUnits } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const client = MoClient.testnet({ account });
const { stables } = client.addresses;
// Inspect the route the SDK picked (V2/V3, single or multi-hop).
const route = await client.swap.routeExactIn({
tokenIn: stables.WMO,
tokenOut: stables.tUSDT,
amountIn: parseUnits('1', 18),
});
console.log(route.label, route.amountOut); // e.g. "V3(3000)" 998312...
// Execute via the Universal Router. The SDK approves Permit2 + signs once,
// reuses the allowance afterwards, and applies 0.5% slippage by default.
const { hash, amountOutMin } = await client.swap.exactInUniversal({
tokenIn: stables.WMO,
tokenOut: stables.tUSDT,
amountIn: parseUnits('1', 18),
slippageBps: 50,
});
console.log('swap tx', hash, 'min out', amountOutMin);
Python (mochain) mirrors the same flow:
from mochain import MoClient
client = MoClient.testnet(private_key=PRIVATE_KEY)
stables = client.addresses["stables"]
res = client.swap.exact_in_universal(
stables["WMO"], stables["tUSDT"], 10**18, slippage_bps=50
)
print(res["hash"], res["amountOutMin"])
V2 vs V3
| V2 | V3 | |
|---|---|---|
| Liquidity | full-range | concentrated |
| Fee tiers | 0.30% fixed | 0.05% / 0.30% / 1% |
| Best for | simple pools | capital-efficient LPs |
Try it
Open the Swap UI to swap, add liquidity, and inspect pools.
Next steps
- SDK & Code Examples — full swap snippets.
- Subgraph — index pools and swaps.