The ladder
| # | Program | Primitive | On devnet |
|---|---|---|---|
| 1 | access-pass | Time-based subscription: pay SOL, a Pass PDA extends its expiry. A cross-chain port of my EVM BotPass — same product, both ecosystems. | Deployed; initialize + subscribe confirmed on-chain. |
| 2 | token-vesting | Linear + cliff, revocable SPL vesting — the VestingWallet pattern, Solana-native: a schedule PDA plus a token vault the PDA signs for via CPI. | Deployed; a real initialize→release cycle with an SPL mint. |
| 3 | escrow | Bilateral atomic swap (make / take / refund): both legs settle in one transaction or neither does. The base pattern of any OTC or marketplace. | Deployed; a real 100↔250 two-token swap settled, signatures in the README. |
| 4 | amm | Constant-product AMM (x·y=k, the Uniswap V2 model): pools per canonical mint pair, PDA-owned vaults and LP mint, √(a·b) initial liquidity, bps fee, all rounding in the pool's favor. | Deployed; full cycle on devnet — pool init, add liquidity, a real x·y=k swap and remove — signatures in the README. |
All four are driven by solana-hub, a React + wallet-adapter console that reads live on-chain state (no mocks): listings work without connecting a wallet, and Phantom/Solflare users can subscribe, crank release(), take escrow offers, and inspect AMM pools.
EVM → Solana: what actually translates
| Concept | EVM habit | Solana reality |
|---|---|---|
| Identity | A contract deployed per instance (factory pattern) | A PDA derived from seeds — the AMM needs no factory: ["pool", mint_a, mint_b] with canonical ordering is the registry. |
| Custody | The contract owns tokens in its own balance | Vault token accounts whose authority is a PDA; the program signs outgoing transfers with invoke_signed — nobody else can move them. |
| Authorization | msg.sender + approve/transferFrom | The user signs the same transaction; their transfers come straight from their token accounts. No allowance step. |
| State reads | Mirrored storage variables (reserve0/reserve1 + sync) | Read the vault accounts at the start of each instruction — one less thing to desynchronize. |
| The LP token | The pair contract is an ERC-20 | A separate SPL mint whose authority is the pool PDA — accounts compose, they don't inherit. |
War stories (native Windows, no WSL)
Building this on native Windows surfaced real toolchain bugs — each documented in the repos so the next person loses minutes, not days:
The interleaved IDL
anchor idl build died with EOF while parsing a value. Root cause: the IDL is emitted in fragments by the test harness, and cargo test runs in parallel, so fragments interleave on stdout and the parser receives corrupted JSON. Fix: capture single-threaded (--test-threads=1) and reassemble the fragments with a small Node script — shipped as scripts/build-idl.cjs in each repo.
The silent stack overflow
The AMM's 10-account liquidity instruction blew Solana's 4096-byte stack frame (try_accounts at 4480 bytes) — and cargo-build-sbf printed the error but still emitted the .so with exit code 0. Deploying that is undefined behavior. Fix: Box the Mint/TokenAccount accounts so deserialization goes to the heap. Lesson: check the build log, not the exit code.
Honest testing when the validator won't start
solana-test-validator doesn't run on native Windows (genesis permission failure). The test suites are written — but instead of faking a green run, each README states it plainly and substitutes stronger evidence: real devnet deploys with cited transaction signatures anyone can open in the Explorer.
Constraints as security
Each program leans on Anchor's account constraints as the security boundary: canonical mint ordering enforced at pool creation, has_one tying vaults to their owners, close = returning rent on settlement, bumps stored and reused instead of re-derived, and every arithmetic path in the AMM rounding in the pool's favor with u128 intermediates and explicit overflow errors. Program upgrade keypairs never touch git.