Positions and notes
are different things.
Vellum separates the balance from the claim on it. The balance sits in the vault; the claim is an ERC-721 you can carry. This page defines the vocabulary the rest of the documentation relies on.
Position vs. note
#A position is the record the vault keeps for each deposit. It is a struct in contract storage, keyed by the note's tokenId:
soliditystruct Position {
address token; // the ERC-20 that was deposited
uint256 amount; // exact balance the vault received
uint64 maturity; // block.timestamp at wrap + term (reference horizon)
bool claimed; // true once the balance has been released
}
mapping(uint256 => Position) public positions;A note is the ERC-721 token whose tokenId points at that position. The note is what moves. The position never moves; it is either live or claimed.
| Position | Note | |
|---|---|---|
| Lives in | Vault storage | ERC-721 ownership mapping |
| Identified by | tokenId | tokenId |
| Can change | Only claimed: false → true | Owner and approvals |
| Read with | positions(tokenId) | ownerOf, getApproved |
The note (ERC-721)
#Every note is a token of the VellumVault contract itself: name Vellum Note, symbol VNOTE. Token ids start at 1 and increase by one per wrap (nextTokenId). The contract implements the ERC-721 core interface (0x80ac58cd) and ERC-165, so wallets, marketplaces and explorers treat notes as ordinary NFTs.
What a note carries is exactly the four fields of its position. There is no separate metadata contract and no tokenURI; the human-readable card you see in the app is rendered by the front end from onchain data plus display-only values (see display-only fields).
A note cannot be split, merged or topped up. If you want two claims on the same token, you wrap twice and receive two notes.
The term
#When wrapping you pass termSeconds. The vault stores maturity = block.timestamp + termSeconds as a uint64. Valid values are:
0— an instant note. Maturity equals the mint timestamp.- Anything from
MIN_TERM(1 day) toMAX_TERM(3650 days) inclusive.
Values between 1 second and 1 day, or above 10 years, revert with Vellum: invalid term. The app exposes presets (30D, 90D, 180D, 1Y), a custom 1–3650 day field and Instant.
The term is immutable disclosure. It tells a counterparty, an access gate or a lending venue what horizon the depositor committed to on paper. It does not restrict claim. This is deliberate: the protocol never holds anyone's tokens hostage, and the test script npm run test:vault asserts that a 90-day note is claimable in the block it is minted.
Claimability
#A note is claimable while both conditions hold:
- It exists —
ownerOf(tokenId)does not revert; the note has not been burned. - Its position has not been claimed —
positions(tokenId).claimed == false.
This is exactly what isClaimable(tokenId) returns. Because claim burns the note and flips the flag in the same transaction, a live note is always claimable and a burned note never is. There is no intermediate state.
Bearer semantics
#"Bearer" means the claim follows the token, not the depositor. The address that wrapped a position has no special rights afterwards; the vault does not even store it. Consequences:
- Only
ownerOf(tokenId)can callclaim. An approved operator can transfer the note but cannot redeem it — redemption is holder-only. - Selling or gifting a note is a complete transfer of the position. The previous holder cannot claw it back.
- Sending a note to an address you do not control is equivalent to sending the tokens there.
- Approvals (
getApproved) are cleared on every transfer and on claim.
The guardian
#The vault has one privileged address, the guardian, set in the constructor and intended to be a multisig. Its authority is narrow:
setWrapsPaused(bool)— stop or resume new deposits. Existing notes remain transferable and claimable while wraps are paused.proposeGuardian(address)/acceptGuardian()— two-step handover to a new guardian.
The guardian cannot move tokens, edit positions, burn notes, change terms or upgrade the contract. There is no code path for any of those. See the security model for the full list of what is and is not possible.
Glossary
#| Term | Meaning |
|---|---|
| Wrap | Deposit an ERC-20 amount and mint a note. The word the contract and the app use for issuance. |
| Claim / unwrap | Burn a note and release its underlying balance to the caller (the current holder). Used interchangeably across the site. |
| Holder / bearer / claimant | The current ERC-721 owner of a note. |
| Term | Seconds chosen at wrap; stored as an absolute maturity timestamp. Metadata only. |
| Maturity | block.timestamp at wrap + term. The note's reference horizon. |
| Instant note | A note wrapped with termSeconds = 0. |
| Live note | A note that exists and has not been claimed. |
| Guardian | The only privileged role. Can pause new wraps and rotate itself; nothing else. |
| CA | Contract address of the deployed vault, as shown in the app header. |