ERC-5507 Refundable Tokens
Final · Tokens · created 2022-08-19
A standard that adds a refund function to the first sale of ERC-20, ERC-721, and ERC-1155 tokens. The sale proceeds are held in escrow until a set point in time, and before then the buyer can return the token and get their ether refunded.
Why it is needed
NFT and token markets lack mechanisms for accountability. The health of the whole ecosystem needs better means to prevent rugpulls, where a project sells tokens and disappears. Offering refunds gives buyers better protection and earns creators trust. Also, if every project handles refunds differently, apps such as wallets and marketplaces cannot handle them in a common way. A standard interface makes it easier to comply with EU distance selling rules, which require a 14-day refund period for goods bought online. Marketplaces can badge refundable NFTs or offer a refund instead of a sale, and decentralized exchanges (DEXs) can recommend a refund when it is more favorable. Wallets can build a confirmation screen that shows the act of refunding clearly to the user.
What to implement
function refund(uint256 amount) external;: Refunds the caller by amount while the refund is active. This is a function of the ERC-20 extension.function refundFrom(address from, uint256 amount) external;: If the refund is active and there is sufficient approval, refunds the tokens of from and sends the ether to msg.sender.function refundOf() external view returns (uint256 _wei);: Returns the amount of ether to be refunded per 1 unit of token (10**decimals smallest units), in wei.function refundDeadlineOf() external view returns (uint256 block);: Returns the first block number at which the refund is no longer valid.function refund(uint256 tokenId) external;: Refunds the caller while the refund for the given tokenId is active. This is a function of the ERC-721 extension.function refundFrom(address from, uint256 tokenId) external;: If there is approval, refunds the tokenId of from and sends the ether to msg.sender. This is a function of the ERC-721 extension.function refundOf(uint256 tokenId) external view returns (uint256 _wei);: Returns the amount of ether received for refunding a specific tokenId, in wei.function refundDeadlineOf(uint256 tokenId) external view returns (uint256 block);: Returns the first block number at which the refund for the given tokenId ends.function refund(uint256 tokenId, uint256 amount) external;: A function of the ERC-1155 extension that refunds amount of the token tokenId.function refundFrom(address from, uint256 tokenId, uint256 amount) external;: If there is approval, refunds amount of the token tokenId held by from and sends the ether to msg.sender.function refundBatch(uint256[] tokenIds) external;: An optional extension that refunds multiple tokenIds at once. All must succeed or all must fail, and there are no partial refunds.function refundFromBatch(address from, uint256[] tokenIds) external;: An optional extension that, with approval, refunds multiple tokens of from at once. There are no partial refunds.
In depth
Extensions for the three token standards
This standard defines a refund extension for each of ERC-20, ERC-721, and ERC-1155. The ERC-165 identifier is 0xf0ca2917 for ERC20Refund (ERC-20), 0xe97f3c83 for ERC721Refund (ERC-721), and 0x94029f5c for ERC1155Refund (ERC-1155). All implementations MUST use ERC-165 and follow its guidance. According to the Rationale, this is because without ERC-165, interface detection by DApps becomes much harder.
Behavior of refund and refundFrom
refund refunds the caller's tokens while the refund is active. ERC-20 takes amount, ERC-721 takes tokenId, and ERC-1155 takes tokenId and amount. refundFrom refunds the assets of from when the refund is active and the caller has sufficient approval, and in this case the ether goes to msg.sender, not to from. Both functions state that they must check that the user actually holds the tokens and must watch for re-entrancy paths. On success they emit the Refund and RefundFrom events respectively.
Querying refundOf and refundDeadlineOf
refundOf returns the refund amount in wei. For ERC-20 it is the amount for 1 unit of token, that is, 10**decimals smallest units, and for ERC-721 and ERC-1155 it is queried per tokenId. refundDeadlineOf returns the first block number at which a refund is no longer possible. The deadline is set as a block number rather than a timestamp because timestamps are less reliable than block numbers. The names refund, refundOf, and refundDeadlineOf were chosen to match the existing naming of ERC-20, ERC-721, and ERC-1155.
Optional batch refund extension
ERC-721 and ERC-1155 have an optional extension with refundBatch and refundFromBatch, which refund multiple tokens at once. A batch refund must either fully succeed or fully fail, and there are no partial refunds. The batch is optional because account abstraction will make such atomic operations much easier, though the standard explains that a proper implementation can still reduce gas costs. The ERC-165 identifiers of the two batch extensions are left blank in the source.
Custom ERC-20 payments are not supported
Refund proceeds are defined only in ether, and refunding in an arbitrary ERC-20 token is not supported. This is because it would add unnecessary complexity, and the standard explains that combining refundFrom with a decentralized exchange can achieve the same effect. No backward compatibility issues were found, and this standard is finalized as Final.
Things to watch out for
- Re-entrancy risk in the refund function: The refund function is exposed to re-entrancy attacks. The ether transfer must be performed only after the token is burned. In other words, the checks, effects, interactions pattern must be followed.