Accounts and PDAs

Download Markdown

This page is reference material for developers who read Akashi vault data directly from the chain. You do not need it to deposit or withdraw.

It lists the on-chain accounts of a vault, how to find their addresses, and how their data is laid out. Investors can also use it to check where their money sits.

Terms used on this page

  • PDA (program-derived address). An address computed from the program ID and a list of seeds. It has no private key, so only the program can sign for it. Given the seeds, anyone can compute the address, for example with PublicKey.findProgramAddressSync in the Solana web3 libraries.
  • Seed. One input to a PDA. Text seeds are UTF-8 bytes, such as "vault". Numbers are little-endian bytes, written "LE" below: vault_id as a u64 is 8 bytes, lowest byte first.
  • Discriminator. The first 8 bytes of every Anchor account. They identify the account type.
  • ATA (associated token account). The standard token account address for a given owner and mint.
  • Integer types. u8, u16, u32 and u64 are unsigned whole numbers of 1, 2, 4 and 8 bytes; i64 is a signed whole number of 8 bytes. A Pubkey (an account address) is 32 bytes. Amounts are in USDC base units, where 1 USDC is 1,000,000 base units, and "bps" fields are basis points, where 10,000 is 100%.

Why addresses are derived

Every account of a vault is a PDA of the akashi_vault program. This has two benefits:

  1. Nobody holds a key to the money. The vault's USDC sits in token accounts owned by the vault authority PDA. Only the program can move it, and only by following its rules.
  2. Anyone can find every account. You do not need an index or an API. Given a vault ID, the seeds below give every address.

The account map

classDiagram
    class GlobalConfig {
        PDA seeds: "global"
        authority
        approved_adapters (max 16)
        vault_creators (max 32)
        registry_delay_secs
        pending_adapters (max 8)
    }
    class Vault {
        PDA seeds: "vault", vault_id LE u64
        admin, guardian, params.executor
        state
        senior_value, junior_value
        idle_ledger, reserved_payouts
        leg_values[8], leg_marks[8]
    }
    class VaultAuthority {
        PDA seeds: "auth", vault
        no data, signs for the vault
    }
    class SeniorMint {
        PDA seeds: "senior_mint", vault
    }
    class JuniorMint {
        PDA seeds: "junior_mint", vault
    }
    class IdleAccount {
        PDA seeds: "idle", vault
        USDC token account
    }
    class PayoutAccount {
        PDA seeds: "payout", vault
        USDC token account
    }
    class Escrow {
        PDA seeds: "escrow", vault, tranche byte
        share token account
    }
    class Leg {
        PDA seeds: "leg", vault, index byte
        adapter_program, bound_accounts
        params, mode
    }
    class LegAuthority {
        PDA seeds: "leg_auth", vault, index byte
        signs adapter calls
    }
    class LegTransit {
        USDC ATA of LegAuthority
    }
    class V2LegConfig {
        PDA seeds: "leg_v2", leg
    }
    class PendingAction {
        PDA seeds: "pending_action", leg
    }
    class TokenLeg {
        PDA seeds: "token_leg", leg
    }
    class WithdrawalRequest {
        PDA seeds: "request", vault, request_id LE u64
    }
    class PendingChange {
        PDA seeds: "change", vault, change_id LE u64
    }
    class CostBudget {
        PDA seeds: "cost_budget", vault
    }
    class SettlementClaim {
        PDA seeds: "settlement_claim", vault, owner, tranche byte
    }
    GlobalConfig "1" --> "many" Vault : approves adapters and creators
    Vault "1" --> "1" VaultAuthority
    VaultAuthority --> SeniorMint : mint authority
    VaultAuthority --> JuniorMint : mint authority
    VaultAuthority --> IdleAccount : owner
    VaultAuthority --> PayoutAccount : owner
    VaultAuthority --> Escrow : owner
    Vault "1" --> "0..8" Leg
    Leg "1" --> "1" LegAuthority
    LegAuthority --> LegTransit : owner
    Leg "1" --> "0..1" V2LegConfig
    Leg "1" --> "0..1" PendingAction
    Leg "1" --> "0..1" TokenLeg
    Vault "1" --> "many" WithdrawalRequest
    Vault "1" --> "many" PendingChange
    Vault "1" --> "0..1" CostBudget
    Vault "1" --> "many" SettlementClaim

Figure: every account hangs off one Vault PDA, and every token account is owned by one of two PDA signers, the vault authority or a leg authority.

The takeaway: there are only two signers that can move a vault's tokens. The vault authority signs for idle cash, payouts, escrowed shares and share minting. Each leg authority signs only for its own leg's transit account and protocol position.

Seeds table

