Contract reference.

Three contracts, all deployed on Robinhood Chain and all verified on Blockscout. Read them yourself before you send anyone to them — that is the point of publishing the addresses.

Updated 2026-07-29 · HoodLock Team

Addresses#

ContractAddressPurpose
Locker0xd0f7d8c6e9f6d80c297bebe4f7fd1b9c8125c32fHolds ERC-20 tokens until a date
Burner0x6bf43ca706faa8ea46803299c191484e82280652Sends tokens to the dead address, on the record
Vesting0x910e19bcC4bce46999994Ed7297E0Fc4431ec72EReleases tokens gradually to a beneficiary

All three are verified, so the source you read on Blockscout is the bytecode that runs. The network parameters are on the network page.

Note

None of these contracts has a function that lets us move your tokens. That is a property of the code, not a promise — the section on each contract below points at the specific functions to check.

Locker#

One vault, many locks. Each lock records an owner, a token, an amount and an unlock time.

Functions#

FunctionNotes
lock(address token, uint256 amount, uint256 unlockTime) payableReturns the new lock id. Requires msg.value >= fee, a non-zero token, a positive amount and a future unlock time. Excess ETH is refunded.
withdraw(uint256 id)Only the lock's owner, only once, and only at or after unlockTime.
extend(uint256 id, uint256 newUnlockTime)Requires newUnlockTime > unlockTime. A lock can be pushed further out but never pulled in.
transferLockOwnership(uint256 id, address newOwner)Moves the right to withdraw. The tokens do not move.
locks(id), lockedAmount(id), isUnlocked(id), timeRemaining(id)Views. lockedAmount returns 0 once withdrawn.
locksByOwner(address), locksByToken(address), totalLocks()Enumeration, used by the explorer and by proof pages.

Events#

solidity
event Locked(uint256 indexed id, address indexed owner, address indexed token, uint256 amount, uint256 unlockTime);
event Withdrawn(uint256 indexed id, address indexed owner, uint256 amount);
event Extended(uint256 indexed id, uint256 newUnlockTime);
event LockOwnershipTransferred(uint256 indexed id, address indexed from, address indexed to);

What the code guarantees#

Careful

The locker has no hard cap on the fee. The admin can change it, and a change applies to new locks only — an existing lock is never re-priced. The vesting contract does have a cap; see below.

Careful

Balance-delta accounting has a blind spot. It measures what the contract gained, which is not the same as what you sent. On a reflection token — where holders are credited during other people's transfers — a deposit can be recorded above or below what actually left your wallet, because rewards earned by the existing pool land inside the same measurement window.

There is also one balance pool per token, not one per lock. If a token's balances can move without a transfer — a negative rebase, an owner burn, a blacklist zeroing — the pool can end up smaller than the sum of what it owes. Withdrawal is all-or-nothing and first-come-first-served, so the shortfall falls entirely on whoever withdraws last. Rebasing and reflection tokens are not supported, and nothing in the contract enforces that.

Burner#

A burn registry rather than a vault. Tokens are moved from your wallet to the dead address in a single transfer and never rest in the contract.

FunctionNotes
burn(address token, uint256 amount) payabletransferFrom(msg.sender, DEAD, amount) in one hop. Returns the burn id. Excess ETH is refunded.
DEAD()0x000000000000000000000000000000000000dEaD
burns(id), burnsByBurner, burnsByTokenViews.
totalBurnedOf(address token)Cumulative amount burned through this contract for one token.
solidity
event Burned(uint256 indexed id, address indexed burner, address indexed token, uint256 amount);
Irreversible

Nothing in this contract can move tokens back out of the dead address, and no key controls that address. From HoodLock's side a burn is final. Read burning versus locking before choosing it.

Careful

The token can still undo it. Sending to the dead address parks tokens there; it does not reduce totalSupply. A token that is mintable can re-issue the same amount, and an upgradeable token can add a function that moves the dead address's balance. Neither is detectable from here.

The same limitation applies to totalBurnedOf: it is derived from the token's own balanceOf readings, so a token that reports whatever it likes produces a burn record that looks legitimate. Check whether the token is mintable or upgradeable before treating a burn total as supply reduction.

Vesting#

Linear release with an optional cliff. There is no revoke, no sweep and no rescue function in this contract, so no HoodLock function can cancel or alter a schedule once it exists.

Careful

“Irrevocable” describes this contract, not the token. A token that is upgradeable, pausable, mintable, or that can blacklist an address, can still make a schedule worthless or unclaimable after the fact, and the schedule's creator is very often the token's deployer, which means they may retain exactly that power.

The reverse also holds: because there is no rescue path, a token that is paused or that freezes the beneficiary locks the tokens permanently, with no recourse from anyone. Irrevocable and unrecoverable are the same property.

FunctionNotes
create(address token, address beneficiary, uint256 amount, uint64 start, uint64 cliff, uint64 end) payableRequires start <= cliff <= end, end > start, and end > block.timestamp + MIN_DURATION.
createMany(...)Batch, up to MAX_BATCH beneficiaries. Requires msg.value == fee * n.
claim(uint256 id)Beneficiary only. Claiming is free — the fee is charged once, at creation.
claimable(uint256 id)0 before the cliff; linear between cliff and end; the exact remainder at end, so nothing is left as dust.
transferBeneficiary(uint256 id, address newBeneficiary)Current beneficiary only.

Constants#

ConstantValueWhat it protects
MAX_FEE0.05 ETHA hard ceiling the admin can never exceed.
MIN_DURATION24 hoursStops a schedule that would be fully vested on arrival.
MAX_BATCH200Bounds gas on createMany.
Careful

The exact-fee rule differs from the other two contracts. create requires msg.value == fee exactly and has no refund path, while the locker and burner accept an overpayment and refund the difference. Read the fee immediately before building the transaction rather than caching it.

Note

A validation gap worth knowing about. The REST API checks that end - start >= 86400, but the contract checks end > block.timestamp + MIN_DURATION. A schedule with a back-dated start can satisfy the API and still revert on-chain. If you build your own UI, check against block.timestamp, not against start.

solidity
event VestingCreated(uint256 indexed id, address indexed token, address indexed beneficiary,
                     address creator, uint256 total, uint64 start, uint64 cliff, uint64 end);
event Claimed(uint256 indexed id, address indexed beneficiary, uint256 amount);
event BeneficiaryTransferred(uint256 indexed id, address indexed from, address indexed to);

Administration#

Across all three contracts the admin can change only the fee, the fee collector and the admin key. No admin function reads or moves user tokens.

How to confirm any of this yourself is covered in the security model, and reading a contract on the explorer is covered in this walkthrough.