Ajna Bucket Index Calculator (Correct Mapping)
Understanding Ajna Bucketsβ
Ajna uses a bucket-based lending system with 7,388 price buckets (Fenwick index 1..7388).
Important:
- Index 0 is invalid for
addQuoteToken/moveQuoteToken(Ajna revertsInvalidIndex). - The indices are not centered at 3696. The anchor for price (=1.0) is 4156.
Each bucket represents a different price point (quote per collateral, WAD precision).
For our Creator Ajna strategy:
- quote token = CREATOR (what we lend)
- collateral = USDC (what borrowers post)
- Ajna bucket price = CREATOR per 1 USDC (i.e., inverse of βUSDC per CREATORβ)
Anchor: Bucket 4156 corresponds to price = 1.0 (1 CREATOR per 1 USDC).
- Lower index (< 4156) β higher price (more CREATOR per USDC)
- Higher index (> 4156) β lower price (less CREATOR per USDC)
Why Market Price Mattersβ
If you set your bucket too far from the current market price:
- Too low: Your funds won't be utilized (no borrowers at that price)
- Too high: Higher yield potential, but riskier if price drops
Optimal: Set bucket near current market price for best capital efficiency.
How to Calculate the Right Bucket (Recommended)β
From Token Contract (Easiest - Recommended for AKITA)β
Many tokens store their Uniswap V4 pool configuration directly in the contract:
# Query token contract for pool key
TOKEN="0x5b674196812451b7cec024fe9d22d2c0b172fa75" # AKITA
POOL_KEY_RESULT=$(cast call $TOKEN \
"getPoolKey()(address,address,uint24,int24,address)")
# Parse the result
CURRENCY0=$(echo $POOL_KEY_RESULT | awk '{print $1}')
CURRENCY1=$(echo $POOL_KEY_RESULT | awk '{print $2}')
FEE=$(echo $POOL_KEY_RESULT | awk '{print $3}')
TICK_SPACING=$(echo $POOL_KEY_RESULT | awk '{print $4}')
HOOKS=$(echo $POOL_KEY_RESULT | awk '{print $5}')
# Calculate PoolId
POOL_KEY=$(cast abi-encode "f(address,address,uint24,int24,address)" \
$CURRENCY0 $CURRENCY1 $FEE $TICK_SPACING $HOOKS)
POOL_ID=$(cast keccak $POOL_KEY)
# Get current tick from PoolManager
POOL_MANAGER="0x498581fF718922c3f8e6A244956aF099B2652b2b"
SLOT0=$(cast call $POOL_MANAGER "getSlot0(bytes32)(uint160,int24,uint24,uint24)" $POOL_ID)
TICK=$(echo $SLOT0 | awk '{print $2}')
# Invert if needed (if token is currency1)
if [[ "$CURRENCY1" == "$TOKEN" ]]; then
TICK=$((-1 * TICK))
fi
#
# Convert Uniswap tick to Ajna bucket (approx):
# - Uniswap tick step: 1.0001^tick
# - Ajna bucket step: 1.005^(4156 - index)
# - 50 Uniswap ticks β 0.5% (β Ajna 1.005 step)
#
# So: ajnaIndex β 4156 - (tick / 50)
#
BUCKET=$((4156 - ($TICK / 50)))
# Clamp to valid Ajna range (1..7388)
if [ $BUCKET -lt 1 ]; then BUCKET=1; fi
if [ $BUCKET -gt 7388 ]; then BUCKET=7388; fi
echo "Suggested bucket: $BUCKET"
From Uniswap V4 Pool (Manual Method)β
If the token doesn't have getPoolKey(), you need to manually construct it:
# 1. Sort tokens (currency0 < currency1)
if [[ "$TOKEN0" < "$TOKEN1" ]]; then
CURRENCY0=$TOKEN0
CURRENCY1=$TOKEN1
else
CURRENCY0=$TOKEN1
CURRENCY1=$TOKEN0
fi
# 2. Build PoolKey (currency0, currency1, fee, tickSpacing, hooks)
FEE=30000 # 3% (common for ZORA pools)
TICK_SPACING=200
HOOKS="0x0000000000000000000000000000000000000000"
# 3. Calculate PoolId
POOL_KEY=$(cast abi-encode "f(address,address,uint24,int24,address)" \
$CURRENCY0 $CURRENCY1 $FEE $TICK_SPACING $HOOKS)
POOL_ID=$(cast keccak $POOL_KEY)
# 4. Get current tick from PoolManager
POOL_MANAGER="0x498581fF718922c3f8e6A244956aF099B2652b2b"
SLOT0=$(cast call $POOL_MANAGER "getSlot0(bytes32)(uint160,int24,uint24,uint24)" $POOL_ID)
TICK=$(echo $SLOT0 | awk '{print $2}')
# 5. Calculate Ajna bucket (approx)
BUCKET=$((4156 - ($TICK / 50)))
# 6. Clamp to valid Ajna range (1..7388). Note: index 0 is invalid on Ajna pools.
if [ $BUCKET -lt 1 ]; then BUCKET=1; fi
if [ $BUCKET -gt 7388 ]; then BUCKET=7388; fi
echo "Suggested bucket: $BUCKET"
Example: AKITA Token (from Contract)β
# AKITA token has getPoolKey() built-in!
AKITA="0x5b674196812451b7cec024fe9d22d2c0b172fa75"
# Query pool key directly
POOL_KEY_RESULT=$(cast call $AKITA \
"getPoolKey()(address,address,uint24,int24,address)")
# Returns (actual values from AKITA contract):
# currency0: 0x1111111111166b7FE7bd91427724B487980aFc69
# currency1: 0x5b674196812451B7cEC024FE9d22D2c0b172fa75 (AKITA)
# fee: 30000 (3%)
# tickSpacing: 200
# hooks: 0xd61A675F8a0c67A73DC3B54FB7318B4D91409040
# Calculate PoolId
CURRENCY0="0x1111111111166b7FE7bd91427724B487980aFc69"
CURRENCY1="0x5b674196812451B7cEC024FE9d22D2c0b172fa75"
FEE=30000
TICK_SPACING=200
HOOKS="0xd61A675F8a0c67A73DC3B54FB7318B4D91409040"
POOL_KEY=$(cast abi-encode "f(address,address,uint24,int24,address)" \
$CURRENCY0 $CURRENCY1 $FEE $TICK_SPACING $HOOKS)
POOL_ID=$(cast keccak $POOL_KEY)
# Get current tick
POOL_MANAGER="0x498581fF718922c3f8e6A244956aF099B2652b2b"
SLOT0=$(cast call $POOL_MANAGER "getSlot0(bytes32)(uint160,int24,uint24,uint24)" $POOL_ID)
TICK=$(echo $SLOT0 | awk '{print $2}')
# AKITA is currency1, so invert tick
TICK=$((-1 * TICK))
# Calculate bucket (approx)
# If tick = 5000: bucket β 4156 - (5000/50) = 4056
BUCKET=$((4156 - ($TICK / 50)))
From Uniswap V3 Pool (Alternative)β
For V3 pools:
# Get pool from factory
POOL=$(cast call $UNISWAP_V3_FACTORY \
"getPool(address,address,uint24)" \
$TOKEN0 $TOKEN1 3000)
# Get tick from slot0
SLOT0=$(cast call $POOL "slot0()(uint160,int24,...)")
TICK=$(echo $SLOT0 | cut -d',' -f2)
# Calculate bucket (approx)
BUCKET=$((4156 - ($TICK / 50)))
Ajna Price Formula (Actual)β
Ajna uses inverse pricing with a fixed 0.5% step:
price = 1.005^(4156 - index)
Where:
index = 4156β price = 1.0 (quote per collateral)index = 4056β price β 1.005^(100) β 1.65index = 4256β price β 1.005^(-100) β 0.61
Examplesβ
Index 1 β extremely high price (very expensive to borrow)
Index 4156 β price = 1.0
Index 7388 β very low price (very cheap to borrow)
Uniswap V3/V4 Tick to Priceβ
Both Uniswap V3 and V4 use the same price formula:
price = 1.0001^tick
Key Differences:
- V3: Pools are separate contracts with
slot0()function - V4: Pools are managed by PoolManager with
getSlot0(poolId)function - V4: Requires calculating PoolId from PoolKey (currency0, currency1, fee, tickSpacing, hooks)
Converting Tick to Bucket (Approximation)β
Since Ajna uses 1.005 and Uniswap uses 1.0001:
# Rough approximation (used in deployment script)
bucket_offset = tick / 50
bucket = 4156 - bucket_offset
# More accurate conversion (if needed)
import math
uniswap_price = 1.0001 ** tick
ajna_index = 4156 - (math.log(uniswap_price) / math.log(1.005))
Uniswap V4 PoolId Calculationβ
// PoolKey struct
struct PoolKey {
Currency currency0; // Lower address token
Currency currency1; // Higher address token
uint24 fee; // Fee tier (3000 = 0.3%)
int24 tickSpacing; // Tick spacing (60 for 0.3%, 200 for 1%)
IHooks hooks; // Hook contract (0x0 for no hooks)
}
// PoolId = keccak256(abi.encode(poolKey))
bytes32 poolId = keccak256(abi.encode(poolKey));
Common V4 Fee Tiersβ
Fee: 30000 (3.0%) β tickSpacing: 200 β AKITA/ZORA uses 3% with tickSpacing 200!
Fee: 10000 (1.0%) β tickSpacing: 200
Fee: 3000 (0.3%) β tickSpacing: 60
Fee: 500 (0.05%)β tickSpacing: 10
Note: The AKITA token's V4 pool uses the 3% fee tier with tickSpacing 200 (confirmed from contract).
Important: Some tokens may use custom tick spacings. Always check the token contract's getPoolKey() function if available!
Deployment Script Integrationβ
The scripts/deploy/ajna/DEPLOY_AKITA_AJNA.sh script automatically:
- β Finds AKITA/ZORA pool
- β Reads current tick
- β Calculates suggested bucket
- β Prompts for confirmation
- β Sets bucket after deployment
Manual Overrideβ
If you want to manually set the bucket:
# After deployment
cast send $STRATEGY_ADDRESS \
"setBucketIndex(uint256)" \
3500 \
--rpc-url base \
--private-key $PRIVATE_KEY
Bucket Selection Strategiesβ
Conservative (Lower Risk, Lower Yield)β
Current market price: 1 AKITA = 0.001 WETH
Set bucket: 4156 + 50 = 4206 # (higher index = lower price = more conservative)
β Willing to lend at 30% lower price
β Safer, but lower utilization
Balanced (Recommended)β
Current market price: 1 AKITA = 0.001 WETH
Set bucket: 4156 (price = 1.0 anchor; for real deployments compute from TWAP tick)
β Good capital efficiency
β Moderate risk/reward
Aggressive (Higher Risk, Higher Yield)β
Current market price: 1 AKITA = 0.001 WETH
Set bucket: 4156 - 50 = 4106 # (lower index = higher price = more aggressive)
β Willing to lend at 35% higher price
β Higher yield potential
β Higher liquidation risk if price drops
Rebalancingβ
If market conditions change, you can move to a different bucket:
# Move all funds to new bucket
cast send $STRATEGY_ADDRESS \
"moveToBucket(uint256,uint256)" \
3800 \ # New bucket
0 \ # 0 = move all LP
--rpc-url base \
--private-key $PRIVATE_KEY
Monitoringβ
Check Current Bucketβ
cast call $STRATEGY_ADDRESS "bucketIndex()(uint256)"
Check LP Balanceβ
cast call $STRATEGY_ADDRESS "totalAjnaLP()(uint256)"
Check Total Assets (Including Interest)β
cast call $STRATEGY_ADDRESS "getTotalAssets()(uint256)"
Check Pool Statsβ
# Pool utilization
cast call $AJNA_POOL "poolUtilization()(uint256)"
# Interest rate
cast call $AJNA_POOL "interestRate()(uint256)"
# Your position in specific bucket
cast call $AJNA_POOL \
"lenderInfo(uint256,address)(uint256,uint256)" \
4156 \
$STRATEGY_ADDRESS
Resourcesβ
Quick Referenceβ
Uniswap V4 (AKITA/ZORA)β
# Sort tokens
if [[ "$AKITA" < "$ZORA" ]]; then C0=$AKITA; C1=$ZORA; else C0=$ZORA; C1=$AKITA; fi
# Calculate PoolId (0.3% fee, tickSpacing 60)
POOL_KEY=$(cast abi-encode "f(address,address,uint24,int24,address)" $C0 $C1 3000 60 0x0)
POOL_ID=$(cast keccak $POOL_KEY)
# Get tick from PoolManager
POOL_MANAGER="0x498581fF718922c3f8e6A244956aF099B2652b2b"
SLOT0=$(cast call $POOL_MANAGER "getSlot0(bytes32)(uint160,int24,uint24,uint24)" $POOL_ID)
TICK=$(echo $SLOT0 | awk '{print $2}')
# Invert if needed
if [[ "$C0" == "$ZORA" ]]; then TICK=$((-1 * TICK)); fi
# Calculate bucket (approx)
BUCKET=$((4156 - ($TICK / 50)))
# Clamp to valid Ajna range (1..7388). Note: index 0 is invalid on Ajna pools.
if [ $BUCKET -lt 1 ]; then BUCKET=1; fi
if [ $BUCKET -gt 7388 ]; then BUCKET=7388; fi
echo "Suggested bucket: $BUCKET"
Uniswap V3 (Alternative)β
# Get pool from factory
POOL=$(cast call $FACTORY "getPool(address,address,uint24)" $TOKEN0 $TOKEN1 3000)
# Get tick from slot0
TICK=$(cast call $POOL "slot0()(uint160,int24,...)" | cut -d',' -f2)
# Calculate bucket (approx)
BUCKET=$((4156 - ($TICK / 50)))
# Clamp to valid Ajna range (1..7388). Note: index 0 is invalid on Ajna pools.
if [ $BUCKET -lt 1 ]; then BUCKET=1; fi
if [ $BUCKET -gt 7388 ]; then BUCKET=7388; fi
Summaryβ
β Use current market price to calculate optimal bucket β Start conservative (use Uniswap TWAP tick β derive index near market, then add a small safety buffer) β Monitor utilization and adjust if needed β Rebalance when market moves significantly β Higher bucket = higher yield potential, higher risk
For AKITA, the deployment script automatically handles this! π