Small surface.
Fewer promises.
Vellum's security argument is that there is very little to argue about. One contract, two state-changing user functions, one narrowly scoped role, no upgrade path. This page lists what is guaranteed, what is assumed and what is not covered.
Guarantees
#- 1:1 custody. A note's
amountis exactly what the vault received, verified by a balance delta at wrap time. - Holder-only redemption. Only
ownerOf(tokenId)can claim. Operators and approved addresses cannot. - No lock-up.
claimnever readsmaturity. Any live note can be redeemed in any block. - Single redemption. The claimed flag is set and the note burned before tokens leave; a reentrancy mutex covers both
wrapandclaim. - Immutable positions. No function writes to
positions[id]after wrap except settingclaimed. - No hidden owner. The contract has no
selfdestruct, nodelegatecall, no proxy admin and nowithdraw.npm run verify:vaultgreps for these and fails the build if any appears.
What nobody can do
#| Action | Possible? | Why |
|---|---|---|
| Withdraw vault tokens without a note | No | Only claim() sends tokens, and only to the note's owner |
| Change a note's amount, token or term | No | No setter exists |
| Burn or freeze someone's note | No | Only claim() burns, and only the owner can call it |
| Block claims | No | setWrapsPaused affects wrap() only |
| Upgrade the contract logic | No | No proxy; bytecode is final at deployment |
| Mint a note without depositing | No | wrap() requires the balance delta to equal amount |
| Claim as an approved operator | No | claim() checks msg.sender == owner, not approval |
Guardian scope
#The guardian is the single privileged address. Its complete authority:
- Pause and unpause new wraps.
- Propose a successor; the successor must accept (two-step, so a typo cannot brick the role).
The intended holder is a multisig; the deploy script refuses to run without VELLUM_GUARDIAN_ADDRESS and reminds you not to use a personal wallet. A compromised guardian can at worst stop new deposits — it cannot touch existing positions. A lost guardian means wraps can never be paused, which is the failure mode the two-step rotation is designed to avoid.
Token assumptions
#The vault trusts the ERC-20 it is given only as far as the balance delta check. Behaviours and their outcomes:
| Token behaviour | Outcome |
|---|---|
| Standard ERC-20 (returns bool) | Supported |
| Non-returning ERC-20 (USDT-style) | Supported — low-level call accepts empty return data |
| Fee-on-transfer | wrap reverts: Vellum: unsupported token transfer |
| Rebasing (balance changes over time) | wrap may succeed; later claims may pay more or fail. Do not wrap rebasing tokens. |
| Pausable / blocklisting token | claim can revert with Vellum: release failed while the token blocks the vault or the holder. Tokens stay in the vault until the token allows the transfer. |
| Reverts on zero-value approve/transfer | Irrelevant — the vault never sends zero |
| Malicious token (reenters) | Reentrancy guard blocks wrap/claim reentry; the vault holds no other state to corrupt |
Vellum guarantees the vault will release exactly amount of token to the holder. It does not guarantee token is worth anything, is not honeypotted, or will still allow transfers tomorrow. Check the token address on the note, not just the symbol on the card.
Display-only fields
#Several values on the site and in the preview card are cosmetic and have no onchain counterpart. Treat them as illustration:
- Entry mark — the current market price from DexScreener (best-liquidity pair on the target chain) at the moment you look at the preview. It is informational: the contract stores no price, and the value shown is not written anywhere at wrap time.
- PnL, mark, explorer link on the sample note page (
/app/note) — static sample content. - Note number 000421, TRANSFERABLE footer, token artwork — card design.
- Token symbol and name — read from the token contract, which any deployer can set to anything.
Known limits
#- No audit yet. The contract is small and covered by an end-to-end script, but has not been formally audited. Treat deployments accordingly.
- No ERC-721 Metadata. Wallets that require
tokenURIwill show a blank NFT. The economic data is inpositions(), not in metadata. - No enumeration. Finding the notes an address holds requires an indexer or event scan.
- Maturity is advisory. Anything built on top that expects a real lock-up (vesting enforcement, time-locked collateral) must add its own escrow; Vellum will not hold the holder to the term.
- Single vault per app build. The front end resolves one CA; multi-chain deployments need separate configuration per environment.
- Admin override is per-browser. The admin panel cannot push a CA to other users; that requires an env var and a redeploy.
Verifying a deployment
#Before trusting a vault address, confirm it is the reference contract:
- Run
npm run verify:vaultlocally — this compilescontracts/VellumVault.solwith the same settings the deploy script uses and asserts the safety invariants. - Compare the deployed bytecode (explorer → Contract → bytecode, or
eth_getCode) withVELLUM_VAULT_BYTECODEinapp/lib/vellumVaultArtifact.ts, ignoring the trailing metadata hash and the constructor argument appended at deployment. - Read
guardian()and confirm it is the published multisig. - Read
name()→Vellum Note,MIN_TERM()→86400,MAX_TERM()→315360000.
Then run npm run test:vault against a local node for the behavioural checks — see testing.
Holder checklist
#- The vault address in the app matches the one published by the team.
positions(id)shows the token address you expect, not just a familiar symbol.isClaimable(id)istruebefore you pay for a note.- You are sending the note to an address that can call
claim— an EOA, or a contract that can make arbitrary calls. A note sent to a contract that cannot callclaimis stranded together with its balance. - You understand the term is informational and does not stop the holder from claiming early.