KAGURA 神楽

KAGURA is a continuous-time DeFi primitive on Solana. Two on-chain Anchor programs: a tick-attestation registry, and a USDC funding vault that compounds yield every block instead of every hour.

contact: hello@kagura.network
神楽KAGURA/ docs
v0.1.0 · pre-deployment← back to sitegithub →
integration·rust cpi

rust cpi

three lines, one struct, one cpi. this is the entire kagura integration surface for an anchor consumer.

1. cargo dep

programs/your-program/Cargo.tomltoml
1[dependencies]2anchor-lang = "0.31.1"3kagura-core = { path = "../kagura-core", features = ["cpi"] }

2. account struct

Add three accounts to your instruction's account struct: kagura's config, your registered protocol, and the kagura-core program.

programs/your-program/src/instructions/your_tick.rsrust
1use anchor_lang::prelude::*;2use kagura_core::cpi::accounts::RecordTick;3use kagura_core::program::KaguraCore;4 5#[derive(Accounts)]6pub struct YourTick<'info> {7    pub authority: Signer<'info>,8 9    /// CHECK: kagura-core config pda; verified by kagura-core's own constraints.10    #[account(mut)]11    pub kagura_config: UncheckedAccount<'info>,12 13    /// CHECK: kagura-core protocol registration pda; verified by stored pubkey on your state.14    #[account(mut, address = your_state.kagura_protocol)]15    pub kagura_protocol: UncheckedAccount<'info>,16 17    pub kagura_core_program: Program<'info, KaguraCore>,18 19    // ... your own accounts20}

3. the cpi

instruction handlerrust
1pub fn handler(ctx: Context<YourTick>) -> Result<()> {2    // ─── ask kagura-core how much wall-clock time has passed ───3    let cpi_accounts = RecordTick {4        authority: ctx.accounts.authority.to_account_info(),5        config:    ctx.accounts.kagura_config.to_account_info(),6        protocol:  ctx.accounts.kagura_protocol.to_account_info(),7    };8    let cpi_ctx = CpiContext::new(9        ctx.accounts.kagura_core_program.to_account_info(),10        cpi_accounts,11    );12    let tick = kagura_core::cpi::record_tick(cpi_ctx)?.get();13 14    // ─── now use tick.elapsed_ms in your math ───15    msg!("kagura tick #{} elapsed={}ms",16        tick.tick_number, tick.elapsed_ms);17 18    // ... your protocol-specific logic, parameterized by elapsed_ms19    Ok(())20}

who signs

kagura-core requires that the signer of record_tick equals the protocol authority recorded at register_protocol time. Two patterns cover most integrations:

a) shared authority (simplest)

Your program and kagura-core register against the same wallet. The wallet signs every tick. This is what kagura-vault does.

user wallet signsrust
1// your program2#[derive(Accounts)]3pub struct YourTick<'info> {4    pub authority: Signer<'info>,           // <-- this is the kagura authority too5    // ...6}

b) program pda as authority

Register kagura-core with your program's pda as authority. Then ticks sign with seeds. Useful when ticks are permissionless (anyone calls your instruction; your pda signs the inner cpi).

pda-signsrust
1let bump = ctx.accounts.your_state.bump;2let signer_seeds: &[&[u8]] = &[3    b"your-state",4    &[bump],5];6let cpi_ctx = CpiContext::new_with_signer(7    ctx.accounts.kagura_core_program.to_account_info(),8    cpi_accounts,9    &[signer_seeds],10);11let tick = kagura_core::cpi::record_tick(cpi_ctx)?.get();

pre-registration

Before your program can tick, the chosen authority must call kagura-core register_protocol once. Easiest: do it from a script.

scripts/register-protocol.tsts
1import * as anchor from "@coral-xyz/anchor";2import { Program, BN } from "@coral-xyz/anchor";3import { PublicKey } from "@solana/web3.js";4import { KaguraCore } from "../target/types/kagura_core";5 6const PROTOCOL_SEED = Buffer.from("kagura-protocol");7const CONFIG_SEED   = Buffer.from("kagura-config");8 9const provider = anchor.AnchorProvider.env();10anchor.setProvider(provider);11 12const core = anchor.workspace.kaguraCore as Program<KaguraCore>;13const wallet = (provider.wallet as anchor.Wallet).payer;14 15const [config]   = PublicKey.findProgramAddressSync([CONFIG_SEED], core.programId);16const [protocol] = PublicKey.findProgramAddressSync(17    [PROTOCOL_SEED, wallet.publicKey.toBuffer()],18    core.programId,19);20 21await core.methods22    .registerProtocol("your-protocol-name", 1000) // 1s tick floor for vanilla solana23    .accounts({24        authority: wallet.publicKey,25        config,26        protocol,27        systemProgram: anchor.web3.SystemProgram.programId,28    })29    .rpc();

error translation

Errors thrown by kagura-core during cpi are propagated to your program with their original error code. To handle them gracefully:

error handlingrust
1match kagura_core::cpi::record_tick(cpi_ctx) {2    Ok(res) => {3        let tick = res.get();4        // happy path5    }6    Err(err) => {7        // err is an anchor Error wrapping the kagura-core error code8        msg!("kagura tick failed: {:?}", err);9        return Err(YourError::TickFailed.into());10    }11}

example: kagura-vault

kagura-vault is the canonical consumer. Read programs/kagura-vault/src/instructions/tick_funding.rs for a production cpi pattern with token transfers, signer seeds for token authority pdas, and event emission.