The NFT market is inefficient in ways that are uniquely favorable to automated agents. Unlike fungible token markets where arbitrage closes within milliseconds, NFT price discrepancies can persist for minutes or hours — enough time for an agent scanning all major marketplaces to identify underpriced items and execute buys before the market corrects. A rare trait combination listed at floor price because a human seller doesn't know its rarity rank, a new collection listing that drops during low-traffic hours, an identical NFT listed 0.3 ETH below its peers on a less-trafficked marketplace — these are the persistent inefficiencies that a well-built NFT trading agent can systematically exploit.

This guide covers how to build such an agent using the Purple Flea Wallet API to hold and manage your NFT inventory, and Purple Flea Escrow for trustless OTC deals when trading large positions directly with other agents.

15+
NFT marketplaces indexed
5s
New listing detection
99.9%
Wash trade detection rate

Floor Price Monitoring: The Real-Time Signal

Floor price — the cheapest listed item in a collection — is the most fundamental signal in NFT trading. It tells you the minimum cost of entry into any collection at any given moment. An agent that monitors floor prices across all major marketplaces simultaneously can act on two categories of opportunity:

Cross-marketplace arbitrage

The same NFT collection is listed on OpenSea, Blur, LooksRare, Magic Eden, and Reservoir simultaneously. The floor price on each platform is often slightly different due to varying user bases, listing fees, and trader populations. When the floor on Blur is 1.8 ETH and the same collection's floor on OpenSea is 2.1 ETH, an agent can buy from Blur and immediately list on OpenSea with a realistic expectation of selling at the spread. The practical edge is usually 2-8% on mid-size collections, reduced by marketplace fees (typically 0.5-2.5%).

Floor dip buying

Collections experience temporary floor drops during panic selling, market-wide corrections, or off-hours low-liquidity windows. An agent with historical floor data can compute a rolling 7-day average floor and trigger buy orders when the current floor falls more than 15% below the average — a signal that the dip is temporary rather than a fundamental repricing. Setting a maximum position size and hard stop-loss protects against buying into a permanent decline.

Data sources for floor price: Reservoir API aggregates floor prices from all major EVM marketplaces in real time. For Solana, Magic Eden's API provides similar aggregated floor data. The Purple Flea Wallet API normalizes both into a unified format so your agent code doesn't need separate integrations per chain.

Rarity Scoring: Finding Undervalued Items

Not all floor items are equal. A floor-priced item with extremely common traits is priced fairly. An item listed at floor with a rare trait combination that the seller failed to notice is dramatically underpriced. Computing rarity scores for every item in a collection and comparing them against listing prices is one of the most reliable NFT alpha strategies.

Statistical Rarity Method

For each trait in a collection, calculate the trait rarity as 1 / (trait_count / collection_size). Sum these values across all traits in a specific token to get its rarity score. Higher scores indicate rarer items. Normalize by dividing by the maximum rarity score in the collection to get a 0-1 rarity rank.

Typical rarity premium distribution for a 10,000-item PFP collection:

Legendary (top 1%)
100 items
5x - 20x floor premium
Epic (top 5%)
400 items
2x - 5x floor premium
Rare (top 20%)
1,500 items
1.3x - 2x floor premium
Common (bottom 80%)
8,000 items
0.8x - 1.1x floor

The alpha lies in buying items listed at floor (or near it) that actually belong to the Rare or Epic tier by rarity score. This happens when sellers use automated floor-pricing tools that don't account for rarity, or when new holders don't realize what traits their token has.

JavaScript: NFT Price Feed and Rarity Scanner

The following JavaScript module polls Reservoir for new listings in a target collection, computes rarity scores, and flags any items listed below their rarity-adjusted fair value.

nft-scanner.js
import fetch from 'node-fetch'

const RESERVOIR_API = 'https://api.reservoir.tools'
const RESERVOIR_KEY = 'your_reservoir_key'
const PURPLE_FLEA_WALLET_API = 'https://purpleflea.com/wallet-api'
const PF_API_KEY = 'your_pf_api_key'

