Guides
Verify listings on-chain
If you would rather not take our API's word for it, you do not have to. Every Exclusivo sale leaves a marker in the transaction itself. This guide shows how to pull one and confirm it independently — and is equally clear about the cases where chain data alone is not enough.
What this proves
We do not run a proprietary settlement contract. EVM sales settle on canonical OpenSea Seaport; Solana sales settle through transfers involving our escrow wallet. So "is this an Exclusivo sale?" is a question about markers inside an otherwise ordinary transaction, and the answer is different per chain.
| Chain | Marker | What it proves |
|---|---|---|
| EVM | Seaport OrderFulfilled.zone | The filled order was built by Exclusivo. Parties, price, fee and royalty all decode from the same log. |
| Solana | SPL Memo prefixed exclusivo:v1: | Combined with escrow-wallet involvement in the same transaction: the action, its parties, and its economics. |
Both conventions apply to activity from 2026-07-26 onward. Everything you need is readable from a public RPC endpoint — no key, and no call to us.
EVM: decode the Seaport log
Every Seaport order Exclusivo builds sets its zone to our platform treasury address. The order stays fully open, so the zone has no execution authority — it is purely an attribution marker, and Seaport echoes it in the OrderFulfilled log.
| Constant | Value |
|---|---|
| Seaport 1.5 | 0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC |
| Seaport 1.6 | 0x0000000000000068f116a894984e2db1123eb395 |
| Exclusivo zone | 0xCD93552140bF932842Dbc767D0c6Cc75F2BA680C |
OrderFulfilled topic0 | 0x9d9af8e38d66c62e2c12f0225249fd9d721c54b83f48d9352c97c6cacdcb6f31 |
Step 1 — get a transaction hash
Take one from a sale row in the API (the tx field) so you can check our claim against the chain, or from any wallet's history if you have no starting point.
curl "https://exclusivo.one/v1/collections/{id}/sales?limit=1"
# → data[0].tx the transaction hash
# → data[0].orderId the Seaport order hash we claim it filledStep 2 — pull the receipt and find our log
Fetch the receipt from any RPC provider, keep only logs emitted by a canonical Seaport deployment, decode them as OrderFulfilled, and compare the zone. Address comparisons are case-insensitive — lowercase both sides.
import { createPublicClient, http, parseAbiItem, decodeEventLog } from "viem";
import { mainnet } from "viem/chains";
const SEAPORT = new Set([
"0x00000000000000adc04c56bf30ac9d3c0aaf14dc", // 1.5
"0x0000000000000068f116a894984e2db1123eb395", // 1.6
]);
const EXCLUSIVO_ZONE = "0xcd93552140bf932842dbc767d0c6cc75f2ba680c";
const ORDER_FULFILLED = parseAbiItem(
"event OrderFulfilled(bytes32 orderHash, address indexed offerer, address indexed zone, address recipient, (uint8 itemType, address token, uint256 identifier, uint256 amount)[] offer, (uint8 itemType, address token, uint256 identifier, uint256 amount, address recipient)[] consideration)"
);
const client = createPublicClient({ chain: mainnet, transport: http(RPC_URL) });
const receipt = await client.getTransactionReceipt({ hash });
const fills = [];
for (const log of receipt.logs) {
if (!SEAPORT.has(log.address.toLowerCase())) continue;
let decoded;
try {
decoded = decodeEventLog({ abi: [ORDER_FULFILLED], data: log.data, topics: log.topics });
} catch {
continue; // some other Seaport event
}
// The whole attribution check is this one comparison.
if (decoded.args.zone.toLowerCase() !== EXCLUSIVO_ZONE) continue;
fills.push(decoded.args);
}Step 3 — decode parties and economics
For a filled listing, the offer is the NFT and the consideration is the money. The offerer is the seller and the recipient is the buyer. Split the consideration by recipient: what goes to the offerer is the seller's proceeds, what goes to our treasury is the marketplace fee, and anything left over is the royalty.
const sum = (items) => items.reduce((total, item) => total + item.amount, 0n);
function decodeListingFill(args) {
const { orderHash, offerer, recipient, offer, consideration } = args;
// itemType 2 = ERC721, 3 = ERC1155
const nft = offer.find((item) => item.itemType === 2 || item.itemType === 3);
const seller = offerer.toLowerCase();
const toSeller = consideration.filter((c) => c.recipient.toLowerCase() === seller);
const toPlatform = consideration.filter((c) => c.recipient.toLowerCase() === EXCLUSIVO_ZONE);
const toRoyalty = consideration.filter(
(c) => !toSeller.includes(c) && !toPlatform.includes(c)
);
const price = sum(toSeller); // seller ask = seller proceeds
const marketplaceFee = sum(toPlatform); // 200 bps of price
const royaltyAmount = sum(toRoyalty);
return {
orderId: orderHash,
collection: nft.token.toLowerCase(),
tokenId: nft.identifier.toString(),
seller,
buyer: recipient.toLowerCase(),
// Seaport ItemType: 0 = NATIVE, 1 = ERC20, 2 = ERC721, 3 = ERC1155.
// Only 0 is native; 1 carries the token address (WETH on accepted offers).
currency:
consideration[0].itemType === 0 ? "native" : consideration[0].token.toLowerCase(),
price: price.toString(),
marketplaceFee: marketplaceFee.toString(),
royaltyAmount: royaltyAmount.toString(),
royaltyRecipient: toRoyalty[0]?.recipient.toLowerCase() ?? null,
};
}
// Sanity check: our EVM marketplace fee is exactly 200 bps of the ask.
const fill = decodeListingFill(fills[0]);
const bps = (BigInt(fill.marketplaceFee) * 10000n) / BigInt(fill.price);
console.assert(bps === 200n, "unexpected fee ratio", bps);Accepted collection offers invert
offerer is the buyer. The full inversion rule is on EVM — Seaport attribution.Step 4 — close the loop against the API
The orderHash you decoded is the same value the API returns as orderId on that sale. If the two match, the row we published and the transaction you just decoded are the same event, and every number in it came from the chain rather than from us.
One further marker exists but is not visible in logs: every order we build carries a constant 8-byte brand prefix 0x455843313a534561 (ASCII EXC1:SEa) in the top bytes of its salt. It brands the signed order struct itself, so it is checkable against raw order JSON or fulfilment calldata, not against a receipt.
Solana: decode the memo
There is no marketplace program on Solana. Our actions are transfers involving the platform escrow wallet, and each marketplace transaction carries exactly one SPL Memo instruction whose payload is exclusivo:v1: followed by compact JSON.
| Constant | Value |
|---|---|
| Memo program | MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr |
| Memo prefix | exclusivo:v1: |
| Escrow wallet | HUxx919u8CSpyzLcmDYvBrvzBNt7caCdgDv2Ucjm4fge |
Step 1 — fetch the transaction
Start from the tx field of an API sale row, or from a signature in the asset's history. Request the parsed form so the memo arrives as a string.
import { Connection } from "@solana/web3.js";
const connection = new Connection(RPC_URL, "confirmed");
const tx = await connection.getParsedTransaction(signature, {
maxSupportedTransactionVersion: 0,
});Step 2 — find the memo and check the prefix
Scan the instructions for the SPL Memo program. Anything that does not begin with the prefix is not one of our events.
const MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
const PREFIX = "exclusivo:v1:";
const memoIx = tx.transaction.message.instructions.find(
(ix) => ix.programId.toBase58() === MEMO_PROGRAM_ID
);
const memo = typeof memoIx?.parsed === "string" ? memoIx.parsed : null;
if (!memo?.startsWith(PREFIX)) throw new Error("not an Exclusivo marketplace transaction");
const event = JSON.parse(memo.slice(PREFIX.length));
// e.g. { t: "sale", lid, m, c, p, cur, s, b, f, r, rt, std }Step 3 — confirm the escrow wallet is involved
This step is what makes the check meaningful. A memo is unauthenticated text; anyone can write our prefix into one. What cannot be forged is our escrow wallet signing or being a counterparty to the transfers in the same transaction.
const ESCROW = "HUxx919u8CSpyzLcmDYvBrvzBNt7caCdgDv2Ucjm4fge";
const keys = tx.transaction.message.accountKeys;
const escrowKey = keys.find((k) => k.pubkey.toBase58() === ESCROW);
if (!escrowKey) throw new Error("escrow wallet not involved — the memo alone proves nothing");
// escrow signs the release / cancel legs; the buyer signs the payment leg.
const escrowSigned = Boolean(escrowKey.signer);Never lowercase a base58 address
Step 4 — check the economics against the balance changes
The buyer pays fees on top of the ask, so the total moved is p + f + r. Cross-check the memo against the transaction's balance deltas:
function lamportDelta(tx, address) {
const index = tx.transaction.message.accountKeys.findIndex(
(k) => k.pubkey.toBase58() === address
);
if (index < 0) return null;
return BigInt(tx.meta.postBalances[index]) - BigInt(tx.meta.preBalances[index]);
}
// On a "sale" / "sale_pay", the seller should receive exactly the recorded ask.
const sellerDelta = lamportDelta(tx, event.s);
console.assert(sellerDelta === BigInt(event.p), "seller proceeds do not match the memo");Prefer the memo over summing transfer legs when the two disagree on the fee/royalty split: creator payouts below the rent-exempt minimum are folded into the escrow leg, so the legs understate the royalty while the memo records the intended split.
Step 5 — join two-transaction sales
Core and compressed sales settle as two transactions: a buyer-signed payment (t: "sale_pay") and a later escrow-signed release (t: "sale_release"). Both memos carry the same lid. Take price and parties from the payment, and treat the release as the point at which the sale is final. Release transactions are retried with fresh blockhashes, so deduplicate by lid rather than by signature.
What you cannot verify
Being honest about the gaps is more useful than pretending they do not exist. None of the following is reconstructible from chain data, at any level of effort:
| Case | Why not | Where to get it |
|---|---|---|
| EVM listings | A listing is an off-chain signed Seaport order in our database. No transaction is ever sent. | /v1 listings |
| EVM cancels | Cancelling removes the order from our book without calling Seaport, so nothing is emitted. The signature itself remains technically fillable until it expires. | /v1 listings |
| Orders created before 2026-07-26 | They carry zone = address(0). The only signal left is the weak heuristic of a 200 bps consideration leg paid to our treasury. | /v1 sales |
| ApeChain activity | ApeChain fills route through Reservoir rather than Seaport, so no zone attribution exists. | /v1 sales |
| External aggregated buys | Buying a foreign OpenSea listing through our interface is an OpenSea sale, not an Exclusivo one. Our 2% aggregator fee is a bare transfer in a separate transaction and emits no log. | Not applicable — do not attribute these to us. |
| Solana history before 2026-07-26 | Older transactions carry unstructured prose memos with no ids or amounts. | /v1 sales |
Absence of a memo is not absence of an event
Full field reference
This guide covers the verification path. The complete field-by-field references live in the On-chain section:
- EVM — Seaport attribution — the full decode rule, the collection-offer inversion, and the indexer caveats.
- Solana — memo format — every memo key, every event type, and which transaction carries which.
- Event schema — the normalized cross-chain shape both of the above map onto.
- Contracts & addresses — every address referenced here, per chain, with explorer links.