Skip to content

Potential price mismatch on Base #24

@callmephilip

Description

@callmephilip

A customer recently reached out and flagged that our API is returning an incorrect price for ZARP. Here’s the API response they received:

➜ curl -X GET "https://withered-fabled-sheet.base-mainnet.quiknode.pro/<API_KEY>/addon/1051/v1/prices?target=aero&limit=50&symbols=ZARP" | jq
{
  "prices": {
    "0xb755506531786c8ac63b756bab1ac387bacb0c04": {
      "token": {
        "address": "0xb755506531786C8aC63B756BaB1ac387bACB0C04",
        "symbol": "ZARP",
        "decimals": 18,
        "listed": true
      },
      "price": 230.2870163751035,
      "price_display": 230.287016
    }
  }
}

The Price of ZARP on the Aerodrome UI is .05 -

So the exact calls on my end:

  1. chain.get_all_tokens() → Calls Sugar contract's all() function → Returns ~5000 tokens
  2. chain.get_prices(tokens) → Calls Oracle contract's getManyRatesToEthWithCustomConnectors() → Returns prices

The second call is where ZARP gets the wrong $230 price.

To debug, we reviewed the source pool being used, which appears to be a concentrated liquidity (CL) pool.
We pulled sqrtPriceX96 directly from the CL pool contract (0xB86D18195349aAA6941042BD539289F26142A87b) and used the standard Uniswap V3 price formula for verification.

Details:

sqrtPriceX96: 345470727104832111786044952533601973

Calculated final price for ZARP: $0.052594

Aerodrome UI shows: $0.0524

Difference: $0.000194

Below is the minimal Python script we used to verify pricing against the CL pool:

#!/usr/bin/env python3
"""
Correctly Calculate ZARP Price from CL Pool
===========================================
Prove that Aerodrome uses the CL pool's sqrtPriceX96 correctly.
"""

import sys
import os
import asyncio
from web3 import Web3

from app.core.config import settings

sys.path.insert(0, 'Desktop/aerodrome/sugar-sdk')
from sugar.chains import AsyncBaseChain

