Sol.College

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 directorySource files
frontend24
escrow11

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)

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 ca496be4f2ee1ce5e4154922ec9a40c35029177b

3. Configure

The repo reads these environment variables. Copy the example file and fill in your own values. Never commit real keys.

4. Install dependencies

DirectoryCommandResultTime
frontendnpm install --no-audit --no-fund --legacy-peer-deps✅ pass19s
escrownpm install --no-audit --no-fund --legacy-peer-deps✅ pass8s
escrownpm install --no-audit --no-fund --legacy-peer-deps✅ pass0s

5. Build

DirectoryCommandResultTime
escrowanchor@0.29.0 build❌ exit 11s
frontendnpm run build❌ exit 112s
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 localnet

Deploy 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.com

How it works

Every instruction handler in the program, with the accounts struct it validates. Each link points to the line at the pinned commit.

InstructionAccountsWhere
acceptAcceptescrow/programs/mm_escrow/src/lib.rs:28
cancelCancelescrow/programs/mm_escrow/src/lib.rs:34

Extend it

  1. Add an integration test that runs the main flow end to end and asserts every account state change.
  2. Add a negative test: call each instruction with the wrong signer or account and check that it fails.
  3. Add an event (emit!) to each state-changing instruction and subscribe to it from the client.
  4. Replace any unchecked AccountInfo with typed accounts plus has_one/seeds constraints.
  5. Make the frontend read the cluster from an env var, deploy it to GitHub Pages and point it at your devnet program.
  6. 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.

e-mk/peer-pay · 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,
  1. Line 13 seller: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 21 init,: Creates and pays for a new account. Space and payer must be specified.
  3. Line 23 seeds = ["escrow".as_bytes(),: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  4. Line 27 bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
e-mk/peer-pay · escrow/programs/mm_escrow/src/instructions/cancel.rs L1–24 @ ca496be · Apache-2.0 · original
pub 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>,
  1. Line 8 seller: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 12 seeds = ["escrow".as_bytes(),: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. Line 14 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
e-mk/peer-pay · escrow/programs/mm_escrow/src/instructions/accept.rs L1–24 @ ca496be · Apache-2.0 · original
use 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>,
  1. Line 17 buyer: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 19 seeds = ["escrow".as_bytes(),: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. 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.

DirectoryCommandRuns
frontendnpm run devnext dev
frontendnpm run startnext start
frontendnpm run buildnext build
frontendnpm run lintnext lint
escrownpm run lintprettier */*.js "*/**/*{.js,.ts}" --check
escrowanchor buildcompile programs and generate IDLs
escrowanchor testlocal validator + test suite
escrowanchor run testyarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts

What you could build from it

Used in lessons