NFTs · DvP Escrow

Atomic NFT Delivery-vs-Payment

Trade NFTs on the XRP Ledger without trusting the counterparty. Payment locks in escrow when the buyer commits — it releases automatically only once the NFT is confirmed in the buyer's wallet. Neither party can take the money and run.

Escrow eliminates counterparty risk

Standard NFT trades on any chain carry counterparty risk: the buyer pays first and hopes the NFT arrives, or the seller transfers first and hopes payment follows. Off-chain agreements, trusted escrow services, and multi-sig setups all require some form of trust.

XRPL's native escrow operates at the protocol layer — not a smart contract, the base ledger itself. Combined with the AgentTrust referee, you get a two-sided atomic trade:

Payment locks immediately

Buyer funds are held in XRPL crypto-condition escrow at the moment of commitment — not held by any platform, not in a hot wallet, locked in the ledger itself.

NFT transfer triggers release

The seller submits proof of NFT transfer (the on-chain token ID in the buyer's wallet). The AI referee verifies the chain state and releases payment automatically.

Both sides protected

Buyer can't receive the NFT and claim non-delivery. Seller can't receive payment and not transfer. The escrow enforces the deal atomically.

~0.00001 XRP transaction fee

XRPL native escrow costs fractions of a cent — not $5–50 in gas. Protocol fee: 0.1 XRP per escrow (~$0.20). Everything else runs on-ledger at negligible cost.

The DvP trade flow

1
Buyer locks payment in escrow
2
Seller transfers NFT on XRPL
3
Seller submits NFT token ID as proof
4
Referee verifies NFT ownership on-chain
5
Payment releases to seller automatically

What the AI referee checks: The task description you write into the escrow becomes the verification spec. Write it as: "NFT [token ID] issued by [issuer address] must be owned by [buyer address] at the time of evaluation." The referee queries the XRPL ledger directly and checks the current owner of that token ID.

Set up a DvP escrow in three steps

1

Agree the NFT token ID and price off-chain

Buyer and seller agree on the XRPL NFT token ID (a 64-character hex string, e.g. 000800006B6B…), the price in XRP or RLUSD, and a cancellation window (e.g. 72 hours if the NFT never arrives). The token ID uniquely identifies the specific NFT on the ledger.

2

Buyer creates and funds the escrow

The buyer pays the 0.1 XRP protocol fee and calls POST /escrow/generate with the task description encoding the expected NFT ownership. The resulting EscrowCreate transaction is submitted on-chain, locking the funds.

3

Seller transfers the NFT, then submits the token ID

Seller uses XRPL's NFTokenCreateOffer / NFTokenAcceptOffer flow to transfer the NFT to the buyer's wallet, then calls POST /evaluate with the token ID as proof. On PASS, payment releases automatically.

Python implementation

Security: Never put a wallet seed in browser JavaScript. Run this server-side or in your agent's trusted environment.

Python
# pip install xrpl-py httpx
import httpx, secrets
from xrpl.clients import JsonRpcClient
from xrpl.models.transactions import Payment, EscrowCreate
from xrpl.wallet import Wallet
from xrpl.utils import xrp_to_drops
from xrpl.transaction import submit_and_wait

REFEREE        = "https://xrpl-referee.onrender.com"
PROTOCOL_FEE   = "rmcSrkpZ2i2kuvtCPeTVetee9SixP4djR"
XRPL_NODE      = "https://s1.ripple.com:51234/"

buyer_wallet   = Wallet.from_seed("sBUYER_SEED_HERE")
seller_address = "rSELLER_ADDRESS_HERE"
client         = JsonRpcClient(XRPL_NODE)

# Agree these off-chain with the seller:
NFT_TOKEN_ID   = "000800006B6B..."  # 64-char hex NFT token ID
PRICE_XRP      = 50.0
CANCEL_HRS     = 72

# Build the task description — this is what the referee will verify
task_desc = (
    f"NFT Delivery-vs-Payment escrow.\n"
    f"NFT token ID: {NFT_TOKEN_ID}\n"
    f"Must be owned by buyer wallet: {buyer_wallet.address}\n"
    f"Payment releases when the NFT is confirmed in the buyer's wallet."
)

# Step 1: Pay the 0.1 XRP protocol fee
fee_tx   = Payment(
    account     = buyer_wallet.address,
    amount      = xrp_to_drops(0.1),
    destination = PROTOCOL_FEE,
)
fee_hash = submit_and_wait(fee_tx, client, buyer_wallet).result["hash"]
print(f"Protocol fee tx: {fee_hash}")

# Step 2: Generate escrow parameters from AgentTrust
escrow_id = f"NFT-{secrets.token_hex(4).upper()}"
params = httpx.post(f"{REFEREE}/escrow/generate", json={
    "escrow_id":         escrow_id,
    "fee_hash":          fee_hash,
    "buyer_name":        "Alice",
    "buyer_address":     buyer_wallet.address,
    "worker_address":    seller_address,
    "task_description":  task_desc,
    "amount_xrp":        PRICE_XRP,
    "cancel_after_hrs":  CANCEL_HRS,
}, timeout=30).json()

# Step 3: Submit the EscrowCreate on-chain
escrow_tx = EscrowCreate(
    account      = buyer_wallet.address,
    destination  = seller_address,
    amount       = xrp_to_drops(PRICE_XRP),
    condition    = params["condition"],
    finish_after = params["finish_after_ripple"],
    cancel_after = params["cancel_after_ripple"],
)
tx_hash = submit_and_wait(escrow_tx, client, buyer_wallet).result["hash"]
print(f"Escrow created: {tx_hash}")

# Step 4: Confirm the escrow with AgentTrust (starts the clock)
httpx.post(f"{REFEREE}/escrow/{escrow_id}/confirm",
    json={"tx_hash": tx_hash}).raise_for_status()
print(f"Escrow ID: {escrow_id}  — share this with the seller")

Flow: Transfer the NFT to the buyer's XRPL wallet first, then call /evaluate with the token ID as your proof. The referee checks on-chain that the NFT is now owned by the buyer before releasing payment.

Python
# pip install xrpl-py httpx
import httpx
from xrpl.clients import JsonRpcClient
from xrpl.models.transactions import NFTokenCreateOffer, NFTokenAcceptOffer
from xrpl.models.requests import AccountNFTs
from xrpl.wallet import Wallet
from xrpl.transaction import submit_and_wait
from xrpl.utils import xrp_to_drops

REFEREE        = "https://xrpl-referee.onrender.com"
XRPL_NODE      = "https://s1.ripple.com:51234/"

seller_wallet  = Wallet.from_seed("sSELLER_SEED_HERE")
buyer_address  = "rBUYER_ADDRESS_HERE"
client         = JsonRpcClient(XRPL_NODE)

ESCROW_ID      = "NFT-XXXX"   # escrow_id shared by the buyer
NFT_TOKEN_ID   = "000800006B6B..."

# Step 1: Create a sell offer for 0 XRP (free transfer to specific buyer)
offer_tx = NFTokenCreateOffer(
    account      = seller_wallet.address,
    nftoken_id   = NFT_TOKEN_ID,
    amount       = "0",                   # buyer already paid via escrow
    destination  = buyer_address,           # restricted to the buyer only
    flags        = 1,                      # tfSellNFToken
)
offer_result = submit_and_wait(offer_tx, client, seller_wallet).result
offer_id     = offer_result["meta"]["offer_id"]
print(f"Sell offer created: {offer_id}")

# Step 2: Buyer accepts the offer (can be a separate script run by buyer)
# buyer_wallet = Wallet.from_seed("sBUYER_SEED")
# accept_tx = NFTokenAcceptOffer(
#     account=buyer_wallet.address, nftoken_sell_offer=offer_id)
# submit_and_wait(accept_tx, client, buyer_wallet)

# -- (once buyer has accepted and NFT is in their wallet) --

# Step 3: Verify the NFT is now in the buyer's wallet
nfts = client.request(AccountNFTs(account=buyer_address)).result["account_nfts"]
owned = any(n["NFTokenID"] == NFT_TOKEN_ID for n in nfts)
assert owned, "NFT not yet in buyer wallet — wait for acceptance"
print("NFT confirmed in buyer wallet. Submitting proof...")

# Step 4: Submit proof to AgentTrust — payment releases automatically on PASS
result = httpx.post(f"{REFEREE}/evaluate", json={
    "escrow_id": ESCROW_ID,
    "work": (
        f"NFT transfer complete.\n"
        f"Token ID: {NFT_TOKEN_ID}\n"
        f"Current owner (buyer): {buyer_address}\n"
        f"Sell offer accepted: {offer_id}\n"
        f"NFT is confirmed in the buyer's wallet on the XRP Ledger."
    ),
}, timeout=120).json()

print(f"Verdict: {result['verdict']} | Score: {result['score']}/100")
# PASS → EscrowFinish submitted on-chain automatically → seller receives XRP

Two-sided acceptance: On XRPL, both parties need to act: the seller creates a sell offer, the buyer accepts it. In a fully automated flow, both sides run their respective code. In a manual flow, share the offer_id with the buyer so they can accept it with Xaman or any XRPL wallet.

What to put in task_description

The task description is the verification spec the AI referee scores against. For NFT DvP trades, be explicit about exactly what constitutes proof of delivery:

Template
NFT Delivery-vs-Payment escrow.

NFT token ID: 000800006B6B4C70EDB8EBC90F2A5EDB45FDBA
Issuer address: rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh
Buyer wallet: rBuyer123...
Seller wallet: rSeller456...
Agreed price: 50 XRP (held in escrow, released on verification)

PAYMENT RELEASES when the submission confirms that:
- The NFT with the above token ID is owned by the buyer wallet
- The transfer has been accepted and finalized on the XRP Ledger mainnet

Seller should include: the sell offer ID, confirmation that the
buyer accepted it, and a statement that the NFT is in the buyer's wallet.

Cancel window matters: Set cancel_after_hrs to give the seller enough time to list, transfer, and have the buyer accept the NFT. 72 hours is reasonable for a manually coordinated trade. If the deadline passes without a PASS verdict, the buyer can reclaim the escrowed funds. The NFT offer will remain open unless explicitly cancelled.

Require the seller to hold a specific NFT

AgentTrust lets you add a trust condition requiring the seller wallet to hold an NFT from a specific issuer before the escrow can be created. This is useful for gating trades to verified platforms, credentialed creators, or whitelisted collections.

JSON — escrow/generate body
{
  "escrow_id":        "NFT-TRADE-001",
  "fee_hash":         "...",
  "buyer_address":    "rBuyer...",
  "worker_address":   "rSeller...",
  "task_description": "...",
  "amount_xrp":       50,
  "cancel_after_hrs": 72,

  // Require seller to hold a verified-creator NFT from this issuer
  "required_nft_issuer": "rISSUER_ADDRESS",
  "required_nft_taxon":  12        // optional: restrict to a specific collection
}

If the seller's wallet doesn't hold a matching NFT, escrow creation is blocked and a clear error is returned. Combine this with domain verification and OFAC screening for a fully trust-gated trade.

Pay in RLUSD instead of XRP

If you want to denominate the trade in USD rather than XRP, use the currency: "RLUSD" field. RLUSD is Ripple's regulated stablecoin on the XRP Ledger, pegged 1:1 to the US dollar.

JSON — RLUSD trade
{
  "escrow_id":        "NFT-USD-001",
  "fee_hash":         "...",
  "buyer_address":    "rBuyer...",
  "worker_address":   "rSeller...",
  "task_description": "...",
  "currency":         "RLUSD",
  "amount_rlusd":     500.00,      // $500 USD
  "cancel_after_hrs": 72
}

Where DvP escrow applies

Digital art sales

Creator sells a 1-of-1 or edition NFT. Buyer's payment locks before the transfer — neither side can ghost after committing.

Ticket and access passes

Event tickets issued as XRPL NFTs. Resale market with atomic settlement — buyer gets the ticket, seller gets paid, simultaneously.

Real-world asset tokens

Tokenised real-world assets (property fractions, carbon credits, trade finance instruments) settling with cryptographic finality.

Agent-to-agent asset trades

AI agents trading NFT-represented licences, data access tokens, or compute credits autonomously — payment and delivery without human approval.

Go further