Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

README.md

10.67 Kb · 310 lines

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 as gno.land/r/gnoland/wugnot.wugnot for both inputs/outputs and route specifications.
  • Do not pass "ugnot" as inputToken or outputToken to 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 wugnot token 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

  1. Native Token Assumptions: Passing "ugnot" to router swap functions will fail because router entrypoints reject native-coin handling.

  2. 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.

  3. Wrong Token Path:

    • Use gno.land/r/gnoland/wugnot.wugnot when swapping wrapped GNOT
    • Do not pass native ugnot to router swap functions
    • Route strings must stay in swap-direction order and use token contract paths

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 (amountOutMin or amountInMax) 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