/r/gnoswap/router
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.
Configuration
- Router Fee: 0.15% on all swaps
- Max Hops: 3 pools per route
- Deadline Buffer: 5-30 minutes recommended
Core Functions
ExactInSwapRoute
Swaps exact input amount for minimum output.
- Fixed input, variable output
- Reverts if output < amountOutMin
- Supports multi-hop routing
ExactOutSwapRoute
Swaps for exact output amount with maximum input.
- Fixed output, variable input
- Reverts if input > amountInMax
- Calculates path backwards
DrySwapRoute
Simulates swap without execution.
- Frontend price quotes
- Slippage calculation
- Gas estimation
- Path validation
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 BAR to BAZ:
gno.land/r/demo/bar:gno.land/r/demo/baz: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/demo/bar:gno.land/r/demo/baz:3000 # if bar < baz 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: When using native GNOT tokens, there's a critical distinction between token identifiers and route paths:
- Token Parameters: Use
"ugnot"forinputTokenandoutputTokenparameters - Route Paths: Must use
"gno.land/r/gnoland/wugnot"in route strings
This dual-identifier system exists because:
- Pools only operate on wrapped tokens (WUGNOT)
- Router functions accept native token identifiers for user convenience
- Internal processing automatically converts between native and wrapped forms
Correct Usage Example:
1// CORRECT: inputToken="ugnot", route uses wugnot path
2ExactInSwapRoute(
3 "ugnot", // input token identifier
4 "gno.land/r/demo/bar", // output token
5 "1000000",
6 "gno.land/r/gnoland/wugnot:gno.land/r/demo/bar:3000", // route uses wugnot
7 "100", "950000", deadline, ""
8)
9
10// INCORRECT: using "ugnot" in route will fail
11ExactInSwapRoute(
12 "ugnot", "gno.land/r/demo/bar", "1000000",
13 "ugnot:gno.land/r/demo/bar:3000", // Wrong: pools don't exist for "ugnot"
14 "100", "950000", deadline, ""
15)
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 BAR to BAZ
Route: gno.land/r/demo/bar:gno.land/r/demo/baz:3000
# Router interprets: tokenIn=bar, tokenOut=baz, fee=3000
Multi-hop example (BAR → BAZ → QUX):
# Each segment follows swap direction, connected by *POOL*
gno.land/r/demo/bar:gno.land/r/demo/baz:3000*POOL*gno.land/r/demo/baz:gno.land/r/demo/qux:500
Quote Distribution
Split large trades across routes to minimize impact:
quoteArr: Percentage per route (must sum to 100)- Example: "30,70" = 30% route1, 70% route2
GNOT Handling
The Router automatically handles native GNOT token operations through wrapping/unwrapping mechanisms:
Token Identifier Requirements
- Input Token: Use
"ugnot"to specify native GNOT as input token - Output Token: Use
"ugnot"to specify native GNOT as output token - Routes: Must always use wrapped token path
"gno.land/r/gnoland/wugnot"in route specifications
Native Token Send Requirements
When using native GNOT tokens, you must send the appropriate amount of native ugnot with your function call:
- ExactInSwapRoute: Send exactly
amountInamount ofugnot - ExactInSingleSwapRoute: Send exactly
amountInamount ofugnot - ExactOutSwapRoute: Send exactly
amountInMaxamount ofugnot - ExactOutSingleSwapRoute: Send exactly
amountInMaxamount ofugnot
WUGNOT Approval Requirements for Refunds
1// Before calling router functions with native tokens
2wugnot.Approve(cross, routerAddress, maxAmount)
This approval is required because:
- Router wraps native GNOT to WUGNOT for internal processing
- Unused GNOT is refunded by transferring WUGNOT from user and unwrapping it
- The
unwrapWithTransferFromfunction requires WUGNOT transfer approval
Refund Logic
- ExactIn Functions: Unused GNOT is automatically refunded after swap
- ExactOut Functions: Excess GNOT (difference between
amountInMaxand actual input used) is refunded - Refunds use
unwrapWithTransferFromwhich requires prior WUGNOT approval
Example with Native GNOT
1// 1. First approve WUGNOT spending for potential refunds
2wugnot.Approve(cross, routerAddress, 1000000) // Approve max amount
3
4// 2. Call swap function with native GNOT, sending ugnot
5// Note: inputToken="ugnot" but route uses wugnot path
6amountIn, amountOut := ExactInSwapRoute(
7 "ugnot", // input token (native)
8 "gno.land/r/demo/bar", // output token
9 "1000000", // amount (send this much ugnot)
10 "gno.land/r/gnoland/wugnot:gno.land/r/demo/bar:3000", // route uses wugnot
11 "100", // 100% through route
12 "950000", // min output
13 time.Now().Unix() + 300, // deadline
14 "", // no referrer
15)
16// Any unused GNOT will be automatically refunded
Slippage Protection
- Set
amountOutMin = expected * (1 - slippage%) - 0.5-1% for stable pairs
- 1-3% for volatile pairs
- Reverts if exceeded
Usage
Basic Token Swaps
1// Simple exact input swap
2amountIn, amountOut := ExactInSwapRoute(
3 "gno.land/r/demo/bar", // input token
4 "gno.land/r/demo/baz", // output token
5 "1000000", // amount (6 decimals)
6 "gno.land/r/demo/bar:gno.land/r/demo/baz:3000", // route
7 "100", // 100% through route
8 "950000", // min output
9 time.Now().Unix() + 300, // deadline
10 "g1referrer...", // referral
11)
12
13// Multi-hop swap
14ExactInSwapRoute(
15 "gno.land/r/demo/bar",
16 "gno.land/r/demo/baz",
17 "1000000",
18 "gno.land/r/demo/bar:gno.land/r/gnoland/wugnot:3000*POOL*gno.land/r/gnoland/wugnot:gno.land/r/demo/baz:3000",
19 "100",
20 "900000",
21 deadline,
22 "",
23)
24
25// Split route for large trades
26ExactInSwapRoute(
27 "gno.land/r/demo/usdc",
28 "ugnot",
29 "10000000000",
30 "gno.land/r/demo/usdc:gno.land/r/gnoland/wugnot:500,gno.land/r/demo/usdc:gno.land/r/gnoland/wugnot:3000",
31 "60,40", // 60% through 0.05%, 40% through 0.3%
32 "9500000000",
33 deadline,
34 "",
35)
Single Swap with Partial Execution
Single swap functions support partial execution through price limits:
1// Partial swap with price limit - may not consume full input amount
2amountIn, amountOut := ExactInSingleSwapRoute(
3 "gno.land/r/demo/bar", // input token
4 "gno.land/r/demo/baz", // output token
5 "1000000", // max amount to swap
6 "gno.land/r/demo/bar:gno.land/r/demo/baz:3000", // single route
7 "950000", // min output
8 "1000000000000000000", // sqrtPriceLimitX96 (price limit)
9 deadline,
10 "",
11)
12// If price limit is reached, only partial amount is swapped
13// Remaining input tokens stay with user (no refund needed for GRC20 tokens)
Native GNOT Swaps with Refunds
When using native GNOT, automatic refunds handle unused amounts:
1// STEP 1: Approve WUGNOT for potential refunds
2wugnot.Approve(cross, routerAddress, 2000000) // Approve more than needed
3
4// STEP 2: ExactIn with native GNOT (send exactly amountIn)
5amountIn, amountOut := ExactInSwapRoute(
6 "ugnot", // native input
7 "gno.land/r/demo/bar", // output token
8 "1000000", // send this amount of ugnot with call
9 "gno.land/r/gnoland/wugnot:gno.land/r/demo/bar:3000",
10 "100", "950000", deadline, ""
11)
12// Any unused GNOT automatically refunded
13
14// STEP 3: ExactOut with native GNOT (send amountInMax)
15amountIn, amountOut := ExactOutSwapRoute(
16 "ugnot", // native input
17 "gno.land/r/demo/bar", // output token
18 "1000000", // exact output desired
19 "gno.land/r/gnoland/wugnot:gno.land/r/demo/bar:3000",
20 "100",
21 "1200000", // send this max amount of ugnot with call
22 deadline, ""
23)
24// Excess GNOT (1200000 - actual_input_used) automatically refunded
25
26// STEP 4: Single swap with partial execution + refund
27amountIn, amountOut := ExactInSingleSwapRoute(
28 "ugnot", // native input
29 "gno.land/r/demo/bar", // output token
30 "1000000", // send this amount of ugnot with call
31 "gno.land/r/gnoland/wugnot:gno.land/r/demo/bar:3000",
32 "950000",
33 "1000000000000000000", // price limit may cause partial swap
34 deadline, ""
35)
36// Unswapped GNOT due to price limit automatically refunded
Important Developer Notes
Common Integration Pitfalls
-
WUGNOT Approval Forgotten: Most transaction failures with native GNOT occur because developers forget to approve WUGNOT spending before calling router functions.
-
Route vs Token Identifier Confusion: Using
"ugnot"in route strings instead of"gno.land/r/gnoland/wugnot"will cause transactions to fail since no pools exist for the"ugnot"identifier. -
Incorrect Native Token Send Amount:
- ExactIn functions: Must send exactly
amountInof native gnot - ExactOut functions: Must send exactly
amountInMaxof native gnot - Sending wrong amounts will cause transaction reversion
- ExactIn functions: Must send exactly
-
Missing Refund Handling: When integrating, remember that native GNOT refunds are automatic but require prior WUGNOT approval.
Frontend Integration Checklist
- Implement WUGNOT approval before native GNOT swaps
- Use correct token identifiers:
"ugnot"for parameters,"gno.land/r/gnoland/wugnot"for routes - Send correct native token amounts with function calls
- Handle automatic refunds in UI balance updates
- Test both partial and full swap scenarios
- Implement proper error handling for failed approvals
Single Swap Partial Execution
The ExactInSingleSwapRoute and ExactOutSingleSwapRoute functions support partial execution when sqrtPriceLimitX96 is set. This means:
- Swap may consume less than the specified input amount
- Price impact is limited by the price limit parameter
- Remaining tokens are handled automatically (refunded for native GNOT, stay with user for GRC20)
- This is useful for large trades to prevent excessive slippage
Security
- Path validation prevents circular routes
- Deadline prevents stale transactions
- Slippage limits protect against MEV
- Router fees immutable per swap
- WUGNOT approval requirement prevents unauthorized token transfers