Marketplace Program
The CollectorCrypt Marketplace Program is a Solana smart contract that facilitates decentralized trading of NFTs and digital collectibles using USDC.
Program Details
- Program ID:
CcmRKTuZCGJBWQwMHvDYApBRvSZNHqGJXkznqpDTSQUr - Framework: Anchor (Solana)
- Blockchain: Solana
- Currency: USDC (SPL Token)
- Version: 5
Devnet USDC Faucet
Use this faucet to get the USDC token used on devnet: https://spl-token-faucet.com/?token-name=USDC-Dev
Overview
The marketplace supports four asset types:
- Programmable NFTs (pNFTs): Token Metadata standard with enforced royalties and transfer delegates
- Compressed NFTs (cNFTs): Merkle-tree-based assets via Metaplex Bubblegum
- Standard NFTs: legacy Token Metadata 1/1 (non-programmable) NFTs — listed via SPL Token
Approve, transferred via SPL TokenTransfer - MPL Core assets: single-account plugin-based assets — listed by installing the listing PDA as a
TransferDelegateplugin authority, transferred via MPL CoreTransferV1
Each asset type has its own set of instructions (*_pnft, *_cnft, *_nft, *_core) that share the same listing/offer/escrow accounts and the same fee model.
Key Features
- Non-custodial: NFTs remain in seller wallets using delegation, not escrow transfers
- Network-agnostic: Environment values injected via initialization
- Deterministic: All PDAs use fixed, reproducible seed schemes
- Whitelisting: Collection-based access control
- Platform fees: Configurable basis points (0% - 2%)
- User escrow: USDC escrow system for offer-backed liquidity
Because this is an escrowless marketplace, NFTs remain in your wallet while listed. You are responsible for canceling listings on NFTs you transfer away. While our backend monitors transfers and attempts to auto-cancel listings, this process is not 100% reliable. If you transfer an NFT and later receive it back, the old listing may still be active. Always verify and cancel stale listings manually.
Architecture
Transaction Flows
Listing Flow
- Seller initiates
list_pnftorlist_cnft - Creates Listing PDA
- Delegates NFT transfer authority to the Listing PDA
Purchase Flow (Direct Buy)
- Buyer initiates
buy_pnft/buy_cnft/buy_nft/buy_core - Validates whitelist and
expected_price - Transfers USDC (
price − fee) to seller - Transfers USDC (
fee) to treasury - Transfers NFT from seller to buyer
- Closes Listing PDA
The buyer's total outlay is exactly price; the platform fee is deducted from the seller's proceeds (see Fee Model).
Offer Flow
- Buyer deposits funds via
deposit_to_user_escrow - Buyer creates offer via
make_offerormake_offer_n_deposit - Creates Offer PDA
- Validates whitelist
Accept Offer Flow
- Seller initiates
accept_offer_for_pnftoraccept_offer_for_cnft - Transfers USDC from escrow (price - fee) to seller
- Transfers USDC from escrow (fee) to treasury
- Transfers NFT from seller to buyer
- Closes Offer PDA
Account Structure
Market (Singleton)
PDA Seeds: ["market"]
Size: 186 bytes
| Field | Type | Description |
|---|---|---|
| version | u32 | Protocol version (currently 5) |
| super_admin | Pubkey | Governance authority |
| market_admin | Pubkey | Operational authority |
| backend_account | Pubkey | Backend co-signer for embedded wallets |
| market_bump | [u8; 1] | PDA bump seed |
| treasury | Pubkey | Fee collection address |
| royalty_fee | u16 | Platform fee in basis points |
| min_offer_amount | u64 | Minimum offer amount (USDC lamports) |
| paused | bool | Global pause flag |
| cnft_depth | u8 | Compressed NFT Merkle tree depth |
| cnft_canopy | u8 | Compressed NFT canopy depth |
| usdc_mint | Pubkey | Validated USDC mint address |
Listing
PDA Seeds: ["listing", asset_id, seller]
Size: 162 bytes
| Field | Type | Description |
|---|---|---|
| seller | Pubkey | Wallet that listed the NFT |
| nft | Pubkey | NFT mint (pNFT) or asset ID (cNFT) |
| collection | Pubkey | Verified collection key used for whitelist checks |
| listing_bump | [u8; 1] | PDA bump seed |
| price | u64 | Listing price in USDC lamports |
| collection_type | CollectionType | PNFT, CNFT, StandardNFT, or CoreNFT |
| created_at | i64 | Unix timestamp of creation |
| updated_at | i64 | Unix timestamp of last update |
| rent_payer | Pubkey | Who paid rent (for refund on close) |
Offer
PDA Seeds: ["offer", asset_id, buyer]
Size: 161 bytes
| Field | Type | Description |
|---|---|---|
| nft | Pubkey | NFT being offered on |
| collection | Pubkey | Verified collection key used for whitelist checks |
| buyer | Pubkey | Wallet making the offer |
| offer_bump | [u8; 1] | PDA bump seed |
| price | u64 | Offer amount in USDC lamports |
| created_at | i64 | Unix timestamp of creation |
| updated_at | i64 | Unix timestamp of last update |
| rent_payer | Pubkey | Who paid rent (for refund on close) |
UserEscrow
PDA Seeds: ["user_escrow", user]
Size: 49 bytes
| Field | Type | Description |
|---|---|---|
| user | Pubkey | Escrow owner |
| user_escrow_bump | [u8; 1] | PDA bump seed |
| balance | u64 | Tracked USDC balance |
CollectionWhitelistEntry (V2)
PDA Seeds: ["whitelist_entry", market, collection_type_str, collection]
Size: 50 bytes
| Field | Type | Description |
|---|---|---|
| collection | Pubkey | Whitelisted collection address |
| collection_type | CollectionType | PNFT, CNFT, StandardNFT, or CoreNFT |
| added_at | i64 | Timestamp when whitelisted |
| bump | u8 | PDA bump seed |
Program Instructions
The instructions below are the ones a marketplace client builds on. Each asset type has a parallel set: *_pnft (pNFTs), *_cnft (compressed NFTs), *_nft (standard NFTs), and *_core (MPL Core assets). The pNFT and cNFT variants are documented in full; the _nft and _core variants take the same parameters and follow the same flow, differing only in how the NFT itself is transferred.
Listing Instructions
list_pnft
Purpose: Create a listing for a programmable NFT
Signers: seller, rent_payer
Parameters: price: u64 (USDC lamports)
Key Accounts:
sellerrent_payermarketwhitelist_entryasset_id(Mint)listing(init PDA)seller_nft_account- Metadata/edition/token_record accounts
- Authorization rules accounts
Preconditions:
- Marketplace not paused
- Collection whitelisted (V2)
- Listing does not already exist
- price > 0
State: Creates Listing PDA. Delegates NFT transfer authority to the Listing PDA.
Important: Because this is an escrowless system, the NFT remains in your wallet. If you transfer the NFT to another wallet, you must cancel the listing. The backend attempts to auto-cancel listings on transfers, but this is not guaranteed.
list_cnft
Purpose: Create a listing for a compressed NFT
Signers: seller, rent_payer
Parameters:
price: u64root: [u8; 32]data_hash: [u8; 32]creator_hash: [u8; 32]asset_data_hash: [u8; 32]collection_hash: [u8; 32]flags: u8nonce: u64index: u32
Key Accounts:
- seller, rent_payer, market, whitelist_entry
- asset_id, listing (init PDA)
- merkle_tree, tree_config
- Bubblegum/compression programs
State: Creates Listing PDA. Delegates cNFT to Listing PDA. Merkle proof accounts passed via remaining_accounts.
Important: Because this is an escrowless system, the cNFT remains in your wallet. If you transfer the cNFT to another wallet, you must cancel the listing. The backend attempts to auto-cancel listings on transfers, but this is not guaranteed.
update_listing
Purpose: Update the price of an existing listing
Signers: seller
Parameters: new_price: u64
Preconditions:
- Marketplace not paused
- new_price > 0
- Listing exists
- Seller matches listing.seller
State: Updates listing.price and listing.updated_at
cancel_pnft_listing
Purpose: Cancel a pNFT listing and revoke the delegate
Signers: seller
Key Accounts:
- seller
- rent_receiver (validated against listing.rent_payer)
- listing (closed)
- NFT accounts, metadata accounts
State: Revokes TransferV1 delegate from Listing PDA. Closes Listing PDA; rent refunded to rent_receiver.
cancel_cnft_listing
Purpose: Cancel a cNFT listing and optionally revoke the delegate
Signers: seller
Parameters:
root: [u8; 32]data_hash: [u8; 32]creator_hash: [u8; 32]asset_data_hash: [u8; 32]collection_hash: [u8; 32]flags: u8nonce: u64index: u32skip_delegate_revoke: bool
State: If !skip_delegate_revoke, re-delegates cNFT from Listing PDA back to seller. Closes Listing PDA; rent refunded.
Trade Instructions
buy_pnft
Purpose: Purchase a listed pNFT directly
Signers: buyer, rent_payer
Parameters: expected_price: u64
Key Accounts:
- buyer, seller, listing_rent_receiver, rent_payer
- market, whitelist_entry, asset_id, listing
- seller/buyer NFT accounts, usdc_mint
- buyer/seller USDC accounts, treasury
- metadata/edition/token record accounts
Preconditions:
- Marketplace not paused
- listing.price == expected_price (frontrun protection)
- Collection still whitelisted at trade time
State Transitions:
- USDC
price − feetransferred from buyer to seller - USDC
feetransferred from buyer to treasury - pNFT transferred from seller to buyer (via Listing PDA delegate)
- Listing PDA closed; rent refunded
Important: Buyer pays exactly price. Seller receives price − fee; the platform fee goes to the treasury.
buy_cnft
Purpose: Purchase a listed cNFT directly
Signers: buyer, rent_payer
Parameters:
expected_price: u64root: [u8; 32]data_hash: [u8; 32]creator_hash: [u8; 32]asset_data_hash: [u8; 32]flags: u8nonce: u64index: u32
State: Same USDC transfer pattern as buy_pnft. cNFT transferred via Bubblegum.
accept_offer_for_pnft
Purpose: Seller accepts a buyer's offer for a pNFT
Signers: seller, rent_payer
Parameters: expected_price: u64
Key Accounts:
- seller, buyer (validated against offer.buyer)
- offer_rent_receiver, market, asset_id
- seller/buyer NFT accounts, offer (closed)
- usdc_mint, buyer_user_escrow_account
- buyer_user_escrow_token_account
- seller USDC account, treasury accounts
- metadata accounts
Preconditions:
- Marketplace not paused
- seller ≠ buyer
- offer.price == expected_price
- Escrow token account balance ≥ tracked balance
- Escrow has sufficient funds
State Transitions:
- Escrow balance decremented by price
- USDC
price - feetransferred from escrow to seller - USDC
feetransferred from escrow to treasury - pNFT transferred from seller to buyer (seller signs directly)
- Offer PDA closed
Important: Seller receives price - fee. Fee is deducted from offer amount.
Note: Whitelist is NOT re-validated at acceptance (validated at offer creation).
accept_offer_for_cnft
Purpose: Seller accepts a buyer's offer for an unlisted cNFT
Signers: seller, rent_payer
Parameters:
expected_price: u64root: [u8; 32]data_hash: [u8; 32]creator_hash: [u8; 32]asset_data_hash: [u8; 32]flags: u8nonce: u64index: u32
State: Same escrow-based USDC flow as accept_offer_for_pnft. cNFT transferred via Bubblegum; seller signs.
accept_offer_for_listed_cnft
Purpose: Seller accepts a buyer's offer for a cNFT that is currently listed
Signers: seller, rent_payer
Parameters:
expected_price: u64root: [u8; 32]data_hash: [u8; 32]creator_hash: [u8; 32]asset_data_hash: [u8; 32]flags: u8nonce: u64index: u32
Key Accounts: All accounts from accept_offer_for_cnft plus listing (closed) and listing_rent_receiver
State: Both Listing PDA and Offer PDA are closed. USDC flow from escrow. cNFT transferred via Bubblegum, signed by Listing PDA.
Standard NFT & MPL Core variants
The same listing/buy/accept flows exist for the other two asset types, with identical parameters and the same USDC fee model:
- Standard NFTs:
list_nft,cancel_nft_listing,buy_nft,accept_offer_for_nft,accept_offer_for_listed_nft. Listing uses SPL TokenApproveto delegate to the Listing PDA; transfers use SPL TokenTransfer. - MPL Core assets:
list_core,cancel_core_listing,buy_core,accept_offer_for_core,accept_offer_for_listed_core. Listing installs the Listing PDA as aTransferDelegateplugin authority; transfers use MPL CoreTransferV1.
Offer Instructions
make_offer
Purpose: Create an offer using existing escrow funds
Signers: buyer, rent_payer
Parameters:
collection_type: CollectionTypecollection_hash: [u8; 32]root: [u8; 32]data_hash: [u8; 32]creator_hash: [u8; 32]asset_data_hash: [u8; 32]flags: u8nonce: u64index: u32price: u64
Preconditions:
- Marketplace not paused
- seller ≠ buyer
- price ≥ market.min_offer_amount
- Collection whitelisted (V2)
- No existing offer for this (NFT, buyer) pair
State: Creates Offer PDA. Does NOT transfer USDC (requires pre-existing escrow balance).
Note: Escrow balance is not locked per-offer; checked at acceptance time.
make_offer_n_deposit
Purpose: Create an offer and deposit USDC into escrow in a single transaction
Signers: buyer, rent_payer
Parameters: Same as make_offer
State Transitions:
- USDC
pricetransferred from buyer to escrow token account - UserEscrow initialized if needed (idempotent)
- Escrow balance incremented
- Offer PDA created
Note: Preferred instruction for users without pre-existing escrow funds.
update_offer
Purpose: Change the price of an existing offer
Signers: buyer
Parameters: new_price: u64
Preconditions:
- Marketplace not paused
- new_price > min_offer_amount
- new_price ≠ current_price
- Offer exists
- Escrow has sufficient funds for new_price
State: Updates offer.price and offer.updated_at
Note: No USDC movement. Escrow balance check is validation only, not a lock.
cancel_offer
Purpose: Cancel an offer, keeping funds in escrow
Signers: buyer
Parameters: force: bool (currently unused)
State: Closes Offer PDA; rent refunded to rent_receiver. USDC remains in escrow.
cancel_offer_n_withdraw
Purpose: Cancel an offer and withdraw funds from escrow
Signers: buyer
State Transitions:
- Escrow balance decremented by offer.price
- USDC transferred from escrow token account to buyer's USDC account
- Offer PDA closed
User Escrow Instructions
deposit_to_user_escrow
Purpose: Deposit USDC into the user's escrow for backing offers
Signers: user, rent_payer
Parameters: amount: u64
Key Accounts:
- user, rent_payer, market, usdc_mint
- user_escrow_account (init_if_needed PDA)
- user_escrow_token_account (init_if_needed ATA)
- user_usdc_account
State Transitions:
- UserEscrow initialized if first deposit (idempotent)
- USDC transferred from user to escrow token account
- Escrow balance incremented
withdraw_from_user_escrow
Purpose: Withdraw USDC from the user's escrow
Signers: user
Parameters: amount: u64
Preconditions:
- Marketplace not paused
- escrow.balance ≥ amount
State Transitions:
- Escrow balance decremented
- USDC transferred from escrow token account to user (signed by UserEscrow PDA)
Constants
| Constant | Value | Description |
|---|---|---|
| DEFAULT_PLATFORM_FEE_BPS | 200 | Default platform fee (2.00%) |
| Default cNFT depth | 20 | Merkle tree depth |
| Default cNFT canopy | 14 | Canopy depth |
Fee Model
Platform Fee Configuration
| Parameter | Value |
|---|---|
| Default | 200 bps (2.00%) |
| Minimum | 0 bps (0.00%) |
| Maximum | 200 bps (2.00%) |
| Stored In | market.royalty_fee |
Fee Calculation
fee = floor(price * platform_fee_bps / 10000)
All arithmetic uses checked operations with u128 intermediate precision to prevent overflow.
Fee Flow by Transaction Type
Direct Purchase (buy_pnft / buy_cnft / buy_nft / buy_core)
Buyer's USDC Account
|
|--- (price - fee) --> Seller's USDC Account
|--- fee -----------> Treasury USDC Account
Buyer pays exactly price. Seller receives price − fee; the fee goes to the treasury.
Offer Acceptance (accept_offer_for_*)
Buyer's UserEscrow Token Account
|
|--- (price - fee) --> Seller's USDC Account
|--- fee -----------> Treasury USDC Account
The full offer price is debited from escrow. Seller receives price − fee; the fee goes to the treasury — the same split as a direct purchase.
Integration Flows
Flow 1: List and Sell a pNFT
1. Seller calls list_pnft(price)
-> Listing PDA created
-> NFT delegated to Listing PDA
2. Buyer calls buy_pnft(expected_price)
-> USDC: buyer -> seller (price - fee)
-> USDC: buyer -> treasury (fee)
-> NFT: seller -> buyer (via Listing PDA delegate)
-> Listing PDA closed
Flow 2: Make Offer and Accept
1. Buyer calls deposit_to_user_escrow(amount)
-> USDC moved to escrow
2. Buyer calls make_offer(collection_type, collection_hash, ..., price)
-> Offer PDA created (no USDC movement)
OR: Buyer calls make_offer_n_deposit (combines steps 1+2)
3. Seller calls accept_offer_for_pnft(expected_price)
-> USDC: escrow -> seller (price - fee)
-> USDC: escrow -> treasury (fee)
-> NFT: seller -> buyer
-> Offer PDA closed
Flow 3: Accept Offer on a Listed cNFT
1. Seller has an active listing (list_cnft)
2. Buyer makes an offer (make_offer_n_deposit)
3. Seller calls accept_offer_for_listed_cnft(expected_price, ...)
-> Both Listing PDA and Offer PDA closed
-> USDC from escrow split between seller and treasury
-> cNFT transferred via Listing PDA (as delegate)
Using Anchor Client
import { Program, AnchorProvider } from '@coral-xyz/anchor';
import { Connection, PublicKey } from '@solana/web3.js';
// Connect to cluster
const connection = new Connection('https://api.mainnet-beta.solana.com');
const provider = new AnchorProvider(connection, wallet, {});
const program = new Program(IDL, PROGRAM_ID, provider);
// Example: List a pNFT
await program.methods
.listPnft(new BN(1000000)) // price in USDC lamports
.accounts({
seller: seller.publicKey,
rentPayer: rentPayer.publicKey,
market: marketPda,
whitelistEntry: whitelistEntryPda,
assetId: nftMint,
listing: listingPda,
// ... additional accounts from IDL
})
.signers([seller, rentPayer])
.rpc();
PDA Derivation Examples
// Market PDA (singleton)
const [marketPda] = PublicKey.findProgramAddressSync(
[Buffer.from("market")],
PROGRAM_ID
);
// Listing PDA
const [listingPda] = PublicKey.findProgramAddressSync(
[Buffer.from("listing"), assetId.toBuffer(), seller.toBuffer()],
PROGRAM_ID
);
// Offer PDA
const [offerPda] = PublicKey.findProgramAddressSync(
[Buffer.from("offer"), assetId.toBuffer(), buyer.toBuffer()],
PROGRAM_ID
);
// User Escrow PDA
const [userEscrowPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user_escrow"), user.toBuffer()],
PROGRAM_ID
);
// Whitelist Entry PDA (V2)
// collectionType is one of: 'PNFT' | 'CNFT' | 'StandardNFT' | 'CoreNFT'
// (the exact CollectionType variant name is used verbatim as the seed)
const collectionTypeStr = collectionType;
const [whitelistEntryPda] = PublicKey.findProgramAddressSync(
[
Buffer.from("whitelist_entry"),
marketPda.toBuffer(),
Buffer.from(collectionTypeStr),
collection.toBuffer(),
],
PROGRAM_ID
);
Transaction Size Considerations
cNFT operations require Merkle proofs passed via remaining_accounts. The proof size depends on tree depth and canopy:
proof_accounts_needed = depth - canopy
With default parameters (depth=20, canopy=14), this requires 6 proof accounts. Each remaining account adds ~32 bytes to the transaction.
Error Reference
Anchor custom error codes (program offset starts at 6000). These are surfaced through the API as on-chain build/submit failures.
| Code | Name | Description |
|---|---|---|
| 6000 | Unauthorized | Signer does not match required authority |
| 6001 | CollectionAlreadyWhitelisted | Collection is already whitelisted |
| 6002 | CollectionNotWhitelisted | Collection is not whitelisted or whitelist entry does not match |
| 6003 | InvalidRoyaltyFee | Royalty fee out of range |
| 6004 | InvalidMinOfferAmount | Minimum offer amount must be > 0 |
| 6005 | MarketplacePaused | Operation rejected — marketplace is paused |
| 6006 | MarketplaceRunning | Marketplace is running |
| 6007 | ListingAlreadyExists | A listing already exists for this (NFT, seller) pair |
| 6008 | ListingNotExists | No active listing found |
| 6009 | ZeroPrice | Price must be greater than 0 |
| 6010 | ArithmeticOverflow | Checked arithmetic operation overflowed |
| 6011 | InsufficientEscrowAmount | Escrow amount insufficient |
| 6012 | InsufficientLockedAmount | Locked amount insufficient |
| 6013 | OfferAlreadyExists | An offer already exists for this (NFT, buyer) pair |
| 6014 | OfferNotExists | No active offer found |
| 6015 | InvalidOfferAmount | Offer amount must be greater than the minimum offer amount |
| 6016 | OfferToOwnListing | Cannot make an offer on / trade with yourself |
| 6017 | InvalidCNFTDepth | cNFT depth must be 1–32 |
| 6018 | InvalidCNFTCanopy | cNFT canopy must be 1–depth |
| 6019 | InvalidCNFTProofSize | cNFT proof size must be 1–(depth − canopy) |
| 6020 | InsufficientEscrowFunds | Escrow balance insufficient for the operation |
| 6021 | PriceMismatch | On-chain price does not match the expected_price parameter |
| 6022 | InvalidUsdcMint | Provided mint does not match market.usdc_mint |
| 6023 | UsdcMintImmutable | USDC mint is immutable after market initialization |
| 6024 | InvalidProgramAddress | External program ID does not match expected hardcoded value |
| 6025 | InvalidMetadataAccount | Metadata account does not match the provided NFT mint |
| 6026 | InvalidStandardNft | Expected a NonFungible 1/1 mint and token account |
| 6027 | InvalidPNft | Expected a ProgrammableNonFungible metadata token standard |
| 6028 | CollectionNotVerified | NFT collection must be verified |
| 6029 | EscrowBalanceMismatch | Tracked escrow balance exceeds actual token account balance |
| 6030 | InvalidMerkleTreeOwner | Merkle tree not owned by SPL Account Compression |
| 6031 | CnftAssetIdMismatch | cNFT asset ID does not match the provided Merkle tree leaf |
| 6032 | MissingCNFTCollectionAccount | Missing cNFT collection account for a collection-whitelisted asset |
| 6033 | InvalidCNFTCollectionHash | cNFT collection hash does not match the provided collection account |
| 6034 | InvalidPlatformFee | Platform fee out of the allowed range |
| 6035 | InvalidCoreAssetAccount | Invalid MPL Core asset account — wrong owner or unparseable |
| 6036 | InvalidCoreCollectionAccount | MPL Core collection account must be owned by the MPL Core program |
| 6037 | CoreAssetOwnerMismatch | MPL Core asset owner does not match the seller for this listing |
Common Failure Scenarios
| Scenario | Error | Resolution |
|---|---|---|
| Marketplace is paused | MarketplacePaused | Temporary — retry later |
| Collection not whitelisted | CollectionNotWhitelisted | Asset's collection isn't tradeable on the marketplace |
| Price changed between view and transaction | PriceMismatch | Re-fetch listing/offer price, rebuild transaction |
| Insufficient escrow balance | InsufficientEscrowFunds | Deposit more USDC via deposit_to_user_escrow |
| Duplicate listing | ListingAlreadyExists | Cancel existing listing first |
| Duplicate offer | OfferAlreadyExists | Cancel existing offer first |
| Escrow balance inconsistency | EscrowBalanceMismatch | Tracked balance exceeds the token account balance |
| Offer on own listing | OfferToOwnListing | Cannot offer on your own NFT |
| Zero price listing/offer | ZeroPrice / InvalidOfferAmount | Price must be greater than 0 (offers must clear the minimum) |
| Unverified NFT collection | CollectionNotVerified | NFT must have a verified collection |
| Wrong cNFT proof data | CnftAssetIdMismatch | Refresh asset proof data and retry |
Things to Know When Integrating
Price matching (frontrun protection)
Both buy_* and accept_offer_for_* require an expected_price parameter and fail with PriceMismatch if the on-chain price differs. This is why the API asks you to pass the price you saw — always build the transaction against the latest listing/offer price, and rebuild if you get PriceMismatch.
Self-trade prevention
accept_offer_for_* and make_offer* enforce seller != buyer (OfferToOwnListing). You cannot make an offer on, or buy, your own listing.
Escrow is shared across offers
A user's escrow balance backs all of their open offers collectively — it is not locked per-offer. If a buyer has 100 USDC in escrow and makes two 60 USDC offers, only one can be accepted. Surface this to your users so they can keep enough escrow funded for the offers they want to stay live.
Escrowless listings — seller responsibility
NFTs stay in the seller's wallet while listed (delegated to the Listing PDA), not held in escrow. If a seller transfers a listed NFT away, they must cancel the listing. The backend tries to auto-cancel on transfer, but this is not 100% reliable — if an NFT is transferred away and later returned, the original listing may still be active.
External Program Dependencies
| Program | ID | Usage |
|---|---|---|
| MPL Token Metadata | metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s | pNFT / standard NFT delegation, transfer, revocation |
| Metaplex Bubblegum | BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY | cNFT delegation, transfer |
| MPL Core | CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d | MPL Core asset delegation, transfer |
| SPL Account Compression | mcmt6YrQEMKw8Mw43FmpRLmf7BqRnFMKmAcbxE3xkAW | Merkle tree verification |
| SPL Noop | mnoopTCrg4p8ry25e4bcWA9XZjbNjMTfgYVGGEdRsf3 | Log wrapper for Bubblegum |
| MPL Token Auth Rules | auth9SigNpDKz4sJJ1DfCTuZrZNSAgh9sFD3rboVmgg | pNFT authorization rules |
| SPL Token | (standard) | USDC transfers |
| SPL Associated Token | (standard) | ATA creation/resolution |
| System Program | (standard) | Account creation, rent |
Support
For technical questions, integration support, or to report issues, please contact the CollectorCrypt development team.