forked from DecentralizedClimateFoundation/DCIPs
333 lines
15 KiB
Markdown
333 lines
15 KiB
Markdown
|
---
|
||
|
eip: 5507
|
||
|
title: Refundable Tokens
|
||
|
description: Adds refund functionality to ERC-20, ERC-721, and ERC-1155 tokens
|
||
|
author: elie222 (@elie222), Gavin John (@Pandapip1)
|
||
|
discussions-to: https://ethereum-magicians.org/t/eip-5507-refundable-nfts/10451
|
||
|
status: Review
|
||
|
type: Standards Track
|
||
|
category: ERC
|
||
|
created: 2022-08-19
|
||
|
requires: 20, 165, 721, 1155
|
||
|
---
|
||
|
|
||
|
## Abstract
|
||
|
|
||
|
This ERC adds refund functionality for initial token offerings to [ERC-20](./eip-20.md), [ERC-721](./eip-721.md), and [ERC-1155](./eip-1155.md). Funds are held in escrow until a predetermined time before they are claimable. Until that predetermined time passes, users can receive a refund for tokens they have purchased.
|
||
|
|
||
|
## Motivation
|
||
|
|
||
|
The NFT and token spaces lack accountability. For the health of the ecosystem as a whole, better mechanisms to prevent rugpulls from happening are needed. Offering refunds provides greater protection for buyers and increases legitimacy for creators.
|
||
|
|
||
|
A standard interface for this particular use case allows for certain benefits:
|
||
|
|
||
|
- Greater Compliance with EU "Distance Selling Regulations," which require a 14-day refund period for goods (such as tokens) purchased online
|
||
|
- Interoperability with various NFT-related applications, such as portfolio browsers, and marketplaces
|
||
|
- NFT marketplaces could place a badge indicating that the NFT is still refundable on listings, and offer to refund NFTs instead of listing them on the marketplace
|
||
|
- DExes could offer to refund tokens if doing so would give a higher yield
|
||
|
- Better wallet confirmation dialogs
|
||
|
- Wallets can better inform the user of the action that is being taken (tokens being refunded), similar to how transfers often have their own unique dialog
|
||
|
- DAOs can better display the functionality of smart proposals that include refunding tokens
|
||
|
|
||
|
## Specification
|
||
|
|
||
|
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
|
||
|
|
||
|
All implementations MUST use and follow the directions of [ERC-165](./eip-165.md).
|
||
|
|
||
|
### ERC-20 Refund Extension
|
||
|
|
||
|
```solidity
|
||
|
// SPDX-License-Identifier: CC0-1.0
|
||
|
|
||
|
pragma solidity ^0.8.17;
|
||
|
|
||
|
import "ERC20.sol";
|
||
|
import "ERC165.sol";
|
||
|
|
||
|
/// @notice Refundable ERC-20 tokens
|
||
|
/// @dev The ERC-165 identifier of this interface is `0xf0ca2917`
|
||
|
interface ERC20Refund is ERC20, ERC165 {
|
||
|
/// @notice Emitted when a token is refunded
|
||
|
/// @dev Emitted by `refund`
|
||
|
/// @param _from The account whose assets are refunded
|
||
|
/// @param _amount The amount of token (in terms of the indivisible unit) that was refunded
|
||
|
event Refund(
|
||
|
address indexed _from,
|
||
|
uint256 indexed _amount
|
||
|
);
|
||
|
|
||
|
/// @notice Emitted when a token is refunded
|
||
|
/// @dev Emitted by `refundFrom`
|
||
|
/// @param _sender The account that sent the refund
|
||
|
/// @param _from The account whose assets are refunded
|
||
|
/// @param _amount The amount of token (in terms of the indivisible unit) that was refunded
|
||
|
event RefundFrom(
|
||
|
address indexed _sender,
|
||
|
address indexed _from,
|
||
|
uint256 indexed _amount
|
||
|
);
|
||
|
|
||
|
/// @notice As long as the refund is active, refunds the user
|
||
|
/// @dev Make sure to check that the user has the token, and be aware of potential re-entrancy vectors
|
||
|
/// @param amount The `amount` to refund
|
||
|
function refund(uint256 amount) external;
|
||
|
|
||
|
/// @notice As long as the refund is active and the sender has sufficient approval, refund the tokens and send the ether to the sender
|
||
|
/// @dev Make sure to check that the user has the token, and be aware of potential re-entrancy vectors
|
||
|
/// The ether goes to msg.sender.
|
||
|
/// @param from The user from which to refund the assets
|
||
|
/// @param amount The `amount` to refund
|
||
|
function refundFrom(address from, uint256 amount) external;
|
||
|
|
||
|
/// @notice Gets the refund price
|
||
|
/// @return _wei The amount of ether (in wei) that would be refunded for a single token unit (10**decimals indivisible units)
|
||
|
function refundOf() external view returns (uint256 _wei);
|
||
|
|
||
|
/// @notice Gets the first block for which the refund is not active
|
||
|
/// @return block The first block where the token cannot be refunded
|
||
|
function refundDeadlineOf() external view returns (uint256 block);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### ERC-721 Refund Extension
|
||
|
|
||
|
```solidity
|
||
|
// SPDX-License-Identifier: CC0-1.0
|
||
|
|
||
|
pragma solidity ^0.8.17;
|
||
|
|
||
|
import "ERC721.sol";
|
||
|
import "ERC165.sol";
|
||
|
|
||
|
/// @notice Refundable ERC-721 tokens
|
||
|
/// @dev The ERC-165 identifier of this interface is `0xe97f3c83`
|
||
|
interface ERC721Refund is ERC721 /* , ERC165 */ {
|
||
|
/// @notice Emitted when a token is refunded
|
||
|
/// @dev Emitted by `refund`
|
||
|
/// @param _from The account whose assets are refunded
|
||
|
/// @param _tokenId The `tokenId` that was refunded
|
||
|
event Refund(
|
||
|
address indexed _from,
|
||
|
uint256 indexed _tokenId
|
||
|
);
|
||
|
|
||
|
/// @notice Emitted when a token is refunded
|
||
|
/// @dev Emitted by `refundFrom`
|
||
|
/// @param _sender The account that sent the refund
|
||
|
/// @param _from The account whose assets are refunded
|
||
|
/// @param _tokenId The `tokenId` that was refunded
|
||
|
event RefundFrom(
|
||
|
address indexed _sender,
|
||
|
address indexed _from,
|
||
|
uint256 indexed _tokenId
|
||
|
);
|
||
|
|
||
|
/// @notice As long as the refund is active for the given `tokenId`, refunds the user
|
||
|
/// @dev Make sure to check that the user has the token, and be aware of potential re-entrancy vectors
|
||
|
/// @param tokenId The `tokenId` to refund
|
||
|
function refund(uint256 tokenId) external;
|
||
|
|
||
|
/// @notice As long as the refund is active and the sender has sufficient approval, refund the token and send the ether to the sender
|
||
|
/// @dev Make sure to check that the user has the token, and be aware of potential re-entrancy vectors
|
||
|
/// The ether goes to msg.sender.
|
||
|
/// @param from The user from which to refund the token
|
||
|
/// @param tokenId The `tokenId` to refund
|
||
|
function refundFrom(address from, uint256 tokenId) external;
|
||
|
|
||
|
/// @notice Gets the refund price of the specific `tokenId`
|
||
|
/// @param tokenId The `tokenId` to query
|
||
|
/// @return _wei The amount of ether (in wei) that would be refunded
|
||
|
function refundOf(uint256 tokenId) external view returns (uint256 _wei);
|
||
|
|
||
|
/// @notice Gets the first block for which the refund is not active for a given `tokenId`
|
||
|
/// @param tokenId The `tokenId` to query
|
||
|
/// @return block The first block where token cannot be refunded
|
||
|
function refundDeadlineOf(uint256 tokenId) external view returns (uint256 block);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Optional ERC-721 Batch Refund Extension
|
||
|
|
||
|
```solidity
|
||
|
// SPDX-License-Identifier: CC0-1.0;
|
||
|
|
||
|
import "ERC721Refund.sol";
|
||
|
|
||
|
/// @notice Batch Refundable ERC-721 tokens
|
||
|
/// @dev The ERC-165 identifier of this interface is ``
|
||
|
contract ERC721BatchRefund is ERC721Refund {
|
||
|
/// @notice Emitted when one or more tokens are batch refunded
|
||
|
/// @dev Emitted by `refundBatch`
|
||
|
/// @param _from The account whose assets are refunded
|
||
|
/// @param _tokenId The `tokenIds` that were refunded
|
||
|
event RefundBatch(
|
||
|
address indexed _from,
|
||
|
uint256[] _tokenIds // This may or may not be indexed
|
||
|
);
|
||
|
|
||
|
/// @notice Emitted when one or more tokens are batch refunded
|
||
|
/// @dev Emitted by `refundFromBatch`
|
||
|
/// @param _sender The account that sent the refund
|
||
|
/// @param _from The account whose assets are refunded
|
||
|
/// @param _tokenId The `tokenId` that was refunded
|
||
|
event RefundFromBatch(
|
||
|
address indexed _sender,
|
||
|
address indexed _from,
|
||
|
uint256 indexed _tokenId
|
||
|
);
|
||
|
|
||
|
/// @notice As long as the refund is active for the given `tokenIds`, refunds the user
|
||
|
/// @dev Make sure to check that the user has the tokens, and be aware of potential re-entrancy vectors
|
||
|
/// These must either succeed or fail together; there are no partial refunds.
|
||
|
/// @param tokenIds The `tokenId`s to refund
|
||
|
function refundBatch(uint256[] tokenIds) external;
|
||
|
|
||
|
/// @notice As long as the refund is active for the given `tokenIds` and the sender has sufficient approval, refund the tokens and send the ether to the sender
|
||
|
/// @dev Make sure to check that the user has the tokens, and be aware of potential re-entrancy vectors
|
||
|
/// The ether goes to msg.sender.
|
||
|
/// These must either succeed or fail together; there are no partial refunds.
|
||
|
/// @param from The user from which to refund the token
|
||
|
/// @param tokenIds The `tokenId`s to refund
|
||
|
function refundFromBatch(address from, uint256[] tokenIds) external;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### ERC-1155 Refund Extension
|
||
|
|
||
|
```solidity
|
||
|
// SPDX-License-Identifier: CC0-1.0
|
||
|
|
||
|
pragma solidity ^0.8.17;
|
||
|
|
||
|
import "ERC1155.sol";
|
||
|
import "ERC165.sol";
|
||
|
|
||
|
/// @notice Refundable ERC-1155 tokens
|
||
|
/// @dev The ERC-165 identifier of this interface is `0x94029f5c`
|
||
|
interface ERC1155Refund is ERC1155 /* , ERC165 */ {
|
||
|
/// @notice Emitted when a token is refunded
|
||
|
/// @dev Emitted by `refund`
|
||
|
/// @param _from The account that requested a refund
|
||
|
/// @param _tokenId The `tokenId` that was refunded
|
||
|
/// @param _amount The amount of `tokenId` that was refunded
|
||
|
event Refund(
|
||
|
address indexed _from,
|
||
|
uint256 indexed _tokenId,
|
||
|
uint256 _amount
|
||
|
);
|
||
|
|
||
|
/// @notice Emitted when a token is refunded
|
||
|
/// @dev Emitted by `refundFrom`
|
||
|
/// @param _sender The account that sent the refund
|
||
|
/// @param _from The account whose assets are refunded
|
||
|
/// @param _tokenId The `tokenId` that was refunded
|
||
|
/// @param _amount The amount of `tokenId` that was refunded
|
||
|
event RefundFrom(
|
||
|
address indexed _sender,
|
||
|
address indexed _from,
|
||
|
uint256 indexed _tokenId
|
||
|
);
|
||
|
|
||
|
/// @notice As long as the refund is active for the given `tokenId`, refunds the user
|
||
|
/// @dev Make sure to check that the user has enough tokens, and be aware of potential re-entrancy vectors
|
||
|
/// @param tokenId The `tokenId` to refund
|
||
|
/// @param amount The amount of `tokenId` to refund
|
||
|
function refund(uint256 tokenId, uint256 amount) external;
|
||
|
|
||
|
/// @notice As long as the refund is active and the sender has sufficient approval, refund the tokens and send the ether to the sender
|
||
|
/// @dev Make sure to check that the user has enough tokens, and be aware of potential re-entrancy vectors
|
||
|
/// The ether goes to msg.sender.
|
||
|
/// @param from The user from which to refund the token
|
||
|
/// @param tokenId The `tokenId` to refund
|
||
|
/// @param amount The amount of `tokenId` to refund
|
||
|
function refundFrom(address from, uint256 tokenId, uint256 amount) external;
|
||
|
|
||
|
/// @notice Gets the refund price of the specific `tokenId`
|
||
|
/// @param tokenId The `tokenId` to query
|
||
|
/// @return _wei The amount of ether (in wei) that would be refunded for a single token
|
||
|
function refundOf(uint256 tokenId) external view returns (uint256 _wei);
|
||
|
|
||
|
/// @notice Gets the first block for which the refund is not active for a given `tokenId`
|
||
|
/// @param tokenId The `tokenId` to query
|
||
|
/// @return block The first block where the token cannot be refunded
|
||
|
function refundDeadlineOf(uint256 tokenId) external view returns (uint256 block);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### Optional ERC-1155 Batch Refund Extension
|
||
|
|
||
|
```solidity
|
||
|
// SPDX-License-Identifier: CC0-1.0;
|
||
|
|
||
|
import "ERC1155Refund.sol";
|
||
|
|
||
|
/// @notice Batch Refundable ERC-1155 tokens
|
||
|
/// @dev The ERC-165 identifier of this interface is ``
|
||
|
contract ERC1155BatchRefund is ERC1155Refund {
|
||
|
/// @notice Emitted when one or more tokens are batch refunded
|
||
|
/// @dev Emitted by `refundBatch`
|
||
|
/// @param _from The account that requested a refund
|
||
|
/// @param _tokenIds The `tokenIds` that were refunded
|
||
|
/// @param _amounts The amount of each `tokenId` that was refunded
|
||
|
event RefundBatch(
|
||
|
address indexed _from,
|
||
|
uint256[] _tokenIds, // This may or may not be indexed
|
||
|
uint256[] _amounts
|
||
|
);
|
||
|
|
||
|
/// @notice Emitted when one or more tokens are batch refunded
|
||
|
/// @dev Emitted by `refundFromBatch`
|
||
|
/// @param _sender The account that sent the refund
|
||
|
/// @param _from The account whose assets are refunded
|
||
|
/// @param _tokenIds The `tokenIds` that was refunded
|
||
|
/// @param _amounts The amount of each `tokenId` that was refunded
|
||
|
event RefundFromBatch(
|
||
|
address indexed _sender,
|
||
|
address indexed _from,
|
||
|
uint256[] _tokenId, // This may or may not be indexed
|
||
|
uint256[] _amounts
|
||
|
);
|
||
|
|
||
|
/// @notice As long as the refund is active for the given `tokenIds`, refunds the user
|
||
|
/// @dev Make sure to check that the user has enough tokens, and be aware of potential re-entrancy vectors
|
||
|
/// These must either succeed or fail together; there are no partial refunds.
|
||
|
/// @param tokenIds The `tokenId`s to refund
|
||
|
/// @param amounts The amount of each `tokenId` to refund
|
||
|
function refundBatch(uint256[] tokenIds, uint256[] amounts) external;
|
||
|
|
||
|
/// @notice As long as the refund is active for the given `tokenIds` and the sender has sufficient approval, refund the tokens and send the ether to the sender
|
||
|
/// @dev Make sure to check that the user has the tokens, and be aware of potential re-entrancy vectors
|
||
|
/// The ether goes to msg.sender.
|
||
|
/// These must either succeed or fail together; there are no partial refunds.
|
||
|
/// @param from The user from which to refund the token
|
||
|
/// @param tokenIds The `tokenId`s to refund
|
||
|
/// @param amounts The amount of each `tokenId` to refund
|
||
|
function refundFromBatch(address from, uint256[] tokenIds, uint256[] amounts external;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
## Rationale
|
||
|
|
||
|
`refundDeadlineOf` uses blocks instead of timestamps, as timestamps are less reliable than block numbers.
|
||
|
|
||
|
The function names of `refund`, `refundOf`, and `refundDeadlineOf` were chosen to fit the naming style of ERC-20, ERC-721, and ERC-1155.
|
||
|
|
||
|
[ERC-165](./eip-165.md) is required as introspection by DApps would be made significantly harder if it were not.
|
||
|
|
||
|
Custom ERC-20 tokens are not supported, as it needlessly increases complexity, and the `refundFrom` function allows for this functionality when combined with a DEx.
|
||
|
|
||
|
Batch refunds are optional, as account abstraction would make atomic operations like these significantly easier. However, they might still reduce gas costs if properly implemented.
|
||
|
|
||
|
## Backwards Compatibility
|
||
|
|
||
|
No backward compatibility issues were found.
|
||
|
|
||
|
## Security Considerations
|
||
|
|
||
|
There is a potential re-entrancy risk with the `refund` function. Make sure to perform the ether transfer **after** the tokens are destroyed (i.e. obey the checks, effects, interactions pattern).
|
||
|
|
||
|
## Copyright
|
||
|
|
||
|
Copyright and related rights waived via [CC0](../LICENSE.md).
|