Skip to content

Commit

Permalink
Add ERC: Deferred Token Transfer
Browse files Browse the repository at this point in the history
Merged by EIP-Bot.
  • Loading branch information
chenly authored Jun 11, 2024
1 parent 3299361 commit 6b99dc3
Showing 1 changed file with 256 additions and 0 deletions.
256 changes: 256 additions & 0 deletions ERCS/erc-7720.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
---
eip: 7720
title: Deferred Token Transfer
description: Allows users to schedule ERC-20 token transfers for withdrawal at a specified future time, enabling deferred payments.
author: Chen Liaoyuan (@chenly) <cly@kip.pro>
discussions-to: https://ethereum-magicians.org/t/erc-7720-deferred-token-transfer/20245
status: Draft
type: Standards Track
category: ERC
created: 2024-06-09
requires: 20
---

## Abstract

This standard specifies that allows users to deposit [ERC-20](./eip-20.md) tokens for a beneficiary. The beneficiary can withdraw the tokens only after a specified future timestamp. Each deposit transaction is assigned a unique ID and includes details such as the token address, sender, recipient, amount, unlock time, and withdrawal status.

### Motivation

In various scenarios, such as vesting schedules, escrow services, or timed rewards, there is a need for deferred payments. This contract provides a secure and reliable mechanism for time-locked token transfers, ensuring that tokens can only be transferred after a specified timestamp is reached. By facilitating structured and delayed payments, it adds an extra layer of security and predictability to token transfers. This is particularly useful for scenarios where payments are contingent upon the passage of time.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Implementers of this standard **MUST** have all of the following functions:

```solidity
pragma solidity ^0.8.0;
interface ITokenTransfer {
// Event emitted when a transfer is initiated.
event Transfer(
uint256 txnId,
address indexed token,
address indexed from,
address indexed to,
uint256 amount,
uint256 unlockTime,
bytes32 referenceNo
);
// Event emitted when tokens are withdrawn.
event Withdraw(
uint256 txnId,
address indexed token,
address indexed from,
address indexed to,
uint256 amount
);
// Function to initiate a token transfer.
// Parameters:
// - _token: Address of the ERC20 token contract.
// - _from: Address of the sender.
// - _to: Address of the recipient.
// - _amount: Amount of tokens to be transferred.
// - _unlockTime: Time after which the tokens can be withdrawn.
// - _reference: Reference ID for the transaction.
// Returns the transaction ID.
function transferFrom(
address _token,
address _from,
address _to,
uint256 _amount,
uint256 _unlockTime,
bytes32 _reference
) external returns (uint256 txnId);
// Function to withdraw tokens from a transaction.
// Parameters:
// - _txnId: ID of the transaction to withdraw from.
function withdraw(uint256 _txnId) external;
// Function to get transaction details.
// Parameters:
// - _txnId: ID of the transaction.
// Returns the transaction details.
function getTransaction(uint256 _txnId)
external
view
returns (
address token,
address from,
address to,
uint256 amount,
uint256 unlockTime,
bytes32 referenceNo,
bool withdrawn
);
}
```

## Rationale

The design of the Deferred Token Transfer contract aims to provide a straightforward and secure method for handling time-locked token transfers. The following considerations were made during its development:

**Simplicity and Usability**: The contract interface is designed to be simple and intuitive, making it easy for users to create deposits and for beneficiaries to withdraw tokens once the conditions are met.

**Security**: The contract ensures secure token transfers, preventing common vulnerabilities associated with ERC-20 transfers. Additionally, the contract includes checks to prevent multiple withdrawals of the same deposit.

**Flexibility**: The contract supports various ERC-20 tokens, allowing users to create deposits with any standard ERC-20 token. This flexibility makes it suitable for a wide range of use cases.

**Event Logging**: Events are emitted for both deposit creation and token withdrawal. This provides transparency and allows easy tracking of contract activities, which is crucial for auditability and user confidence.

**Conditional Payments**: By implementing a time-lock mechanism, the contract ensures that tokens are only transferred after a specific timestamp. This feature is essential for use cases like vesting schedules, escrow arrangements, and timed rewards, where payments need to be delayed until certain conditions are met.

