> 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/technical-description/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.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://hinkal-team.gitbook.io/hinkal/technical-description/solana-program/program-and-state.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
