VaultsDocs
Suede Artist Vaults: Docs
Same product, four explanations. Pick your depth and skip the rest.
1. The plain-English version
Imagine an artist needs $10,000 to record their next album.
Instead of signing a label deal that takes a permanent cut of everything they ever make, they open a vault — a transparent online piggy bank. Fans deposit US dollars (in the form of USDC, a digital dollar that lives on the internet) into the vault. In return, each fan owns a small slice of the album's future revenue.
The artist also locks up some SUEDE tokens in the same vault. Think of those as a deposit — proof that the artist isn't going to take the money and ghost the project. The more SUEDE they lock, the higher their tier (Seed, Studio, Featured, Premier).
When the album makes money — streams, licensing deals, sync placements — the income flows back into the vault. Fans can pull their share out anytime by clicking Claim. They get paid in the same digital dollars they put in.
When the project term ends, the artist can request to unlock their SUEDE deposit. Suede operators check that the artist held up their end (delivered the work, kept enough SUEDE locked through the term), and approve the release.
Three places you can use this:
- You're an artist: /vaults/create
- You're a fan / supporter: /vaults
- You're a Suede operator: /vaults/admin (operator-gated)
That's it. No middlemen taking a permanent cut. Every number visible on chain. If you want to see how it actually works under the hood, keep scrolling — the next sections get progressively nerdier.
2. The operator / PM version
Three actors
- Artist — deploys a vault for one project, locks SUEDE as a commitment bond, receives their cut of routed revenue, withdraws SUEDE on operator authorization at term end.
- Supporter — deposits USDC during Funding, holds non-transferable supporter shares, claims pro-rata revenue permissionlessly.
- Operator (Suede) — promotes Draft → Funding, wires registry workIds into the router, authorizes artist exits, handles emergency pauses. Cannot move USDC out of vaults arbitrarily; cannot mint shares; can only walk vaults through a constrained status machine.
Vault lifecycle
A vault moves through five statuses. The first four are sequential; the fifth (Paused) is orthogonal — it can suspend any non-Closed state.
- Draft — vault deployed, no deposits, no revenue routing. Operator review state.
- Funding — supporters can deposit until
maxRaiseis hit. Artist can lock SUEDE. - Active — funding window closed; revenue routes through the router; supporters claim as revenue accrues.
- Closed — terminal state; no further deposits or ingestion. Final claims still permissionless.
- Paused — emergency stop. Blocks deposits, lockSuede, and revenue ingestion. Claim path stays open.
Splits
At creation the artist sets three basis-point splits that define how incoming revenue divides. They must sum to ≤ 10000 bps (100%); anything left over stays in the vault as undistributed reserve.
supporterRevenueBps— share routed to depositors, pro-rata to their supporter shares.artistRevenueBps— share routed to the artist on every distribution.protocolFeeBps— Suede protocol fee on revenue.
Tier table
Tier is computed live from lockStrength (locked SUEDE weighted by lock duration). Defaults:
| Tier | Threshold (SUEDE) |
|---|---|
| Seed | 0 |
| Studio | 10,000 |
| Featured | 50,000 |
| Premier | 250,000 |
Two revenue paths
- Router-driven (canonical). A registry license fee in the main app is paid by an agent or licensee against a workId; the registry calls
router.routeUsdcFee(workId, amount); the router looks upvaultForWork[workId], takes a platform skim (default 500 bps), and pushes the rest into the vault viavault.receiveRevenue(...). Operator wires this once per work via /vaults/admin/router. - Direct push. Anyone with USDC can call
vault.receiveRevenue(amount)directly. Useful for backfilling off-platform royalties, sponsor donations, or manual registry-event reconciliation. Available per-row on /vaults/admin (operator-gated UI; permissionless on chain).
Cross-chain entry
Vaults are Base-USDC-only at the contract layer. To bring funds in from any other chain, supporters route through the LI.FI widget at /swap. There's no in-vault bridge — by design, the deposit path stays atomic and audit-tight.
UI map
| Route | Audience | What it does |
|---|---|---|
| /vaults | Public | Live aggregation index |
/vaults/[projectId] | Public | Per-vault detail + deposit + claim + lockSuede (artist-only) |
| /vaults/create | Artist | Self-serve vault deployment |
| /vaults/admin | Operator | Status transitions, metadata, exits, manual revenue push |
| /vaults/admin/router | Operator | Wire workIds → vaults, set platform skim |
| /swap | Public | Cross-chain entry into Base USDC (LI.FI) |
3. The engineer version
Three contracts
ArtistVaultFactory ── deploys + administrates ──> ArtistVault (per project) VaultRoyaltyRouter ── pushes revenue ──> ArtistVault (per work link)
ArtistVaultFactory— Ownable. DeploysArtistVaultinstances. Owner-only status transitions (setStatuson the vault isonlyFactory; the factory wraps it withonlyOwner). Tier table (studioThreshold/featuredThreshold/premierThreshold) configurable post-deploy.ArtistVault— per project. Holds USDC + SUEDE. Implements deposit, claim, lockSuede, receiveRevenue, withdrawArtistSuede. Reentrancy-guarded throughout.VaultRoyaltyRouter— Ownable. Maps registryworkId → projectId → ArtistVaultand routes USDC license fees with a platformBps skim.
Status machine
openFunding activate close
Draft ─────────────► Funding ─────────► Active ─────► Closed
│ │
└──── close ──────►│
▲ │
│ (no path) ▼
Funding Closed (terminal)
Paused is orthogonal: pauseVault / unpauseVault from any non-terminal state
blocks deposit / lockSuede / receiveRevenue. claim() stays open.Reward-debt accumulator (claim math)
The claim path uses a MasterChef-style accumulator. State variables on the vault:
shares[supporter]— non-transferable share count.totalSupporterSharesaccRevenuePerShare— running accumulator (uint256, scaled byACC_PRECISION = 1e18).rewardDebt[supporter]— what the supporter would have earned at their entry tip if they'd held since genesis.
On deposit(amount):
minted = amount; // 1:1 with USDC wei (6 decimals) shares[msg.sender] += minted; totalSupporterShares += minted; totalDeposited += amount; rewardDebt[msg.sender] += minted * accRevenuePerShare / ACC_PRECISION;
On receiveRevenue(amount):
if (totalSupporterShares > 0) {
accRevenuePerShare += amount * ACC_PRECISION / totalSupporterShares;
}
totalRevenueReceived += amount;
// USDC pulled from msg.sender via SafeERC20.transferFromOn claim() (and the claimable(supporter) view):
accrued = shares[supporter] * accRevenuePerShare / ACC_PRECISION;
owed = accrued > rewardDebt[supporter]
? accrued - rewardDebt[supporter]
: 0;
rewardDebt[supporter] = accrued; // advance to current tip
usdc.safeTransfer(msg.sender, owed);Late entrants don't retroactively claim revenue that landed before their deposit because their rewardDebt is set to the current tip on join. Revenue that arrives while totalSupporterShares == 0 is undistributable and accumulates as vault-locked USDC; this is intentional v1 behavior (the alternative — refund or burn — adds attack surface).
Lock strength + tier
lockStrength() = artistSuedeLocked * commitmentScore()
commitmentScore() = f(daysLocked) // monotonically non-decreasing
tierForStrength(s) = 3 if s >= premier
2 if s >= featured
1 if s >= studio
0 otherwiseModifier matrix (key external functions)
| Function | Caller gate | State guard |
|---|---|---|
vault.deposit | — | Funding, !paused, totalDeposited+amount ≤ maxRaise |
vault.claim | — | owed > 0 |
vault.lockSuede | onlyArtist | !paused |
vault.receiveRevenue | — | !paused |
vault.withdrawArtistSuede | onlyFactory | artistSuedeLocked − amount ≥ floor |
vault.setStatus | onlyFactory | — |
factory.createVault | — | !paused, projectId unused |
factory.openFunding/activate/close/pause* | onlyOwner | — |
factory.authorizeArtistSuedeWithdrawal | onlyOwner | — |
router.linkWorkToVault/unlinkWork/setPlatform* | onlyOwner | — |
router.routeUsdcFee | — | vaultForWork[workId] != 0, amount > 0 |
Reentrancy + token safety
nonReentranton every state-changing function that touches USDC or SUEDE.SafeERC20wraps every transfer, transferFrom, and approve. No rawtransfercalls.USDC pullhappens before share state indeposit(CEI satisfied: external call → state mutation → no further external calls).claimadvances rewardDebt before transfer (state mutation → external call).- Router approves vault for exact gross-minus-skim before each
receiveRevenuecall; allowance is consumed completely so no stale approval can compound across calls.
Events
| Contract | Event |
|---|---|
| ArtistVault | Initialized, StatusChanged, Deposited, SuedeLocked, SuedeUnlocked, RevenueReceived, Claimed, MetadataUpdated |
| ArtistVaultFactory | VaultCreated, TierThresholdsUpdated, ProtocolTreasuryUpdated |
| VaultRoyaltyRouter | WorkLinked, WorkUnlinked, FeeRouted, PlatformBpsUpdated, PlatformWalletUpdated |
Test coverage
33 / 33 passing in base-contracts/test/: 16 across ArtistVault (deposit, share minting, claim math, late entrants, lockSuede, withdraw with floor enforcement, pause / status guards, cap enforcement) plus 17 across VaultRoyaltyRouter (link / unlink, platform skim arithmetic incl. zero-skim, owner gating, state-change emissions, back-to-back fee routing).
Frontend integration
- All read paths use viem
multicallagainst a public Base RPC. Seesuede-home/src/lib/vault-reads.ts. - All write paths use viem
WalletClientoverwindow.ethereumviasuede-home/src/lib/wallet-client.ts. No wagmi context — keeps suede-home parallel to the LI.FI widget's wallet stack on /swap. - Per-flow libs:
vault-deposit.ts,vault-create.ts,vault-claim.ts,vault-lock.ts,vault-revenue.ts,vault-admin.ts,vault-router-admin.ts.
4. The whitepaper
Abstract
Suede Artist Vaults are a non-custodial primitive for artist-led rights factoring on Base. Each vault is a per-project on-chain escrow with three economic positions: a supporter pool that earns pro-rata USDC revenue against the project's future income, an artist position that locks SUEDE as a commitment bond and receives a configurable artist share of routed revenue, and a protocol position that takes a configurable skim. Trust is minimised: deposits and claims are permissionless; status transitions are gated by an operator role that cannot move funds arbitrarily; share accounting is enforced by a reward-debt accumulator with no privileged write paths.
Problem
Independent creators face three financing options today, all structurally hostile:
- Royalty advances from labels, distributors, or specialised lenders carry implicit APRs of 30–60% once the recoupable cut and term are accounted for. Worse, advances often collateralise the entire artist's catalog rather than the specific work being funded.
- Equity rounds dilute the creative entity in perpetuity, invert the artist's incentive toward exit-driven outcomes, and assume an institutional cap-table apparatus most independent artists don't want.
- Subscription / patronage generates real revenue but distributes it on a recurring basis with no commitment from either side; supporters who would happily fund the production phase have no way to translate that into ongoing share of revenue.
Vaults are the missing primitive: project-bounded, time-bounded, revenue-routing.
Mechanism
Vault formation
An artist deploys a vault by calling factory.createVault(projectId, targetRaise, maxRaise, artistRevenueBps, supporterRevenueBps, protocolFeeBps, metadataURI). projectId is a freely-chosen bytes32 (typically keccak256(slug)); targetRaise and maxRaise bound the cap; the three basis-point fields declare how routed revenue divides. The vault is born in Draft; the operator promotes to Funding after off-chain review (KYC / project-validity / tier policy).
Supporter share accounting
Deposits during Funding mint non-transferable supporter shares 1:1 with deposited USDC wei. The vault tracks a single accumulator accRevenuePerShare (scaled by ACC_PRECISION = 1e18) that is incremented on every revenue ingestion event. Claim entitlement at any tip is:
claimable(s) = max(0, shares(s) * accRevenuePerShare / ACC_PRECISION
- rewardDebt(s))On deposit, rewardDebt advances to the current tip, ensuring late entrants cannot retroactively claim revenue that arrived before they joined. On claim, rewardDebt re-anchors to the current tip and the difference is paid out in USDC. This is the standard MasterChef-style accumulator, audited and battle-tested across the DeFi ecosystem; we add no novel math to that sub-component.
Revenue ingestion
Two paths feed the accumulator. The canonical path is via the VaultRoyaltyRouter: a registry workId is wired to a vault by the operator (linkWorkToVault), then any caller — including agent-commerce flows from the main app — can pay a license fee against that work via routeUsdcFee(workId, amount). The router takes a configurable platform skim (default 500 bps) to a designated platformWallet, then forwards the residual to the vault via vault.receiveRevenue(net). The fallback path is permissionless direct push to vault.receiveRevenue(amount), used by operators for backfills, sponsors for direct support, or rights holders for off-platform royalty rebates.
Artist commitment bond
Artists lock SUEDE in the same vault via lockSuede(amount). Locked SUEDE is non-circulating for the duration of the lock; it counts toward a tier ladder (Seed / Studio / Featured / Premier) with thresholds set by the factory owner. Tier influences external visibility (presentation in the index, sort priority, etc.) but does not gate the contracts themselves — every vault can transact regardless of tier.
Locked SUEDE leaves the vault only via factory.authorizeArtistSuedeWithdrawal(vault, amount, floor): the factory owner approves a specific withdrawal amount with a specific authorized floor; the vault then enforces on chain that artistSuedeLocked − amount ≥ floor before transferring SUEDE to the artist. The floor is the lever that lets the operator demand artist commitment over a project term: e.g., set floor = studioThreshold until the term completes, then drop to zero for full unlock.
Trust model
- Supporters trust: (a) the contracts are non-upgradeable and audited; (b) the artist will deliver the work (mitigated, not eliminated, by the SUEDE bond); (c) the operator won't maliciously block
activateorclose. They do not trust the operator with custody, with their share count, or with claim availability — those are enforced by code. - Artists trust: (a) the operator authorizes their SUEDE exit on time and against an honest floor; (b) the operator doesn't pause a healthy vault. They do not trust the operator with their artist-cut routing — that flows automatically on every revenue event.
- Operator is the only role with a privileged seat, but its powers are bounded: it can advance status, set metadata URIs, set tier thresholds, set platform skim, authorize SUEDE withdrawals to the floor, and pause / unpause. It cannot move USDC out of vaults, mint shares, alter rewardDebt, or change the artist on a vault.
Comparable systems
| System | Custody | Granularity | Artist bond |
|---|---|---|---|
| Royalty Exchange | Custodial | Whole catalog | None |
| Audius / streaming-tokens | Non-custodial | Per stream | Implicit (token holdings) |
| Royalty advances (labels) | Custodial | Whole career | Recoupable, not bonded |
| Patreon / Substack | Custodial | Per period | None |
| Suede Vaults | Non-custodial | Per project | Explicit SUEDE bond |
Future work
- ERC-4626 wrapper for transferable supporter shares. Current shares are non-transferable to keep v1 accounting simple; an opt-in wrapper unlocks secondary markets without forcing transfer-hook complexity into the core vault.
- Cross-chain shares. The deposit path stays Base-USDC-only at the contract layer (the bridge / OFT / LayerZero stack on /swap is the user's problem, not the vault's). A future composer that lzReceives on Base, calls
vault.deposit, and emits supporter shares to the origin-chain wallet would close the cross-chain UX gap without making the vault itself cross-chain. - Quadratic supporter weighting. 1:1 share-to-USDC keeps math and trust simple but is whale-friendly. A v2 share curve (e.g., square-root weighting) would equalise governance among supporters without changing the claim path.
- Registry-side hook. Today the operator wires workIds to vaults manually via
router.linkWorkToVault. A future automatic hook on the registry's license-attach flow can derive the projectId from work metadata and link without operator round-trip.
Status
Contracts: 33 / 33 tests passing locally on Hardhat. Frontend: fully wired, all surfaces shipping at suedeai.ai. Mainnet deployment of ArtistVaultFactory, ArtistVault instances, and VaultRoyaltyRouter pending operational sign-off and the LayerZero / Stargate DVN security move from 1/1 to 2/2 on the adjacent OFT stack.
Source: base-contracts/contracts/{ArtistVaultFactory,ArtistVault,VaultRoyaltyRouter}.sol · Tests: base-contracts/test/{ArtistVault,VaultRoyaltyRouter}.test.cjs · Frontend: suede-home/src/{lib,app/vaults}/.