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

Two Anchor programs govern the system: 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.

rust
// 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.

rust
// 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

Sharded state enables true parallel execution across currency pairs. A single swap processes in approximately 400ms on Solana Devnet, with finality in under 1 second.

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.

rust
// 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.

rust
// 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(())
}

Conservative Deployment

Yield deployment is capped at 10% of total pool notional to ensure sufficient liquidity remains for settlement and FX swaps. The 5% APY is a stub for hackathon demo; real yields vary by market condition and venue.

Technical Constraints

Max Wallets in Merkle Tree8 wallets (3-level tree, ~3 proof nodes)
Max SIX Feed Staleness90 seconds before fallback to Pyth
Oracle Relay Lock TTL60 seconds
Per-Swap Max Slippage50 bps (0.5%)
Per-Epoch Max Slippage100 bps (1%) per currency pair
Travel Rule Threshold$3,000 USD equivalent
Daily Transfer LimitConfigurable per subsidiary (default: $50,000)
Protocol Transfer Fee0.05% via Token-2022 TransferFeeConfig
Yield Deployment Cap10% of pool total notional
Account Versionversion: u8 on all structs, lazy migration enabled