> For the complete documentation index, see [llms.txt](https://hinkal-team.gitbook.io/hinkal/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hinkal-team.gitbook.io/hinkal/protocol/solana-program/program-and-state.md).

# Program & State

The Hinkal Solana program is a single Anchor program. It exposes one set of instructions for users (deposits, transfers, swaps), one for relayers (nullifier housekeeping), and one for admins (initialize, ownership, access-token, token-limit, relayer whitelist). All persistent state lives in **PDAs** seeded by `original_deployer.key()`, allowing parallel deployments to coexist under the same program ID.

#### Seeds

`seed_constants.rs`:

| Constant                           | Bytes                           | Used by                           |
| ---------------------------------- | ------------------------------- | --------------------------------- |
| `STORAGE_ACCOUNT_SEED`             | `b"hinkal"`                     | `StorageAccount`                  |
| `MERKLE_ACCOUNT_SEED`              | `b"hinkal_merkle"`              | Commitment Merkle tree            |
| `MERKLE_ACCESS_TOKEN_ACCOUNT_SEED` | `b"hinkal_merkle_access_token"` | Access-token Merkle tree          |
| `NULLIFIER_ACCOUNT_SEED`           | `b"hinkal_nullifier"`           | Per-nullifier PDA                 |
| `VAULT_ACCOUNT_SEED`               | `b"hinkal_vault"`               | Storage vault (SystemAccount PDA) |
| `ACCESS_TOKEN_ACCOUNT_SEED`        | `b"hinkal_access_token"`        | `AccessTokenStorageAccount`       |
| `ACCESS_KEYS_ACCOUNT_SEED`         | `b"hinkal_access_keys"`         | Per-access-key PDA                |
| `TOKEN_LIMIT_ACCOUNT_SEED`         | `b"hinkal_token_limit"`         | Per-mint deposit/withdraw limit   |
| `SWAPPER_ACCOUNT_SEED`             | `b"hinkal_swapper"`             | Per-swap CPI signer PDA           |
| `PROOF_PART1_ACCOUNT_SEED`         | `b"hinkal_proof_part1"`         | Staged proof + signals (part 1)   |
| `PROOF_PART2_ACCOUNT_SEED`         | `b"hinkal_proof_part2"`         | Staged signals (part 2)           |
| `ENCRYPTED_OUTPUTS_ACCOUNT_SEED`   | `b"hinkal_encrypted_outputs"`   | Staged encrypted UTXO outputs     |
| `INSTRUCTIONS_SEED`                | `b"hinkal_instructions"`        | Staged CPI instructions for swaps |

#### Account types

All account structs live in `hinkal_accounts.rs` and `types.rs`. Each carries a `version: u8` enforced by the `declare_versioned!` macro (`versions.rs`), so old layouts revert with `InvalidVersion` after upgrades.

**`StorageAccount`**

* `version: u8`
* `whitelisted_relayers: Vec<Pubkey>` (max 10)
* `owner: Pubkey`
* `pending_owner: Pubkey` (two-step ownership transfer; defaults to the system-program ID after acceptance)

Single per-deployment storage of relayer policy and ownership. Mutated only by admin instructions.

**`Merkle` (zero-copy)**

* `tree_levels: [[u8; 32]; 200]`
* `roots: [[u8; 32]; 100]` (`MAX_ROOT_NUMBER = 100`)
* `m_index`, `root_index`, `levels`, `minimum_index` - internal counters

Append-only commitment tree using Poseidon (`poseidon::hash2`). `insert` adds a leaf and writes a new root into a circular buffer of the last 100 roots; `root_hash_exists` walks the buffer in reverse from the latest pointer. The same struct is reused for the **access-token** tree under a different seed.

**`AccessTokenStorageAccount`**

* `access_token_owner_key: Pubkey`

Identifies the only signer permitted to mint new access keys (`add_access_token`) or to authorize over-limit deposits.

**`AccesskeyAccount`**

* `is_added: bool`

One PDA per access key, used as a uniqueness lock so a key cannot be inserted twice.

**`TokenLimitStorageAccount`**

* `amount_limit: u64`

Per-(deployer, mint) threshold above which a flow requires an access token / access-token Merkle root membership.

**`NullifierAccount`**

* `is_used: bool`

One PDA per nullifier. Pre-created via `init_nullifier`, flipped by `process_nullifiers` during a transact/transfer/swap, and recoverable via `close_nullifier` (relayer-only).

**`InstructionsAccount`**

* `instructions: Vec<SolanaInstructionData>`
* `INIT_SPACE = 10232` (\~10 KB) - large enough to hold a multi-hop router's CPI batch.

**`EncryptedOutputsAccount`**

* `encrypted_outputs: Vec<Vec<u8>>` (up to 2 outputs × \~500 bytes)

**`ProofAccountPart1`**

* `proof_a_arr: [u8; 64]`
* `proof_b_arr: [u8; 128]`
* `proof_c_arr: [u8; 64]`
* `public_signals_part1: Vec<[u8; 32]>` (max 20)

**`ProofAccountPart2`**

* `public_signals_part2: Vec<[u8; 32]>` (max 100)

The proof + signals are split across two PDAs so each `store_*` instruction fits in a single Solana transaction.

#### Storage vault

A `SystemAccount` PDA at `["hinkal_vault", original_deployer]`. It custodies all shielded SOL directly and is the **authority** for the SPL token ATAs that hold shielded token balances. It signs outgoing transfers via `invoke_signed` with the vault seeds (see `transfer_utils.rs`).

#### Init flow

`initialize` (`instructions/initialize.rs`) is called once per deployment by `original_deployer`:

1. Allocates `StorageAccount`, `AccessTokenStorageAccount`, the commitment `Merkle`, and the access-token `Merkle`.
2. Pushes `original_deployer` into `whitelisted_relayers` and sets `owner = original_deployer`, `pending_owner = default`.
3. Calls `Merkle::init(MAX_TREE_LEVELS)` for both trees (zeroes all levels and roots, seeds `m_index` / `minimum_index`).
4. Funds the storage vault PDA with rent-minimum lamports so it can hold balances.
5. Sets `access_token_owner_key = original_deployer`.

After `initialize`, the per-mint flows still need:

* `init_token_limit` (owner-only) for any mint the deployment will accept.
* `init_nullifier` for each new nullifier before its consuming instruction.
