Protocol Contract reference

Protocol

Contract reference.

Functions, structs, events and errors of ObolLadderManager and ObolLadderManagerV4.

Both ladder managers share the same shape of API. The only difference is how the pool is identified: a pool address on V3, a PoolKey on V4.

Open parameters

ILadderManager.OpenParams (V3)
struct OpenParams {
address pool; // canonical Uniswap V3 pool
int24 tickLower; // absolute ticks, aligned by the contract
int24 tickUpper;
uint256 amount0; // max token0 to deposit
uint256 amount1; // max token1 to deposit
uint8 shape; // 1 Uniform · 2 Bell · 3 Ascending · 4 Descending · 5 Barbell
uint8 numBins; // 1 to 20
int24 minTick; // price guard: current tick must be in [minTick, maxTick]
int24 maxTick;
}
ILadderManagerV4.OpenParams (V4)
struct OpenParams {
PoolKey poolKey; // replaces the pool address
int24 tickLower;
int24 tickUpper;
uint256 amount0; // = msg.value when currency0 is native ETH
uint256 amount1;
uint8 shape;
uint8 numBins;
int24 minTick;
int24 maxTick;
}

Functions

FunctionDescription
openLadder(OpenParams) → ladderIdOpens a ladder. V4 version is payable. Unused funds are refunded.
claimFees(ladderId) → (amount0, amount1)Owner only. Sends fees minus 7.5%.
closeLadder(ladderId) → (amount0, amount1)Owner only. Withdraws everything; 7.5% applies to fees only.
getLadder(ladderId)Owner, pool, bins, range, shape, active flag.
getLaddersByOwner(owner)All ladder ids of an address, including closed ones.
pendingFees(ladderId)Unclaimed fees, net of the protocol fee.
positionValue(ladderId)Token amounts held at the current price, excluding fees.
inRange(ladderId)Whether the pool's current tick is inside the ladder range.
protocolFeeBps() · nextLadderId()750, and the id the next ladder will get.

Events

V3 (V4 names end in V4 and carry a PoolKey instead of the pool)
event LadderOpened(address indexed owner, uint256 indexed ladderId, address pool,
int24 tickLower, int24 tickUpper, uint256[] positionIds, uint128[] liquidities,
uint256 amount0Used, uint256 amount1Used, uint8 shape, uint8 numBins);
event FeesClaimed(address indexed owner, uint256 indexed ladderId,
uint256 amount0, uint256 amount1, uint256 protocolFee0, uint256 protocolFee1);
event LadderClosed(address indexed owner, uint256 indexed ladderId,
uint256 amount0, uint256 amount1, uint256 protocolFee0, uint256 protocolFee1);

Errors

ErrorWhen
InvalidPoolNot a canonical V3 pool, V4 pool not initialized, or currencies not sorted.
PriceOutOfBounds(currentTick)Current tick outside [minTick, maxTick].
InvalidRangetickLower ≥ tickUpper after alignment.
InvalidShape · InvalidNumBinsShape not in 1–5, bins not in 1–20.
InsufficientAmountBoth amounts are zero.
NoLiquidityNo bin can take the deposited tokens at the current price.
InvalidValueV4: msg.value does not match the native amount.
ExceedsDepositV4: the pool asked for more than was deposited.
NotLadderOwner · LadderNotActiveClaim or close by someone else, or on a closed ladder.

Example: open a V4 ladder with native ETH

The app computes ticks from the pool's current tick. The same logic, stripped down, with viem:

open-ladder-v4.ts
const [, tick] = await client.readContract({
address: STATE_VIEW, abi: stateViewAbi, functionName: "getSlot0", args: [poolId],
})
// Token is currency1: a higher token price is a lower tick.
// Range -30% / +40% of the current price:
const tickLower = Math.floor(tick - Math.log(1.4) / Math.log(1.0001))
const tickUpper = Math.ceil(tick - Math.log(0.7) / Math.log(1.0001))
await wallet.writeContract({
address: OBOL_LADDER_MANAGER_V4,
abi: ladderManagerV4Abi,
functionName: "openLadder",
args: [{
poolKey, tickLower, tickUpper,
amount0: parseEther("0.5"), // ETH (currency0)
amount1: 0n,
shape: 2, numBins: 10,
minTick: tick - 50, maxTick: tick + 50, // ~0.5% price guard
}],
value: parseEther("0.5"),
})