ADR-001: Recipient and Mint in PDA Seeds
Context
Section titled “Context”Solana PDAs (Program Derived Addresses) require a set of seeds that deterministically generate a unique address. The initial architecture documentation proposed seed layouts that did not include the recipient or mint in the derivation path:
["vesting", creator, mint, vesting_count]— recipient verified viahas_oneconstraint["stream", creator, recipient]— one stream per (creator, recipient) pair, no mint in seeds (cross-token collisions)
The stub code also used ["stream", creator, recipient].
We needed to decide which seed layout to use for the StreamAccount PDA, balancing address uniqueness, safety invariants, and gas cost.
Decision
Section titled “Decision”Use ["stream", creator, recipient, mint, vesting_count] as the PDA seed layout.
The recipient and mint are both included in the derivation path. Address uniqueness is guaranteed by the combination of (creator, recipient, mint, vesting_count).
Alternatives Considered
Section titled “Alternatives Considered”| Layout | Pros | Cons |
|---|---|---|
["creator", mint, vesting_count] + has_one(recipient) | Saves 32 bytes of seed data | Address doesn’t prove beneficiary; recipient set-race possible |
["stream", creator, recipient] | Simple, no nonce needed if 1:1 | Only one stream per pair; no cross-token support |
["stream", creator, recipient, mint, vesting_count] (chosen) | Cryptographic beneficiary proof; unlimited streams | 64 extra bytes in seed derivation |
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- Cryptographic beneficiary commitment: The PDA address itself proves who the stream is for. Even if account data is corrupted, the address is an immutable proof of the recipient. This is an extra safety invariant beyond Anchor’s
has_oneconstraint. - Cross-token support: Mint in the seed prevents address collisions between streams for different tokens sent to the same (creator, recipient) pair. A founder can vest USDC and their own token to the same recipient.
- Unlimited streams: The
vesting_countnonce allows the same (creator, recipient, mint) triple to have unlimited streams — useful for multi-tranche vesting schedules. - Milestone streams follow the same pattern:
["milestone-stream", creator, recipient, mint, vesting_count]mirrors the layout, keeping mental model consistent.
Negative
Section titled “Negative”- Immutable recipient: The recipient cannot be changed after creation without closing and recreating the stream. This is intentional — recipient immutability is a protocol invariant, not a limitation.
- Longer PDA derivation: 5 seed components instead of 3. The cost is negligible (PDA derivation is off-chain).