Three different implementations of a token vesting (airdrop-style claim) contract, each demonstrating a different pattern for authorizing claims. This repository accompanies the article Vesting contract implemented in 3 different ways.
| Contract | Allocation stored | Vesting gas cost | Claim verification |
|---|---|---|---|
DefaultVesting |
On-chain, per address | Grows with the number of recipients | Simple mapping lookup |
MerkleVesting |
Off-chain, only the Merkle root on-chain | One storage write regardless of recipients | Merkle proof supplied by the claimer |
SignatureVesting |
Off-chain, nothing written on vest | Zero — no vesting transaction at all | ECDSA signature issued by the owner |
DefaultVesting is the straightforward approach: the owner writes each recipient's claimable amount into contract storage, and recipients withdraw against their balance. Simple and fully on-chain, but the owner pays gas for every recipient.
MerkleVesting moves the allocation table off-chain. The owner builds a Merkle tree of (address, amount) leaves and publishes only the root. A recipient claims by presenting their amount plus a Merkle proof. Storage cost is constant no matter how many recipients there are.
SignatureVesting removes the vesting transaction entirely. The owner signs a message binding (recipient, amount, nonce, contract address) off-chain and hands the signature to the recipient, who submits it with their claim. The contract recovers the signer and checks it is the owner.
Each pattern trades gas costs against operational complexity — the article walks through the pros and cons in detail.
contracts/
DefaultVesting.sol # storage-based vesting
MerkleVesting.sol # Merkle-proof vesting
SignatureVesting.sol # ECDSA-signature vesting
DefaultERC20.sol # minimal ERC-20 used in tests
test/
DefaultVesting.ts
MerkleVesting.ts # includes Merkle tree construction with merkletreejs
SignatureVesting.ts # includes off-chain message signing
Requires Node.js 18 or newer.
npm install
npm run compile
npm testOther useful commands:
npm run coverage # solidity-coverage report
npm run lint # solhint + eslint
REPORT_GAS=1 npm test # gas usage per methodCopy .env.example to .env and fill in your RPC URL, private key, and (optionally) an Etherscan API key:
cp .env.example .envThe Hardhat config includes a sepolia network entry; deploy scripts are left as an exercise — the contracts take a single constructor argument, the address of the ERC-20 token being vested.
These contracts are educational examples written to illustrate the trade-offs between the three patterns. They are intentionally minimal, have not been audited, and are not suitable for production use as-is.