async def test_oracle_calls():
    print("=" * 80)
    print("CORRECTLY CALCULATING ZARP PRICE FROM CL POOL")
    print("=" * 80)

    config = {
        "rpc_uri": settings.SUGAR_RPC_URI_8453,
    }

    if settings.SUGAR_PK:
        os.environ["SUGAR_PK"] = settings.SUGAR_PK

    async with AsyncBaseChain(**config) as chain:
        cl_pool_address = "0xB86D18195349aAA6941042BD539289F26142A87b"
        print(f"\nStep 1: Fetching CL pool structure")

        pools = await chain.get_pools()
        cl_pool = next((p for p in pools if p.lp.lower() == cl_pool_address.lower()), None)

        if cl_pool:
            print(f"   Token0: {cl_pool.token0.symbol} ({cl_pool.token0.decimals} decimals)")
            print(f"   Token1: {cl_pool.token1.symbol} ({cl_pool.token1.decimals} decimals)")
            print(f"   Pool: {cl_pool.symbol}")

        print(f"\nStep 2: Retrieving slot0 and token addresses")

        CL_POOL_ABI = [
            {
                "inputs": [],
                "name": "slot0",
                "outputs": [
                    {"internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"},
                    {"internalType": "int24", "name": "tick", "type": "int24"},
                    {"internalType": "uint16", "name": "observationIndex", "type": "uint16"},
                    {"internalType": "uint16", "name": "observationCardinality", "type": "uint16"},
                    {"internalType": "uint16", "name": "observationCardinalityNext", "type": "uint16"},
                    {"internalType": "bool", "name": "unlocked", "type": "bool"}
                ],
                "stateMutability": "view",
                "type": "function"
            },
            {
                "inputs": [],
                "name": "token0",
                "outputs": [{"internalType": "address", "name": "", "type": "address"}],
                "stateMutability": "view",
                "type": "function"
            },
            {
                "inputs": [],
                "name": "token1",
                "outputs": [{"internalType": "address", "name": "", "type": "address"}],
                "stateMutability": "view",
                "type": "function"
            }
        ]

        try:
            pool_contract = chain.web3.eth.contract(
                address=Web3.to_checksum_address(cl_pool_address),
                abi=CL_POOL_ABI
            )

            token0_addr = await pool_contract.functions.token0().call()
            token1_addr = await pool_contract.functions.token1().call()

            print(f"   Token0 address: {token0_addr}")
            print(f"   Token1 address: {token1_addr}")

            slot0 = await pool_contract.functions.slot0().call()
            sqrt_price_x96 = slot0[0]
            tick = slot0[1]

            print(f"   sqrtPriceX96: {sqrt_price_x96}")
            print(f"   Tick: {tick}")

            print(f"\nStep 3: Calculating price from sqrtPriceX96")

            sqrt_price = sqrt_price_x96 / (2**96)
            zarp_per_usdc = sqrt_price ** 2
            usdc_per_zarp = 1 / zarp_per_usdc

            decimal_adjustment = (10**18) / (10**6)
            final_price = usdc_per_zarp * decimal_adjustment

            print(f"   Final ZARP price: ${final_price:.6f}")

            aerodrome_price = 0.0524
            difference = abs(final_price - aerodrome_price)
            ratio = final_price / aerodrome_price

            print(f"\nComparison with Aerodrome:")
            print(f"   Our calculation: ${final_price:.6f}")
            print(f"   Aerodrome shows: ${aerodrome_price:.6f}")
            print(f"   Difference: ${difference:.6f}")
            print(f"   Ratio: {ratio:.2f}x")

            if difference < 0.01:
                print(f"\n   SUCCESS: Price matches closely with Aerodrome.")
                print(f"   Confirmed: Aerodrome uses sqrtPriceX96 correctly.")
            else:
                print(f"\n   Warning: Price mismatch detected. Further investigation may be needed.")

        except Exception as e:
            print(f"   Error: {e}")
            import traceback
            traceback.print_exc()

print("Running CL pool price calculation test...")
asyncio.run(test_oracle_calls())

python3 test_actual_oracle_call.py
2025-07-21 18:16:12,216 - root - INFO - Set Sugar SDK thread pool size to 20 workers
2025-07-21 18:16:12,216 - root - INFO - Set Sugar SDK price threshold filter to 0
2025-07-21 18:16:12,216 - root - INFO - Logging configured. Level: INFO, Debug mode: False
Running CL pool price calculation test...
================================================================================
CORRECTLY CALCULATING ZARP PRICE FROM CL POOL
================================================================================

Step 1: Fetching CL pool structure
   Token0: USDC (6 decimals)
   Token1: ZARP (18 decimals)
   Pool: CL10-USDC/ZARP

Step 2: Retrieving slot0 and token addresses
   Token0 address: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
   Token1 address: 0xb755506531786C8aC63B756BaB1ac387bACB0C04
   sqrtPriceX96: 345470727104832111786044952533601973
   Tick: 305777

Step 3: Calculating price from sqrtPriceX96
   Final ZARP price: $0.052594

Comparison with Aerodrome:
   Our calculation: $0.052594
   Aerodrome shows: $0.052400
   Difference: $0.000194
   Ratio: 1.00x

   SUCCESS: Price matches closely with Aerodrome.
   Confirmed: Aerodrome uses sqrtPriceX96 correctly.
    
 
# The SDK we use, however, is returning ~230 as the price. Based on the contract data and comparison with your UI, it seems the SDK oracle may not be 
# interpreting sqrtPriceX96 from the CL pool correctly — potentially treating ZARP as the base unit incorrectly or failing to handle token decimals.

# Is there a known issue or nuance when reading CL pool pricing from Aerodrome that we should account for? Or anything subtle in the SDK vs. UI logic?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions