刻tick attestation
kagura-core is a tiny registry. it holds a tick counter and a last-tick timestamp per registered protocol, and returns elapsed_ms to its callers via cpi.
the shape of an attestation
Every call to record_tick returns a TickResult containing three fields:
| field | type | meaning |
|---|---|---|
now_unix_ms | i64 | the solana clock at the moment of attestation, in milliseconds since unix epoch. |
elapsed_ms | u32 | the wall-clock delta since the last tick on this protocol, clamped to u32::MAX. |
tick_number | u64 | monotonic counter of attestations for this protocol, starting at 1. |
the account graph
Two seeded PDAs participate in any tick:
1#[account]2pub struct KaguraConfig {3 pub authority: Pubkey,4 pub total_protocols: u64,5 pub total_ticks: u128,6 pub created_at: i64,7 pub bump: u8,8}9 10#[account]11pub struct ProtocolRegistration {12 pub authority: Pubkey,13 pub name: String, // <= 32 chars14 pub tick_interval_ms: u32, // 50..=60_00015 pub last_tick_unix_ms: i64,16 pub total_ticks: u64,17 pub paused: bool,18 pub created_at: i64,19 pub bump: u8,20}KaguraConfig is global (one per program). Its only job is to count how many protocols are registered and how many ticks have ever been recorded. PDA seeds:
1config: [b"kagura-config"]2protocol: [b"kagura-protocol", authority.key().as_ref()]lifecycle
- once per network:
initialize_configseeds the global config pda. The first caller becomes the config authority. - once per protocol:
register_protocol(name, tick_interval_ms)creates a registration pda.tick_interval_msmust be in[50, 60_000]. - continuous:
record_tickis called on a schedule (by the operator's ticker bot or, soon, by anyone). Each call updateslast_tick_unix_ms, incrementstotal_ticks, and emits aTickEmittedevent. - at any time:
set_paused(true|false)is callable by the protocol authority. While paused,record_tickerrors withProtocolPausedand consumers must handle the gap.
errors
| error | raised when |
|---|---|
Unauthorized | signer does not match protocol.authority. |
InvalidTickInterval | tick_interval_ms outside [50, 60_000]. |
ProtocolPaused | record_tick called while paused = true. |
ClockRegression | the solana clock returned now < last_tick_unix_ms (should never happen on mainnet; defensive check). |
MathOverflow | total_ticks arithmetic overflowed (would require a u64 of ticks; ~50ms × 2^64 ≈ 29 trillion years). |
events
kagura-core emits one event:
1#[event]2pub struct TickEmitted {3 pub protocol: Pubkey,4 pub tick_number: u64,5 pub now_unix_ms: i64,6 pub elapsed_ms: u32,7}Indexers should subscribe to this event to build dashboards, alerts, or chainwide tick charts. Every kagura-vault::tick_funding call produces one of these.
the cpi contract
For consumer programs, the cpi shape is fixed. See integration/cpi for the full Anchor cpi example. The short version:
1use kagura_core::cpi::accounts::RecordTick;2use kagura_core::program::KaguraCore;3 4let cpi = CpiContext::new(5 ctx.accounts.kagura_core_program.to_account_info(),6 RecordTick {7 authority: ctx.accounts.authority.to_account_info(),8 config: ctx.accounts.kagura_config.to_account_info(),9 protocol: ctx.accounts.kagura_protocol.to_account_info(),10 },11);12let tick = kagura_core::cpi::record_tick(cpi)?.get();