ERC-5507: Refundable Tokens
ERC-5507 standardises a time-bounded refund mechanism for NFTs: within a refund window after purchase, the holder can return the NFT to the contract and receive their payment back. Used for:
- Anti-rug-pull primary mints — buyers get a refund window if the project disappears.
- Trial periods for premium content NFTs — return for refund if unused.
- DAO-vetoed mints — community vote can trigger collection-wide refunds.
- EU consumer-protection compliance — 14-day return windows for digital goods.
The contract escrows the payment (in ETH or an ERC-20) until the refund window closes; refundable funds can't be withdrawn by the project until then.
Required Interface
interface IERC5507 {
event Refund(uint256 indexed tokenId, address indexed buyer, uint256 amount);
function refund(uint256 tokenId) external;
function refundDeadlineOf(uint256 tokenId) external view returns (uint64);
function refundOf(uint256 tokenId) external view returns (uint256);
}The refund returns the NFT to the contract (effectively burning it from the buyer's perspective) and pays the original price back to the buyer.
Neo Equivalent: NEP-11 + Per-Token Refund Window + Escrow
public static void MintRefundable(UInt160 buyer, ByteString tokenId,
UInt160 paymentToken, BigInteger price, BigInteger windowSeconds)
{
if (!Runtime.CheckWitness(GetIssuer())) throw new Exception("NEP11:NotIssuer");
var deadline = Runtime.Time + windowSeconds * 1000;
var entry = new Map<string, object>
{
["paymentToken"] = paymentToken,
["amount"] = price,
["deadline"] = deadline,
};
Storage.Put(Storage.CurrentContext, RefundKey(tokenId), StdLib.Serialize(entry));
Mint(tokenId, new TokenState { Owner = buyer }); // NEP-11 base: Mint(tokenId, TokenState)
}
public static void Refund(ByteString tokenId)
{
var owner = OwnerOf(tokenId);
if (!Runtime.CheckWitness(owner)) throw new Exception("NEP11:NotOwner");
var raw = Storage.Get(Storage.CurrentContext, RefundKey(tokenId));
if (raw is null) throw new Exception("NEP11:NotRefundable");
var entry = (Map<string, object>)StdLib.Deserialize((ByteString)raw);
if ((BigInteger)entry["deadline"] < Runtime.Time) throw new Exception("NEP11:RefundExpired");
var token = (UInt160)entry["paymentToken"];
var amount = (BigInteger)entry["amount"];
Burn(owner, tokenId); // remove the NFT
Storage.Delete(Storage.CurrentContext, RefundKey(tokenId));
// Pay refund.
Contract.Call(token, "transfer", CallFlags.All,
new object[] { Runtime.ExecutingScriptHash, owner, amount, null });
OnRefund(tokenId, owner, amount);
}| ERC-5507 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
refund(tokenId) | Refund(tokenId) owner-witness-checked | Burns NFT + pays refund |
refundDeadlineOf(tokenId) | Returns deadline from refund storage | |
refundOf(tokenId) | Returns amount from refund storage | |
| Refund event | OnRefund notification | |
| Funds escrowed in contract until deadline | Contract holds payment as NEP-17 balance until withdraw | Withdrawal gated on per-token deadline-passed |
Composition
- ERC-5528 — refundable fungible token. ERC-5507 is for NFTs; ERC-5528 is for ERC-20 / NEP-17.
- ERC-7066 — lockable. Lock during a dispute / refund-review period.
- ERC-7944 — async cancellation. Refunds and vault cancellations share the same time-bounded undo pattern.
Migration Notes
For mints with refund windows:
- Set the refund window at mint time (typically 7-14 days).
- The payment escrows in the contract; the issuer can only withdraw funds for tokens past their deadline.
- Refund triggers a burn + payment back; no marketplace intervention.
For EU compliance (14-day return for digital goods), set windowSeconds = 14 * 24 * 3600 and document the policy prominently. The Neo Runtime.Time is millisecond-precision so multiply by 1000 in the deadline calculation.
