> 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/instructions.md).

# Instructions

The user-facing instructions exposed by `lib.rs`. They fall into four groups: **proof staging**, **transact / transfer / swap** (proof-bearing), **proofless deposit**, and **admin / lifecycle**.

#### Proof staging

Solana transactions cannot fit a Groth16 proof, all public signals, encrypted outputs, and a multi-hop CPI batch in one message. Hinkal stages them into PDAs first, then references those PDAs from the final proof-bearing instruction. All staging PDAs are seeded by `(deployer, signer, id)` so a single signer can run multiple flows in flight.

| Instruction                                                              | Account it populates      | Fields                                                                                  |
| ------------------------------------------------------------------------ | ------------------------- | --------------------------------------------------------------------------------------- |
| `init_nullifier(nullifier)`                                              | `NullifierAccount`        | `is_used = false`. Required because `init_if_needed` on every nullifier exhausts stack. |
| `close_nullifier(nullifier)`                                             | (closes)                  | Relayers only. Returns rent to signer. Reverts if `is_used`.                            |
| `store_proof_part1(id, proof_a, proof_b, proof_c, public_signals_part1)` | `ProofAccountPart1`       | Proof bytes + first slice of public signals (≤ 20).                                     |
| `store_proof_part2(id, public_signals_part2)`                            | `ProofAccountPart2`       | Remaining public signals (≤ 100).                                                       |
| `store_encrypted_outputs(id, encrypted_outputs)`                         | `EncryptedOutputsAccount` | Up to 2 encrypted UTXO blobs.                                                           |
| `store_instructions(id, instructions)`                                   | `InstructionsAccount`     | CPI batch for swaps; unused for transact/transfer.                                      |

`merge_proof_data.rs` reads the three staged PDAs and reassembles a `MergedProofData` struct for the proof pipeline.

#### Transact (deposit / withdraw)

**`transact2(id, forbid_deposit, in_nullifiers, relayer_fee, dimensions=1×2×1)`**

**`transact6(id, forbid_deposit, in_nullifiers, relayer_fee, dimensions=1×6×1)`**

Bridges a wallet's public balance to or from the shielded vault.

* If `mint` is `None` → SOL flow (`perform_transact_funds`); otherwise → SPL flow (`perform_transact_tokens`).
* `forbid_deposit = true` rejects flows whose net `amount_change` is non-negative.
* Net positive `amount_change` → `deposit_funds` from signer to vault.
* Net negative → vault transfers `|amount_change| - relayer_fee` to recipient and `relayer_fee` to signer (signer is the relayer).
* The `1×6×1` variant supports up to 6 input nullifiers per leg, used when consolidating fragmented UTXOs.
* After movement, `validate_*_balance_diff` enforces the closing balance equation.

#### Transfer (shielded → shielded)

**`transfer2(id, in_nullifiers, relayer_fee, dimensions=1×2×2)`**

**`transfer6(id, in_nullifiers, relayer_fee, dimensions=1×6×2)`**

Moves value between two shielded UTXOs without exiting the vault. Internally calls `transact_common` with `forbid_deposit = true` and recipient pinned to the system program (no on-chain recipient). The only fund movement is paying `relayer_fee` from the vault to the signer.

#### Swap (shielded → DEX → shielded)

**`swap2(id, swapper_account_additional_seed, in_nullifiers, relayer_fee, variable_rate, dimensions=2×2×1)`**

**`swap6(id, swapper_account_additional_seed, in_nullifiers, relayer_fee, variable_rate, dimensions=2×6×1)`**

Routes shielded value through any program reachable via CPI. Both variants share `swap_common`:

1. `validate_swap_atas` — the supplied `mint_from` / `mint_to` and their ATAs must be consistent (no SOL pretending to be a token, etc.).
2. `process_proof_and_state` with `on_chain_creation = [false, true]` — the input leg uses proof-derived nullifiers/commitments; the output leg's commitment is built on-chain from the actual swap result.
3. `swapper_account_additional_seed` from the proof must match the seed used to derive the swapper PDA (`SwapperAccountAdditionalSeedMismatch`).
4. `determine_swap_mode` → `TokenToToken | TokenToSol | SolToToken`.
5. Vault → swapper PDA: input liquidity is moved to the swapper (which is the CPI authority).
6. `perform_swap` invokes the staged `InstructionsAccount` against `remaining_accounts`. Account index `255` is replaced with the swapper PDA at execution time and the call is signed via `invoke_signed` with the swapper seeds. Self-CPI back into Hinkal is rejected (`SelfCPIForbidden`).
7. `swapped_amount` = balance delta on the destination ATA / SOL account.
8. `calculate_total_fee = relayer_fee + swapped_amount * variable_rate / 10000`. `variable_rate` is in basis points × 10 (i.e. denominator 10\_000).
9. Fee is paid to the signer; the remainder is the UTXO amount. A new commitment is built on-chain from `(amount, mint_to, stealth_address_structure, timestamp)` and inserted into the Merkle tree.
10. `validate_token_balance_diff` / `validate_sol_balance_diff` is run on both legs to enforce the closing equation.

#### Proofless deposits

**`proofless_deposit(amount, stealth_address_structure)`**

Single-output deposit. SOL or SPL, selected by whether `mint` is supplied.

**`multi_payment_deposit(amounts, stealth_address_structure[], create_blocked_utxos)`**

Up to N outputs in a single instruction. When `create_blocked_utxos = true`, also emits `BlockedUtxosCreated { sender, mint, amounts }` so a downstream service can build "blocked" UTXOs (e.g. for cooldowns).

Both call `proofless_deposit_common`:

* `amount > 0` for every output.
* If `access_token_owner` is supplied as a signer, validate it against `AccessTokenStorageAccount.access_token_owner_key`. Otherwise, fall back to `is_access_token_required(total_amount, token_limit)` — if `total_amount` exceeds the per-mint limit, revert with `AccessTokenRequired`.
* Run the deposit (SOL via `deposit_funds`, SPL via `deposit_tokens`) and assert `balance_diff == total_amount`.
* For each output, build an on-chain UTXO via `build_onchain_utxo` and insert it into the Merkle tree (`process_onchain_commitments`).

#### Admin & lifecycle

| Instruction                                | Authority            | Effect                                                                                                                      |
| ------------------------------------------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `initialize`                               | `original_deployer`  | Allocates and seeds storage, both Merkle trees, ownership, and access-token storage. Funds the vault PDA with rent-minimum. |
| `transfer_ownership(new_owner)`            | current `owner`      | Sets `pending_owner`. Emits `OwnershipTransferred`.                                                                         |
| `accept_ownership`                         | `pending_owner`      | Promotes pending → current. Emits `OwnershipAccepted`.                                                                      |
| `update_relayer_status(status)`            | `owner`              | Adds or removes a `relayer` from the whitelist. Emits `RelayerStatusUpdated`.                                               |
| `init_token_limit`                         | anyone (rent payer)  | Allocates a per-`(deployer, mint)` `TokenLimitStorageAccount` with `amount_limit = 0`.                                      |
| `update_token_limit(amount)`               | `owner`              | Sets the per-mint deposit/withdraw threshold. Emits `TokenLimitUpdated`.                                                    |
| `add_access_token(access_key)`             | `access_token_owner` | Creates the `AccesskeyAccount` lock and inserts `access_key` into the access-token Merkle tree. Emits `NewAccessKeyAdded`.  |
| `change_access_token_owner_key(new_owner)` | `owner`              | Rotates `access_token_owner_key`. Emits `AccessTokenOwnerChanged`.                                                          |


---

# 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/instructions.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.
