Full lesson
peer-pay
Fiat-to-Solana payment connector using Stripe. 2nd place, CoinW x Superteam UAE side track, Solana Renaissance Hackathon 2024.
What it does
From the project README:
Fiat payment connector to Solana that allows user to interact with Web3 protocols and on chain programs by paying with modern payment systems such as Stripe.
- Original
- e-mk/peer-pay
- SuperStack fork
- Blockchains/peer-pay @
ca496be - Stars
- 0
- Last push (original)
- 2026-10-21
- Languages
- TypeScript, Rust, JavaScript, CSS
- Detected stack
- AnchorNext.js
- Categories
- Payments
- Licence
- Apache-2.0
- Homepage
- https://x.com/PeerPayOnSol
Architecture
35 source files. By extension: .ts ×12, .tsx ×11, .rs ×10, .js ×2.
| Top-level directory | Source files |
|---|---|
frontend | 24 |
escrow | 11 |
On-chain programs: escrow/programs/mm_escrow
❌ Does not build as-is, checked 2026-11-02 Tested on 2026-11-02: full log results.json
Build it yourself: step-by-step guide
Every command below was run on 2026-11-02 by tests/run_fork.py against the pinned commit, and the results come from that run. The full output is in the committed log.
1. Prerequisites (versions we tested with)
- Rust:
rustc 1.99.1 (58cf8f9c3 2026-10-13) - Solana CLI (Agave):
solana-cli 2.3.13 (src:5466f459; feat:2142755730, client:Agave) - Anchor (via avm):
0.29.0 - Node.js:
v24.22.1 - npm:
11.21.1 - pnpm:
10.34.6 - Go:
go version go1.27.3 linux/amd64
cargo install --git https://github.com/coral-xyz/anchor avm --locked
avm install 0.29.0 && avm use 0.29.0
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"2. Clone at the tested commit
git clone https://github.com/Blockchains/peer-pay.git && cd peer-pay
git checkout ca496be4f2ee1ce5e4154922ec9a40c35029177b3. Configure
The repo reads these environment variables. Copy the example file and fill in your own values. Never commit real keys.
frontend/.env.example:NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY,NEXT_PUBLIC_STRIPE_SECRET_KEY,NEXT_PUBLIC_HOST,NEXT_PUBLIC_SOLANA_PROGRAM_ID,NEXT_PUBLIC_SOLANA
4. Install dependencies
| Directory | Command | Result | Time |
|---|---|---|---|
frontend | npm install --no-audit --no-fund --legacy-peer-deps | ✅ pass | 19s |
escrow | npm install --no-audit --no-fund --legacy-peer-deps | ✅ pass | 8s |
escrow | npm install --no-audit --no-fund --legacy-peer-deps | ✅ pass | 0s |
5. Build
| Directory | Command | Result | Time |
|---|---|---|---|
escrow | anchor@0.29.0 build | ❌ exit 1 | 1s |
frontend | npm run build | ❌ exit 1 | 12s |
Output tail: anchor@0.29.0 build
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
error: failed to parse lock file at: /tmp/scbuild/peer-pay/escrow/Cargo.lock
Caused by:
lock file version 4 requires `-Znext-lockfile-bump`Output tail: npm run build
> Build error occurred
Error: Failed to collect page data for /utils
at /tmp/scbuild/peer-pay/frontend/node_modules/next/dist/build/utils.js:1258:15
at process.processTicksAndRejections (node:internal/process/task_queues:104:5) {
type: 'Error'
}7. Deploy to a local validator
solana-test-validator --reset # separate terminal
solana config set -ul && solana airdrop 100
anchor deploy --provider.cluster localnetDeploy to devnet (free)
solana config set --url devnet
solana airdrop 2 # or https://faucet.solana.com
anchor deploy --provider.cluster devnet # native: solana program deploy target/deploy/<name>.so
# point the frontend RPC env var at https://api.devnet.solana.comHow it works
Every instruction handler in the program, with the accounts struct it validates. Each link points to the line at the pinned commit.
| Instruction | Accounts | Where |
|---|---|---|
accept | Accept | escrow/programs/mm_escrow/src/lib.rs:28 |
cancel | Cancel | escrow/programs/mm_escrow/src/lib.rs:34 |
Extend it
- Add an integration test that runs the main flow end to end and asserts every account state change.
- Add a negative test: call each instruction with the wrong signer or account and check that it fails.
- Add an event (
emit!) to each state-changing instruction and subscribe to it from the client. - Replace any unchecked
AccountInfowith typed accounts plushas_one/seedsconstraints. - Make the frontend read the cluster from an env var, deploy it to GitHub Pages and point it at your devnet program.
- Wire the build into CI by copying this site's verify-forks workflow.
Dapp: deploying
This fork is queued for its own live dapp on Sol.College, with a Devnet/Mainnet switch. It appears here, at /dapps/peer-pay/, once it is deployed and checked on devnet. Until then, run it locally with the commands below.
Key code, explained
Excerpts chosen automatically where the code uses the most Solana concepts, pinned to the fork's commit.
escrow/programs/mm_escrow/src/instructions/initialize.rs L13–36 @ ca496be · Apache-2.0 · original seller: Signer<'info>, x_mint: Account<'info, Mint>, y_mint: Account<'info, Mint>, #[account(mut, constraint = sellers_x_token.mint == x_mint.key() && sellers_x_token.owner == seller.key())] sellers_x_token: Account<'info, TokenAccount>, #[account( init, payer = seller, seeds = ["escrow".as_bytes(), seller.key().as_ref(), product_seed.as_str().as_bytes()], space = Escrow::LEN, bump, )] pub escrow: Account<'info, Escrow>, #[account( init, payer = seller, seeds = ["escrow".as_bytes(), escrow.key().as_ref()], token::mint = x_mint, token::authority = escrow, bump,- Line 13
seller: Signer<'info>,: Requires this account to have signed the transaction. - Line 21
init,: Creates and pays for a new account. Space and payer must be specified. - Line 23
seeds = ["escrow".as_bytes(),: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 27
bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
escrow/programs/mm_escrow/src/instructions/cancel.rs L1–24 @ ca496be · Apache-2.0 · originalpub use crate::state::*;use anchor_lang::prelude::*;use anchor_spl::token::{close_account, transfer, CloseAccount, Token, TokenAccount, Transfer}; #[derive(Accounts)]pub struct Cancel<'info> { #[account(mut)] seller: Signer<'info>, #[account( mut, constraint = escrowed_x_tokens.key() == escrow.escrowed_x_tokens, seeds = ["escrow".as_bytes(), escrow.key().as_ref()], bump )] pub escrowed_x_tokens: Account<'info, TokenAccount>, #[account( seeds = ["escrow".as_bytes(), escrow.authority.key().as_ref(), escrow.product_seed.as_str().as_bytes()], bump, )] pub escrow: Account<'info, Escrow>, #[account(mut, constraint = sellers_x_token.mint == escrowed_x_tokens.mint && sellers_x_token.owner == seller.key())] sellers_x_token: Account<'info, TokenAccount>,- Line 8
seller: Signer<'info>,: Requires this account to have signed the transaction. - Line 12
seeds = ["escrow".as_bytes(),: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 14
bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
escrow/programs/mm_escrow/src/instructions/accept.rs L1–24 @ ca496be · Apache-2.0 · originaluse anchor_lang::prelude::*;use anchor_spl::token::{transfer, Token, TokenAccount, Transfer};use std::str::FromStr; use solana_program::sysvar::instructions::{ self, load_current_index_checked, load_instruction_at_checked,}; pub use crate::constants::*;pub use crate::error::Errors;pub use crate::helpers::helpers::check_price;pub use crate::state::*; #[derive(Accounts)]pub struct Accept<'info> { #[account(mut)] buyer: Signer<'info>, #[account( seeds = ["escrow".as_bytes(), escrow.authority.key().as_ref(), escrow.product_seed.as_str().as_bytes()], bump, )] pub escrow: Account<'info, Escrow>,- Line 17
buyer: Signer<'info>,: Requires this account to have signed the transaction. - Line 19
seeds = ["escrow".as_bytes(),: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 22
bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
How to run, build and test
These commands come from the repo's own manifests (package.json scripts, Anchor.toml, Cargo, go.mod, Makefile). Check the README for prerequisites and environment variables.
| Directory | Command | Runs |
|---|---|---|
frontend | npm run dev | next dev |
frontend | npm run start | next start |
frontend | npm run build | next build |
frontend | npm run lint | next lint |
escrow | npm run lint | prettier */*.js "*/**/*{.js,.ts}" --check |
escrow | anchor build | compile programs and generate IDLs |
escrow | anchor test | local validator + test suite |
escrow | anchor run test | yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts |
What you could build from it
- Fork the SuperStack copy and extend it within the Payments gaps listed on the idea page.
- Learn the pattern first: Solana Pay
- Learn the pattern first: SPL Tokens: mints, accounts and transfers
- Learn the pattern first: Blinks and Solana Actions