Note: The front-end now relies on Multicall3. Set NEXT_PUBLIC_MULTICALL_ADDRESS
accordingly.
This is a smart contract and dApp that manages token grants with vesting schedules.
Each grant is represented as an NFT. It has the following properties:
tokenAddress
- The address of the tokens being provided by the grant.startTimestamp
- The timestamp (in seconds) when the grant begins to vest.cliffTimestamp
- The timestamp (in seconds) before which no tokens can be redeemed.vestInterval
- The duration (in seconds) for each additional amount to vest.vestAmount
- The amount of tokens that will vest each interval.totalAmount
- The total amount of tokens that will vest for this grant.amountRedeemed
- The amount of tokens already redeemed under this grant.cancelled
- Whether this grant was cancelled by the contract, this is the only mutable property.
The contract owner is able to manage grants using the mint()
, cancelGrant()
, and replaceGrant()
functions. The owner can also withdraw tokens using the withdraw()
function. The dApp assumes this will be a multisig wallet on Gnosis Safe. Ownership can be transferred using the nominateOwner()
and acceptOwnership()
functions.
Holders of the NFT are able to redeem available tokens using the redeem()
, redeemMultiple()
, redeemAll()
, or redeemWithTransfer()
methods.
- Run
npx hardhat node
- In a separate tab,
npx hardhat run --network localhost scripts/local-deploy.js
- Then start the front end,
cd frontend && npm run dev
(ensure tonpm i
here as well). Owner is HH wallet 0, grantee is HH wallet 1.
This an ERC-721 contract that implements the enumerable extension. Each NFT corresponds to a grant and allows the grantee to redeem ERC-20 tokens from the contract according to a specified vesting scheduling.
- The contract has a single owner, initialized in the constructor.
- Ownership can be transferred by nominating a new owner using the
nominateOwner()
function. The nominated owner can then claim ownership by callingacceptOwnership()
. Only the current owner should be able to nominate a new owner. Only the nominated owner (nominatedOwner
) should be able to accept ownership. - The owner is expected to supply tokens being granted with this contract (
tokenAddress
) to the contract usingsupply()
, though anyone could transfer any tokens to this contract. - The owner, and only the owner, should be able to withdraw all of any token from the contract using the
withdraw()
method. - The owner, and only the owner, can issue a new grant using the
mint()
function, specifying the grantee's address and all of the properties in theGrant
struct.tokenCounter
increments such that each grant always has a unique ID. - The owner, and only the owner, can replace a grant based on it's token ID. This should mint a replacement grant with new attributes to the previous grantee.
- The owner, and only the owner, should be able to cancel a grant with the
cancelGrant()
function. No one, including the grantee assigned to that grant, should be able to redeem tokens based on a grant that has been cancelled.
- The grantee (and only the grantee) should be able to redeem tokens from the contract according to the vesting schedule associated with the NFTs they hold.
- Starting at the
startTimestamp
, eachvestInterval
should vestvestAmount
of tokens. - No tokens should vest prior to the
cliffTimestamp
, but this should not be taken into account when calculating the amount vested after the cliff has passed. For example, given avestAmount
of 100, avestInterval
of 3 months, and acliffTimestamp
of +6 months, 0 tokens should be vested at +5 months, 200 tokens should be vested at +6 months, and 300 tokens should be vested at +9 months. - Under no circumstances should a grant provide access to more than the
totalAmount
of tokens. - Users are able to redeem the amount of vested tokens minus the amount of tokens they've already redeemed.
- Starting at the
- The grantee (and only the grantee) should be able to redeem tokens (as specified above) and also transfer any amount of any ERC-20 token to this contract in a single transaction using the
redeemWithTransfer()
function. - The grantee should be able to transfer that grant to another address using the standard functions in the ERC-721 specification.