Savings you can't touch until you reach your goal.
TimeVault locks your digital dollars (USDC or USDT) in a safe on the blockchain. Nobody can take them out early, not even you. When you reach your goal, the money goes back to your wallet.
- Connect your walletAny wallet app that supports WalletConnect.
- Set a goalFor example $1,000, plus a backup date in case you never get there.
- Add money every paydayThe coach tells you how much. It unlocks when you hit the goal.
What do I need?
- A crypto wallet app such as MetaMask, Trust Wallet, Tangem, Rainbow or Coinbase Wallet.
- USDC or USDT on the Arbitrum network. Most exchanges let you withdraw straight to Arbitrum.
- About $1 of ETH on Arbitrum to pay the small network fees (a few cents each time).
How do I get USDC on Arbitrum?
- Get a wallet app that supports WalletConnect, such as MetaMask, Trust Wallet or Tangem, and write down its recovery phrase on paper.
- Buy USDT or USDC on an exchange. Many people buy with local money through the exchange's P2P market (for example on Binance, Bybit or OKX).
- Withdraw to your wallet on the Arbitrum network. Paste your wallet address and choose the network Arbitrum One. Any other network and the coins won't show up here.
- Also withdraw a little ETH on Arbitrum One, about $1 to $2. It pays the network fees, which are a few cents each time.
- Send a small test first, like $5, and check it arrives before sending more.
Your wallet needs a little ETH on the Arbitrum network (about $1) to pay transaction fees.
New lock
- There is no early exit. Not for emergencies, not for anyone. Only lock money you know you won't need.
- Keep a buffer outside. Leave at least 1 to 2 months of spending in your wallet.
- Anyone can add to your goal. If someone sends coins to your lock and it reaches the goal, it opens early. The money still only goes to you.
- Only the lock's own coin, on Arbitrum One. You can add money from your wallet or straight from an exchange. Any other coin or network sent to a lock is lost forever.
Payday plan
Tell the coach when you get paid. It works out how much to lock each payday and when you'll reach your goal.
Your plan stays on this phone. It never moves money by itself.
Your streak
Money lessons
Achievements
Badges
Badges and levels are just for fun. They never touch your money.
Settings
Show in my currency
Type today's rate and TimeVault also shows your savings in your own currency. Nothing is fetched from the internet: you choose the rate, so it is only as up to date as you keep it.
Leave the rate empty and save to turn it off.
Wallet
TimeVault works with any wallet app that supports WalletConnect, such as MetaMask, Trust Wallet, Tangem, Rainbow or Coinbase Wallet. Your wallet must be set to the Arbitrum network.
How do I get USDC on Arbitrum?
- Get a wallet app that supports WalletConnect, such as MetaMask, Trust Wallet or Tangem, and write down its recovery phrase on paper.
- Buy USDT or USDC on an exchange. Many people buy with local money through the exchange's P2P market (for example on Binance, Bybit or OKX).
- Withdraw to your wallet on the Arbitrum network. Paste your wallet address and choose the network Arbitrum One. Any other network and the coins won't show up here.
- Also withdraw a little ETH on Arbitrum One, about $1 to $2. It pays the network fees, which are a few cents each time.
- Send a small test first, like $5, and check it arrives before sending more.
Your locks
Your money is kept on the blockchain, not in this app. If you lose your phone, install TimeVault again, connect the same wallet and tap Find my locks on the Savings screen.
Advanced
Only needed if you run your own copy of TimeVault. Get one free at cloud.reown.com.
Safety
How your money is protected
- Nobody can take it out earlyNot you, not the people who made this app, not a hacker. The lock is a contract on the blockchain with no owner, no admin and no back door. It can only pay out to your wallet.
- Proven, tested codeBuilt on OpenZeppelin's widely used, audited timelock contract and tested 53 ways, including 560 random actions and attacks on the app.
- Fake locks are refusedBefore every deposit, the app checks that the lock's code matches the tested code exactly, using 4 separate servers.
- You approve everythingThe app can't move money by itself. Every action needs your approval in your wallet, and the app shows you the address first.
Check my locks
Re-checks every lock you have against 4 independent servers.
Technical details
These should match FINGERPRINTS.txt on GitHub. Run the 53 safety tests yourself.
Publish a lock's code on Arbiscan- Get the file for your lock type below.
- On Arbiscan, open your lock address → Contract → Verify and Publish.
- Choose Solidity (Standard-Json-Input), compiler v0.8.24+commit.e11b9ed9, license MIT, and upload the file.
Read the goal lock contract
// SPDX-License-Identifier: MIT
// Based on OpenZeppelin Contracts v4.9.6 (token/ERC20/utils/TokenTimelock.sol).
// The ONLY changes from OpenZeppelin's audited TokenTimelock are marked "CHANGE".
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @dev A token holder contract that will allow a beneficiary to extract the
* tokens once a savings goal is reached, or after a backup release time,
* whichever comes first.
*/
contract GoalTimelock {
using SafeERC20 for IERC20;
// ERC20 basic token contract being held
IERC20 private immutable _token;
// beneficiary of tokens after they are released
address private immutable _beneficiary;
// timestamp when token release is enabled (backup date)
uint256 private immutable _releaseTime;
// CHANGE: savings goal, in the token's smallest units. Release is enabled once the balance reaches it.
uint256 private immutable _goal;
/**
* @dev Deploys a lock that holds `token_` and only releases it to `beneficiary_` when {release} is
* invoked after `releaseTime_` or once the balance is at least `goal_`.
*/
constructor(IERC20 token_, address beneficiary_, uint256 releaseTime_, uint256 goal_) {
require(releaseTime_ > block.timestamp, "TokenTimelock: release time is before current time");
require(goal_ > 0, "GoalTimelock: goal is zero"); // CHANGE
_token = token_;
_beneficiary = beneficiary_;
_releaseTime = releaseTime_;
_goal = goal_; // CHANGE
}
/**
* @dev Returns the token being held.
*/
function token() public view virtual returns (IERC20) {
return _token;
}
/**
* @dev Returns the beneficiary that will receive the tokens.
*/
function beneficiary() public view virtual returns (address) {
return _beneficiary;
}
/**
* @dev Returns the backup release time in seconds since Unix epoch (i.e. Unix timestamp).
*/
function releaseTime() public view virtual returns (uint256) {
return _releaseTime;
}
/**
* @dev CHANGE: Returns the savings goal.
*/
function goal() public view virtual returns (uint256) {
return _goal;
}
/**
* @dev Transfers tokens held by the lock to the beneficiary. Will only succeed if the goal is reached
* or the backup release time has passed.
*/
function release() public virtual {
uint256 amount = token().balanceOf(address(this));
require(amount > 0, "TokenTimelock: no tokens to release");
// CHANGE: was `require(block.timestamp >= releaseTime(), ...)`
require(
amount >= goal() || block.timestamp >= releaseTime(),
"GoalTimelock: goal not reached and before release time"
);
token().safeTransfer(beneficiary(), amount);
}
}
Read the date lock contract (OpenZeppelin)
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/TokenTimelock.sol)
pragma solidity ^0.8.0;
import "./SafeERC20.sol";
/**
* @dev A token holder contract that will allow a beneficiary to extract the
* tokens after a given release time.
*
* Useful for simple vesting schedules like "advisors get all of their tokens
* after 1 year".
*/
contract TokenTimelock {
using SafeERC20 for IERC20;
// ERC20 basic token contract being held
IERC20 private immutable _token;
// beneficiary of tokens after they are released
address private immutable _beneficiary;
// timestamp when token release is enabled
uint256 private immutable _releaseTime;
/**
* @dev Deploys a timelock instance that is able to hold the token specified, and will only release it to
* `beneficiary_` when {release} is invoked after `releaseTime_`. The release time is specified as a Unix timestamp
* (in seconds).
*/
constructor(IERC20 token_, address beneficiary_, uint256 releaseTime_) {
require(releaseTime_ > block.timestamp, "TokenTimelock: release time is before current time");
_token = token_;
_beneficiary = beneficiary_;
_releaseTime = releaseTime_;
}
/**
* @dev Returns the token being held.
*/
function token() public view virtual returns (IERC20) {
return _token;
}
/**
* @dev Returns the beneficiary that will receive the tokens.
*/
function beneficiary() public view virtual returns (address) {
return _beneficiary;
}
/**
* @dev Returns the time when the tokens are released in seconds since Unix epoch (i.e. Unix timestamp).
*/
function releaseTime() public view virtual returns (uint256) {
return _releaseTime;
}
/**
* @dev Transfers tokens held by the timelock to the beneficiary. Will only succeed if invoked after the release
* time.
*/
function release() public virtual {
require(block.timestamp >= releaseTime(), "TokenTimelock: current time is before release time");
uint256 amount = token().balanceOf(address(this));
require(amount > 0, "TokenTimelock: no tokens to release");
token().safeTransfer(beneficiary(), amount);
}
}