A PDA-based SOL vault system on Solana. Manage the full lifecycle: create vaults, deposit SOL, assign claim amounts, process claims, withdraw leftovers, and clean up accounts.
Built with Anchor framework.
Warning
This software has not undergone a professional security audit. Use it at your own risk. See the security policy for vulnerability reporting.
- Upgrade-authority-controlled vault creation
- PDA-based vault and claim-record accounts
- SOL deposits, authority-managed payouts, and withdrawals
- Explicit open and closed lifecycle states
- Rent recovery through vault and claim-record cleanup
- Anchor client example and
@solana/kitPDA helpers
- Anchor CLI 1.1.2
- Solana CLI 1.18 or newer
- Rust and Cargo
- Bun (for the integration test)
Windows is supported through WSL 2. Install the requirements and run all build, test, and deployment commands from a WSL terminal.
The integration test runs against a disposable local validator. It generates a temporary program ID and wallets, leaving the placeholders in this repository unchanged.
cd tests
bun install
bun run testThe test requires Anchor CLI, Solana CLI, solana-test-validator, and Bun.
Compiled dependencies are cached under the ignored target/local-test-cache/
directory; temporary IDs, keypairs, and validator data are still deleted after
every run.
Vault — Holds deposited SOL for a single vault instance.
| Field | Type | Description |
|---|---|---|
authority | Pubkey | Wallet that controls the vault |
vault_id | [u8; 32] | Unique fixed-size vault identifier |
status | VaultStatus | Current lifecycle state |
total_deposited | u64 | Cumulative lamports deposited |
total_claimed | u64 | Cumulative lamports claimed |
total_withdrawn | u64 | Cumulative lamports withdrawn |
bump | u8 | PDA bump seed |
Space: 98 bytes (8 + 32 + 32 + 1 + 8 + 8 + 8 + 1)
ClaimRecord — Tracks a payout that the vault authority may process for a claimant. It does not reserve or escrow SOL.
| Field | Type | Description |
|---|---|---|
vault | Pubkey | Associated vault address |
authority | Pubkey | Vault authority at time of creation |
claimant | Pubkey | Wallet that gets the payout |
amount | u64 | Authority-managed payout (lamports) |
claimed_at | i64 | Unix timestamp of claim (0 if unclaimed) |
bump | u8 | PDA bump seed |
Space: 121 bytes (8 + 32 + 32 + 32 + 8 + 8 + 1)
| Account | Seeds |
|---|---|
Vault | ["vault", vault_id] |
ClaimRecord | ["claim", vault_pubkey, claimant_pubkey] |
Open ──deposit──► Open ──close_vault──► Closed
│
set_claimable / claim
withdraw / withdraw_all
│
cleanup_vault
| Variant | Description |
|---|---|
Open | Vault is active — deposits and close are allowed |
Closed | Locked — claims, withdrawals, and cleanup are now enabled |
Creates a new vault PDA. Only the program's upgrade authority can call this — only the deployer can create vaults.
- Signer:
authority(must be program upgrade authority) - Accounts: vault (init), authority, program, program_data, system_program
- Validations:
vault_idcannot be all zeroes; authority must matchprogram_data.upgrade_authority_address - Status after:
Open
Transfers SOL from the authority into the vault via CPI to the system program.
- Signer:
authority(vault authority) - Accounts: vault (mut), authority (mut), system_program
- Validations:
amount > 0; vault must beOpen
Locks the vault, preventing further deposits. Enables claims, withdrawals, and cleanup.
- Signer:
authority(vault authority) - Accounts: vault (mut), authority
- Validations: vault status must be
Open - Status after:
Closed
Creates a ClaimRecord PDA for a claimant, recording an intended payout. No SOL
is transferred or reserved. The authority can still withdraw funds before the
payout is processed, so a claim record is not a funding guarantee.
- Signer:
authority(vault authority) - Accounts: vault (mut), claimant (unchecked), claim_record (init), authority (mut), system_program
- Validations:
amount > 0; vault must beClosed
Pays out a claimant. The authority—not the claimant—must sign, and SOL transfers directly from the vault to the recipient. Claimants cannot redeem records independently.
- Signer:
authority(vault authority) - Accounts: vault (mut), claim_record (mut), authority, recipient (mut)
- Validations: vault
Closed; claim_record matches vault + recipient; not already claimed; sufficient funds
Authority withdraws a specific amount of SOL from the vault.
- Signer:
authority(vault authority) - Accounts: vault (mut), authority, destination (mut)
- Validations:
amount > 0; vaultClosed; amount ≤ available lamports (above rent-exempt minimum)
Authority withdraws all available SOL (above rent-exempt minimum) from the vault.
- Signer:
authority(vault authority) - Accounts: vault (mut), authority, destination (mut)
- Validations: vault
Closed; available > 0
Closes the vault account and gets rent back. All payouts need to be settled first (total_deposited == total_claimed + total_withdrawn). Any leftover dust is drained before closing.
- Signer:
authority(vault authority) - Accounts: vault (mut, close → destination), authority, destination (mut)
- Validations: vault
Closed; accounting needs to balance
Closes a claim record and gets rent back. Two ways:
- Claimed records: The claimant signs to get their rent back
- Unclaimed records: The authority signs to clean up abandoned records
Vault-independent — works even after the vault is closed.
- Signer:
signer(claimant or authority) - Accounts: claim_record (mut, close → signer), signer (mut)
| Code | Message |
|---|---|
InvalidVaultId | Invalid vault ID |
InvalidAmount | Invalid amount |
InvalidVaultStatus | Vault is not in the correct status for this operation |
InsufficientFunds | Insufficient funds in vault |
Overflow | Arithmetic overflow |
AlreadyClaimed | Payout already claimed |
InvalidClaimRecord | Claim record does not match expected claimant |
NothingToClaim | Nothing to claim |
NothingToWithdraw | Nothing to withdraw |
Unauthorized | Unauthorized |
VaultAccountingMismatch | Vault accounting mismatch |
Please report vulnerabilities privately by following the security policy.
Licensed under the MIT License.