A decentralized NFT marketplace built on Solana using the Anchor framework, supporting Token-2022 standard NFTs. This marketplace enables users to create auctions, place bids, and finalize sales in a trustless, on-chain environment.
- Overview
- Features
- User Stories
- Architecture
- Prerequisites
- Installation
- Usage
- Program Structure
- Testing
- Deployment
- Security Considerations
- Contributing
- License
This NFT marketplace is a Solana program that facilitates auction-based trading of NFTs. It leverages Solana's high throughput and low transaction costs to provide a seamless experience for NFT sellers and buyers. The program uses Token-2022 standard, which provides enhanced token functionality compared to the standard SPL Token program.
- Auction Creation: Sellers can create time-limited auctions for their NFTs
- Bidding System: Buyers can place bids, with automatic refunds for outbid participants
- Cooldown Extension: Auctions automatically extend when bids are placed near the end time
- Automatic Settlement: Winners receive NFTs and sellers receive payment upon finalization
- No-Bid Handling: NFTs are returned to sellers if no bids are placed
- Token-2022 Support: Full compatibility with Token-2022 standard NFTs
- Program Derived Addresses (PDAs): Secure, deterministic account management
- Automatic Refunds: Previous bidders are automatically refunded when outbid
- Cooldown Mechanism: Prevents last-second sniping by extending auction time
- Trustless Escrow: NFTs are held in a program-controlled vault during auctions
- Flexible Auction Parameters: Configurable starting bid, duration, and cooldown periods
- Account Validation: Comprehensive constraints ensure account ownership and validity
- Reentrancy Protection: State is marked inactive before transfers to prevent reentrancy
- Bid Validation: Ensures bids are higher than current highest bid
- Time-based Validation: Prevents actions on inactive or expired auctions
-
As a seller, I want to create an auction for my NFT so that I can sell it to the highest bidder
- I can specify a starting bid amount
- I can set the auction duration
- I can set a cooldown period to prevent last-second sniping
- My NFT is securely held in escrow during the auction
-
As a seller, I want to receive payment automatically when my auction ends
- Payment is transferred directly to my wallet
- No manual intervention required
- If no bids are placed, my NFT is returned to me
-
As a seller, I want to see the current highest bid so I can track my auction's progress
- I can query the auction state at any time
- I can see who the current highest bidder is
-
As a buyer, I want to place bids on NFTs so I can participate in auctions
- I can place a bid higher than the current highest bid
- My bid is automatically recorded on-chain
- I can see if my bid is the current highest
-
As a buyer, I want to be refunded automatically if I'm outbid
- When someone places a higher bid, my previous bid is automatically refunded
- I don't need to manually claim my refund
- I can immediately use the refunded funds for other bids
-
As a buyer, I want to win the NFT when I place the highest bid and the auction ends
- The NFT is automatically transferred to my wallet
- I receive the NFT in my associated token account
- The transaction is atomic and trustless
-
As a buyer, I want protection from last-second sniping so I have time to respond to bids
- When a bid is placed near the auction end time, the auction extends by the cooldown period
- This gives me a fair chance to place a counter-bid
-
As a developer, I want to integrate this marketplace into my application
- The program provides a clear IDL for type-safe integration
- All instructions are well-documented
- Test suite demonstrates usage patterns
-
As a developer, I want to query auction state to display information to users
- Auction accounts are publicly readable
- All state is stored on-chain and queryable
┌──────────────────────────────────────────────────────────────┐
│ Auction Lifecycle │
└──────────────────────────────────────────────────────────────┘
1. CREATE AUCTION
┌──────────┐
│ Seller │──┐
└──────────┘ │
│ create_auction()
▼
┌─────────────────────────────┐
│ Auction PDA │
│ ┌───────────────────────┐ │
│ │ seller: Pubkey │ │
│ │ mint: Pubkey │ │
│ │ highest_bid: u64 │ │
│ │ highest_bidder: Pubkey│ │
│ │ start_time: i64 │ │
│ │ end_time: i64 │ │
│ │ cooldown: i64 │ │
│ │ is_active: bool │ │
│ └───────────────────────┘ │
└─────────────────────────────┘
│
│ NFT Transfer
▼
┌─────────────────────────────┐
│ Vault PDA │
│ (Token-2022 Account) │
│ Holds NFT during auction │
└─────────────────────────────┘
2. PLACE BID
┌──────────┐
│ Bidder │──┐
└──────────┘ │
│ place_bid(amount)
▼
┌─────────────────────────────┐
│ Auction PDA │
│ • Updates highest_bid │
│ • Updates highest_bidder │
│ • Extends end_time if │
│ needed (cooldown) │
└─────────────────────────────┘
│
│ Refund previous bidder
▼
┌─────────────────────────────┐
│ Previous Bidder (if any) │
│ Receives refund │
└─────────────────────────────┘
3. FINALIZE AUCTION
│
│ finalize_auction()
▼
┌─────────────────────────────┐
│ Auction PDA │
│ • Marks is_active = false │
│ • Validates winner │
└─────────────────────────────┘
│
┌────────┴────────┐
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ Winner │ │ Seller │
│ Receives │ │ Receives │
│ NFT │ │ Payment │
└──────────┘ └──────────┘
┌─────────────────────────────────────────────────────────────┐
│ Instruction Flow │
└─────────────────────────────────────────────────────────────┘
create_auction()
├── Validate accounts (seller, mint, token account)
├── Initialize Auction PDA
│ └── Seeds: [b"auction", seller, mint]
├── Create Vault PDA
│ └── Seeds: [b"vault", auction_pda]
├── Initialize Vault Token Account
└── Transfer NFT: seller → vault
└── Amount: 1 (NFT)
place_bid()
├── Validate auction (active, not ended)
├── Validate bid amount (higher than current)
├── Transfer SOL: bidder → auction_pda
├── Refund previous bidder (if exists)
│ └── Transfer SOL: auction_pda → previous_bidder
├── Update auction state
│ ├── highest_bid = new_amount
│ ├── highest_bidder = bidder
│ └── Extend end_time if needed (cooldown)
└── Return success
finalize_auction()
├── Validate auction (active, ended)
├── Mark auction inactive (prevent reentrancy)
├── Check if there's a winner
│ ├── No winner → Return NFT to seller
│ └── Has winner:
│ ├── Transfer NFT: vault → winner_ata
│ └── Transfer SOL: auction_pda → seller
└── Return success
Auction Account Structure:
┌──────────────────────────────────────────┐
│ Field │ Type │ Size(bytes)│
├─────────────────┼───────────┼────────────┤
│ seller │ Pubkey │ 32 │
│ mint │ Pubkey │ 32 │
│ highest_bidder │ Pubkey │ 32 │
│ highest_bid │ u64 │ 8 │
│ start_time │ i64 │ 8 │
│ end_time │ i64 │ 8 │
│ cooldown │ i64 │ 8 │
│ is_active │ bool │ 1 │
│ bump │ u8 │ 1 │
│ Discriminator │ │ 8 │
└─────────────────┴───────────┴────────────┘
Total: ~146 bytes
Before you begin, ensure you have the following installed:
- Rust (latest stable version): Install Rust
- Solana CLI (v1.18+): Install Solana
- Anchor Framework (v0.32.1+): Install Anchor
- Node.js (v18+): Install Node.js
- Yarn or npm: Package manager
rustc --version
solana --version
anchor --version
node --version-
Clone the repository (if not already done):
git clone <repository-url> cd nft-marketplace
-
Install dependencies:
yarn install # or npm install -
Build the program:
anchor build
-
Generate TypeScript types:
anchor build
This will generate TypeScript types in
target/types/
-
Start a local Solana validator:
solana-test-validator
-
In a new terminal, set the cluster to localnet:
solana config set --url localhost -
Airdrop SOL to your wallet (for testing):
solana airdrop 10
The test suite demonstrates the complete auction lifecycle:
anchor testThis will:
- Create a Token-2022 NFT mint
- Mint an NFT to the seller
- Create an auction
- Place bids
- Finalize the auction
await program.methods
.createAuction(
new BN(1_000_000), // starting_bid (in lamports)
new BN(3600), // duration_seconds
new BN(300) // cooldown_seconds
)
.accounts({
auction: auctionPda,
seller: seller.publicKey,
nftMint: nftMint,
nftVault: vaultPda,
sellerTokenAccount: sellerTokenAccount,
associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
tokenProgram: TOKEN_2022_PROGRAM_ID,
systemProgram: SystemProgram.programId,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
})
.signers([seller])
.rpc();await program.methods
.placeBid(new BN(1_500_000)) // bid amount in lamports
.accounts({
auction: auctionPda,
bidder: bidder.publicKey,
systemProgram: SystemProgram.programId,
})
.signers([bidder])
.rpc();await program.methods
.finalizeAuction()
.accounts({
auction: auctionPda,
seller: seller.publicKey,
winner: winner.publicKey,
nftVault: vaultPda,
winnerAta: winnerTokenAccount,
sellerTokenAccount: sellerTokenAccount,
nftMint: nftMint,
tokenProgram: TOKEN_2022_PROGRAM_ID,
systemProgram: SystemProgram.programId,
})
.rpc();-
lib.rs: Contains all program logiccreate_auction: Initializes auction and transfers NFT to vaultplace_bid: Handles bidding with automatic refundsfinalize_auction: Settles auction and transfers assets
-
Account Contexts:
CreateAuction: Validates accounts for auction creationPlaceBid: Validates accounts for placing bidsFinalizeAuction: Validates accounts for auction finalization
-
State:
Auction: On-chain auction state structure
-
Errors:
AuctionError: Custom error codes for various failure scenarios
The test suite (tests/nft-marketplace.ts) covers:
- Setup: Creating Token-2022 mint and minting NFT
- Auction Creation: Creating an auction with specified parameters
- Bidding: Placing bids and verifying state updates
- Finalization: Completing auction and verifying asset transfers
Run tests with:
anchor testanchor deploy-
Update
Anchor.toml:[provider] cluster = "devnet"
-
Set your wallet:
solana config set --url devnet solana airdrop 2 # Get some devnet SOL
-
Deploy:
anchor deploy
-
Update
Anchor.toml:[provider] cluster = "mainnet-beta"
-
Deploy:
anchor deploy
- ✅ Account ownership validation
- ✅ Reentrancy protection (state marked inactive before transfers)
- ✅ Bid amount validation
- ✅ Time-based validation
- ✅ PDA-based account management
- Always validate accounts on the client side before submitting transactions
- Handle errors gracefully - check for all possible error codes
- Monitor auction state - ensure auctions are finalized after end time
- Test thoroughly - use the test suite and additional integration tests
- Audit before mainnet - consider professional security audits
- No fee mechanism (all proceeds go to seller)
- No minimum bid increment enforcement
- No maximum bid limit
- Cooldown extension is automatic (no configurable threshold)
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow Rust and Anchor best practices
- Write tests for new features
- Update documentation as needed
- Ensure all tests pass before submitting PR
For issues, questions, or contributions, please open an issue on the repository.
Built with ❤️ using Anchor and Solana