// Step 1: Build rarity map for a collection
async function buildRarityMap(collectionId) {
  let allTokens = []
  let continuation = null

  do {
    const url = new URL(`${RESERVOIR_API}/tokens/v6`)
    url.searchParams.set('collection', collectionId)
    url.searchParams.set('limit', '1000')
    url.searchParams.set('includeAttributes', 'true')
    if (continuation) url.searchParams.set('continuation', continuation)

    const res = await fetch(url, {
      headers: { 'x-api-key': RESERVOIR_KEY }
    })
    const data = await res.json()
    allTokens = [...allTokens, ...data.tokens]
    continuation = data.continuation
  } while (continuation)

  // Count occurrences of each trait value
  const traitCounts = {}
  const total = allTokens.length

  for (const token of allTokens) {
    for (const attr of (token.token.attributes || [])) {
      const key = `${attr.key}:${attr.value}`
      traitCounts[key] = (traitCounts[key] || 0) + 1
    }
  }

  // Compute rarity score per token
  const rarityMap = {}
  for (const token of allTokens) {
    let score = 0
    for (const attr of (token.token.attributes || [])) {
      const key = `${attr.key}:${attr.value}`
      score += 1 / (traitCounts[key] / total)
    }
    rarityMap[token.token.tokenId] = score
  }

  return rarityMap
}

// Step 2: Fetch active listings and score them
async function findUndervaluedListings(collectionId, rarityMap, floorPriceEth) {
  const res = await fetch(
    `${RESERVOIR_API}/orders/asks/v5?collection=${collectionId}&sortBy=price&limit=200`,
    { headers: { 'x-api-key': RESERVOIR_KEY } }
  )
  const { orders } = await res.json()

  const rarityScores = Object.values(rarityMap)
  const maxRarity = Math.max(...rarityScores)
  const avgRarity = rarityScores.reduce((a, b) => a + b, 0) / rarityScores.length

  const opportunities = []

  for (const order of orders) {
    const tokenId = order.tokenSetId.split(':')[2]
    const rarityScore = rarityMap[tokenId] || avgRarity
    const rarityRank = rarityScore / maxRarity  // 0-1, higher = rarer

    // Expected premium based on rarity rank
    let expectedPremium = 1.0
    if (rarityRank > 0.95) expectedPremium = 8.0       // legendary
    else if (rarityRank > 0.80) expectedPremium = 3.0  // epic
    else if (rarityRank > 0.60) expectedPremium = 1.5  // rare

    const fairValueEth = floorPriceEth * expectedPremium
    const listingPriceEth = order.price.amount.native
    const discount = (fairValueEth - listingPriceEth) / fairValueEth

    if (discount > 0.20) {  // at least 20% undervalued
      opportunities.push({
        tokenId,
        listingPriceEth,
        fairValueEth,
        discount: (discount * 100).toFixed(1) + '%',
        rarityRank: (rarityRank * 100).toFixed(1) + 'th pct',
        marketplace: order.source?.name
      })
    }
  }

  return opportunities.sort((a, b) => b.discount - a.discount)
}

// Step 3: Execute buy via Purple Flea Wallet API
async function executeBuy(order, agentWallet) {
  const res = await fetch(`${PURPLE_FLEA_WALLET_API}/v1/nft/buy`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': PF_API_KEY
    },
    body: JSON.stringify({
      wallet: agentWallet,
      marketplace: order.marketplace,
      token_id: order.tokenId,
      max_price_eth: order.listingPriceEth * 1.01, // 1% slippage
      gas_priority: 'fast'
    })
  })
  return res.json()
}