## Reference Implementation

```solidity
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract TokenTransfer {
using SafeERC20 for IERC20;
struct Transaction {
address token; // Address of the ERC20 token contract.
address from; // Address of the sender.
address to; // Address of the recipient.
uint256 amount; // Amount of tokens to be transferred.
uint256 unlockTime; // Time after which the tokens can be withdrawn.
bytes32 referenceNo; // Reference ID for the transaction.
bool withdrawn; // Flag indicating if the tokens have been withdrawn.
}
// Mapping from transaction ID to Transaction structure.
mapping(uint256 => Transaction) public transactions;
// Variable to keep track of the next transaction ID.
uint256 public lastTxnId = 0;
// Event emitted when a transfer is initiated.
event Transfer(
uint256 txnId,
address indexed token,
address indexed from,
address indexed to,
uint256 amount,
uint256 unlockTime,
bytes32 referenceNo
);
// Event emitted when tokens are withdrawn.
event Withdraw(
uint256 txnId,
address indexed token,
address indexed from,
address indexed to,
uint256 amount
);
constructor() {}
// Function to initiate a token transfer.
// Parameters:
// - _token: Address of the ERC20 token contract.
// - _from: Address of the sender.
// - _to: Address of the recipient.
// - _amount: Amount of tokens to be transferred.
// - _unlockTime: Time after which the tokens can be withdrawn.
// - _reference: Reference ID for the transaction.
// Returns the transaction ID.
function transferFrom(
address _token,
address _from,
address _to,
uint256 _amount,
uint256 _unlockTime,
bytes32 _reference
) external returns (uint256 txnId) {
require(_amount > 0, "Invalid transfer amount");
// Transfer tokens from sender to this contract.
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
lastTxnId++;
// Store the transaction details.
transactions[lastTxnId] = Transaction({
token: _token,
from: _from,
to: _to,
amount: _amount,
unlockTime: _unlockTime,
referenceNo: _reference,
withdrawn: false
});
// Emit an event for the transaction creation.
emit Transfer(lastTxnId, _token, _from, _to, _amount, _unlockTime, _reference);
return lastTxnId;
}
// Function to withdraw tokens from a transaction.
// Parameters:
// - _txnId: ID of the transaction to withdraw from.
function withdraw(uint256 _txnId) external {
Transaction storage transaction = transactions[_txnId];
require(transaction.amount > 0, "Invalid transaction ID");
require(block.timestamp >= transaction.unlockTime, "Current time is before unlock time");
// require(transaction.to == msg.sender, "Only the recipient can withdraw the tokens");
require(!transaction.withdrawn, "Tokens already withdrawn");
IERC20(transaction.token).safeTransfer(transaction.to, transaction.amount);
transaction.withdrawn = true;
// Emit an event for the token withdrawal.
emit Withdraw(_txnId, transaction.token, transaction.from, transaction.to, transaction.amount);
}
// Function to get transaction details.
// Parameters:
// - _txnId: ID of the transaction.
// Returns the transaction details.
function getTransaction(uint256 _txnId)
external
view
returns (
address token,
address from,
address to,
uint256 amount,
uint256 unlockTime,
bytes32 referenceNo,
bool withdrawn
)
{
Transaction storage transaction = transactions[_txnId];
require(transaction.amount > 0, "Invalid transaction ID");
return (
transaction.token,
transaction.from,
transaction.to,
transaction.amount,
transaction.unlockTime,
transaction.referenceNo,
transaction.withdrawn
);
}
}
```

## Security Considerations

**Ownerless Contract Design**: To prevent the risk of token loss after deposit, the contract should not have an owner. This ensures that the contract's token balance cannot be transferred to any address other than the designated beneficiary.

**Strict Beneficiary Control**: During withdrawal, the contract must strictly ensure that tokens are transferred only to the beneficiary specified at the time of deposit. This prevents unauthorized access and ensures that only the intended recipient can withdraw the tokens.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).

0 comments on commit 6b99dc3

Please sign in to comment.