AccountSeedsCreated by
GlobalConfig"global"init_global
GlobalAuthorityProposal"global_authority"propose_global_authority
Vault"vault", vault_id (u64 LE)create_vault
Vault authority"auth", vaultNo account data; derived only
Senior share mint"senior_mint", vaultinit_vault_accounts
Junior share mint"junior_mint", vaultinit_vault_accounts
Idle USDC account"idle", vaultinit_vault_accounts
Payout USDC account"payout", vaultinit_vault_accounts
Share escrow"escrow", vault, [0] for Senior or [1] for Juniorinit_vault_accounts
Leg"leg", vault, [index]add_leg
Leg authority"leg_auth", vault, [index]No account data; derived only
Leg transitThe leg authority's USDC associated token accountadd_leg
V2LegConfig"leg_v2", legconfigure_v2_leg
PendingAction"pending_action", legbegin_async
TokenLeg"token_leg", leginit_token_leg
WithdrawalRequest"request", vault, request_id (u64 LE)request_withdrawal
PendingChange"change", vault, change_id (u64 LE)queue_change
CostBudget"cost_budget", vaultinit_cost_budget
SettlementClaim"settlement_claim", vault, owner, [tranche]settle_shares

Table: the seeds of every vault account and the instruction that creates it.

Example. To find the Senior escrow of vault 7: compute the vault PDA from ["vault", 07 00 00 00 00 00 00 00], then the escrow PDA from ["escrow", <vault address>, 00].

The next request ID is Vault.next_request_id, so the address of a new withdrawal request is known before it is created.

Share mints

  • Senior and Junior share mints have the same number of decimals as USDC, which is 6, so one share starts at 1 USDC.
  • Their mint authority is the vault authority PDA. There is no freeze authority, so nobody can freeze your shares.
  • set_share_metadata (admin) sets each tranche's name, symbol and URI.

Byte layouts

All layouts are Borsh, the encoding Anchor uses: integers are little-endian, Pubkey is 32 bytes, bool and enums are 1 byte, and a Vec is a 4-byte length followed by its items. Offsets are in bytes from the start of the account. The maximum list lengths shown below (16 approved adapters, 32 vault creators, 8 pending adapters, 64 bound accounts per leg, 4 premium curve points, 8 legs) are fixed by the program.

Vault (1,312 bytes)

The size assumes a premium curve with the maximum of 4 points, which is how the account is allocated. With fewer points, the curve is shorter and every field after it moves 4 bytes earlier per missing point. Always decode with the program's IDL (its published interface description) rather than fixed offsets.

offset size  field
     0    8  discriminator
     8    8  vault_id                      u64
    16   32  admin                         Pubkey
    48   32  params.executor               Pubkey
    80    2  params.min_coverage_bps       u16
    82    2  params.max_coverage_bps       u16
    84   20  params.premium_curve          Vec<PremiumPoint>: u32 len + 4 x (u16 coverage_bps, u16 premium_bps)
   104    4  params.senior_delay_secs      u32
   108    4  params.junior_notice_secs     u32
   112    8  params.min_deposit            u64
   120    8  params.vault_cap              u64
   128    2  params.idle_target_bps        u16
   130    2  params.idle_min_bps           u16
   132    8  params.max_valuation_age_slots u64
   140    8  params.junior_seed            u64
   148    1  state                         0 Draft, 1 Active, 2 Recovery, 3 Settlement
   149   32  usdc_mint
   181   32  senior_mint
   213   32  junior_mint
   245   32  idle_account
   277   32  payout_account
   309   32  senior_escrow
   341   32  junior_escrow
   373    1  accounts_initialized          bool
   374    1  leg_count                     u8
   375   64  leg_values                    [u64; 8]
   439   64  leg_value_slots               [u64; 8]
   503    8  senior_value                  u64
   511    8  junior_value                  u64
   519    8  idle_ledger                   u64
   527    8  reserved_payouts              u64
   535    8  pending_seed_amount           u64
   543   32  pending_seeder                Pubkey
   575    8  next_request_id               u64
   583    1  bump
   584    1  authority_bump
   585    1  v2_leg_mask                   bit i = leg i
   586    1  pending_leg_mask
   587    8  next_action_nonce             u64
   595    1  pending_valued_mask
   596   32  guardian
   628   32  pending_admin
   660    1  paused                        bits: 1 DEPOSITS, 2 FUNDING, 4 ALLOCATION
   661   18  settings                      risk settings (see below)
   679   64  leg_value_times               [i64; 8]
   743    8  senior_pending_shares         u64
   751    1  impaired_leg_mask
   752   64  leg_marks                     [u64; 8]
   816    8  senior_impairment             u64
   824   56  settlement                    settlement record: 7 x u64
   880    8  next_change_id                u64
   888   64  leg_values_low                [u64; 8]
   952   64  leg_values_high               [u64; 8]
  1016    8  locked_profit                 u64
  1024    8  locked_profit_at              i64
  1032    8  locked_profit_end             i64
  1040    8  senior_outflow_window_start   i64
  1048    8  senior_outflow_in_window      u64
  1056   64  leg_positions                 [u64; 8]
  1120   64  leg_cost_basis                [u64; 8]
  1184    8  junior_impairment             u64
  1192    8  recovery_value                u64
  1200   43  open_trade                    open trade (see below)
  1243    1  token_leg_mask
  1244    1  suspended_leg_mask
  1245   67  reserved
  ----
  1312