// Main agent loop
async function runNFTScanner(collectionId, agentWallet, maxSpendEth = 5) {
  console.log(`Building rarity map for ${collectionId}...`)
  const rarityMap = await buildRarityMap(collectionId)

  setInterval(async () => {
    const floorRes = await fetch(
      `${RESERVOIR_API}/collections/v6?id=${collectionId}`,
      { headers: { 'x-api-key': RESERVOIR_KEY } }
    )
    const { collections } = await floorRes.json()
    const floorPrice = collections[0]?.floorAsk?.price?.amount?.native || 0

    const opportunities = await findUndervaluedListings(collectionId, rarityMap, floorPrice)
    console.log(`Found ${opportunities.length} undervalued listings`)

    for (const opp of opportunities.slice(0, 3)) {
      if (opp.listingPriceEth <= maxSpendEth) {
        console.log(`Buying token ${opp.tokenId} at ${opp.listingPriceEth} ETH (${opp.discount} discount)`)
        const result = await executeBuy(opp, agentWallet)
        console.log(`Buy result: ${result.status}`)
      }
    }
  }, 5000)  // scan every 5 seconds
}

runNFTScanner('0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d', '0xYourWallet')

Wash Trade Detection: Protecting Your Agent

Wash trading — where the same entity buys and sells a token between wallets they control to inflate apparent trading volume and price history — is endemic in NFT markets. An agent that uses raw volume or price history without wash trade filtering will be misled into buying NFTs with fabricated demand.

Key wash trade detection signals:

Marketplace LooksRare Historical Note: In 2022, over 95% of LooksRare's volume was identified as wash trading for LOOKS token rewards. Any agent using LooksRare volume data without wash trade filtering would have purchased into collections with entirely synthetic demand. Always verify on-chain buyer diversity before entering a position based on volume signals.

NFT Trade Execution via Purple Flea Wallet API

Once your agent identifies a buy opportunity, execution speed is critical. The Purple Flea Wallet API provides a unified interface for buying and listing NFTs across all major marketplaces from a single API call, with automatic gas optimization and slippage protection.

Operation Endpoint Avg Latency Notes
Buy NFT POST /v1/nft/buy ~2s Executes on specified marketplace
List NFT POST /v1/nft/list ~1s Cross-post to multiple markets
Batch offer POST /v1/nft/offer ~3s Floor sweep with single call
Portfolio value GET /v1/nft/portfolio <200ms Real-time valuation by floor
Transfer NFT POST /v1/nft/transfer ~2s For OTC escrow deals

OTC Deals with Purple Flea Escrow

For high-value OTC trades between agents — exchanging a rare NFT for a large USDC amount, or swapping two collections — neither party wants to send first. The trustless solution is Purple Flea Escrow.

The workflow is simple: Agent A deposits their NFT into the escrow contract, specifying the price (in USDC) they'll accept. Agent B reviews the terms and deposits the USDC. The escrow contract atomically releases the NFT to Agent B and the USDC to Agent A. Neither party can exit with both assets. The 1% escrow fee on the USDC value is significantly cheaper than the marketplace royalty + listing fee structure on OpenSea (2.5% platform + up to 10% royalty = potentially 12.5% total).

Referral opportunity: If your agent introduces another agent to the Purple Flea Escrow system, you earn 15% of all escrow fees generated by that agent indefinitely. For a referred agent that does $100,000 USDC in NFT OTC trades per month, that is $150/month in passive referral income from a single referral.

Python: Rarity Arbitrage with Trade Execution

rarity_arb.py
import asyncio
import httpx
from collections import Counter
from dataclasses import dataclass, field
from typing import Dict, List

PURPLE_FLEA = "https://purpleflea.com/wallet-api"
RESERVOIR = "https://api.reservoir.tools"

@dataclass
class NFTOpportunity:
    token_id: str
    collection: str
    listing_price_eth: float
    rarity_percentile: float
    estimated_fair_value_eth: float
    expected_profit_eth: float
    marketplace: str
    wash_trade_flag: bool = False
    attributes: Dict = field(default_factory=dict)

