Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

KuruFlow Router

Gas-lean byte-code DEX aggregator for Monad that executes byte-encoded programs to compose swaps across multiple exchanges in one atomic transaction.

Byte Encoding Flow

The router reads a byte stream sequentially using an InputStream. Here's the exact flow of how bytes are parsed:

1. Main Execution Loop

The program consists of a sequence of operations. Each operation starts with:

uint8 opcode

The router reads opcodes in a loop until the byte stream is empty.

2. Opcode Processing

Based on the opcode read, the router expects specific subsequent bytes:

0x01 - PROCESS_ROUTER_ERC20

Uses router's existing token balance

Byte sequence:

uint8(0x01)           // Opcode
address(token)        // Token to process from router's balance
<distribution_block>  // Distribution and swap parameters

What happens:

  • Reads the token address (20 bytes)
  • Gets router's balance of that token
  • Proceeds to distribution block

0x02 - PROCESS_USER_ERC20

Pulls tokens from user (first encounter only)

Byte sequence:

uint8(0x02)           // Opcode
address(token)        // Token to pull from user
<distribution_block>  // Distribution and swap parameters

What happens:

  • Reads the token address (20 bytes)
  • Validates it matches the expected input token
  • On first encounter, pulls amountIn from user
  • Proceeds to distribution block

0x03 - PROCESS_NATIVE

Uses msg.value (native MON)

Byte sequence:

uint8(0x03)           // Opcode
<distribution_block>  // Distribution and swap parameters (no token address)

What happens:

  • No additional reads - uses address(this).balance as amount
  • Proceeds to distribution block

0x04 - PROCESS_ONE_POOL

Single downstream pool optimization (saves one token transfer)

Byte sequence:

uint8(0x04)           // Opcode
address(token)        // Token to process
<swap_block>          // Single swap parameters (no distribution)

What happens:

  • Reads the token address (20 bytes)
  • Directly calls swap with amount = 0
  • When amount = 0, the router calculates the effective input by reading the pool's balance delta
  • This saves one token transfer: tokens can be sent directly to the pool, bypassing the router temporarily
  • Proceeds directly to swap block (skips distribution)

0x05 - APPLY_PERMIT

Apply ERC-2612 permit

Byte sequence:

uint8(0x05)           // Opcode
address(token)        // Token contract address (20 bytes)
uint256(value)        // Permit amount (32 bytes)
uint256(deadline)     // Permit deadline (32 bytes)
uint8(v)              // Signature v component (1 byte)
uint256(r)            // Signature r component (32 bytes)
uint256(s)            // Signature s component (32 bytes)

What happens:

  • Reads all permit parameters sequentially
  • Calls permit() on the token contract
  • Continues to next opcode (no swap happens)

3. Distribution Block

For opcodes 0x01, 0x02, and 0x03, after the opcode-specific reads, the router reads:

uint8(numDistributions)    // Number of ways to split the amount

Then for each distribution (repeated numDistributions times):

uint16(share)              // Share of total amount (out of 65535)
<swap_block>               // Swap parameters for this distribution

Share calculation:

  • amount = (totalAmount * share) / 65535
  • All shares should sum to 65535 (100%)
  • Each distribution gets its calculated amount and proceeds to swap

4. Swap Block

For each swap (whether from distribution or direct), the router reads:

uint8(poolType)            // Pool type identifier
<pool_specific_params>     // Parameters specific to pool type

Pool Type 0 - PT_UNIV2 (Uniswap V2)

Byte sequence:

uint8(0)                   // Pool type
address(pool)              // UniV2 pair address (20 bytes)
uint8(direction)           // 0: token0→token1, 1: token1→token0
uint24(fee)                // Fee in basis points (3 bytes)

Notes:

  • Output tokens are always sent to address(this) (the router)
  • Supports single pool optimization: when used with opcode 0x04, calculates input from pool balance delta

Pool Type 1 - PT_UNIV3 (Uniswap V3 / PancakeV3)

Byte sequence:

uint8(1)                   // Pool type
address(pool)              // UniV3 pool address (20 bytes)
uint8(direction)           // 0: token1→token0, 1: token0→token1 (converted to bool)

Notes:

  • Output tokens are always sent to address(this) (the router)
  • Uses callback mechanism (uniswapV3SwapCallback, pancakeV3SwapCallback, zfV3SwapCallback)

Pool Type 2 - PT_WRAP (Native wrap/unwrap)

Byte sequence:

uint8(2)                   // Pool type
uint8(flags)               // Bit 0: 1=wrap, 0=unwrap | Bit 1: 1=custom WMON address
[address(wmonAddress)]     // Only if flags & 2 == 2 (20 bytes)

