Akari Technical Whitepaper
Compliance-first notional cash pooling on Solana. Version 3.0 — Final Architecture.
Overview
Akari is a notional stablecoin cash pooling system built on Solana for multinational corporate treasury. It replaces slow, opaque interbank FX rails with instant, programmable, fully auditable on-chain settlement using USDC and EURC.
Three architectural principles guide every design decision:
- Regulatory alignment over regulatory bypass — compliance is embedded, not bolted on
- Deterministic settlement over optimistic execution — every outcome is verifiable on-chain
- Institutional UX over crypto-native complexity — CFOs, not degens, are the target user
System Overview
akari (business logic) and transfer_hook (KYC enforcement). They communicate through shared PDA addresses, not CPI calls — this keeps the hook non-circumventable.KYC & Compliance Architecture
Legacy DeFi compliance gates at the application layer. A user can always bypass the UI and interact with the program directly. Akari moves compliance to the token layer via SPL Token-2022 Transfer Hook, making circumvention mathematically impossible.
Merkle-Proof KYC
A single KycMerkleRoot account stores the current root hash of up to 8 verified wallets (a maximum 3-level Merkle tree). This limit ensures the proof array passed to the transfer hook fits within Solana compute budget constraints.
// transfer_hook::execute — Merkle Proof Verification
pub fn execute(ctx: Context<Execute>, amount: u64, proof: Vec<[u8; 32]>) -> Result<()> {
let root = ctx.accounts.kyc_merkle_root.root;
let destination = ctx.accounts.destination_account.owner;
// Simplified leaf for MVP
let leaf = solana_program::hash::hash(destination.as_ref()).to_bytes();
// Canonical sort-before-hash
let mut current = leaf;
for proof_node in proof.iter() {
let (left, right) = if current <= *proof_node {
(current, *proof_node)
} else {
(*proof_node, current)
};
let mut combined = [0u8; 64];
combined[..32].copy_from_slice(&left);
combined[32..].copy_from_slice(&right);
current = solana_program::hash::hash(&combined).to_bytes();
}
require!(current == root, TransferHookError::UnauthorizedRecipient);
Ok(())
}AML & Travel Rule
Each SubsidiaryAccount tracks daily_transfer_total and last_transfer_day. Transfers above the configured threshold automatically create a TravelRuleRecord PDA with full sender and receiver VASP metadata.
FX Swap Engine
The FX swap engine is the core differentiator. Instead of using a synthetic mid-price, Akari uses directional bid/ask from SIX — ask when buying EURC, bid when selling. This mirrors real FX desk convention and eliminates the structural arbitrage of mid-only pricing.
// Directional pricing in fx_swap
let oracle_price = if from_currency == CURRENCY_USDC {
six_price_feed.ask // Buying EURC, pay ask
} else {
six_price_feed.bid // Selling EURC, receive bid
};
// Per-swap slippage check
require!(
received >= expected * (10_000 - max_slippage_bps) / 10_000,
AkariError::SlippageExceeded
);
// Epoch budget check (sharded per pair)
let epoch_budget = (epoch_state.max_epoch_slippage_bps as u128)
* (epoch_state.vault_nav_snapshot_usdc as u128)
/ 10_000u128;
require!(
(epoch_state.epoch_accumulated_slippage as u128) + (this_slippage as u128)
<= epoch_budget,
AkariError::EpochSlippageBudgetExhausted
);Sharded Epoch Slippage
Previously, all FX swaps wrote to a single PoolVault.epoch_accumulated_slippage, serializing concurrent swaps. Now each currency pair has its own EpochState PDA. EUR_USD and CHF_USD swaps execute in parallel with zero contention.
Performance
Oracle & Relay Lock
The SIX Web API is the primary oracle for EUR/USD and CHF/USD spot rates. A redundant relay architecture with on-chain leader election via OracleRelayLock ensures zero downtime during relay failover.
// OracleRelayLock — leader election
pub fn acquire_relay_lock(ctx: Context<AcquireRelayLock>) -> Result<()> {
let lock = &mut ctx.accounts.oracle_relay_lock;
let clock = Clock::get()?;
let caller = ctx.accounts.caller.key();
let lock_expired = clock.unix_timestamp - lock.acquired_at >= lock.ttl;
let caller_holds = lock.holder == caller;
require!(lock_expired || caller_holds, AkariError::RelayLockHeldByAnother);
lock.holder = caller;
lock.acquired_at = clock.unix_timestamp;
lock.renewal_count += 1;
Ok(())
}Redundancy Model
Primary Relay
Polls every 30s, acquires/renews lock
Standby Relay
Checks lock every 15s, takes over if expired
TTL
60 seconds — dead relay loses lock automatically
Fallback
Pyth on-chain price feed if SIX is stale
Yield Router Integration
Idle balances in the pool vault earn zero. The Yield Router deploys conservative portions to compliant lending venues via the Steakhouse model. Kamino CPI integration allows direct deposit from the PoolVault PDA, with market accounts fetched dynamically via the @kamino-finance/klend-sdk.
// deploy_yield — Kamino CPI integration
pub fn deploy_yield(ctx: Context<DeployYield>, currency: u8, venue: [u8; 16], amount: u64) -> Result<()> {
let idle = compute_idle_balance(&ctx.accounts.pool_vault, currency)?;
require!(amount <= idle, AkariError::InsufficientIdleBalance);
// Kamino CPI: PoolVault PDA acts as signer
let cpi_accounts = kamino::cpi::accounts::Deposit {
market: ctx.accounts.kamino_market.to_account_info(),
reserve: ctx.accounts.kamino_reserve.to_account_info(),
lending_market_authority: ctx.accounts.lending_market_authority.to_account_info(),
lending_pool: ctx.accounts.lending_pool.to_account_info(),
lending_pool_token_account: ctx.accounts.lending_pool_token_account.to_account_info(),
owner: ctx.accounts.pool_vault.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
};
let cpi_ctx = CpiContext::new(ctx.accounts.kamino_program.to_account_info(), cpi_accounts);
kamino::cpi::deposit(cpi_ctx, amount)?;
emit!(YieldDeployedEvent { currency, venue, amount, timestamp: Clock::get()?.unix_timestamp });
Ok(())
}