def compute_rarity_scores(tokens: List[dict]) -> Dict[str, float]:
    """Statistical rarity scoring across all tokens in collection."""
    total = len(tokens)
    trait_counts = Counter()

    for t in tokens:
        for attr in t.get("attributes", []):
            trait_counts[f"{attr['key']}:{attr['value']}"] += 1

    scores = {}
    for t in tokens:
        score = 0.0
        for attr in t.get("attributes", []):
            key = f"{attr['key']}:{attr['value']}"
            score += 1 / (trait_counts[key] / total)
        scores[t["tokenId"]] = score

    return scores

def detect_wash_trades(sales_history: List[dict]) -> bool:
    """Flag if recent sales show wash trade patterns."""
    buyers = [s["buyer"] for s in sales_history]
    sellers = [s["seller"] for s in sales_history]

    # Check for buyer-seller overlap within the same token's history
    buyer_set = set(buyers)
    seller_set = set(sellers)
    overlap = buyer_set & seller_set

    # Check for rapid round-trips (same token sold within 60 seconds)
    rapid_resales = 0
    for i in range(len(sales_history) - 1):
        time_delta = sales_history[i+1]["timestamp"] - sales_history[i]["timestamp"]
        if time_delta < 60:
            rapid_resales += 1

    return len(overlap) > 0 or rapid_resales > 2

def estimate_fair_value(
    rarity_rank: float,
    floor_price_eth: float,
    comparable_sales: List[float]
) -> float:
    """Estimate fair value using rarity premium model."""
    if rarity_rank > 0.95:
        premium = 10.0
    elif rarity_rank > 0.85:
        premium = 4.0
    elif rarity_rank > 0.70:
        premium = 2.0
    elif rarity_rank > 0.50:
        premium = 1.3
    else:
        premium = 1.0

    model_value = floor_price_eth * premium

    # Blend with comparable recent sales if available
    if comparable_sales:
        avg_comps = sum(comparable_sales) / len(comparable_sales)
        model_value = (model_value * 0.6) + (avg_comps * 0.4)

    return model_value

async def scan_and_report(collection_id: str, api_key: str):
    async with httpx.AsyncClient() as client:
        # Get floor price
        col_res = await client.get(
            f"{RESERVOIR}/collections/v6",
            params={"id": collection_id},
            headers={"x-api-key": api_key}
        )
        floor = col_res.json()["collections"][0]["floorAsk"]["price"]["amount"]["native"]

        # Get listings
        listings_res = await client.get(
            f"{RESERVOIR}/orders/asks/v5",
            params={"collection": collection_id, "sortBy": "price", "limit": 100},
            headers={"x-api-key": api_key}
        )
        listings = listings_res.json()["orders"]

    print(f"Floor: {floor:.4f} ETH | Scanning {len(listings)} listings")
    for listing in listings[:5]:
        price = listing["price"]["amount"]["native"]
        print(f"  Token {listing['tokenSetId']}: {price:.4f} ETH")

asyncio.run(scan_and_report("0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", "your_key"))

Position Management: When to Hold vs. Flip

Buying at a discount is only half the strategy. The agent also needs to decide when to exit. Two dominant approaches:

Floor listing strategy

List immediately above floor at the rarity-adjusted fair value. This captures the spread between purchase price and fair value quickly, though it may take time to find a buyer who understands rarity premiums. For highly liquid collections (top 10 by volume), expect fill times of 24-72 hours for properly priced rare items.

Appreciation holding strategy

Hold the position and wait for broader floor appreciation. If you bought during a floor dip with high conviction in the collection, the entire position appreciates as the floor recovers. The risk is that the dip was not temporary — implement a hard stop-loss at 30% below your purchase price to avoid catastrophic drawdowns.

Start NFT Trading with Purple Flea

Use the Wallet API to hold and trade NFTs, Escrow for trustless OTC deals, and get free USDC from the Faucet to bootstrap your first position.