Flag bits:

  • flags & 1 == 1: Wrap (MON → WMON)
  • flags & 1 == 0: Unwrap (WMON → MON)
  • flags & 2 == 2: Read custom WMON address
  • flags & 2 == 0: Use default WMON address

Notes:

  • Output is held by the router (does not need explicit transfer)

Pool Type 3 - PT_KURU (Kuru orderbook)

Byte sequence:

uint8(3)                   // Pool type
address(market)            // Kuru market address (20 bytes)
uint8(isBuy)               // 0: sell, 1: buy
uint32(pricePrecision)     // Price precision multiplier (4 bytes)
uint256(sizePrecision)     // Size precision multiplier (32 bytes)
uint8(tokenDecimals)       // Input token decimals (1 byte)

Parameter calculation:

uint256 divisor = 10 ** tokenDecimals;
uint96 param = isBuy
    ? (amountIn * pricePrecision) / divisor    // For buy orders
    : (amountIn * sizePrecision) / divisor;    // For sell orders

Pool Type 4 - PT_CRYSTAL (Crystal market)

Byte sequence:

uint8(4)                   // Pool type
address(market)            // Crystal market address (20 bytes)
uint8(isBuy)               // 0: sell, 1: buy
uint256(orderType)         // Crystal-specific order type (32 bytes)

Pool Type 5 - PT_CURVE (Curve pools)

Byte sequence:

uint8(5)                   // Pool type
address(pool)              // Curve pool address (20 bytes)
uint8(poolType)            // 0: StableSwap, 1: Crypto
uint8(fromIndex)           // Input token index in pool (cast to int128)
uint8(toIndex)             // Output token index in pool (cast to int128)

Notes:

  • Supports both StableSwap and Crypto pool variants
  • Handles native token swaps when tokenIn is NATIVE
  • Legacy pools that don't return amountOut are not supported

Pool Type 6 - PT_PALINDROME_FI (PalindromeFi)

Byte sequence:

uint8(6)                   // Pool type
address(pool)              // PalindromeFi pool address (20 bytes)
uint8(tokenInIsBase)       // 0: tokenIn is quote, 1: tokenIn is base

Notes:

  • Uses quoteAndSwap function with min_dy = 0
  • Output is sent to address(this) (the router)

Pool Type 7 - PT_LIQUIDITY_BOOK (TraderJoe/BeanExchange)

Byte sequence:

uint8(7)                   // Pool type
address(pool)              // LBPair pool address (20 bytes)
uint8(swapForY)            // 0: swap for tokenX, 1: swap for tokenY

Notes:

  • Tokens are transferred to the pool before calling swap()
  • Output is sent to address(this) (the router)

Building Programs Step by Step

Step 1: Choose Starting Opcode

Determine how tokens enter the router:

  • 0x02: Pull from user (most common)
  • 0x03: Use native MON from msg.value
  • 0x01: Use router's existing balance (for multi-hop)
  • 0x05: Apply permit first (if needed)

Step 2: Add Distribution

For opcodes 0x01, 0x02, 0x03, specify how to split the amount:

uint8(1)        // Single distribution (100% to one swap)
uint16(65535)   // 100% share

Or for multiple distributions:

uint8(2)        // Two distributions
uint16(32767)   // 50% to first swap (32767/65535)
uint16(32768)   // 50% to second swap (32768/65535)

Step 3: Add Swap Parameters

For each distribution, add the swap block based on the pool type needed.

Multi-hop Programs

Chain operations by using router balance from previous swaps:

[Initial opcode + distribution + swap] → Router has tokenA
[0x01 + tokenA + distribution + swap] → Router has tokenB
[0x01 + tokenB + distribution + swap] → Router has tokenC

Example: Simple Wrap

bytes memory program = abi.encodePacked(
    uint8(0x03),              // PROCESS_NATIVE
    uint8(1),                 // 1 distribution
    uint16(65535),            // 100% allocation
    uint8(2),                 // PT_WRAP
    uint8(1)                  // flags: wrap=true, no custom address
);

Byte breakdown:

  • 0x03: Use native MON
  • 0x01: One distribution
  • 0xFFFF: 100% share (65535)
  • 0x02: Wrap pool type
  • 0x01: Wrap flag (wrapped MON stays in router)

Example: UniV2 Swap

bytes memory program = abi.encodePacked(
    uint8(0x02),              // PROCESS_USER_ERC20
    address(WMON),            // Token to pull from user
    uint8(1),                 // 1 distribution
    uint16(65535),            // 100% allocation
    uint8(0),                 // PT_UNIV2
    address(pair),            // Pool address
    uint8(0),                 // Direction: token0→token1
    uint24(3000)              // 0.3% fee
);

