Overview
An equity grant is a promise with a calendar attached. Until the calendar runs out the shares exist, have a price, and cannot be spent. The usual escape is a secondary desk that buys the promise at a thirty to fifty percent discount and keeps everything above it.
Vestora replaces the sale with a loan. A tokenised award is deposited into a vesting position; the protocol values the remaining schedule, applies a haircut, and lends USDG against it. As tranches unlock they either repay the loan or flow to the wallet. When the debt is clear the position burns and the shares leave untouched.
Three properties define the system: the schedule is immutable once written, the protocol has no owner or pause switch, and the only party that can move a position is its beneficiary — or a liquidator, once the position is demonstrably underwater.
Vision and mission
Employee equity is the largest illiquid asset most technical workers will ever hold, and the market that serves it is opaque, relationship-gated, and priced against the seller. A grant worth two hundred thousand dollars routinely trades for a hundred and twenty because the holder needed a deposit on an apartment in the wrong quarter.
The mission is narrow: make the schedule itself bankable. Not a new asset class, not a synthetic derivative — a credit line whose collateral is a published, verifiable unlock curve. The price of liquidity should be an interest rate, not the upside.
Architecture
Two contracts, no shared vault, no router between them, no proxy. Each one can be read and used on its own.
VestingCredit.sol positions ERC-721, one per grant lender reserve USDG supplied by funds debt index per-second accrual at RATE_PER_SEC pricing Uniswap v3 spot, USDG pair per asset UnlockMarket.sol tranche listings sale of future unlock slices settlement USDG in, claim rights out fee protocol fee, discounted for $VSTR holders
Value flows one way through a position: shares in, credit out, unlocks back against the debt. There is no path by which the protocol can move a beneficiary’s shares while the position is healthy.
Vesting positions
open(address asset, uint256 amount, uint64 cliff, uint64 duration, address beneficiary) returns (uint256 id) claim(uint256 id) close(uint256 id)
open pulls the tokenised award into the contract and mints an ERC-721 to the beneficiary. Cliff and duration are written at mint and are immutable; there is no function that edits them. Duration is capped at 730 days.
Vested fraction is linear after the cliff: vested = amount × min(now − start, duration) ÷ duration, and zero while now < start + cliff. claim transfers vested − claimed, minus any auto-repay share.
close reverts while debt is outstanding. With zero debt it sends the unclaimed remainder to the beneficiary and burns the token.
Borrowing
supply(uint256 usdg) // lender in redeem(uint256 shares) // lender out borrow(uint256 id, uint256 usdg) repay(uint256 id, uint256 usdg)
Collateral value is the present value of what has not yet been claimed, discounted by the asset haircut:
spot = unclaimedShares × price(asset) // Uniswap v3 sqrtPriceX96 pv = spot × (10000 − HAIRCUT_BPS) / 10000 maxDebt = pv × MAX_LTV_BPS / 10000
With a 25% haircut and a 35% cap, a grant worth $100,000 at spot supports at most $26250 of debt. The haircut is deliberately heavy: unlike a liquid stock position, the collateral here cannot be seized and sold in full on the day it goes bad.
Interest accrues per second on a global index at RATE_PER_SEC = 2535100000 (~8% APR). All of it goes to the lender reserve; there is no treasury cut and no spread.
Unlock stream
setAutoRepay(uint256 id, bool on) stream(uint256 id) // permissionless once a tranche has vested
With auto-repay on, stream sells nothing — it routes the newly vested tranche’s value against debt first and forwards any excess to the beneficiary. With it off, the whole tranche goes to the wallet and the debt keeps accruing. The call is permissionless so a keeper can advance it, but it can only ever move value in the beneficiary’s favour or against their own debt.
Missed periods queue. One call settles one tranche; nothing is skipped or forfeited.
Early unlock market
list(uint256 id, uint64 fromTs, uint64 toTs, uint256 askUsdg) fill(uint256 listingId) delist(uint256 listingId)
A beneficiary may sell a future slice of their own schedule outright rather than borrow against it. The listing names a time window; the buyer receives the claim rights to tranches vesting inside that window, and the seller receives USDG now, minus the protocol fee. Positions with outstanding debt must clear the debt attached to the listed window first.
This is a sale, not credit. It is the right tool when the holder wants certainty and is willing to pay a discount for it; the loan is the right tool when they want to keep the upside.
Liquidation
Three bands, all measured against the haircut present value:
| LTV | State | What you can do |
|---|---|---|
| 0 – 35% | Healthy | Borrow more, claim, close |
| 35 – 50% | Buffer | No new borrow; repay or wait for the next unlock |
| > 50% | Liquidatable | Anyone may repay up to half the debt |
seized = repayAmount × (1 + LIQ_BONUS_BPS/10000) / price(asset)
A liquidator repays at most 50% of the debt and seizes vested-but-unclaimed shares at a 8% discount. Shares still behind the cliff cannot be seized — which is exactly why the haircut is set where it is. A position opened at the 35% cap needs roughly a 30% drop in the underlying before it becomes liquidatable.
Custody model
| Your key | Anyone | Protocol |
|---|---|---|
| open, claim, close | stream a vested tranche | nothing |
| borrow, repay, set auto-repay | liquidate above 50% | no pause, no fee switch, no upgrade |
| list, delist on the market | read all state | no freeze, no seize |
There is no owner address, no guardian, no proxy admin, and no migration hatch. The deployed bytecode is the final version.
Cryptography and safety
Authorisation is plain ECDSA over EVM transactions; ownership of the position NFT is the only credential that matters. Every state-changing external is nonReentrant, and all token movement goes through SafeERC20.
Price comes from sqrtPriceX96, a Q64.96 value squared with a full-width 512-bit mulDiv and rescaled between USDG’s six decimals and the asset’s eighteen. The classic bug in this codebase shape is a 1e12 error in that rescale, so the test suite pins the conversion in both directions.
Risks and limits
- Legal restrictions on your grant. Most employer equity plans prohibit pledging, transferring, or creating a security interest in unvested awards, and doing so can forfeit the grant outright. Tax treatment of borrowing against an award differs by jurisdiction. Read your plan documents; the contract cannot and does not check them.
- Spot pricing. Collateral is valued at Uniswap v3 spot, not a TWAP. A thin pool can be pushed within a block. Caps limit the damage; manipulation remains possible.
- Illiquid collateral. Only vested-but-unclaimed shares can be seized. If the underlying gaps down hard before the next tranche vests, the reserve can end up short even after full liquidation. First-loss $VSTR staking absorbs that gap before lenders do — up to its size, and no further.
- Wrapper and custody risk. A tokenised equity is an issuer’s claim wrapper. If the wrapper depegs or the issuer fails, the collateral fails with it.
- Immutability. No pause, no upgrade. A bug discovered after deployment stays deployed. Nothing here is insured, regulated, or recoverable through a counterparty.
Vesting credit is the highest-risk module in this design. Size positions on the assumption that the loan will not be rescued by anyone.
Reference
| Name | Role | Address |
|---|---|---|
| VestingCredit | Positions, reserve, borrow | 0x…0x |
| UnlockMarket | Tranche sales | 0x…0x |
| USDG | Quote, 6 dp | 0x5fc5…d168 |
| Uniswap v3 factory | Pool lookup | 0x1f7d…2EfA |
| $VSTR | Product token — live | $VSTR |
| Constant | Value | Meaning |
|---|---|---|
| MAX_LTV_BPS | 3500 | Borrow cap against present value |
| HAIRCUT_BPS | 2500 | Discount applied to spot |
| LIQ_THRESHOLD_BPS | 5000 | Liquidatable from here |
| LIQ_BONUS_BPS | 800 | Liquidator discount |
| CLOSE_FACTOR_BPS | 5000 | Max debt repaid per call |
| RATE_PER_SEC | 2535100000 | ~8% APR, 1e18 index |
| MAX_DURATION | 730 days | Longest vesting schedule |
Explorer: robinhoodchain.blockscout.com. Ready to try it on testnet funds? Open the app.