Client Repository: https://github.com/franRappazzini/p2p-client
A peer-to-peer (P2P) escrow program built on Solana using the Anchor framework. This project facilitates secure SPL token exchanges between buyers and sellers, with dispute resolution mechanisms and protection for both parties.
- How Solana Powers the Core of Our Project
- Overview
- Features
- Project Structure
- Program Architecture
- Program Methods
- States and Accounts
- Events
- Tests
- Installation and Setup
- Local Development
- Deployment
- License
Solana is not just a blockchain choice for our P2P exchange platformβit's the fundamental enabler that makes decentralized fiat-to-crypto trading actually viable and competitive with centralized solutions.
P2P exchanges require immediate transaction finality. When a buyer confirms fiat payment, the crypto needs to be released from escrow instantly. Solana's 400ms block times and sub-second finality make this possible. Users experience the responsiveness they expect from modern financial applications, with transactions confirming almost immediately. This speed is crucial for building trust in the P2P exchange process, where any delay creates uncertainty and friction.
Our platform targets global adoption, including users in emerging markets who may trade small amounts. With transaction costs averaging $0.00025, Solana makes every trade economically viable regardless of size. Whether someone is trading $10 or $10,000, the fees remain negligible. This truly democratizes access to cryptocurrency, ensuring that cost is never a barrier to entry for anyone, anywhere in the world.
Our smart contract leverages Solana's account model and Program Derived Addresses (PDAs) to create secure, trustless escrow mechanics. When an order is created, funds are locked in a PDA controlled by our program logicβneither party can access them unilaterally. The escrow automatically releases crypto to the buyer only when conditions are met, or returns it to the seller if the trade is cancelled. This eliminates counterparty risk without requiring a trusted third party, making peer-to-peer trading truly safe and decentralized.
Solana's capacity to handle 65,000+ transactions per second means our platform can scale globally without congestion or degraded performance. As trading volume grows, the network maintains consistent speed and costs. This scalability is critical for a P2P platform where thousands of users might be creating orders, updating escrows, and completing trades simultaneously across different regions and time zones.
In summary: Solana's combination of speed, cost-efficiency, and robust programming capabilities makes decentralized P2P fiat-crypto exchange not just possible, but practical and scalable. The network provides the infrastructure needed to deliver a smooth, affordable, and trustless experience that can serve users globally without compromise.
This Solana program implements a decentralized escrow system that enables secure P2P transactions between users. The typical flow includes:
- Seller deposits tokens into an escrow account
- Buyer makes the fiat payment off-chain
- Buyer marks the escrow as paid
- Seller releases the tokens to the buyer after confirming the fiat payment
- If there are issues, either party can create a dispute
The program includes:
- Configurable fee system
- Configurable deadlines for fiat payments and disputes
- Dispute mechanism with security deposit
- Fraud protection with signature validation
- Multi SPL token management
- β Secure Escrow: Tokens locked until both parties fulfill their obligations
- β Dispute System: Two-level mechanism (dispute and re-dispute)
- β Direct Release: Seller releases tokens after confirming fiat payment
- β Configurable Deadlines: Time limits for payments and disputes
- β Flexible Fees: Configurable basis points (BPS) system
- β Multi-Token: Support for any SPL token
- β Events: Event emission for tracking and monitoring
- β Fund Management: Vault system to store fees
p2p/
βββ programs/p2p/
β βββ src/
β βββ lib.rs # Program entry point
β βββ constants.rs # System constants
β βββ errors.rs # Custom errors
β βββ events.rs # Emitted events
β βββ instructions/ # Instruction logic
β β βββ mod.rs
β β βββ initialize.rs # Global configuration initialization
β β βββ update_global_config.rs # Update global configuration
β β βββ create_escrow.rs # Escrow creation
β β βββ mark_escrow_as_paid.rs # Mark fiat payment
β β βββ release_tokens_in_escrow.rs # Release tokens
β β βββ cancel_escrow.rs # Cancel escrow
β β βββ create_dispute.rs # Create dispute
β β βββ resolve_dispute.rs # Resolve dispute
β β βββ withdraw_spl.rs # Withdraw fees
β βββ states/ # Account definitions
β βββ mod.rs
β βββ global_config.rs # Global configuration
β βββ escrow.rs # Escrow state
β βββ mint_vault.rs # Token vault
βββ tests/
β βββ p2p.test.ts # Main tests
β βββ utils/ # Testing utilities
β βββ accounts.ts
β βββ constants.ts
β βββ events.ts
β βββ functions.ts
β βββ parsers.ts
βββ target/
β βββ idl/p2p.json # Generated IDL
β βββ types/p2p.ts # TypeScript types
βββ Anchor.toml # Anchor configuration
βββ Cargo.toml # Rust dependencies
βββ package.json # Node dependencies
βββ tsconfig.json # TypeScript configuration
-
GlobalConfig: Program global configuration
- Authority (administrator)
- Escrow counter
- Fee and deadline parameters
- Available funds for withdrawal
-
Escrow: Represents a P2P transaction
- Unique ID
- Seller and buyer
- Token mint and amount
- Current state (Open, FiatPaid, Dispute, ReDispute)
- Dispute information
-
MintVault: Stores tokens and fees per mint
- Total deposited amount
- Available amount for withdrawal
Initializes the program's global configuration (only once).
pub fn initialize(
ctx: Context<Initialize>,
fee_bps: u16, // Fee in basis points (e.g., 100 = 1%)
fiat_deadline_secs: i64, // Deadline for fiat payment in seconds
dispute_deadline_secs: i64, // Deadline to create dispute in seconds
dispute_fee_escrow: u64, // Required deposit for disputes (lamports)
) -> Result<()>Parameters:
fee_bps: System fee (e.g., 100 = 1%, 250 = 2.5%)fiat_deadline_secs: Time limit for buyer to pay (e.g., 1800 = 30 min)dispute_deadline_secs: Minimum time before being able to dispute (e.g., 43200 = 12 hours)dispute_fee_escrow: Deposit in lamports to create a dispute
Usage: Only the authority can call this function once during the program's lifecycle.
Creates a new escrow by depositing tokens from the seller.
pub fn create_escrow(
ctx: Context<CreateEscrow>,
amount: u64, // Amount of tokens to deposit
) -> Result<()>Process:
- Transfers tokens from seller's account to vault
- Creates an Escrow account with
Openstate - Increments global escrow counter
- Emits
EscrowCreatedevent
Requirements:
- Seller must have sufficient tokens
- Buyer must be specified in context accounts
The buyer marks the escrow as paid after transferring fiat off-chain.
pub fn mark_escrow_as_paid(
ctx: Context<MarkEscrowAsPaid>,
escrow_id: u64,
) -> Result<()>Process:
- Verifies the caller is the buyer
- Changes escrow state to
FiatPaid - Records the timestamp
- Emits
MarkEscrowAsPaidevent
Requirements:
- Only the buyer can call this function
- Escrow must be in
Openstate
Releases tokens to the buyer. Called by the seller after confirming the fiat payment.
pub fn release_tokens_in_escrow(
ctx: Context<ReleaseTokensInEscrow>,
escrow_id: u64,
) -> Result<()>Process:
- Verifies the caller is the seller
- Calculates and deducts the fee
- Transfers tokens to buyer
- Updates vault with fees
- Closes escrow account
- Emits
TokensReleasedevent
Requirements:
- Only the seller can call this function
- Escrow must be in
FiatPaidstate
Cancels an escrow and returns tokens to the seller.
pub fn cancel_escrow(
ctx: Context<CancelEscrow>,
escrow_id: u64,
) -> Result<()>Process:
- Verifies that the fiat payment deadline has passed
- Verifies the state is
Open - Returns tokens to seller
- Closes escrow account
- Emits
EscrowCancelledevent
Requirements:
- Only the seller can cancel
fiat_deadline_secsmust have elapsed since creation- State must be
Open(not paid)
Creates a dispute on an escrow (can be dispute or re-dispute).
pub fn create_dispute(
ctx: Context<CreateDispute>,
escrow_id: u64,
) -> Result<()>Process:
- Verifies that the dispute deadline has passed
- Charges the dispute deposit in lamports
- Changes state to
DisputeorReDispute - Records who disputes (seller or buyer)
- Emits
DisputeCreatedevent
Possible states:
FiatPaidβDispute: First disputeDisputeβReDispute: Counter-dispute from the other party
Requirements:
dispute_deadline_secsmust have passed since last state change- Disputant must deposit
dispute_fee_escrowlamports - In re-dispute, only the counterparty can dispute
Resolves a dispute by sending tokens to the winner (authority only).
pub fn resolve_dispute(
ctx: Context<ResolveDispute>,
escrow_id: u64,
) -> Result<()>Process:
- Verifies the caller is the authority
- Transfers tokens to specified winner
- Calculates and distributes dispute funds
- Updates available fees
- Closes escrow account
- Emits
DisputeResolvedevent
Requirements:
- Only authority can resolve disputes
- Escrow must be in
DisputeorReDisputestate - Must specify the winner (
toin accounts)
Withdraws accumulated fees from a specific token (authority only).
pub fn withdraw_spl(
ctx: Context<WithdrawSpl>,
) -> Result<()>Process:
- Verifies the caller is the authority
- Transfers available tokens from vault to authority
- Updates available counter in vault
Requirements:
- Only authority can withdraw
- Must have available funds in the vault of the specified mint
Updates the program's global configuration parameters (authority only).
pub fn update_global_config(
ctx: Context<UpdateGlobalConfig>,
authority: Option<Pubkey>,
fee_bps: Option<u16>,
fiat_deadline_secs: Option<i64>,
dispute_deadline_secs: Option<i64>,
dispute_fee_escrow: Option<u64>,
) -> Result<()>Requirements:
- Only the current authority can call this function
- All parameters are optional - provide only what needs to be changed
- Changes affect all future escrows, not existing ones
pub enum EscrowState {
Open(i64), // Escrow created, waiting for payment (timestamp)
FiatPaid(i64), // Buyer marked as paid (timestamp)
Dispute(i64), // In dispute (timestamp)
ReDispute(i64), // In re-dispute (timestamp)
}pub enum EscrowDisputedBy {
Nobody, // No disputes
Seller, // Disputed by seller
Buyer, // Disputed by buyer
}The program emits the following events for tracking:
pub struct EscrowCreated {
pub id: u64,
pub seller: Pubkey,
pub mint: Pubkey,
pub amount: u64,
}pub struct MarkEscrowAsPaid {
pub id: u64,
pub seller: Pubkey,
pub buyer: Pubkey,
pub marked_at: i64,
}pub struct TokensReleased {
pub id: u64,
pub seller: Pubkey,
pub buyer: Pubkey,
pub mint: Pubkey,
pub amount: u64,
}pub struct EscrowCancelled {
pub id: u64,
pub seller: Pubkey,
pub mint: Pubkey,
pub returned_amount: u64,
pub canceled_at: i64,
}pub struct DisputeCreated {
pub id: u64,
pub disputant: Pubkey,
pub disputed_at: i64,
}pub struct DisputeResolved {
pub id: u64,
pub winner: Pubkey,
pub resolved_at: i64,
}The project includes a complete TypeScript test suite that covers all program flows:
initialize: Global configuration initializationcreate_escrow: Escrow creation with tokensmark_escrow_as_paid: Mark as paid by buyerrelease_tokens_in_escrow: Token release with signaturecancel_escrow: Escrow cancellation by timeoutcreate_dispute: Dispute and re-dispute creationresolve_dispute: Dispute resolution by authoritywithdraw_spl: Accumulated fees withdrawalupdate_global_config: Update global configuration parameters
tests/
βββ p2p.test.ts # Main test suite
βββ utils/
βββ accounts.ts # Helpers to fetch accounts
βββ constants.ts # Testing constants
βββ events.ts # Event listeners
βββ functions.ts # Auxiliary functions
βββ parsers.ts # Data parsers# Run all tests
anchor test
# Or with yarn
yarn test
# Only compile without tests
anchor buildexport const FEE_BPS = 100; // 1% fee
export const FIAT_DEADLINE_SECS = new BN(1800); // 30 minutes
export const DISPUTE_DEADLINE_SECS = new BN(43200); // 12 hours
export const DISPUTE_FEE_ESCROW = new BN(1_000_000); // 0.001 SOL- Rust 1.70+
- Solana CLI 1.18+
- Anchor Framework 0.31+
- Node.js 18+
- Yarn
-
Clone the repository:
git clone <repository-url> cd p2p
-
Install Node dependencies:
yarn install
-
Install Rust dependencies:
cargo build-sbf
-
Configure Solana CLI:
# Configure for local development solana config set --url localhost # Generate keypair if it doesn't exist solana-keygen new
In a terminal, start the Solana local validator:
solana-test-validatorThis will start a validator at http://localhost:8899.
In another terminal, compile the program:
anchor buildThis will generate:
- Program binary in
target/deploy/ - IDL in
target/idl/p2p.json - TypeScript types in
target/types/p2p.ts
Deploy the program to the local validator:
anchor deployOr use the pre-configured program ID:
# The program is configured with this ID
# GQKqoMVW3BuSzFRRkfeVsLPArAkRiZkd1vkVNGeqRmJGRun the complete test suite:
anchor testOr only tests without rebuild:
yarn test# View program logs
solana logs
# View account information
solana account <PUBKEY>
# View balance
solana balance
# Airdrop SOL for testing
solana airdrop 2
# Clean and rebuild
anchor clean && anchor build-
Configure Solana for Devnet:
solana config set --url devnet -
Request SOL from Devnet:
solana airdrop 2
-
Deploy to Devnet:
anchor build anchor deploy --provider.cluster devnet
-
Update Anchor.toml:
[provider] cluster = "devnet"
- Have thoroughly tested on devnet
- Have a professional security audit
- Configure the authority correctly
- Have sufficient SOL for deployment
solana config set --url mainnet-beta
anchor build --verifiable
anchor deploy --provider.cluster mainnetFor production, consider these values:
fee_bps: 100, // 1% fee
fiat_deadline_secs: 1800, // 30 minutes to pay
dispute_deadline_secs: 43200, // 12 hours before being able to dispute
dispute_fee_escrow: 1_000_000, // 0.001 SOL (~$0.10 at current price)The authority is the account that can:
- Resolve disputes
- Withdraw accumulated fees
- Update global configuration parameters (fees, deadlines, dispute deposit)
- Transfer authority to a new account
Make sure to use a secure wallet (e.g., Ledger) for mainnet.
- Signature Validation: All token releases require seller's signature
- Timeouts: Escrows have deadlines to avoid permanently locked funds
- Disputes: Two-level system to resolve conflicts
- Limited Authority: Can only resolve disputes, not move funds arbitrarily
- Dispute Deposits: Protection against dispute spam
- Always test on devnet first
- Use small amounts initially on mainnet
- Keep the authority in cold storage
- Monitor events to detect suspicious activity
- Implement a frontend with additional validations
ISC
Contributions are welcome. Please:
- Fork the project
- Create a branch for your feature (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
For questions or support, please open an issue in the repository.
Program ID: GQKqoMVW3BuSzFRRkfeVsLPArAkRiZkd1vkVNGeqRmJG
Cluster: Localnet (configured in Anchor.toml)