GangVesting is a simpler version with a single merkle root for all vesting schedules.
Deploy the GangVesting contract with the following constructor parameters:
constructor(bytes32 _merkleRoot, address _vestingToken)Required setup:
_merkleRoot: The merkle root for all vesting schedules_vestingToken: Address of the deployed Gang token
- Transfer required Gang tokens to the GangVesting contract
- Set ecosystem address using
setEcosystemAddress() - Update the merkle root if needed using
updateMerkleRoot() - Once configuration is final, call
lockRoot()to permanently lock the setup
Users can claim their vested tokens using:
function claim(
bytes32[] calldata proof,
Collection collection,
address recipient,
uint256 totalClaim,
uint32 start,
uint32 end
) externalKey considerations:
- Generate valid merkle proofs for each claim
- Claims must be made within the vesting period plus 69-day expiry window
- Tokens vest linearly between start and end times
- Minimum 1 day between claims
After the 69-day expiry window, unclaimed tokens can be recovered:
function claimEcosystemFunds(bytes32 leaf) externalCheck vesting details and claimable amounts:
function getVesting(
Collection collection,
uint256 tokenId,
address recipient,
uint256 totalClaim,
uint32 start,
uint32 end
) external view returns (Vesting memory, uint256 amount)Use the provided JavaScript example to generate merkle trees for GangVesting:
// Define vesting data
const vestingData = [
{
collection: 0, // Collection.Cat = 0
recipient: '0x77f2Dc5d302e71Ab6645622FAB27123E52e3e035',
totalClaim: parseEther('100'),
start: timestamp,
end: timestamp + (365 * 24 * 60 * 60) // timestamp + 365 days
}
];
// Create leaves for the Merkle tree
const leaves = vestingData.map(vesting => {
return keccak256(encodePacked(
['uint8', 'address', 'uint256', 'uint32', 'uint32'],
[
vesting.collection,
vesting.recipient,
vesting.totalClaim,
BigInt(vesting.start),
BigInt(vesting.end)
]
));
});
// Create the Merkle tree and get the root
const merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true });
const merkleRoot = merkleTree.getHexRoot();Run tests using Foundry:
forge testTo run a specific test:
forge test --match-test testClaimVesting -vv- Generate a merkle tree and root:
node MerkleTreeGenerationExample.jsSample output:
ApeChain Testnet Current Block: {
number: 16699306n,
timestamp: 1742817422n,
date: '2025-03-24T11:57:02.000Z'
}
Timestamp from 2 days ago: 1742644628
Date from 2 days ago: 2025-03-22T11:57:08.000Z
Merkle Root: 0x4fda1c3907cb51b1ef808011619b06a909ad419484fb68b66c53315e3fd4bbd9
Merkle Proof for first vesting: [
'0x86d90af917e1de28b530e664df266fc7081c9b791bc19acdef13d791950df786',
'0x442e396fb9684817865b881f0a7bded188ddf389f6359235c4441b3eeddb4b2f'
]
- Send a claim transaction using Cast:
cast send CONTRACT_ADDRESS \
"claim(bytes32[],uint8,address,uint256,uint32,uint32)" \
"[PROOF_LEAVES]" \
0 \
CLAIM_ADDRESS \
TOTAL_AMOUNT \
START_TS \
END_TS \
--rpc-url https://rpc.curtis.apechain.com \
--private-key $PRIVATE_KEY- Each claim requires a valid merkle proof matching the collection's root
- NFT-based vestings automatically redirect to current NFT owners (in MultiRootVesting)
- Claims must be made at least 1 day apart
- Vesting expires 69 days after end date
- Once roots are locked, they cannot be modified
- Collection enum must be within valid range (0-11 for MultiRootVesting, 0-17 for GangVesting)
The merkle tree leaves should be constructed as:
For MultiRootVesting:
bytes32 leaf = keccak256(abi.encodePacked(
collection,
tokenId,
recipient,
totalClaim,
start,
end
));For GangVesting:
bytes32 leaf = keccak256(abi.encodePacked(
uint8(collection),
recipient,
totalClaim,
start,
end
));Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.
Foundry consists of:
- Forge: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- Cast: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- Anvil: Local Ethereum node, akin to Ganache, Hardhat Network.
- Chisel: Fast, utilitarian, and verbose solidity REPL.
$ forge build$ forge test$ forge fmt$ forge snapshot$ anvil$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>$ cast <subcommand>$ forge --help
$ anvil --help
$ cast --help