Module 09: Vesting
Client: client.vesting
Weight: Light
Prerequisites
- Wallet funded with USDB (→02) and a token to vest
- Understand loan mechanics if you plan to take a vesting loan
Next steps after vesting
- Need liquidity before unlock? Take a vesting loan (§d below)
- Launching with a distribution plan? Combine with token creation
- Distributing to many recipients? Use batch creation (§a)
Why Vesting Matters
Vesting locks tokens and releases them on a schedule you define. Use it to:
- Lock team/advisor/investor tokens to signal long-term commitment
- Distribute allocations that release over months or years
- Build trust with your community — locked tokens can't be dumped immediately
- Access liquidity before unlock by taking a loan against a vesting position
How It Works
Two vesting types:
| Type | Behavior |
|---|---|
| Cliff | All tokens unlock at a single point in time (minimum 1 hour from now) |
| Gradual | Tokens stream linearly over a duration, unlocking at each time unit interval |
Roles:
- Creator — creates the schedule, adds tokens, changes beneficiary, extends duration, transfers creator role. Must call from creator wallet.
- Beneficiary — claims unlocked tokens, takes/repays vesting loans. Must call from beneficiary wallet.
These roles are strictly separate. Wrong caller reverts.
Active loans block modifications. If a vesting position has an active loan, you cannot modify it until the loan is repaid.
No price liquidation on vesting loans — expiry is time-based only, same as regular loans.
Actions
a. Create Vesting
createGradualVesting(beneficiary, token, totalAmount, startTime, durationInDays, timeUnit, memo, ecosystem)
Creates a linear vesting schedule. Tokens unlock progressively at each timeUnit interval throughout the duration.
Critical: Use now() + 60 for startTime. Using now() will be in the past by the time the transaction confirms.
// JS
const result = await client.vesting.createGradualVesting(
"0xBeneficiary",
"0xToken",
parseUnits("10000", 18),
Math.floor(Date.now() / 1000) + 60, // now + 60 seconds
365, // duration in days
3, // timeUnit: 3 = daily unlocks
"Team allocation",
MAINTOKEN
);
# Python
import time
result = client.vesting.create_gradual_vesting(
"0xBeneficiary", "0xToken", 10000 * 10**18,
int(time.time()) + 60, 365, 3, "Team allocation", MAINTOKEN
)
| Param | Type | Notes |
|---|---|---|
beneficiary | string | Recipient address |
token | string | Token contract to vest |
totalAmount | bigint/int | Total tokens (18 decimals) |
startTime | bigint/int | Unix timestamp — use now() + 60, never now() |
durationInDays | bigint/int | Total vesting duration, always expressed in days |
timeUnit | number | Unlock frequency: 0=seconds, 1=minutes, 2=hours, 3=days |
memo | string | Optional label |
ecosystem | string | MAINTOKEN address |
timeUnit examples: durationInDays=30, timeUnit=2 = hourly unlocks over 30 days (720 events). durationInDays=30, timeUnit=3 = daily unlocks over 30 days (30 events).
createCliffVesting(beneficiary, token, totalAmount, unlockTime, memo, ecosystem)
All tokens unlock at unlockTime in a single event. Minimum unlockTime is 1 hour from now — anything less reverts.
| Param | Type | Notes |
|---|---|---|
beneficiary | string | Recipient address |
token | string | Token contract to vest |
totalAmount | bigint/int | Total tokens (18 decimals) |
unlockTime | bigint/int | Unix timestamp, minimum now + 3600 |
memo | string | Optional label |
ecosystem | string | MAINTOKEN address |
Batch Creation
| Method | Description |
|---|---|
batchCreateGradualVesting(...) | Multiple gradual schedules in one transaction. Same params as singular, passed as arrays. |
batchCreateCliffVesting(...) | Multiple cliff schedules in one transaction. |
Batch creation is the efficient path when distributing to many recipients at once (e.g., investor round, team allocation).
b. Claim Tokens
claimTokens(vestingId)
Claims all currently unlocked tokens. Only the beneficiary can call this.
- For cliff vesting: only callable after
unlockTime - For gradual vesting: callable anytime after vesting starts, claims whatever has vested since last claim
- Check
getClaimableAmount(vestingId)before calling to confirm non-zero amount
const claimable = await client.vesting.getClaimableAmount(vestingId);
if (claimable > 0n) {
await client.vesting.claimTokens(vestingId);
}
c. Manage Vesting (Creator Only)
All methods below require the caller to be the creator of the vesting schedule.
| Method | Params | Notes |
|---|---|---|
changeBeneficiary(vestingId, newBeneficiary) | vestingId, address | Transfers recipient role to new address |
extendVestingPeriod(vestingId, additionalDays) | vestingId, number | Adds days to the vesting duration; fails if vesting already ended |
addTokensToVesting(vestingId, additionalAmount) | vestingId, bigint | Adds tokens to the schedule; auto-approves |
transferCreatorRole(vestingId, newCreator) | vestingId, address | Hands off creator control |
Blocked by active loans. Repay the vesting loan first before modifying the schedule.
d. Vesting Loans (Beneficiary Only)
Borrow against a vesting position to access liquidity before tokens unlock.
takeLoanOnVesting(vestingId, amount, days)
Takes a USDB loan against the vesting position. Same mechanics as regular BASIS loans:
| Param | Type | Description |
|---|---|---|
vestingId | bigint/int | Vesting schedule ID |
amount | bigint/int | Collateral amount from the vesting (18 decimals) |
days | bigint/int | Loan duration in days (minimum: 10, maximum: 1000) |
- Origination fee: 2% flat
- Interest: 0.005%/day
- Duration: 10–1000 days
- Expiry: time-based only — no price liquidation
repayLoanOnVesting(vestingId)
Repays the active loan on the vesting position. Auto-approves USDB.
Only one active loan per vesting position at a time. Check getActiveLoan(vestingId) to see current loan ID.
Read Methods
| Method | Returns | Description |
|---|---|---|
getVestingDetails(vestingId) | Vesting struct | Full schedule details: parties, token, amounts, timing, loan status |
getClaimableAmount(vestingId) | bigint | Tokens available to claim right now |
getVestedAmount(vestingId) | bigint | Total tokens vested so far (including already claimed) |
getVestingsByBeneficiary(address) | bigint[] | All vesting IDs where address is beneficiary |
getVestingsByCreator(address) | bigint[] | All vesting IDs created by address |
getActiveLoan(vestingId) | bigint | Active loan ID, or 0 if none |
getTokenVestingIds(token, startIndex, endIndex) | bigint[] | Vesting IDs for a token within index range |
getVestingDetailsBatch(vestingIds[]) | Vesting[] | Details for multiple schedules in one call |
getVestingCount() | bigint | Total vesting schedules ever created |
getVestingDetails struct fields:
| Field | Type | Description |
|---|---|---|
creator | address | Who created the schedule |
beneficiary | address | Who receives tokens |
token | address | Vested token contract |
totalAmount | uint256 | Total tokens in schedule |
claimedAmount | uint256 | Tokens already claimed |
startTime | uint256 | Unix timestamp when vesting begins |
durationInDays | uint256 | Gradual duration (0 for cliff) |
unlockTime | uint256 | Cliff unlock timestamp (0 for gradual) |
isGradual | bool | true = gradual, false = cliff |
activeLoanId | uint256 | Active loan ID, 0 if none |
memo | string | Label set at creation |
timeUnit | uint8 | 0=seconds, 1=minutes, 2=hours, 3=days |
Fees
| Action | Fee |
|---|---|
| Create vesting (any type) | None |
| Vesting loan origination | 2% of loan amount |
| Vesting loan interest | 0.005% per day |
Errors
| Error | When | Fix |
|---|---|---|
| "Vesting does not exist" | Any operation on invalid ID | Verify via getVestingsByBeneficiary() or getVestingsByCreator() |
| "Only beneficiary can claim/repay/take loan" | Wrong caller | Call from the beneficiary wallet |
| "Only creator can add tokens/change beneficiary" | Wrong caller | Call from the creator wallet |
| "No unclaimed tokens" | claimTokens() | Still in cliff period or all tokens claimed; check getClaimableAmount() |
| "Vesting already ended" | extendVestingPeriod() | Can't extend a completed schedule; create a new one |
| "Vesting duration must be at least 1 hour" | createVesting() | Duration or unlockTime must be at least 3600 seconds from now |
| "Start time must not be in the past" | createGradualVesting() | Use a future timestamp; now() + 60 is the safe minimum |
| "Active loan exists" | Modifying vesting | Repay the vesting loan first, then modify |
What Next
- Monitor claims: Poll
getClaimableAmount()periodically for beneficiaries to know when to claim - Loan against positions: Use
takeLoanOnVesting()for liquidity before unlock - Trust signal: Publicly visible locked tokens signal commitment; use alongside token launches
- Batch distribution: Use
batchCreateGradualVesting()orbatchCreateCliffVesting()for investor rounds to save gas
See Also
- Module 05 — Lending: Loan fees, extensions, repay flow — same mechanics apply to vesting loans
- Module 07 — Token Creation: Launch a token, then vest allocations from it
- Module 10 — Portfolio & Info: Query all your vesting schedules and claimable amounts
- Module 16 — Trust & Security: Why locked tokens build platform trust
- Module 18 — SDK Reference: Full vesting method signatures and errors