Yes, you can do it. It will be too involved to show a complete implementation, so I will just assume you know your way around an ERC20 contract.
Generally, when an ERC20 is deployed, the initial supply is "minted" to the deployer. The next step is to transfer some tokens to a time-lock contract. The time-lock contract can simply hold balances for specific addresses until a certain time or block number has passed, then permit the claimant addresses to withdraw.
This is a really quick and untested scribble to give you some ideas. Starting with the ERC20 interface by openzeppelin: https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/IERC20.sol
pragma solidity ^0.4.24;
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender)
external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value)
external returns (bool);
function transferFrom(address from, address to, uint256 value)
external returns (bool);
event Transfer(
address indexed from,
address indexed to,
uint256 value
);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
Then, carry on with the time-lock contract, using the ERC20 interface defined above.
We'll make numbered lockBoxes. Users are required to specify the number of the box containing their funds. This is easily discovered by looking at event logs. Numbered boxes mean the contract can easily address multiple tranches for a single address, with different amounts and deadlines.
In case you haven't seen it before, the deposit function uses transferFrom
. This presumes that the user first signs an approve
authorizing this contract to help itself to a certain amount of funds. That's usually coordinated from the client-side. It goes, client sends approve(addressTimeLock, uintAmount)
to the token contract, then deposit(addressBeneficiary, uintAmount, uintDeadline)
to the timeLock contract.
contract TimeLock {
IERC20 token;
struct LockBoxStruct {
address beneficiary;
uint balance;
uint releaseTime;
}
LockBoxStruct[] public lockBoxStructs; // This could be a mapping by address, but these numbered lockBoxes support possibility of multiple tranches per address
event LogLockBoxDeposit(address sender, uint amount, uint releaseTime);
event LogLockBoxWithdrawal(address receiver, uint amount);
constructor(address tokenContract) public {
token = IERC20(tokenContract);
}
function deposit(address beneficiary, uint amount, uint releaseTime) public returns(bool success) {
require(token.transferFrom(msg.sender, address(this), amount));
LockBoxStruct memory l;
l.beneficiary = beneficiary;
l.balance = amount;
l.releaseTime = releaseTime;
lockBoxStructs.push(l);
emit LogLockBoxDeposit(msg.sender, amount, releaseTime);
return true;
}
function withdraw(uint lockBoxNumber) public returns(bool success) {
LockBoxStruct storage l = lockBoxStructs[lockBoxNumber];
require(l.beneficiary == msg.sender);
require(l.releaseTime <= now);
uint amount = l.balance;
l.balance = 0;
emit LogLockBoxWithdrawal(msg.sender, amount);
require(token.transfer(msg.sender, amount));
return true;
}
}
There is no notion of an owner
and no way to recover tokens in the case that the beneficiary is unable to sign a transaction when it's time to claim tokens deposited in this contract. Input validations could be strenthened but I wanted to keep it as short as possible for clarity.
Hope it helps.