ERC-6105: No-Intermediary NFT Trading
ERC-6105 bakes a listing + atomic swap protocol directly into the NFT contract, eliminating the need for marketplace contracts (OpenSea, Blur, Reservoir) for basic trading. The NFT holder lists their token at a price; any buyer can call buy(tokenId) paying the asking price; the NFT swaps for the payment in a single atomic call. Royalties (per ERC-2981) and protocol fees are paid out automatically.
Goals:
- Eliminate marketplace fees (typically 2-2.5%) — the NFT contract charges nothing beyond the configured royalty.
- Reduce smart-contract risk — no separate marketplace contract to audit or compromise.
- Standardise the listing UX — wallets show the listed price directly; no marketplace API integration needed.
Required Interface
interface IERC6105 {
event UpdateListing(uint256 indexed tokenId, address indexed from,
uint256 salePrice, uint64 expires,
address supportedToken, address indexed benchmarkPrice);
event Purchase(uint256 indexed tokenId, address indexed from, address indexed to,
address supportedToken, uint256 salePrice, address royaltyRecipient,
uint256 royaltyAmount);
function listItem(uint256 tokenId, uint256 salePrice, uint64 expires) external;
function listItem(uint256 tokenId, uint256 salePrice, uint64 expires,
address supportedToken) external;
function unlistItem(uint256 tokenId) external;
function buyItem(uint256 tokenId) external payable;
function getListing(uint256 tokenId)
external view returns (address from, uint256 salePrice, uint64 expires, address supportedToken);
}The supportedToken parameter lets sellers price in any ERC-20 (USDC, DAI, custom currency) instead of just ETH. benchmarkPrice is an oracle reference for off-chain UI rendering.
Neo Equivalent: NEP-11 with Built-In Listing + Atomic Swap
The Neo port stores listings per-tokenId in the NFT contract; BuyItem pulls the payment via NEP-17 transfer, splits royalty + payment to seller, and transfers the NFT — all in one transaction. No separate marketplace contract.
public static void ListItem(ByteString tokenId, BigInteger salePrice,
BigInteger expires, UInt160 paymentToken)
{
var owner = OwnerOf(tokenId);
if (!Runtime.CheckWitness(owner)) throw new Exception("NEP11:NotOwner");
if (expires <= Runtime.Time) throw new Exception("NEP11:ExpiryInPast");
var listing = new Map<string, object>
{
["seller"] = owner,
["salePrice"] = salePrice,
["expires"] = expires,
["payment"] = paymentToken,
};
Storage.Put(Storage.CurrentContext, ListingKey(tokenId), StdLib.Serialize(listing));
OnUpdateListing(tokenId, owner, salePrice, expires, paymentToken);
}
public static void BuyItem(UInt160 buyer, ByteString tokenId)
{
if (!Runtime.CheckWitness(buyer)) throw new Exception("NEP11:NoAuth");
var listing = LoadListing(tokenId);
if (listing is null) throw new Exception("NEP11:NotListed");
if ((BigInteger)listing["expires"] < Runtime.Time) throw new Exception("NEP11:ListingExpired");
var seller = (UInt160)listing["seller"];
var salePrice = (BigInteger)listing["salePrice"];
var payment = (UInt160)listing["payment"];
// Compute royalty per ERC-2981 / NEP-24.
var (royaltyRecipient, royaltyAmount) = ComputeRoyalty(tokenId, salePrice);
var sellerAmount = salePrice - royaltyAmount;
// Pull payment from buyer; split royalty + seller proceeds.
Contract.Call(payment, "transfer", CallFlags.All,
new object[] { buyer, seller, sellerAmount, null });
if (royaltyAmount > 0 && royaltyRecipient is not null)
Contract.Call(payment, "transfer", CallFlags.All,
new object[] { buyer, royaltyRecipient, royaltyAmount, null });
// Transfer NFT seller → buyer.
Storage.Delete(Storage.CurrentContext, ListingKey(tokenId));
Nep11Token.Transfer(buyer, tokenId, null); // base NEP-11 transfer
OnPurchase(tokenId, seller, buyer, payment, salePrice, royaltyRecipient, royaltyAmount);
}| ERC-6105 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
listItem(tokenId, price, expires) | ListItem(tokenId, price, expires, paymentToken) | NEP-17 paymentToken instead of ETH default |
unlistItem(tokenId) | UnlistItem(tokenId) owner-only | |
buyItem(tokenId) payable | BuyItem(buyer, tokenId) — pulls payment via NEP-17 | No payable semantics needed |
Purchase event | OnPurchase notification | |
| ERC-2981 royalty integration | NEP-24 royalty integration | Direct equivalent |
supportedToken (ERC-20) | paymentToken (NEP-17) | Native cross-token support |
Composition
- ERC-2981 — royalties. Critical: ERC-6105 only works correctly with on-chain royalty enforcement.
- ERC-7066 — lockable. Locked NFTs can't be listed for sale.
- ERC-2135 — consumable. Listed NFT cannot be consumed mid-listing without first unlisting.
- ERC-7528 — native asset address. The
paymentTokenisNativeContract.GAS.Hashfor GAS-priced sales.
Migration Notes
For Solidity NFT collections adopting ERC-6105:
- Add the listing storage prefix.
- Implement
ListItem/UnlistItem/BuyItem/GetListing. - Wire ERC-2981 royalty into
BuyItem. - Document the supported payment tokens (typically USDC + GAS).
For Neo dApps building NFT marketplaces, ERC-6105 is the anti- marketplace — instead of building yet another marketplace contract, adopt the standard so any wallet can buy / sell directly from the NFT contract. Marketplaces become aggregator UIs, not on-chain intermediaries.
