Case study · Solana / Anchor

The Solana track — four programs, one ladder of DeFi primitives

Not one-off demos: a deliberate progression through Solana's core patterns — unilateral payment → time-locked release → bilateral atomic swap → continuous market — each deployed and exercised for real on devnet, all consumable from a single live console. Coming from EVM, the interesting part is what translates and what doesn't.

RustAnchor 1.1.2SPL TokenPDAsinvoke_signedwallet-adapter

The ladder

#ProgramPrimitiveOn devnet
1access-passTime-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.
2token-vestingLinear + 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.
3escrowBilateral 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.
4ammConstant-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

ConceptEVM habitSolana reality
IdentityA 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.
CustodyThe contract owns tokens in its own balanceVault token accounts whose authority is a PDA; the program signs outgoing transfers with invoke_signed — nobody else can move them.
Authorizationmsg.sender + approve/transferFromThe user signs the same transaction; their transfers come straight from their token accounts. No allowance step.
State readsMirrored storage variables (reserve0/reserve1 + sync)Read the vault accounts at the start of each instruction — one less thing to desynchronize.
The LP tokenThe pair contract is an ERC-20A 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.