README.md
Router
Swap routing engine for optimal trade execution across pools.
Overview
Router handles swap execution across multiple pools, finding optimal paths and managing slippage protection for traders.
Gnoweb
The root Render("") delegates to the active implementation. It exposes separate
status and read-only swap-fee sections. Native Gnoweb execution forms show
one user-facing swap action per page:
"":ExactInSingleSwapRoute"swap/exact-in":ExactInSwapRoute"swap/exact-out-single":ExactOutSingleSwapRoute"swap/exact-out":ExactOutSwapRoute
Input placeholders show token-key, route, and percentage examples without
separate explanatory paragraphs. Token amounts are integer base units.
Set amountOutMin or amountInMax for slippage
protection, use a future Unix deadline, and set multi-route quoteArr
percentages to sum to 100. Approve each input GRC20 token for the router realm
before swapping. The single-route price limit accepts sqrtPriceLimitX96 as a
base-10 Q64.96 square-root price; 0 disables it.
No privileged fee-setting form, guidance route, or navigation link is exposed.
SetSwapFee itself is unchanged. No internal callback form is exposed.
Unsupported render paths, including "fee", return 404.
Configuration
- Router Fee: 15 bps (0.15%) by default on output tokens; configurable by admin/governance from 0 through 1000 bps (0–10%)
- Max Hops: 3 pools per route
- Deadline Buffer: 5-30 minutes recommended for live swaps
Core Functions
ExactInSwapRoute
Swaps an exact input amount for output, subject to a minimum net output.
- Fixed input, variable output
- The returned output is after the router fee
- Reverts if output < amountOutMin
- Supports multi-hop routing
ExactOutSwapRoute
Swaps for a requested final user output amount with maximum input. The
amountOut target is post-router-fee: the router requests the corresponding
gross pool output, deducts the fee, then validates and transfers the net output.
- With no single-hop price limit, targets the requested post-fee output within the implementation's small per-hop rounding tolerance
- A nonzero single-hop price limit may stop early and return a partial output
- Reverts if input > amountInMax
- Calculates path backwards
DrySwapRoute
Simulates a swap without execution.
- Frontend price quotes
- Slippage calculation
- Path validation
- No deadline check or token transfer
Technical Details
Route Format vs Pool Format - IMPORTANT DISTINCTION
Route Format (Swap Direction)
Routes in the router follow swap direction ordering: tokenIn:tokenOut:fee
- First token = Input token (what you're swapping FROM)
- Second token = Output token (what you're swapping TO)
- This represents the actual flow of the swap
Example for swapping GNS to WUGNOT:
gno.land/r/gnoswap/gns.GNS:gno.land/r/gnoland/wugnot.wugnot:3000
Pool Format (Alphabetical)
Pools are identified using alphabetical ordering: token0:token1:fee
- token0 < token1 (lexicographically sorted)
- This is the canonical pool identifier
Example pool identifier (same pool as above):
gno.land/r/gnoland/wugnot.wugnot:gno.land/r/gnoswap/gns.GNS:3000 # gnoland/wugnot < gnoswap/gns alphabetically
Key Difference
- Router routes: Follow your swap direction (BAR→BAZ means bar:baz in route)
- Pool identifiers: Always alphabetically sorted (might be bar:baz or baz:bar)
- The router automatically handles the conversion between these formats
Native Token Route Specification
IMPORTANT: Router swap functions do not accept native ugnot directly.
- Token Parameters: Use token keys (
pkgPath.SYMBOL) such as"gno.land/r/gnoland/wugnot.wugnot" - Route Paths: Also use token keys (
pkgPath.SYMBOL) such as"gno.land/r/gnoland/wugnot.wugnot"
This matches the current implementation:
- Pools operate on token contract paths, including wrapped GNOT (
wugnot) - Router swap entrypoints reject native-coin handling
- Native-token refund and unwrap flows are not part of the current router implementation
Route String Format
Single-hop format:
tokenIn:tokenOut:fee
Multi-hop format (using POOL separator):
tokenIn:tokenB:fee1*POOL*tokenB:tokenC:fee2*POOL*tokenC:tokenOut:fee3
Single-hop example:
# Swapping GNS to WUGNOT
Route: gno.land/r/gnoswap/gns.GNS:gno.land/r/gnoland/wugnot.wugnot:3000
# Router interprets: tokenIn=gns, tokenOut=wugnot, fee=3000
Multi-hop example (GNS → WUGNOT → TOKEN_C):
# Each segment follows swap direction, connected by *POOL*
gno.land/r/gnoswap/gns.GNS:gno.land/r/gnoland/wugnot.wugnot:3000*POOL*gno.land/r/gnoland/wugnot.wugnot:gno.land/r/<namespace>/token_c.TOKEN_C:500
Quote Distribution
Split large trades across routes to minimize impact:
quoteArr: positive percentage per route, with one quote for each route- Quotes must sum to 100; at most 7 routes are accepted
- Example: "30,70" = 30% route1, 70% route2
Native Token Handling
The current router implementation does not handle native ugnot directly. It rejects native-coin handling and routes swaps only through token keys (pkgPath.SYMBOL) such as wrapped GNOT (wugnot).
Token Identifier Requirements
- Use token keys (
pkgPath.SYMBOL) such asgno.land/r/gnoland/wugnot.wugnotfor both inputs/outputs and route specifications. - Do not pass
"ugnot"asinputTokenoroutputTokento router swap functions.
Approval and Transfer Requirements
- Approve the router to spend the token contract you are swapping from.
- If you want wrapped GNOT exposure, use the
wugnottoken contract path directly. - Native-token refund and unwrap flows are not part of the current router implementation.
Example with Wrapped GNOT
Examples in this document call the public domain proxy from a realm function with a current cur token.
Import the proxy package and qualify its function names in integrating code.
1// 1. Approve WUGNOT spending for the router
2wugnot.Approve(cross(cur), routerAddress, 1000000)
3
4// 2. Call swap function with wrapped GNOT paths
5amountIn, amountOut := ExactInSwapRoute(
6 cross(cur),
7 "gno.land/r/gnoland/wugnot.wugnot", // input token
8 "gno.land/r/gnoswap/gns.GNS", // output token
9 "1000000", // amount in wrapped token units
10 "gno.land/r/gnoland/wugnot.wugnot:gno.land/r/gnoswap/gns.GNS:3000",
11 "100", // 100% through route
12 "950000", // min output
13 time.Now().Unix() + 300, // deadline
14 "", // no referrer
15)
For live liquidity-changing swaps:
- Set
amountOutMin = expected * (1 - slippage%) - 0.5-1% for stable pairs
- 1-3% for volatile pairs
- Reverts if the net output is below the minimum
Usage
Basic Token Swaps
1// Simple exact input swap
2amountIn, amountOut := ExactInSwapRoute(
3 cross(cur),
4 "gno.land/r/gnoswap/gns.GNS", // input token
5 "gno.land/r/gnoland/wugnot.wugnot", // output token
6 "1000000", // amount (6 decimals)
7 "gno.land/r/gnoswap/gns.GNS:gno.land/r/gnoland/wugnot.wugnot:3000", // route
8 "100", // 100% through route
9 "950000", // min output
10 time.Now().Unix() + 300, // deadline
11 "g1referrer...", // referral
12)
13
14// Multi-hop swap
15ExactInSwapRoute(
16 cross(cur),
17 "gno.land/r/gnoswap/gns.GNS",
18 "gno.land/r/<namespace>/token_c.TOKEN_C",
19 "1000000",
20 "gno.land/r/gnoswap/gns.GNS:gno.land/r/gnoland/wugnot.wugnot:3000*POOL*gno.land/r/gnoland/wugnot.wugnot:gno.land/r/<namespace>/token_c.TOKEN_C:3000",
21 "100",
22 "900000",
23 deadline,
24 "",
25)
26
27// Split route for large trades
28ExactInSwapRoute(
29 cross(cur),
30 "gno.land/r/gnoswap/gns.GNS",
31 "gno.land/r/gnoland/wugnot.wugnot",
32 "10000000000",
33 "gno.land/r/gnoswap/gns.GNS:gno.land/r/gnoland/wugnot.wugnot:500,gno.land/r/gnoswap/gns.GNS:gno.land/r/gnoland/wugnot.wugnot:3000",
34 "60,40", // 60% through 0.05%, 40% through 0.3%
35 "9500000000",
36 deadline,
37 "",
38)
Single-hop functions support partial execution through a nonzero
sqrtPriceLimitX96:
1// Partial swap with price limit - may not consume full input amount
2amountIn, amountOut := ExactInSingleSwapRoute(
3 cross(cur),
4 "gno.land/r/gnoswap/gns.GNS", // input token
5 "gno.land/r/gnoland/wugnot.wugnot", // output token
6 "1000000", // max amount to swap
7 "gno.land/r/gnoswap/gns.GNS:gno.land/r/gnoland/wugnot.wugnot:3000", // single route
8 "950000", // min output
9 "1000000000000000000", // sqrtPriceLimitX96 (price limit)
10 deadline,
11 "",
12)
13// If the price limit is reached, only a partial amount is swapped. For exact-in
14// this can consume less input; exact-out can deliver less than its target.
15// amountOutMin or amountInMax remains enforced, respectively.
Important Developer Notes
Common Integration Pitfalls
-
Native Token Assumptions: Passing
"ugnot"to router swap functions will fail because router entrypoints reject native-coin handling. -
Route vs Token Identifier Confusion: Using
"ugnot"in route strings instead of"gno.land/r/gnoland/wugnot.wugnot"will cause transactions to fail since no pools exist for the"ugnot"identifier. -
Wrong Token Path:
- Use
gno.land/r/gnoland/wugnot.wugnotwhen swapping wrapped GNOT - Do not pass native
ugnotto router swap functions - Route strings must stay in swap-direction order and use token contract paths
- Use
Frontend Integration Checklist
- Implement WUGNOT approval before wrapped-GNOT swaps
- Use token keys (
pkgPath.SYMBOL) such as"gno.land/r/gnoland/wugnot.wugnot"for both parameters and routes - Test both partial and full swap scenarios
- Implement proper error handling for failed approvals
Both single-hop functions support partial execution when
sqrtPriceLimitX96 is nonzero:
- Exact-in may consume less than the specified input amount
- Exact-out may deliver less than the requested post-fee output
- The relevant amount limit (
amountOutMinoramountInMax) still applies - Remaining input tokens stay with the user because the router uses token contract transfers
- A zero limit uses the global tick-math boundary and preserves full exact semantics
Security
- Path validation checks syntax, endpoints, hop continuity, and pool existence; it does not reject circular routes
- Deadline prevents stale live transactions
- Slippage limits protect against unfavorable execution
- The router fee rate is configurable; the current rate is fixed during one execution
- WUGNOT approval requirement prevents unauthorized token transfers