Kind Realm (renderable)
Name v2
Namespace gnoswap / router
Exported functions 7
Module gno.land/r/gnoswap/router/v2
gno 0.9 utils.gno gno
⧉
1 package router 2 3 import ( 4 "bytes" 5 "errors" 6 "strconv" 7 "strings" 8 BuildSingleHopRoutePath (tokenA string, tokenB string, fee uint32) string
NewExactInParams (baseParams struct{InputToken string; OutputToken string; RouteArr string; QuoteArr string; SqrtPriceLimitX96 *gno.land/p/gnoswap/uint256/v1.Uint; Deadline int64}, amountIn int64, amountOutMin int64) struct{BaseSwapParams gno.land/r/gnoswap/router/v2.BaseSwapParams; AmountIn int64; AmountOutMin int64}
NewExactInSwapOperation (r *gno.land/r/gnoswap/router/v2.routerV1, pp struct{BaseSwapParams gno.land/r/gnoswap/router/v2.BaseSwapParams; AmountIn int64; AmountOutMin int64}) *gno.land/r/gnoswap/router/v2.ExactInSwapOperation
NewExactOutParams (baseParams struct{InputToken string; OutputToken string; RouteArr string; QuoteArr string; SqrtPriceLimitX96 *gno.land/p/gnoswap/uint256/v1.Uint; Deadline int64}, amountOut int64, amountInMax int64) struct{BaseSwapParams gno.land/r/gnoswap/router/v2.BaseSwapParams; AmountOut int64; AmountInMax int64}
NewExactOutSwapOperation (r *gno.land/r/gnoswap/router/v2.routerV1, pp struct{BaseSwapParams gno.land/r/gnoswap/router/v2.BaseSwapParams; AmountOut int64; AmountInMax int64}) *gno.land/r/gnoswap/router/v2.ExactOutSwapOperation
NewRouteParser () *gno.land/r/gnoswap/router/v2.RouteParser
NewRouterV1 (routerStore 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}) interface {DrySwapRoute func(string, string, string, string, string, string, string) (string, string, .uverse.error); ExactInSingleSwapRoute func(int, .uverse.realm, string, string, string, string, string, string, int64, string) (string, string); ExactInSwapRoute func(int, .uverse.realm, string, string, string, string, string, string, int64, string) (string, string); ExactOutSingleSwapRoute func(int, .uverse.realm, string, string, string, string, string, string, int64, string) (string, string); ExactOutSwapRoute func(int, .uverse.realm, string, string, string, string, string, string, int64, string) (string, string); GetPendingProtocolFees func() map[string]int64; GetSwapFee func() uint64; Render func(string) string; SetSwapFee func(int, .uverse.realm, uint64); SwapCallback func(int, .uverse.realm, string, string, int64, int64, .uverse.address) .uverse.error}
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
This realm does not provide renderable output (vm/qrender → NoRenderDeclError).
View on gnoweb ↗ 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.
9 gnsmath "gno.land/p/gnoswap/gnsmath/v1"
10 "gno.land/p/gnoswap/utils/v1"
11 ufmt "gno.land/p/nt/ufmt/v0"
12 "gno.land/r/gnoswap/pool"
13
14 i256 "gno.land/p/gnoswap/int256/v1"
15 u256 "gno.land/p/gnoswap/uint256/v1"
16 )
17
18 // calculateSwapAmountByQuote calculates swap amount based on quote percentage.
19 func calculateSwapAmountByQuote(amountSpecified int64 , quote string ) ( int64 , error ) {
20 quoteInt, err := strconv.ParseInt(quote, 10 , 64 )
21 if err != nil {
22 return 0 , ufmt.Errorf( "invalid quote(%s)" , quote)
23 }
24
25 if quoteInt < MinQuotePercentage || quoteInt > MaxQuotePercentage {
26 return 0 , ufmt.Errorf(ErrInvalidQuoteRange, quoteInt, MinQuotePercentage, MaxQuotePercentage)
27 }
28
29 toSwap := gnsmath.SafeMulDivInt64(amountSpecified, quoteInt, PERCENTAGE_DENOMINATOR)
30 if toSwap == 0 {
31 return 0 , errors.New(errInvalidSwapAmount)
32 }
33
34 return toSwap, nil
35 }
36
37 // assertHopsInRange ensures the number of hops is within the valid range of 1-3.
38 func assertHopsInRange(hops int ) {
39 switch hops {
40 case 1 , 2 , 3 :
41 return
42 default :
43 panic(errors.New(errHopsOutOfRange))
44 }
45 }
46
47 // getDataForSinglePath extracts token addresses and fee from a single pool path.
48 //
49 // IMPORTANT: This function returns tokens in the order they appear in the route string,
50 // which represents the swap direction (tokenIn:tokenOut:fee), NOT the canonical pool ordering.
51 func getDataForSinglePath(poolPath string ) (token0, token1 string , fee uint32 ) {
52 token0, token1, fee, err := getDataForSinglePathWithError(poolPath)
53 if err != nil {
54 panic(err)
55 }
56
57 return token0, token1, fee
58 }
59
60 // getDataForSinglePathWithError extracts token addresses and fee from a single pool path with error handling.
61 func getDataForSinglePathWithError(poolPath string ) ( string , string , uint32 , error ) {
62 poolPathSplit := strings.Split(poolPath, ":" )
63 if len(poolPathSplit) != 3 {
64 return "" , "" , 0 , makeErrorWithDetails(
65 errInvalidPoolPath,
66 ufmt.Sprintf( "len(poolPathSplit) != 3, poolPath: %s" , poolPath),
67 )
68 }
69
70 poolPathSplit[ 0 ] = strings.TrimSpace(poolPathSplit[ 0 ])
71 poolPathSplit[ 1 ] = strings.TrimSpace(poolPathSplit[ 1 ])
72
73 if poolPathSplit[ 0 ] == "" || poolPathSplit[ 1 ] == "" {
74 return "" , "" , 0 , makeErrorWithDetails(
75 errInvalidPoolPath,
76 ufmt.Sprintf( "token addresses cannot be empty: %s" , poolPath),
77 )
78 }
79
80 f, err := strconv.Atoi(poolPathSplit[ 2 ])
81 if err != nil {
82 return "" , "" , 0 , makeErrorWithDetails(
83 errInvalidPoolPath,
84 ufmt.Sprintf( "invalid fee: %s" , poolPathSplit[ 2 ]),
85 )
86 }
87
88 return poolPathSplit[ 0 ], poolPathSplit[ 1 ], uint32 (f), nil
89 }
90
91 // getDataForMultiPath extracts token addresses and fee from a multi-hop path at specified index.
92 func getDataForMultiPath(possiblePath string , poolIdx int ) (token0, token1 string , fee uint32 ) {
93 pools := strings.Split(possiblePath, POOL_SEPARATOR)
94
95 switch poolIdx {
96 case 0 :
97 return getDataForSinglePath(pools[ 0 ])
98 case 1 :
99 return getDataForSinglePath(pools[ 1 ])
100 case 2 :
101 return getDataForSinglePath(pools[ 2 ])
102 default :
103 return "" , "" , uint32 ( 0 )
104 }
105 }
106
107 // i256MinMax returns the absolute values of x and y in min-max order.
108 func i256MinMax(x, y *i256.Int) (min, max *u256.Uint) {
109 if x.Lt(y) || x.Eq(y) {
110 return x.Abs(), y.Abs()
111 }
112 return y.Abs(), x.Abs()
113 }
114
115 // validateRoutePaths validates multiple route paths to ensure they all start with inputToken and end with outputToken.
116 // This function processes comma-separated route paths and validates each path individually.
117 //
118 // Validates:
119 // - Each route path starts with the specified inputToken
120 // - Each route path ends with the specified outputToken
121 // - Route path format consistency (prevents swap-direction vs alphabetical pool ordering confusion)
122 //
123 // Parameters:
124 // - routePathArrString: comma-separated route paths (e.g., "gno.land/r/gnoland/wugnot.wugnot:gno.land/r/gnoswap/gns.GNS:500,gno.land/r/gnoland/wugnot.wugnot:gno.land/r/gnoswap/gns.GNS:3000")
125 // - inputToken: expected first token in all route paths
126 // - outputToken: expected last token in all route paths
127 //
128 // Examples:
129 // - Single route: "tokenA:tokenB:500" with inputToken="tokenA", outputToken="tokenB"
130 // - Multi-route: "tokenA:tokenB:500,tokenA:tokenC:3000*POOL*tokenC:tokenB:500" with inputToken="tokenA", outputToken="tokenB"
131 //
132 // Returns error if any route path validation fails.
133 func validateRoutePaths(routePathArrString, inputToken, outputToken string ) error {
134 routePaths := strings.Split(routePathArrString, "," )
135
136 for _, routePath := range routePaths {
137 if err := validateRoutePath(routePath, inputToken, outputToken); err != nil {
138 return err
139 }
140 }
141
142 return nil
143 }
144
145 // validateRoutePath validates a single route path to ensure it starts with inputToken and ends with outputToken.
146 // This function handles both single-hop and multi-hop route paths.
147 //
148 // Validates:
149 // - Route path starts with the specified inputToken
150 // - Route path ends with the specified outputToken
151 // - Proper token ordering in swap direction (not alphabetical pool ordering)
152 //
153 // Route Path Formats:
154 // - single-hop: "tokenA:tokenB:fee" (direct swap between two tokens)
155 // - multi-hop: "tokenA:tokenB:fee1*POOL*tokenB:tokenC:fee2" (swap through intermediate tokens)
156 //
157 // Parameters:
158 // - routePath: single route path string (e.g., "gno.land/r/gnoland/wugnot.wugnot:gno.land/r/gnoswap/gns.GNS:500")
159 // - inputToken: expected first token in the route path
160 // - outputToken: expected last token in the route path
161 //
162 // Examples:
163 // - single-hop: "tokenA:tokenB:500" with inputToken="tokenA", outputToken="tokenB"
164 // - multi-hop: "tokenA:tokenB:3000*POOL*tokenB:tokenC:500" with inputToken="tokenA", outputToken="tokenC"
165 //
166 // Returns error with specific details if validation fails.
167 func validateRoutePath(routePath, inputToken, outputToken string ) error {
168 // Extract first and last tokens from the routePath
169 var (
170 firstToken, lastToken string
171 err error
172 )
173
174 // multi-hop routePath
175 if strings.Contains(routePath, POOL_SEPARATOR) {
176 pools := strings.Split(routePath, POOL_SEPARATOR)
177
178 // Get first token from first pool
179 firstPool := pools[ 0 ]
180 firstToken, _, _, err = getDataForSinglePathWithError(firstPool)
181 if err != nil {
182 return err
183 }
184
185 // Validate hop-to-hop continuity: output token of hop N must equal input token of hop N+1
186 err = validatePoolPathHopContinuity(pools)
187 if err != nil {
188 return err
189 }
190
191 // Get last token from last pool
192 lastPool := pools[len(pools)- 1 ]
193 _, lastToken, _, err = getDataForSinglePathWithError(lastPool)
194 if err != nil {
195 return err
196 }
197 } else {
198 // single-hop routePath
199 firstToken, lastToken, _, err = getDataForSinglePathWithError(routePath)
200 if err != nil {
201 return err
202 }
203 }
204
205 if firstToken == "" || lastToken == "" {
206 return makeErrorWithDetails(errInvalidRoutePath, ufmt.Sprintf( "firstToken: %s, lastToken: %s" , firstToken, lastToken))
207 }
208
209 // Validate consistency
210 if firstToken != inputToken {
211 return makeErrorWithDetails(errInvalidRouteFirstToken, ufmt.Sprintf( "firstToken: %s, inputToken: %s" , firstToken, inputToken))
212 }
213
214 if lastToken != outputToken {
215 return makeErrorWithDetails(errInvalidRouteLastToken, ufmt.Sprintf( "lastToken: %s, outputToken: %s" , lastToken, outputToken))
216 }
217
218 return nil
219 }
220
221 func validatePoolPathHopContinuity(routePoolPaths [] string ) error {
222 if len(routePoolPaths) < 2 {
223 return nil
224 }
225
226 _, previousOut, _, pErr := getDataForSinglePathWithError(routePoolPaths[ 0 ])
227 if pErr != nil {
228 return pErr
229 }
230
231 for i := 1 ; i < len(routePoolPaths); i++ {
232 nextIn, nextOut, _, nErr := getDataForSinglePathWithError(routePoolPaths[i])
233 if nErr != nil {
234 return nErr
235 }
236
237 if previousOut != nextIn {
238 return makeErrorWithDetails(
239 errRouteHopDisconnected,
240 ufmt.Sprintf( "hop %d output(%s) != hop %d input(%s)" , i- 1 , previousOut, i, nextIn),
241 )
242 }
243
244 previousOut = nextOut
245 }
246
247 return nil
248 }
249
250 // splitSingleChar splits a string by a single character separator.
251 // This function is optimized for splitting strings with a single-byte separator
252 // and is more memory efficient than strings.Split for this use case.
253 func splitSingleChar(s string , sep byte ) [] string {
254 if s == "" {
255 return [] string { "" }
256 }
257
258 result := make([] string , 0 , bytes.Count([] byte (s), [] byte {sep})+ 1 )
259 start := 0
260 for i := range s {
261 if s[i] == sep {
262 result = append(result, s[start:i])
263 start = i + 1
264 }
265 }
266 result = append(result, s[start:])
267 return result
268 }
269
270 // parsePoolPathsByRoutePathArr parses route path array string and returns a slice of pool paths.
271 // This function converts route paths (which maintain swap direction) into canonical pool paths
272 // (which use alphabetical token ordering).
273 //
274 // The function processes comma-separated route paths and extracts individual pool information
275 // from each route, ensuring tokens are ordered alphabetically for consistent pool identification.
276 //
277 // Parameters:
278 // - routePathArr: comma-separated route paths string containing single or multi-hop routes
279 // Format examples:
280 // - Single route: "tokenA:tokenB:500"
281 // - Multiple routes: "tokenA:tokenB:500,tokenC:tokenD:3000"
282 // - Multi-hop routes: "tokenA:tokenB:500*POOL*tokenB:tokenC:3000"
283 //
284 // Returns:
285 // - []string: slice of canonical pool paths with alphabetically ordered tokens
286 // - error: parsing error if any route path is invalid
287 //
288 // Example:
289 //
290 // input: "gno.land/r/gnoland/wugnot.wugnot:gno.land/r/gnoswap/gns.GNS:500,gno.land/r/gnoland/wugnot.wugnot:gno.land/r/gnoswap/gns.GNS:3000"
291 // output: ["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"]
292 func parsePoolPathsByRoutePathArr(routePathArr string ) ([] string , error ) {
293 poolPaths := make([] string , 0 )
294
295 for _, routePaths := range strings.Split(routePathArr, "," ) {
296 for _, routePath := range strings.Split(routePaths, POOL_SEPARATOR) {
297 token0Path, token1Path, fee, err := getDataForSinglePathWithError(routePath)
298 if err != nil {
299 return [] string {}, err
300 }
301
302 if token0Path > token1Path {
303 token0Path, token1Path = token1Path, token0Path
304 }
305
306 poolPath := pool.GetPoolPath(token0Path, token1Path, fee)
307 poolPaths = append(poolPaths, poolPath)
308 }
309 }
310
311 return poolPaths, nil
312 }
313
314 // BuildSingleHopRoutePath formats one route-direction single-hop path.
315 //
316 // Parameters:
317 // - tokenA: input token contract path
318 // - tokenB: output token contract path
319 // - fee: pool fee tier appended as a decimal value
320 //
321 // Returns:
322 // - routePath: tokenA:tokenB:fee formatted route string; panics for empty or identical tokens
323 func BuildSingleHopRoutePath(tokenA, tokenB string , fee uint32 ) string {
324 if tokenA == "" || tokenB == "" {
325 panic( "token addresses cannot be empty" )
326 }
327 if tokenA == tokenB {
328 panic( "tokenA and tokenB cannot be the same" )
329 }
330
331 return tokenA + ":" + tokenB + ":" + utils.FormatUint(fee)
332 }
333