On-chain

Solana — memo format

There is no Exclusivo Solana program. Marketplace actions are transfers involving a custodial escrow wallet, and from 2026-07-26 each one carries a structured SPL memo that makes it machine-decodable.

The crawl anchor

Every Exclusivo Solana marketplace transaction carries exactly one SPL Memo instruction (program MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr) whose UTF-8 payload is:

Memo payload
exclusivo:v1:<compact JSON>

The anchor is two conditions, and you want both:

  • The memo payload starts with exclusivo:v1:, and
  • the platform escrow wallet is a signer or a counterparty in the same transaction.

Escrow wallet

Platform escrow wallet
HUxx919u8CSpyzLcmDYvBrvzBNt7caCdgDv2Ucjm4fge

The memo prefix alone is not a security boundary: anyone can write the same bytes into their own transaction. Requiring the escrow wallet as signer or counterparty is what makes the match meaningful.

Payload keys

Keys are single letters because Solana transactions are capped at 1232 bytes and the memo competes with real instructions for that budget. Any key whose value is unknown is omitted entirely rather than sent as null — treat every key except t as optional.

KeyTypeMeaning
tstringEvent type — see the table below. Always present.
lidstringListing id — the normalized orderId and the cross-transaction join key.
mbase58Asset mint, Core asset address, or cNFT asset id. Case-sensitive.
cbase58Collection mint, when known at build time.
pstringPrice in lamports, decimal string. The seller ask.
curstringCurrency. Always "SOL" today.
sbase58Seller wallet.
bbase58Buyer wallet.
fstringMarketplace fee in lamports (300 bps of p).
rstringTotal royalty in lamports.
rtbase58Royalty recipient — the first/primary creator only.
xnumberListing expiry, unix seconds.
stdstringlegacy | core | cnft.
nnumberItem count. Bulk purchases only.
lidsstring[]Listing ids in a bulk purchase. Truncated, or dropped entirely, to fit the transaction — see Limitations.

Event types

tTransactionSignerNormalized type
listSeller deposits the NFT into escrowSellerlisted
cancelEscrow returns the NFT to the seller (delist, expiry cron, recovery, reclaim)Escrowcancelled
saleLegacy atomic sale — NFT release and every payment leg in one transactionBuyer + escrowsold
sale_payCore/cNFT payment transaction (SOL legs only)Buyersold (half)
sale_releaseCore/cNFT NFT release transactionEscrowsold (half)
sale_bulkBundled purchase, up to 10 items in one transactionBuyer + escrowsold, once per item
offer_depositBuyer deposits SOL into offer escrowBuyernot a v1 event
offer_settleInstant-sell settlement payouts from escrowEscrowsold
offer_refundOffer deposit refundedEscrownot a v1 event

Payload contents vary by type: sale, sale_pay, and offer_settle carry the full economics (lid,m,c,p,cur,s,b,f,r,rt,std); sale_release carries lid,m,b,std; cancel carries lid,m,s,std; list carries m,c,p,cur,s,x,f,r,rt,std but no lid.

Worked examples

A listing

Memo — t:'list'
exclusivo:v1:{"t":"list","m":"7Nx4Qk...","c":"85Ghq5...","p":"12500000000","cur":"SOL","s":"9WzQ2f...","f":"375000000","r":"625000000","rt":"3JmPfd...","x":1785110400,"std":"core"}

Reads as: seller 9WzQ2f… deposited Core asset 7Nx4Qk… into escrow, asking 12.5 SOL. A buyer will pay 12.5 SOL to the seller plus 0.375 SOL platform fee and 0.625 SOL royalty, for 13.5 SOL total. The listing expires at unix 1785110400. There is no lid on a list memo.

A Core sale — payment leg

Memo — t:'sale_pay'
exclusivo:v1:{"t":"sale_pay","lid":"kQ7bTm2xR9","m":"7Nx4Qk...","c":"85Ghq5...","p":"12500000000","cur":"SOL","s":"9WzQ2f...","b":"4Ht8Lv...","f":"375000000","r":"625000000","rt":"3JmPfd...","std":"core"}

A Core sale — release leg

Memo — t:'sale_release'
exclusivo:v1:{"t":"sale_release","lid":"kQ7bTm2xR9","m":"7Nx4Qk...","b":"4Ht8Lv...","std":"core"}

A cancel

Memo — t:'cancel'
exclusivo:v1:{"t":"cancel","lid":"kQ7bTm2xR9","m":"7Nx4Qk...","s":"9WzQ2f...","std":"legacy"}