Byte breakdown:

  • 0x02: Pull from user
  • 20 bytes: WMON address
  • 0x01: One distribution
  • 0xFFFF: 100% share
  • 0x00: UniV2 pool type
  • 20 bytes: Pair address
  • 0x00: Direction
  • 3 bytes: Fee (3000) - output stays in router

Example: Single Pool Optimization (Opcode 0x04)

When you have only one pool and want to save gas by skipping the distribution step and one token transfer:

// Assuming tokens are already sent directly to the pool
bytes memory program = abi.encodePacked(
    uint8(0x04),              // PROCESS_ONE_POOL (optimization)
    address(WMON),            // Token being swapped
    uint8(0),                 // PT_UNIV2
    address(pair),            // Pool address
    uint8(0),                 // Direction: token0→token1
    uint24(3000)              // 0.3% fee
);

Byte breakdown:

  • 0x04: Single pool optimization
  • 20 bytes: WMON address
  • 0x00: UniV2 pool type
  • 20 bytes: Pair address
  • 0x00: Direction
  • 3 bytes: Fee (3000)

How it works:

  • Tokens are sent directly to the pool (bypassing router temporarily)
  • Router calculates the input amount by reading pool.balanceOf(token) - pool.reserves
  • Saves ~21,000 gas by eliminating one token transfer

Example: Complex Multi-hop

// MON → WMON → CHOG → WMON → MON
bytes memory program = abi.encodePacked(
    // Step 1: Wrap MON to WMON
    uint8(0x03),              // PROCESS_NATIVE
    uint8(1), uint16(65535),  // 100% allocation
    uint8(2), uint8(1),       // Wrap to WMON

    // Step 2: Swap WMON to CHOG
    uint8(0x01),              // PROCESS_ROUTER_ERC20
    address(WMON),            // Process WMON balance
    uint8(1), uint16(65535),  // 100% allocation
    uint8(0),                 // PT_UNIV2
    address(pair), uint8(0), uint24(3000),

    // Step 3: Swap CHOG back to WMON
    uint8(0x01),              // PROCESS_ROUTER_ERC20
    address(CHOG),            // Process CHOG balance
    uint8(1), uint16(65535),  // 100% allocation
    uint8(0),                 // PT_UNIV2
    address(pair), uint8(1), uint24(3000),

    // Step 4: Unwrap WMON to MON
    uint8(0x01),              // PROCESS_ROUTER_ERC20
    address(WMON),            // Process WMON balance
    uint8(1), uint16(65535),  // 100% allocation
    uint8(2), uint8(0)        // Unwrap to MON
);

This creates a complete roundtrip route that the router executes atomically. All intermediate tokens stay in the router until the final output is sent to msg.sender.

Error Codes

KuruFlowEntrypoint

Error Selector
KuruFlowEntrypoint_BuyAndSellTokensAreSame() f8aa715b
KuruFlowEntrypoint_InsufficientAmountAfterFees() 5264a63f
KuruFlowEntrypoint_InsufficientNativeValue() 266ae8e1
KuruFlowEntrypoint_InvalidFeeCollector() 0bc52ea8
KuruFlowEntrypoint_InvalidFeeStructure() 0040cf18
KuruFlowEntrypoint_InvalidReferrer() 4d1e7c56
KuruFlowEntrypoint_InvalidRouter() fa1b73c8
OwnableInvalidOwner(address) 1e4fbdf7
OwnableUnauthorizedAccount(address) 118cdaa7
ReentrancyGuardReentrantCall() 3ee5aeb5

KuruFlowRouter

Error Selector
ApproveFailed() 3e3f8f73
ApproveResetFailed() 25f0fa4c
InsufficientNativeBalance() dbc3a71f
InvalidOpCode() bf337638
InvalidPoolType() 2946cbf1
InvalidTokenForUnwrap() 2366c6ba
InvalidTokenForWrap() 2b2def77
NativeSendFailed() a0c968e7
ReentrancyGuardReentrantCall() 3ee5aeb5
SafeERC20FailedOperation(address) 5274afe7
SlippageExceeded() 8199f5f3
TokenMismatch() 936bb5ad
Uint96Overflow() e233e012
UniV3CallbackInvalidSource() f0cbbb4b
UniV3CallbackMissed() 6d09f943
UniV3CallbackNegativeAmount() 24292634
ZeroRouterBalance() 1e2ce7e0

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages