router source realm
Package router provides swap routing and execution across GnoSwap liquidity pools.
View source
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
Package router provides swap routing and execution across GnoSwap liquidity pools.
The router handles token swaps with multi-hop routing, slippage protection, and automatic GNOT wrapping/unwrapping. It supports both exact input and exact output swap modes with deadline validation.
Key features: - Multi-hop routing across up to 3 pools - Quote distribution for optimal execution - Native GNOT handling with automatic wrap/unwrap - Slippage protection via min/max amounts - Router fee (0.15%) on all swaps - Deadline enforcement to prevent stale transactions
The router acts as a proxy to version-specific implementations, currently routing to v1 for all swap operations.
1
12
func DrySwapRoute
ActionDrySwapRoute simulates a swap route without executing it.
Parameters:
- inputToken: path of input token
- outputToken: path of output token
- specifiedAmount: specified amount for the swap
- swapTypeStr: swap type string ("ExactIn" or "ExactOut")
- strRouteArr: encoded route array
- quoteArr: encoded quote array
- tokenAmountLimit: token amount limit
Returns:
- string: estimated input amount
- string: estimated output amount
- bool: success status
func ExactInSingleSwapRoute
crossing Action1func ExactInSingleSwapRoute(cur realm, inputToken string, outputToken string, amountIn string, routeArr string, amountOutMin string, sqrtPriceLimitX96 string, deadline int64, referrer string) (string, string)ExactInSingleSwapRoute executes a single-hop swap with exact input amount.
Parameters:
- inputToken: path of input token
- outputToken: path of output token
- amountIn: exact input amount
- routeArr: encoded route
- amountOutMin: minimum output amount
- sqrtPriceLimitX96: price limit for the swap
- deadline: transaction deadline
- referrer: referrer address for reward tracking
Returns:
- string: actual input amount
- string: actual output amount
func ExactInSwapRoute
crossing Action1func ExactInSwapRoute(cur realm, inputToken string, outputToken string, amountIn string, routeArr string, quoteArr string, amountOutMin string, deadline int64, referrer string) (string, string)ExactInSwapRoute executes a multi-hop swap with exact input amount.
Parameters:
- inputToken: path of input token
- outputToken: path of output token
- amountIn: exact input amount
- routeArr: encoded route array
- quoteArr: encoded quote array
- amountOutMin: minimum output amount
- deadline: transaction deadline
- referrer: referrer address for reward tracking
Returns:
- string: actual input amount
- string: actual output amount
func ExactOutSingleSwapRoute
crossing Action1func ExactOutSingleSwapRoute(cur realm, inputToken string, outputToken string, amountOut string, routeArr string, amountInMax string, sqrtPriceLimitX96 string, deadline int64, referrer string) (string, string)ExactOutSingleSwapRoute executes a single-hop swap with exact output amount.
Parameters:
- inputToken: path of input token
- outputToken: path of output token
- amountOut: exact output amount
- routeArr: encoded route
- amountInMax: maximum input amount
- sqrtPriceLimitX96: price limit for the swap
- deadline: transaction deadline
- referrer: referrer address for reward tracking
Returns:
- string: actual input amount
- string: actual output amount
func ExactOutSwapRoute
crossing Action1func ExactOutSwapRoute(cur realm, inputToken string, outputToken string, amountOut string, routeArr string, quoteArr string, amountInMax string, deadline int64, referrer string) (string, string)ExactOutSwapRoute executes a multi-hop swap with exact output amount.
Parameters:
- inputToken: path of input token
- outputToken: path of output token
- amountOut: exact output amount
- routeArr: encoded route array
- quoteArr: encoded quote array
- amountInMax: maximum input amount
- deadline: transaction deadline
- referrer: referrer address for reward tracking
Returns:
- string: actual input amount
- string: actual output amount
func GetImplementationPackagePath
ActionGetImplementationPackagePath returns the package path of the currently active implementation.
func GetSwapFee
ActionGetSwapFee returns the current swap fee rate.
func RegisterInitializer
crossing Action1func RegisterInitializer(cur realm, initializer func(_ int, rlm realm, routerStore IRouterStore) IRouter)RegisterInitializer registers a new router implementation version. This function is called by each version (v1, v2, etc.) during initialization to register their implementation with the proxy system.
The initializer function creates a new instance of the implementation using the provided routerStore interface.
Security: Only contracts within the domain path can register initializers. Each package path can only register once to prevent duplicate registrations.
func SetSwapFee
crossing ActionSetSwapFee sets the swap fee rate.
Parameters:
- fee: new swap fee in basis points
func SwapCallback
crossing Action1func SwapCallback(cur realm, token0Path string, token1Path string, amount0Delta int64, amount1Delta int64, payer address) errorSwapCallback is called by pools to transfer tokens during a swap.
func UpgradeImpl
crossing ActionUpgradeImpl switches the active router implementation to a different version. This function allows seamless upgrades from one version to another without data migration or downtime.
Security: Only admin or governance can perform upgrades. The new implementation must have been previously registered via RegisterInitializer.
func NewRouterStore
ActionNewRouterStore creates a new router store instance with the provided KV store. This function is used by the upgrade system to create storage instances for each implementation.
3
type IRouter
interface 1type IRouter interface {
2 ExactInSwapRoute(_ int, rlm realm, inputToken string, outputToken string, amountIn string, routeArr string, quoteArr string, amountOutMin string, deadline int64, referrer string) (string, string)
3 ExactInSingleSwapRoute(_ int, rlm realm, inputToken string, outputToken string, amountIn string, routeArr string, amountOutMin string, sqrtPriceLimitX96 string, deadline int64, referrer string) (string, string)
4
5 ExactOutSwapRoute(_ int, rlm realm, inputToken string, outputToken string, amountOut string, routeArr string, quoteArr string, amountInMax string, deadline int64, referrer string) (string, string)
6 ExactOutSingleSwapRoute(_ int, rlm realm, inputToken string, outputToken string, amountOut string, routeArr string, amountInMax string, sqrtPriceLimitX96 string, deadline int64, referrer string) (string, string)
7
8 DrySwapRoute(inputToken, outputToken, specifiedAmount, swapTypeStr, strRouteArr, quoteArr, tokenAmountLimit string) (string, string, bool)
9 SwapCallback(_ int, rlm realm, token0Path string, token1Path string, amount0Delta int64, amount1Delta int64, payer address) error
10
11 GetSwapFee() uint64
12 SetSwapFee(_ int, rlm realm, fee uint64)
13}