Figure: the Vault account field by field; a 4-point premium curve is assumed.

settings, the risk settings (18 bytes): max_leg_growth_apr_bps u16, profit_unlock_secs u32, senior_outflow_limit_bps u16, incident_loss_bps u16, governance_delay_secs u32, add_leg_delay_secs u32.

settlement, the settlement record (56 bytes): entitled_senior, entitled_junior, supply_senior, supply_junior, cash_total, cum_senior, cum_junior, all u64.

open_trade, the open trade (43 bytes): active bool, leg_index u8, direction u8 (0 Enter, 1 Exit), then amount, min_out, value_before, units_before, mark_before, all u64.

Leg (294 bytes plus 32 per bound account)

offset    size  field
     0       8  discriminator
     8      32  vault
    40       1  index                       u8
    41      32  adapter_program
    73  4+32*n  bound_accounts              Vec<Pubkey>, n up to 64
then, with n = 0:
    77      64  account_config              8 x u64 masks (init, deposit, withdraw, value; then the writable masks)
   141      32  transit_account
   173      14  params                      target_bps u16, max_bps u16, cap u64, max_slippage_bps u16
   187       1  authority_bump
   188       1  bump
   189       1  mode                        0 Active, 1 CloseOnly, 2 Impaired, 3 Retired, 4 Suspended
   190      32  adapter_program_data
   222       8  adapter_slot                u64
   230       1  mode_before_suspension
   231      63  reserved

Figure: the Leg account; every field after bound_accounts moves 32 bytes later for each bound account.

WithdrawalRequest (139 bytes)

offset size  field
     0    8  discriminator
     8   32  vault
    40   32  owner
    72    8  request_id      u64
    80    1  tranche         0 Senior, 1 Junior
    81    8  shares          u64, shares escrowed
    89    8  min_usdc_out    u64
    97    8  requested_at    i64, unix seconds
   105    8  eligible_at     i64, unix seconds
   113    1  funded          bool, true once every share is funded
   114    8  usdc_reserved   u64, USDC set aside in the payout account so far
   122    1  bump
   123    8  funded_shares   u64
   131    8  claimed_usdc    u64

Figure: the WithdrawalRequest account; a request can be funded in parts, tracked by funded_shares and usdc_reserved.

ValuationReport (53 bytes, adapter return data)

offset size  field
     0    1  version        u8, the adapter interface version
     1    8  value_low      u64, cautious exit value in USDC base units
     9    8  value_mid      u64, book value
    17    8  value_high     u64, upper value
    25    8  liquid_usdc    u64, USDC withdrawable now
    33    8  receipt_units  u64, protocol units the leg holds
    41    8  protocol_slot  u64
    49    4  flags          u32: 1 PROTOCOL_PAUSED, 2 WITHDRAW_DISABLED, 4 DEBT_PRESENT,
                                 8 PRICE_UNCERTAIN, 16 MARKET_STRESS

Figure: the report every adapter returns from its aai_value, aai_deposit and aai_withdraw instructions.

GlobalConfig (up to 1,913 bytes)

offset  size  field
     0     8  discriminator
     8    32  authority
    40   516  approved_adapters    Vec<Pubkey>, up to 16
   556  1028  vault_creators       Vec<Pubkey>, up to 32
  1584     1  bump
  1585     4  registry_delay_secs  u32
  1589   324  pending_adapters     Vec, up to 8 entries of (program Pubkey, approvable_at i64)

Figure: the protocol-wide registry at its maximum size; offsets shift when the lists are shorter.

Other accounts

AccountFields
SettlementClaimvault, owner, tranche, units (shares converted), collected (USDC paid so far), bump
PendingChangevault, change_id, change (a ConfigChange), queued_at, executable_at, expires_at, payer, bump
PendingActionvault, leg, payer, direction, amount, input_units_settled, nonce, external_message_id, expected_return_mint, expected_return_account, observed_return_balance, returned_units, claim_value_low, claim_value_mid, claim_value_high, started_slot, deadline, bump
V2LegConfigvault, leg, capabilities, eight account masks for begin and settle of deposits and withdrawals, deposit_return_mint, deposit_return_account, bump
TokenLegleg, mint, token_account, max_trade_usdc, bump
CostBudgetvault, budget_bps, days and spent (7 entries each, one per day), bump
GlobalAuthorityProposalproposer, proposed, bump

Table: the remaining account types and their fields, in order.

Advanced details

  • Vault.idle_ledger is the vault's own count of idle USDC. It changes only through the program's own flows, never by reading the token balance. USDC sent to the idle account directly is not counted, so nobody can change the share price by donating to it. NAV is idle_ledger plus the recognised value of every leg.
  • Vault.reserved_payouts is the USDC in the payout account that funded requests are still owed.
  • A WithdrawalRequest is closed when it is fully funded and fully claimed, and its rent returns to the owner.

On this page