Hooks
TWAP oracle
A built-in hook that records the pool's tick around every swap so any program, or anyone off chain, can read a time-weighted mean over a recent window.
The TWAP hook turns a pool into an on-chain price record. Before and after every swap it accrues the tick the price sat at for the seconds since the last update. Reading a time-weighted average then costs one read-only call.
What it stores#
A fixed ring of 32 observations, each a timestamp, the cumulative tick up to that moment, and the tick that applied from then on. Updates in the same second replace the current tick instead of adding an entry, so the ring covers the last 32 seconds that saw a swap, however far back those reach.
History starts at the hook's first callback. A new pool can't answer a window older than its first swap.
Reading it#
Observe { seconds_ago } returns the arithmetic mean tick over the last seconds_ago seconds. It extrapolates the current tick up to the present Clock time, interpolates the window's start inside the retained history, and rounds the mean toward negative infinity.
The call takes two read-only accounts, the pool and its hook state, and answers with return data:
pub struct TwapReply {
pub version: u8,
pub window_seconds: u32,
pub start_timestamp: i64,
pub end_timestamp: i64,
pub mean_tick: i32,
pub cumulative_tick: i128,
}GET /pools/{address}/twap?seconds=300The API simulates Observe against finalized state and returns { windowSeconds, startTimestamp, endTimestamp, meanTick }. It never submits a transaction.
import { observeTwapInstruction, decodeTwapReply } from "@socket/backend/sdk";
const ix = observeTwapInstruction(pool, 300);
// simulate a transaction containing ix, then read its return data.
// The runtime trims trailing zero bytes; restore the fixed 41-byte reply first.
const data = Buffer.alloc(41);
returnData.copy(data);
const reply = decodeTwapReply(returnProgram, data);
console.log(reply.meanTick);Invoke the built-in hooks program with HookInstruction::Observe { seconds_ago } and the two read-only accounts, then read get_return_data(). Check that the returning program is the hooks program and that the payload decodes exactly as a TwapReply.
From tick to price#
A tick is a price on a fixed grid: price = 1.0001^tick, in atomic units of B per A. For a human price, adjust for decimals:
price (B per A) = 1.0001^mean_tick × 10^(decimals_A − decimals_B)The mean of ticks is a geometric mean of prices. It is the pool's own record, not a fair-value feed: it is only as good as the liquidity and trading in this pool.
Windows it refuses#
- Shorter than the pool's minimum window, set at creation (30 s in the app by default).
- Older than the oldest retained observation.
Both fail with 7008 InsufficientHistory; the API answers TWAP_UNAVAILABLE.
Permissions#
The hook needs before swap (1) and after swap (2): mask 3. With before liquidity (4) and after liquidity (8) as well, it also records around deposits and withdrawals; the app offers that as "Also record the price when liquidity changes".