Skip to content

ADR-002: Custom PDA Token Account for Vault

The protocol needs an escrow account to hold tokens while they vest. The two standard Solana approaches are:

  1. Associated Token Account (ATA): Deterministic address from (wallet, mint, program). Auto-discovered by explorers and wallets.
  2. Custom PDA Token Account: A token account with a PDA-based address using arbitrary seeds, where the stream PDA is the authority.

The initial architecture documentation proposed using an ATA for the vault. This was reconsidered based on the rent economics of completed streams.

Use a custom PDA token account ["vault", stream.key()] instead of an ATA.

The vault is a standard SPL Token account created with the Token Program, but its address is a PDA derived from the stream’s address. The stream PDA is set as the token account authority.

When a stream completes or is cancelled, the accounts are closed to return rent-exempt SOL to the creator. A custom PDA token account can be fully closed via CPI to the Token Program’s close_account instruction. An ATA would remain on-chain permanently because ATAs are never closed by convention — they persist even with zero balance.

At ~0.002 SOL per account, a creator with 1,000 completed streams would permanently lock ~2 SOL in unclosable ATA accounts. At scale (10,000+ recipients), this becomes a material cost with no recovery mechanism.

ApproachProsCons
ATAWallet-compatible, auto-discovered by explorersCannot be closed; rent locked permanently
Custom PDA token account (chosen)Fully closeable; rent returned to creatorNot auto-discovered by wallets; requires derivation
  • Rent reclaim: Vault accounts are closed on completion and cancellation, returning rent-exempt SOL to the creator. This aligns with the protocol’s fee transparency promise — founders know exactly what they pay and get refunds when streams end.
  • Deterministic address: The vault address is findProgramAddress(["vault", streamPDA]), which is trivially derivable from the stream PDA.
  • Clean on-chain state: No zombie token accounts accumulating over time.
  • Not wallet-discoverable: Wallets and explorers don’t automatically discover custom PDA token accounts. Users must derive the vault address from the stream PDA. The SDK provides getVaultPda() for this.
  • Extra account in instruction lists: Every instruction that interacts with the vault needs the vault account explicitly passed. With an ATA, some frameworks could derive it automatically.