PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/gnoswap/router

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
router
Namespace
gnoswap
Files
10 (README)(gnomod.toml)
Exported functions
14
Module
gno.land/r/gnoswap/router
gno
0.9

Files (10)

  • README.mdmarkdown
  • gnomod.tomltoml
  • doc.gnogno
  • getter_utils.gnogno
  • proxy.gnogno
  • render.gnogno
  • state.gnogno
  • store.gnogno
  • types.gnogno
  • upgrade.gnogno
README.mdPreviewRaw
# 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.

```go
// 1. Approve WUGNOT spending for the router
wugnot.Approve(cross(cur), routerAddress, 1000000)

// 2. Call swap function with wrapped GNOT paths
amountIn, amountOut := ExactInSwapRoute(
    cross(cur),
    "gno.land/r/gnoland/wugnot.wugnot",       // input token
    "gno.land/r/gnoswap/gns.GNS",             // output token
    "1000000",                                // amount in wrapped token units
    "gno.land/r/gnoland/wugnot.wugnot:gno.land/r/gnoswap/gns.GNS:3000",
    "100",                                    // 100% through route
    "950000",                                 // min output
    time.Now().Unix() + 300,                  // deadline
    "",                                       // no referrer
)
```

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

```go
// Simple exact input swap
amountIn, amountOut := ExactInSwapRoute(
    cross(cur),
    "gno.land/r/gnoswap/gns.GNS",       // input token
    "gno.land/r/gnoland/wugnot.wugnot", // output token
    "1000000",                 // amount (6 decimals)
    "gno.land/r/gnoswap/gns.GNS:gno.land/r/gnoland/wugnot.wugnot:3000", // route
    "100",                     // 100% through route
    "950000",                  // min output
    time.Now().Unix() + 300,   // deadline
    "g1referrer...",           // referral
)

// Multi-hop swap
ExactInSwapRoute(
    cross(cur),
    "gno.land/r/gnoswap/gns.GNS",
    "gno.land/r/<namespace>/token_c.TOKEN_C",
    "1000000",
    "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",
    "100",
    "900000",
    deadline,
    "",
)

// Split route for large trades
ExactInSwapRoute(
    cross(cur),
    "gno.land/r/gnoswap/gns.GNS",
    "gno.land/r/gnoland/wugnot.wugnot",
    "10000000000",
    "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",
    "60,40",  // 60% through 0.05%, 40% through 0.3%
    "9500000000",
    deadline,
    "",
)
```

Single-hop functions support partial execution through a nonzero
`sqrtPriceLimitX96`:

```go
// Partial swap with price limit - may not consume full input amount
amountIn, amountOut := ExactInSingleSwapRoute(
    cross(cur),
    "gno.land/r/gnoswap/gns.GNS",       // input token
    "gno.land/r/gnoland/wugnot.wugnot", // output token
    "1000000",                 // max amount to swap
    "gno.land/r/gnoswap/gns.GNS:gno.land/r/gnoland/wugnot.wugnot:3000", // single route
    "950000",                  // min output
    "1000000000000000000",     // sqrtPriceLimitX96 (price limit)
    deadline,
    "",
)
// If the price limit is reached, only a partial amount is swapped. For exact-in
// this can consume less input; exact-out can deliver less than its target.
// 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

Functions

  • DrySwapRoute(inputToken string, outputToken string, specifiedAmount string, swapTypeStr string, strRouteArr string, quoteArr string, tokenAmountLimit string) (string, string, interface {Error func() string})

  • ExactInSingleSwapRoute(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, inputToken string, outputToken string, amountIn string, routeArr string, amountOutMin string, sqrtPriceLimitX96 string, deadline int64, referrer string) (string, string)

  • ExactInSwapRoute(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, inputToken string, outputToken string, amountIn string, routeArr string, quoteArr string, amountOutMin string, deadline int64, referrer string) (string, string)

  • ExactOutSingleSwapRoute(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, inputToken string, outputToken string, amountOut string, routeArr string, amountInMax string, sqrtPriceLimitX96 string, deadline int64, referrer string) (string, string)

  • ExactOutSwapRoute(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, inputToken string, outputToken string, amountOut string, routeArr string, quoteArr string, amountInMax string, deadline int64, referrer string) (string, string)

  • GetImplementationPackagePath() string

  • GetPendingProtocolFees() map[string]int64

  • GetSwapFee() uint64

  • NewRouterStore(kvStore interface {AddAuthorizedCaller func(int, .uverse.realm, .uverse.address, gno.land/p/gnoswap/store/v1.Permission) .uverse.error; Delete func(int, .uverse.realm, string) .uverse.error; Get func(string) (interface {}, .uverse.error); GetAddress func(string) (.uverse.address, .uverse.error); GetAllKeys func() ([]string, .uverse.error); GetAuthorizedCallers func() (map[.uverse.address]gno.land/p/gnoswap/store/v1.Permission, .uverse.error); GetBPTree func(string) (*gno.land/p/nt/bptree/v0.BPTree, .uverse.error); GetBool func(string) (bool, .uverse.error); GetDomainAddress func() .uverse.address; GetInt64 func(string) (int64, .uverse.error); GetString func(string) (string, .uverse.error); GetUint64 func(string) (uint64, .uverse.error); Has func(string) bool; IsWriteAuthorized func(.uverse.address) bool; RemoveAuthorizedCaller func(int, .uverse.realm, .uverse.address) .uverse.error; Set func(int, .uverse.realm, string, interface {}) .uverse.error; UpdateAuthorizedCaller func(int, .uverse.realm, .uverse.address, gno.land/p/gnoswap/store/v1.Permission) .uverse.error}) interface {GetPendingProtocolFee func(string) int64; GetPendingProtocolFees func() map[string]int64; GetSwapFee func() uint64; HasPendingProtocolFeesKey func() bool; HasSwapFeeKey func() bool; RemovePendingProtocolFee func(int, .uverse.realm, string) .uverse.error; SetPendingProtocolFee func(int, .uverse.realm, string, int64) .uverse.error; SetPendingProtocolFees func(int, .uverse.realm, map[string]int64) .uverse.error; SetSwapFee func(int, .uverse.realm, uint64) .uverse.error}

  • RegisterInitializer(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, initializer func(int, .uverse.realm, gno.land/r/gnoswap/router.IRouterStore) gno.land/r/gnoswap/router.IRouter)

  • Render(path string) string

  • SetSwapFee(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, fee uint64)

  • SwapCallback(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, token0Path string, token1Path string, amount0Delta int64, amount1Delta int64, payer string) interface {Error func() string}

  • UpgradeImpl(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, packagePath string)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.