PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

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/pool/v1

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v1
Namespace
gnoswap / pool
Files
24 (README)(gnomod.toml)
Exported functions
3
Module
gno.land/r/gnoswap/pool/v1
gno
0.9

Files (24)

  • README.mdmarkdown
  • gnomod.tomltoml
  • assert.gnogno
  • doc.gnogno
  • errors.gnogno
  • event_info.gnogno
  • factory_param.gnogno
  • getter_utils.gnogno
  • getter.gnogno
  • init.gnogno
  • instance.gnogno
  • lock.gnogno
  • manager.gnogno
  • oracle.gnogno
  • pool.gnogno
  • position.gnogno
  • protocol_fee.gnogno
  • render.gnogno
  • swap.gnogno
  • tick_bitmap.gnogno
  • tick.gnogno
  • transfer.gnogno
  • type.gnogno
  • utils.gnogno
  • manager.gnogno
    1package pool23import (4	"chain"5	"errors"6	"time"78

    Functions

    • GetPoolPath(token0Path string, token1Path string, fee uint32) string

    • NewPoolV1(store interface {GetFeeAmountTickSpacing func() map[uint32]int32; GetObservations func() *gno.land/p/nt/bptree/v0.BPTree; GetPendingProtocolFee func(string) int64; GetPendingProtocolFees func() map[string]int64; GetPoolCreationFee func() int64; GetPools func() *gno.land/p/nt/bptree/v0.BPTree; GetSlot0FeeProtocol func() uint8; GetSwapEndHook func() func(.uverse.realm, string) .uverse.error; GetSwapStartHook func() func(.uverse.realm, string, int64); GetTickCrossHook func() func(.uverse.realm, string, int32, bool, int64); GetUnlocked func() bool; GetWithdrawalFeeBPS func() uint64; HasFeeAmountTickSpacing func() bool; HasObservations func() bool; HasPendingProtocolFees func() bool; HasPoolCreationFee func() bool; HasPools func() bool; HasSlot0FeeProtocol func() bool; HasSwapEndHook func() bool; HasSwapStartHook func() bool; HasTickCrossHook func() bool; HasUnlocked func() bool; HasWithdrawalFeeBPS func() bool; RemovePendingProtocolFee func(int, .uverse.realm, string) .uverse.error; SetFeeAmountTickSpacing func(int, .uverse.realm, map[uint32]int32) .uverse.error; SetObservations func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetPendingProtocolFee func(int, .uverse.realm, string, int64) .uverse.error; SetPendingProtocolFees func(int, .uverse.realm, map[string]int64) .uverse.error; SetPoolCreationFee func(int, .uverse.realm, int64) .uverse.error; SetPools func(int, .uverse.realm, *gno.land/p/nt/bptree/v0.BPTree) .uverse.error; SetSlot0FeeProtocol func(int, .uverse.realm, uint8) .uverse.error; SetSwapEndHook func(int, .uverse.realm, func(.uverse.realm, string) .uverse.error) .uverse.error; SetSwapStartHook func(int, .uverse.realm, func(.uverse.realm, string, int64)) .uverse.error; SetTickCrossHook func(int, .uverse.realm, func(.uverse.realm, string, int32, bool, int64)) .uverse.error; SetUnlocked func(int, .uverse.realm, bool) .uverse.error; SetWithdrawalFeeBPS func(int, .uverse.realm, uint64) .uverse.error}) interface {Burn func(int, .uverse.realm, string, string, uint32, int32, int32, string, .uverse.address) (string, string); Collect func(int, .uverse.realm, string, string, uint32, .uverse.address, int32, int32, string, string) (string, string); CollectProtocol func(int, .uverse.realm, string, string, uint32, .uverse.address, string, string) (string, string); CollectSwapFee func(int, .uverse.realm, string, string, uint32, .uverse.address, int32, int32, string, string) (string, string, string, string); CreatePool func(int, .uverse.realm, string, string, uint32, string); DrySwap func(string, string, uint32, bool, string, string) (string, string, .uverse.error); ExistsPoolPath func(string) bool; GetBalanceToken0 func(string) (int64, .uverse.error); GetBalanceToken1 func(string) (int64, .uverse.error); GetFee func(string) (uint32, .uverse.error); GetFeeAmountTickSpacing func(uint32) (int32, .uverse.error); GetFeeAmountTickSpacings func() map[uint32]int32; GetFeeGrowthGlobal0X128 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetFeeGrowthGlobal1X128 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetFeeGrowthGlobalX128 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, *gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetInitializedTicksInRange func(string, int32, int32) ([]int32, .uverse.error); GetLiquidity func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetObservationAt func(string, uint16) (gno.land/r/gnoswap/pool.Observation, .uverse.error); GetPendingProtocolFees func() map[string]int64; GetPoolCreationFee func() int64; GetPoolPositions func(string) *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree; GetPools func() *gno.land/p/nt/bptree/rotree/v0.ReadOnlyTree; GetPositionFeeGrowthInside0LastX128 func(string, string) (string, .uverse.error); GetPositionFeeGrowthInside1LastX128 func(string, string) (string, .uverse.error); GetPositionFeeGrowthInsideLastX128 func(string, string) (string, string, .uverse.error); GetPositionLiquidity func(string, string) (string, .uverse.error); GetPositionTokensOwed0 func(string, string) (int64, .uverse.error); GetPositionTokensOwed1 func(string, string) (int64, .uverse.error); GetProtocolFeesToken0 func(string) (int64, .uverse.error); GetProtocolFeesToken1 func(string) (int64, .uverse.error); GetSlot0 func(string) gno.land/r/gnoswap/pool.Slot0; GetSlot0FeeProtocol func(string) (uint8, .uverse.error); GetSlot0SqrtPriceX96 func(string) (*gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); GetSlot0Tick func(string) (int32, .uverse.error); GetSlot0Unlocked func(string) (bool, .uverse.error); GetTickBitmaps func(string, int16) (string, .uverse.error); GetTickCumulativeOutside func(string, int32) (int64, .uverse.error); GetTickFeeGrowthOutside0X128 func(string, int32) (string, .uverse.error); GetTickFeeGrowthOutside1X128 func(string, int32) (string, .uverse.error); GetTickFeeGrowthOutsideX128 func(string, int32) (string, string, .uverse.error); GetTickInfo func(string, int32) (gno.land/r/gnoswap/pool.TickInfo, .uverse.error); GetTickInitialized func(string, int32) (bool, .uverse.error); GetTickLiquidityGross func(string, int32) (string, .uverse.error); GetTickLiquidityNet func(string, int32) (string, .uverse.error); GetTickSecondsOutside func(string, int32) (uint32, .uverse.error); GetTickSecondsPerLiquidityOutsideX128 func(string, int32) (string, .uverse.error); GetTickSpacing func(string) (int32, .uverse.error); GetToken0Path func(string) (string, .uverse.error); GetToken1Path func(string) (string, .uverse.error); GetWithdrawalFee func() uint64; IncreaseObservationCardinalityNext func(int, .uverse.realm, string, string, uint32, uint16); Mint func(int, .uverse.realm, string, string, uint32, int32, int32, string, .uverse.address) (string, string); Observe func(string, []uint32) ([]int64, []string, .uverse.error); OracleConsult func(string, uint32) (int32, *gno.land/p/gnoswap/uint256/v1.Uint, .uverse.error); Render func(string) string; SetFeeProtocol func(int, .uverse.realm, uint8, uint8); SetPoolCreationFee func(int, .uverse.realm, int64); SetSwapEndHook func(int, .uverse.realm, func(.uverse.realm, string) .uverse.error); SetSwapStartHook func(int, .uverse.realm, func(.uverse.realm, string, int64)); SetTickCrossHook func(int, .uverse.realm, func(.uverse.realm, string, int32, bool, int64)); SetWithdrawalFee func(int, .uverse.realm, uint64); SnapshotCumulativesInside func(string, int32, int32) (int64, *gno.land/p/gnoswap/uint256/v1.Uint, uint32, .uverse.error); Swap func(int, .uverse.realm, string, string, uint32, .uverse.address, bool, string, string, func(.uverse.realm, int64, int64, *gno.land/r/gnoswap/pool.CallbackMarker) .uverse.error) (string, string)}

    • NewTickEventInfo(tickID int32, tickInfo struct{liquidityGross string; liquidityNet string; feeGrowthOutside0X128 string; feeGrowthOutside1X128 string; tickCumulativeOutside int64; secondsPerLiquidityOutsideX128 string; secondsOutside uint32; initialized bool}) *gno.land/r/gnoswap/pool/v1.tickEventInfo

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

    Rendered

    RenderedRawgnoweb ↗
    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.

    prabc "gno.land/p/gnoswap/rbac/v1"
    9 "gno.land/p/gnoswap/utils/v1"
    10 ufmt "gno.land/p/nt/ufmt/v0"
    11
    12 "gno.land/r/gnoswap/access/v1"
    13 "gno.land/r/gnoswap/common"
    14 gns "gno.land/r/gnoswap/gns"
    15 "gno.land/r/gnoswap/halt/v1"
    16 pl "gno.land/r/gnoswap/pool"
    17
    18 en "gno.land/r/gnoswap/emission"
    19)
    20
    21const GNS_TOKEN_KEY string = "gno.land/r/gnoswap/gns.GNS"
    22
    23// CreatePool creates a new concentrated liquidity pool.
    24//
    25// Deploys a new AMM pool for a token pair with the specified fee tier.
    26// Charges the configured pool-creation fee (100 GNS by default).
    27// Accepts either token path order, canonicalizes token0/token1, and sets the
    28// initial price and tick spacing for that canonical order.
    29//
    30// Parameters:
    31// - _: Noncrossing implementation-call discriminator; pass 0.
    32// - rlm: Current realm context forwarded unchanged by the pool proxy.
    33// - token0Path: first token contract path; either order is accepted and the
    34// stored pair is canonicalized.
    35// - token1Path: second token contract path; either order is accepted and the
    36// stored pair is canonicalized.
    37// - fee: supported pool fee tier (100=0.01%, 500=0.05%, 3000=0.3%, 10000=1%).
    38// - sqrtPriceX96: initial square-root price in Q64.96 format, constrained to
    39// [MIN_SQRT_RATIO, MAX_SQRT_RATIO).
    40//
    41// Tick spacing by fee tier:
    42// - 0.01%: 1 tick
    43// - 0.05%: 10 ticks
    44// - 0.30%: 60 ticks
    45// - 1.00%: 200 ticks
    46//
    47// Requirements:
    48// - Tokens must be different after canonicalization/wrapping
    49// - Fee tier must be supported
    50// - Pool must not already exist
    51// - Caller must have the configured creation fee
    52//
    53// Price initialization:
    54// - The initial sqrt price must be in [MIN_SQRT_RATIO, MAX_SQRT_RATIO).
    55// - No oracle or external market-price sanity check is performed.
    56// - If paths are supplied in reverse order, the stored sqrt price is
    57// inverted to match the canonical token0/token1 order.
    58//
    59// Recovery from an economically inappropriate price:
    60// 1. Add wide-range liquidity at the current price.
    61// 2. Execute a swap to move the price toward the desired market rate.
    62// 3. Remove the liquidity.
    63//
    64// The executor's LP losses offset arbitrage gains, minus fees and slippage.
    65func (i *poolV1) CreatePool(
    66 _ int,
    67 rlm realm,
    68 token0Path string,
    69 token1Path string,
    70 fee uint32,
    71 sqrtPriceX96 string,
    72) {
    73 access.AssertIsRlmCurrent(0, rlm)
    74
    75 i.assertPoolUnlocked()
    76 halt.AssertIsNotHaltedPool()
    77
    78 assertIsSupportedFeeTier(fee)
    79 assertIsNotExistsPoolPath(i, token0Path, token1Path, fee)
    80
    81 i.lockPool(0, rlm)
    82 defer i.unlockPool(0, rlm)
    83
    84 en.MintAndDistributeGns(cross(rlm))
    85
    86 slot0FeeProtocol := i.store.GetSlot0FeeProtocol()
    87
    88 tickSpacing, err := i.GetFeeAmountTickSpacing(fee)
    89 if err != nil {
    90 panic(err)
    91 }
    92
    93 poolInfo := newPoolParams(
    94 token0Path,
    95 token1Path,
    96 fee,
    97 sqrtPriceX96,
    98 tickSpacing,
    99 slot0FeeProtocol,
    100 )
    101
    102 err = poolInfo.update()
    103 if err != nil {
    104 panic(err)
    105 }
    106
    107 // validate token paths are not the same after wrapping
    108 assertIsNotEqualsTokens(poolInfo.token0Path, poolInfo.token1Path)
    109
    110 // check if wrapped token paths are registered
    111 common.MustRegistered(poolInfo.token0Path, poolInfo.token1Path)
    112
    113 pool := newPool(poolInfo)
    114 poolPath := poolInfo.poolPath()
    115
    116 pools := i.store.GetPools()
    117 pools.Set(poolPath, pool)
    118
    119 err = i.store.SetPools(0, rlm, pools)
    120 if err != nil {
    121 panic(err)
    122 }
    123
    124 observations := i.store.GetObservations()
    125 observations.Set(poolPath, pl.NewPoolObservationsTree(time.Now().Unix()))
    126 if err := i.store.SetObservations(0, rlm, observations); err != nil {
    127 panic(err)
    128 }
    129
    130 poolCreationFee := i.store.GetPoolCreationFee()
    131
    132 if poolCreationFee > 0 {
    133 previousRealm := rlm.Previous()
    134 previousRealmAddr := previousRealm.Address()
    135 poolAddr := access.MustGetAddress(prabc.ROLE_POOL.String())
    136 gns.TransferFrom(cross(rlm), previousRealmAddr, poolAddr, poolCreationFee)
    137
    138 chain.Emit(
    139 "PoolCreationFee",
    140 "prevAddr", previousRealmAddr.String(),
    141 "prevRealm", previousRealm.PkgPath(),
    142 "poolPath", poolPath,
    143 "feeTokenPath", GNS_TOKEN_KEY,
    144 "feeAmount", utils.FormatInt(poolCreationFee),
    145 )
    146 }
    147
    148 i.settleProtocolFee(0, rlm, GNS_TOKEN_KEY, poolCreationFee)
    149
    150 previousRealm := rlm.Previous()
    151 chain.Emit(
    152 "CreatePool",
    153 "prevAddr", previousRealm.Address().String(),
    154 "prevRealm", previousRealm.PkgPath(),
    155 "token0Path", poolInfo.Token0Path(),
    156 "token1Path", poolInfo.Token1Path(),
    157 "fee", utils.FormatUint(fee),
    158 "sqrtPriceX96", poolInfo.SqrtPriceX96().ToString(),
    159 "poolPath", poolPath,
    160 "tick", utils.FormatInt(pool.Slot0Tick()),
    161 "tickSpacing", utils.FormatInt(poolInfo.TickSpacing()),
    162 )
    163}
    164
    165// SetFeeProtocol sets the protocol fee denominators for all managed pools.
    166//
    167// Parameters:
    168// - _: Noncrossing implementation-call discriminator; pass 0.
    169// - rlm: Current realm context forwarded unchanged by the pool proxy.
    170// - feeProtocol0: token0 protocol-fee denominator; must be 0 or 4-10.
    171// Zero disables protocol fees; 4 routes one quarter of swap fees and 10
    172// routes one tenth to the protocol.
    173// - feeProtocol1: token1 protocol-fee denominator with the same 0 or 4-10
    174// constraint and fee share semantics as feeProtocol0.
    175//
    176// Only callable by admin or governance.
    177func (i *poolV1) SetFeeProtocol(_ int, rlm realm, feeProtocol0, feeProtocol1 uint8) {
    178 access.AssertIsRlmCurrent(0, rlm)
    179
    180 i.assertPoolUnlocked()
    181 halt.AssertIsNotHaltedPool()
    182
    183 caller := rlm.Previous().Address()
    184 access.AssertIsAdminOrGovernance(caller)
    185
    186 i.lockPool(0, rlm)
    187 defer i.unlockPool(0, rlm)
    188
    189 err := i.setFeeProtocolInternal(0, rlm, feeProtocol0, feeProtocol1)
    190 if err != nil {
    191 panic(err)
    192 }
    193}
    194
    195// setFeeAmountTickSpacing associates a tick spacing value with a fee amount.
    196func (i *poolV1) setFeeAmountTickSpacing(_ int, rlm realm, fee uint32, tickSpacing int32) error {
    197 feeAmountTickSpacing := i.store.GetFeeAmountTickSpacing()
    198 feeAmountTickSpacing[fee] = tickSpacing
    199
    200 return i.store.SetFeeAmountTickSpacing(0, rlm, feeAmountTickSpacing)
    201}
    202
    203func (i *poolV1) getPool(poolPath string) (*pl.Pool, error) {
    204 pools := i.store.GetPools()
    205
    206 iPool := pools.Get(poolPath)
    207 if iPool == nil {
    208 return nil, ufmt.Errorf("expected poolPath(%s) to exist", poolPath)
    209 }
    210
    211 p, ok := iPool.(*pl.Pool)
    212 if !ok {
    213 return nil, ufmt.Errorf("failed to cast pool to *Pool: %T", iPool)
    214 }
    215
    216 return p, nil
    217}
    218
    219// mustGetPool retrieves a pool instance by its path and ensures it exists.
    220func (i *poolV1) mustGetPool(poolPath string) (pool *pl.Pool) {
    221 p, err := i.getPool(poolPath)
    222 if err != nil {
    223 panic(makeErrorWithDetails(errDataNotFound, err.Error()))
    224 }
    225
    226 return p
    227}
    228
    229func (i *poolV1) mustGetPoolBy(token0Path, token1Path string, fee uint32) *pl.Pool {
    230 poolPath := GetPoolPath(token0Path, token1Path, fee)
    231 return i.mustGetPool(poolPath)
    232}
    233
    234func (i *poolV1) getObservations(poolPath string) (*pl.ObservationTree, error) {
    235 observations := i.store.GetObservations()
    236 value := observations.Get(poolPath)
    237 if value == nil {
    238 return nil, ufmt.Errorf("expected observations for poolPath(%s) to exist", poolPath)
    239 }
    240
    241 tree, ok := value.(*pl.ObservationTree)
    242 if !ok {
    243 return nil, ufmt.Errorf("failed to cast observations for poolPath(%s): %T", poolPath, value)
    244 }
    245
    246 return tree, nil
    247}
    248
    249func (i *poolV1) mustGetObservations(poolPath string) *pl.ObservationTree {
    250 tree, err := i.getObservations(poolPath)
    251 if err != nil {
    252 panic(makeErrorWithDetails(errDataNotFound, err.Error()))
    253 }
    254 return tree
    255}
    256
    257// savePool updates a pool in the pools map and persists to storage.
    258// This ensures that modifications to pool objects are properly saved.
    259func (i *poolV1) savePool(_ int, rlm realm, pool *pl.Pool) error {
    260 pools := i.store.GetPools()
    261 pools.Set(pool.PoolPath(), pool)
    262
    263 return i.store.SetPools(0, rlm, pools)
    264}
    265
    266// setFeeProtocolInternal updates the protocol fee for all pools and emits an event.
    267func (i *poolV1) setFeeProtocolInternal(_ int, rlm realm, feeProtocol0, feeProtocol1 uint8) error {
    268 oldFee := i.store.GetSlot0FeeProtocol()
    269 newFee, err := i.setFeeProtocol(0, rlm, feeProtocol0, feeProtocol1)
    270 if err != nil {
    271 return err
    272 }
    273
    274 feeProtocol0Old := oldFee % 16
    275 feeProtocol1Old := oldFee >> 4
    276
    277 previousRealm := rlm.Previous()
    278 chain.Emit(
    279 "SetFeeProtocol",
    280 "prevAddr", previousRealm.Address().String(),
    281 "prevRealm", previousRealm.PkgPath(),
    282 "prevFeeProtocol0", utils.FormatUint(feeProtocol0Old),
    283 "prevFeeProtocol1", utils.FormatUint(feeProtocol1Old),
    284 "feeProtocol0", utils.FormatUint(feeProtocol0),
    285 "feeProtocol1", utils.FormatUint(feeProtocol1),
    286 "newFee", utils.FormatUint(newFee),
    287 )
    288
    289 return nil
    290}
    291
    292// setFeeProtocol updates the protocol fee configuration for all managed pools.
    293//
    294// This function combines the protocol fee values for token0 and token1 into a single `uint8` value,
    295// where:
    296// - Lower 4 bits store feeProtocol0 (for token0).
    297// - Upper 4 bits store feeProtocol1 (for token1).
    298//
    299// The updated fee protocol is applied uniformly to all pools managed by the system.
    300//
    301// Parameters:
    302// - feeProtocol0: protocol fee for token0 (must be 0 or between 4 and 10 inclusive).
    303// - feeProtocol1: protocol fee for token1 (must be 0 or between 4 and 10 inclusive).
    304//
    305// Returns:
    306// - newFee (uint8): the combined fee protocol value.
    307//
    308// Example:
    309// If feeProtocol0 = 4 and feeProtocol1 = 5:
    310//
    311// newFee = 4 + (5 << 4)
    312// // Results in: 0x54 (84 in decimal)
    313// // Binary: 0101 0100
    314// // ^^^^ ^^^^
    315// // fee1=5 fee0=4
    316//
    317// Notes:
    318// - This function ensures that all pools under management are updated to use the same fee protocol.
    319// - Caller restrictions (e.g., admin or governance) are not enforced in this function.
    320// - Ensure the system is not halted before updating fees.
    321func (i *poolV1) setFeeProtocol(_ int, rlm realm, feeProtocol0, feeProtocol1 uint8) (uint8, error) {
    322 if err := validateFeeProtocol(feeProtocol0, feeProtocol1); err != nil {
    323 return 0, err
    324 }
    325
    326 // combine both protocol fee into a single byte:
    327 // - feePrtocol0 occupies the lower 4 bits
    328 // - feeProtocol1 is shifted the lower 4 positions to occupy the upper 4 bits
    329 newFee := feeProtocol0 + (feeProtocol1 << 4) // ( << 4 ) = ( * 16 )
    330
    331 pools := i.store.GetPools()
    332
    333 // Update slot0 for each pool
    334 pools.Iterate("", "", func(poolPath string, poolI any) bool {
    335 pool, ok := poolI.(*pl.Pool)
    336 if !ok {
    337 panic(ufmt.Errorf("failed to cast pool to *Pool: %T", pool))
    338 }
    339
    340 slot0 := pool.Slot0()
    341 slot0.SetFeeProtocol(newFee)
    342
    343 pool.SetSlot0(slot0)
    344 return false
    345 })
    346
    347 // update slot0
    348 err := i.store.SetSlot0FeeProtocol(0, rlm, newFee)
    349 if err != nil {
    350 return 0, err
    351 }
    352
    353 return newFee, nil
    354}
    355
    356// validateFeeProtocol validates the fee protocol values for token0 and token1.
    357//
    358// This function checks whether the provided fee protocol values (`feeProtocol0` and `feeProtocol1`)
    359// are valid using the `isValidFeeProtocolValue` function. If either value is invalid, it returns
    360// an error indicating that the protocol fee percentage is invalid.
    361//
    362// Parameters:
    363// - feeProtocol0: uint8, the fee protocol value for token0.
    364// - feeProtocol1: uint8, the fee protocol value for token1.
    365//
    366// Returns:
    367// - error: Returns `errInvalidProtocolFeePct` if either `feeProtocol0` or `feeProtocol1` is invalid.
    368// Returns `nil` if both values are valid.
    369func validateFeeProtocol(feeProtocol0, feeProtocol1 uint8) error {
    370 if !isValidFeeProtocolValue(feeProtocol0) || !isValidFeeProtocolValue(feeProtocol1) {
    371 return errors.New(errInvalidProtocolFeePct)
    372 }
    373 return nil
    374}
    375
    376// isValidFeeProtocolValue checks if a fee protocol value is within acceptable range.
    377// Valid values are either 0 (disabled) or between 4 and 10 inclusive.
    378//
    379// The value is used as a denominator: protocolFee = swapFee / feeProtocol
    380// (e.g., feeProtocol=4 means 1/4 = 25% of swap fees go to protocol)
    381func isValidFeeProtocolValue(value uint8) bool {
    382 return value == 0 || (value >= 4 && value <= 10)
    383}
    384