VestingWallet
A vesting wallet is an ownable contract that can receive native currency and ERC-20 tokens, and release these assets to the wallet owner, also referred to as "beneficiary", according to a vesting schedule.
Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning. Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) be immediately releasable.
By setting the duration to 0, one can configure this contract to behave like an asset timelock that hold tokens for a beneficiary until a specified time.
Since the wallet is Ownable, and ownership can be transferred, it is possible to sell unvested tokens. Preventing this in a smart contract is difficult, considering that: 1) a beneficiary address could be a counterfactually deployed contract, 2) there is likely to be a migration path for EOAs to become contracts in the near future. |
When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make sure to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended. |
Chains with support for native ERC20s may allow the vesting wallet to withdraw the underlying asset as both an ERC20 and as native currency. For example, if chain C supports token A and the wallet gets deposited 100 A, then at 50% of the vesting period, the beneficiary can withdraw 50 A as ERC20 and 25 A as native currency (totaling 75 A). Consider disabling one of the withdrawal methods. |
Usage
In order to make VestingWallet
methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows:
use openzeppelin_stylus::finance::vesting_wallet::{
self, IVestingWallet, VestingWallet,
};
#[entrypoint]
#[storage]
struct VestingWalletExample {
vesting_wallet: VestingWallet,
}
#[public]
#[implements(IVestingWallet<Error = vesting_wallet::Error>)]
impl VestingWalletExample {
#[constructor]
fn constructor(
&mut self,
beneficiary: Address,
start_timestamp: U64,
duration_seconds: U64,
) -> Result<(), vesting_wallet::Error> {
self.vesting_wallet.constructor(
beneficiary,
start_timestamp,
duration_seconds,
)
}
#[receive]
fn receive(&mut self) -> Result<(), Vec<u8>> {
self.vesting_wallet.receive()
}
}
#[public]
impl IVestingWallet for VestingWalletExample {
type Error = vesting_wallet::Error;
fn owner(&self) -> Address {
self.vesting_wallet.owner()
}
fn transfer_ownership(
&mut self,
new_owner: Address,
) -> Result<(), Self::Error> {
self.vesting_wallet.transfer_ownership(new_owner)
}
fn renounce_ownership(
&mut self,
) -> Result<(), Self::Error> {
self.vesting_wallet.renounce_ownership()
}
fn start(&self) -> U256 {
self.vesting_wallet.start()
}
fn duration(&self) -> U256 {
self.vesting_wallet.duration()
}
fn end(&self) -> U256 {
self.vesting_wallet.end()
}
#[selector(name = "released")]
fn released_eth(&self) -> U256 {
self.vesting_wallet.released_eth()
}
#[selector(name = "released")]
fn released_erc20(&self, token: Address) -> U256 {
self.vesting_wallet.released_erc20(token)
}
#[selector(name = "releasable")]
fn releasable_eth(&self) -> U256 {
self.vesting_wallet.releasable_eth()
}
#[selector(name = "releasable")]
fn releasable_erc20(
&mut self,
token: Address,
) -> Result<U256, Self::Error> {
self.vesting_wallet.releasable_erc20(token)
}
#[selector(name = "release")]
fn release_eth(&mut self) -> Result<(), Self::Error> {
self.vesting_wallet.release_eth()
}
#[selector(name = "release")]
fn release_erc20(
&mut self,
token: Address,
) -> Result<(), Self::Error> {
self.vesting_wallet.release_erc20(token)
}
#[selector(name = "vestedAmount")]
fn vested_amount_eth(&self, timestamp: u64) -> U256 {
self.vesting_wallet.vested_amount_eth(timestamp)
}
#[selector(name = "vestedAmount")]
fn vested_amount_erc20(
&mut self,
token: Address,
timestamp: u64,
) -> Result<U256, Self::Error> {
self.vesting_wallet.vested_amount_erc20(token, timestamp)
}
}