Integration Guide
Create a Lock
struct Schedule {
uint64 tgeDate; // first release timestamp, must be in the future
uint16 tgeBps; // released at tgeDate; 10000 = full lock
uint64 cycle; // seconds between later releases
uint16 cycleBps; // released per cycle
}
struct LockParams {
address taxToken;
address beneficiary;
uint256 amount; // pulled from the caller; the lock records what actually arrives
Schedule schedule;
}
function lock(LockParams calldata params) external payable returns (uint256 lockId);Approve the locker for amount, then call lock with msg.value equal to the current defaultFee.creationFee (0 today). The lock records the amount that actually arrived, so a token that taxes the transfer in records less than amount. lockId is the index into the lock array and is emitted in FlapLockCreated.
The first lock of a token opens its pool: the registry is asked whether the token is a Flap token, which version it is and where its Dividend contract lives. A token the Portal does not know opens a pool with no dividend wiring.
Entry Points
| Function | Who may call | What it does |
|---|---|---|
claim(lockId) | beneficiary | Settles pending dividends, then pays all currently releasable principal. Reverts NothingToClaim when nothing is releasable |
collect(lockId) | anyone | Pays the lock's pending dividends to its beneficiary. Returns (0, 0) when there are none |
transferBeneficiary(lockId, to) | beneficiary | Hands the whole lock to to, including unclaimed released principal and unsettled dividends. Not available on custom-fee locks |
renounceBeneficiary(lockId) | beneficiary | Irreversible. Settles dividends, then burns all remaining principal, released or not, to 0xdead |
Order of operations: claim includes dividend settlement, so collect alone is only needed while nothing is releasable. Call claim (or collect) before transferBeneficiary to keep what has been earned, and claim before renounceBeneficiary so released principal is not burned.
Read a Lock
function getLock(uint256 lockId) external view returns (Lock memory);
function unlockedAmount(uint256 lockId) external view returns (uint256); // released so far, claimed or not
function claimable(uint256 lockId) external view returns (uint256); // released and not yet claimed
function pendingDividend(uint256 lockId) external view returns (uint256); // estimate for UIs; collect is exact
function locksLength() external view returns (uint256);struct Lock {
address taxToken;
address beneficiary; // address(0) once renounced
uint256 locked; // principal held for this lock, after the principal fee
uint256 claimed;
uint256 shares; // locked - claimed while live, 0 after renounce
uint256 rewardDebt;
uint16 dividendBps; // fee snapshotted at creation
bool fromCustomFee;
Schedule schedule;
}Whether a token's locks earn dividends: pools(token).dividendContract is non-zero when the pool is wired to a Dividend contract, zero when the token locks as principal only.
Check Admission
Admission lives on FlapTokenRegistry, not the locker. Before the first lock of a token, check it there:
function allowedTokenVersions(uint8 version) external view returns (bool); // Flap token versions that may open pools
function allowedDividendTokens(address token) external view returns (bool); // dividend tokens admitted by the registry owner
function query(address token) external view
returns (bool isAllow, address dividendContract, address dividendToken, address quoteToken);
function isDividendTokenAllowed(address token, address quoteToken, address dividendToken) external view returns (bool);query is what lock asks before opening a pool: isAllow is false for a Flap token whose version is closed (TokenNotAllowed), and dividendContract is zero when the token locks as principal only. isDividendTokenAllowed is the full dividend-token rule (quote token, the token itself, Flap-launched tokens and the owner allowlist); pass the quoteToken returned by query. allowedDividendTokens shows the owner allowlist alone.
Custom Fee
A negotiated fee is an EIP-712 message signed by feeSigner and submitted together with the lock:
function lockWithCustomFee(LockParams calldata params, CustomFee calldata customFee, bytes calldata signature)
external payable returns (uint256 lockId);Domain: name = "FlapTokenLocker", version = "1", chainId = 56 (mainnet) or 97 (testnet), verifyingContract = <locker address>.
CustomFee(address beneficiary,address taxToken,address lockBeneficiary,uint256 amount,
uint64 tgeDate,uint16 tgeBps,uint64 cycle,uint16 cycleBps,
uint96 creationFee,uint16 principalBps,uint16 dividendBps,uint256 nonce,uint256 deadline)
beneficiary is the only address allowed to submit the fee; every other field must match the submitted LockParams, so an offer signed for one deal cannot be spent on another. Each signature is consumed once, expires at deadline, and both bps fields stay under the 10% cap. Rotating feeSigner invalidates every unconsumed offer; re-request if it changed since issuance. Custom-fee locks cannot be transferred.
Events
| Event | Emitted on |
|---|---|
FlapLockCreated | lock / lockWithCustomFee; carries the full Schedule, so an indexer can compute vesting from this event alone |
FlapClaimed | claim |
FlapDividendsSettled | every dividend payout, whichever entry point triggered it |
FlapCollected | an explicit collect call |
FlapPoked | every time a pool pulls dividends |
FlapBeneficiaryTransferred | transferBeneficiary and renounceBeneficiary (new beneficiary address(0)) |
FlapRenounced | renounceBeneficiary |
Errors
| Error | Meaning |
|---|---|
TokenNotAllowed | Flap token whose version is not open in the registry |
DividendTokenNotSupported | The token's dividend token is not admitted (or has no code) |
PortalUnavailable | The registry could not reach the Portal; retry |
InvalidSchedule | TGE in the past, bps over 10000, or a partial release without a cycle |
ZeroAmount | Nothing arrived at the locker |
WrongCreationFee | msg.value differs from the creation fee |
NotBeneficiary, NothingToClaim | Caller is not the beneficiary; nothing is releasable |
CustomFeeLockNotTransferable | Custom-fee locks cannot change beneficiary |
InvalidSignature, SignatureExpired, SignatureUsed, NotFeeBeneficiary, Fee*Mismatch, FeeOverCap | Custom-fee validation |