A cancel memo covers every return path: a seller delisting, the expiry cron, the recovery cron, and an escrow reclaim. The memo does not say which. If you need that distinction, take it from the API.

Decoding

Pseudocode
for each transaction where the escrow wallet is a signer or counterparty:
  memo = the one SPL Memo instruction's UTF-8 data
  if not memo.startsWith("exclusivo:v1:"): skip

  event = JSON.parse(memo.slice("exclusivo:v1:".length))

  switch (event.t):
    "list"                       -> listed
    "cancel"                     -> cancelled
    "sale" | "sale_bulk"         -> sold, complete in this transaction
    "offer_settle"               -> sold
    "sale_pay" | "sale_release"  -> half a sale; join on event.lid
    "offer_deposit" | "offer_refund" -> not a v1 event, ignore

  // amounts are lamports as decimal strings — parse as BigInt, never Number

Joining a two-tx sale

Core and compressed (cNFT) sales settle as two transactions: a buyer-signed payment transaction carrying the SOL legs (sale_pay) and a later escrow-signed release transaction carrying the NFT movement (sale_release). Both memos carry the same lid and the same m.

Join rule
sold(core | cnft) := sale_pay(lid) join sale_release(lid)

price, fees, parties  <- sale_pay
provenance, finality  <- sale_release

A sale is final when the release lands, not when the payment lands. Do not publish a sale on the payment transaction alone; if the release never lands you will have published a sale that did not happen.

Release transactions can look duplicated

Release transactions are sent with skipPreflight and retried with fresh blockhashes, so more than one signature can appear for the same release even though only one lands. Deduplicate on lid, never on signature.

Naive indexers get this wrong

Without the shared lid, a release transaction reads as an unpaid NFT transfer out of the platform wallet, and the payment transaction reads as SOL moving for no reason. That was the situation before 2026-07-26 and it is why history from earlier dates has to come from the API.

Reading the transfer legs

The buyer always pays fees on top of the ask, so the buyer's total is p + f + r. Inside a sale transaction the system-transfer legs are:

LegAmountNotes
Buyer → sellerpThe ask. Exactly what the seller receives.
Buyer → escrowfThe platform fee, plus any royalty remainder that could not be paid out directly.
Buyer → creator(s)rThe royalty total, split pro-rata across creators from DAS metadata.

Prefer the memo over summing the legs

Creator legs below the rent-exempt minimum (890,880 lamports) cannot be sent as standalone transfers to a never-funded address, so they are folded into the escrow leg. The legs you observe therefore under-report royalty and over-report platform fee. The memo's f and r record the intended split and are the correct source.

Limitations

List memos carry no lid

The Firestore listing id is assigned server-side only after the escrow deposit confirms, so a list memo cannot contain it. You cannot join a listing transaction to its listing record from chain data alone. Join through the API instead (the listing record carries escrowTxSig), or approximately by (mint, seller) plus recency — which is a heuristic and will mis-attribute if the same wallet relists the same asset quickly.

The memo degrades, and sometimes disappears

Transactions with deep merkle proofs (compressed NFTs especially) can exceed the 1232-byte cap with a full memo attached. The builder then shrinks the payload to t, m, s|b, std, and if it still does not fit, release and return transactions drop the memo entirely rather than fail settlement over logging. On bulk purchases, lids is truncated — and dropped whole rather than partially garbled — while n is always preserved. A missing or minimal memo is expected behaviour, not corruption: your decoder must tolerate an event with only t and a mint, and must not treat a memo-less escrow transfer as a non-Exclusivo transaction.

Recovery-path listings have no transaction at all

Some active, priced listings exist with escrowTxSig: 'recovery' — the asset is in escrow but the listing was reconstructed rather than created by a deposit. There is no transaction and therefore no memo. These listings are API-only by definition, and a chain-only index will not see them.

One address does several jobs

The escrow wallet receives platform fees, holds NFT custody, receives unattributed royalty remainders, and holds offer deposits. Balance-based fee accounting over that address will over-count badly. Use the memo's labelled f per event; the address separation is a product decision we have not made.
  • History before 2026-07-26 has no structured memos — earlier transactions carried static English prose identical across standards. Use the API for backfill.
  • rt is the primary creator only. When royalty is split across several creators, the memo names the first; the full split is visible in the transfer legs, subject to the rent-folding caveat above.
  • The SolGods blind auction program is out of scope. It emits Anchor events about bids, not listings or sales. Auction NFT releases still go through the escrow path and carry the memos documented here.