Run a Vault
Download create-a-vault-llm.md and pass it to Claude, ChatGPT, or any LLM. It covers contract rate resolution, SDK operations, indexer reads, and the checks a rate engine needs before submitting updates.
How to create and operate a vault on Peer. A vault lets you manage conversion rates on behalf of depositors (liquidity providers) and earn a fee on every fulfilled order.
Vaults use the RateManagerV1 contract. All rate floor enforcement happens on EscrowV2.
The Peer app presents compatible verified vaults to depositors as Earning Strategies. This guide uses vault for the underlying contract, SDK, registry, and operator concepts.
What is a vault?
A vault is a rate management layer. Depositors delegate their deposits to your vault, and you set the conversion rates for their payment method and currency pairs.
You can optionally charge a fee on every intent fulfilled through deposits delegated to your vault. The vault is non-custodial. Depositor USDC stays in escrow at all times.
Contracts involved:
RateManagerV1(0xeEd7Db23e724aC4590D6dB6F78fDa6DB203535F3) for vault creation and rate settingEscrowV2for delegation, floor enforcement, and rate resolution
1. Create your vault
Vault creation is done directly on the RateManagerV1 contract. The @zkp2p/sdk has full support for all vault operations if you prefer to integrate programmatically.
Call createRateManager() on RateManagerV1 with a RateManagerConfig struct:
| Parameter | What it does | Can you change it later? |
|---|---|---|
manager | Address that controls this vault (rate setting, fee changes) | Yes |
name | Display name for your vault | Yes |
uri | Metadata URI for your vault | Yes |
fee | Fee charged on each fill (1e18 precision, e.g. 2e16 = 2%) | Yes (up to maxFee) |
feeRecipient | Address that receives your fees | Yes |
maxFee | Hard cap on your fee, ever (1e18 precision) | No. Immutable. |
minLiquidity | Minimum deposit size to delegate to your vault (0 = no minimum) | Yes |
maxFee is permanent. If you set it to 2%, you can never charge more than 2% on this vault, even if you lower your fee and want to raise it later. The global protocol cap is 5%.
Your vault gets a rateManagerId on creation. You'll use this for all rate operations.
2. Set rates
You set rates per (paymentMethod, currencyCode) pair.
Single pair:
setRate(rateManagerId, paymentMethod, currencyCode, rate)
Multiple pairs at once:
setRateBatch(rateManagerId, paymentMethods[], currencyCodes[][], rates[][])
Currency codes and rates are nested arrays grouped by payment method index — each payment method maps to its own array of currencies and rates.
The paymentMethod and currencyCode values are bytes32 identifiers. You can find the full list of supported payment methods and currency codes in the V3 deployments reference.
How rates are denominated
Rates are fiat per USDC. A rate of 0.7505 on GBP means 1 USDC costs 0.7505 GBP. Higher rate = more fiat per USDC = better for the depositor.
Setting rate to 0
Setting your rate to 0 disables that delegated pair for new intents, even when the depositor has a non-zero floor. A manager returning zero is different from a manager call reverting: only a revert falls back to a usable escrow floor.
Example
You manage a vault with Revolut/GBP and Revolut/EUR.
| Pair | Your rate | Depositor's floor | Effective rate |
|---|---|---|---|
| Revolut/GBP | 0.7505 | 0.7400 | 0.7505 (your rate wins) |
| Revolut/EUR | 0.8300 | 0.8450 | 0.8450 (floor wins) |
| Revolut/USD | 0 | 0 | 0 (pair inactive — no rate or floor) |
| Revolut/JPY | 0 | 152.00 | 0 (manager disabled the pair) |
When both the escrow floor and manager rate are positive, the protocol takes max(yourRate, depositorFloor). You can't undercut a depositor's floor. If your rate is higher, yours is used. If theirs is higher, theirs is used.
3. Rate resolution
For every intent, EscrowV2.getEffectiveRate() runs:
if an oracle is configured but its spread rate is unusable: return 0
escrowFloor = max(fixedFloor, oracleSpreadRate)
if escrowFloor == 0: return 0
if no manager is configured: return escrowFloor
if the manager call reverts: return escrowFloor
if managerRate == 0: return 0
effectiveRate = max(managerRate, escrowFloor)
The indexer and orderbook surface this on-chain result as conversionRate (also called grossRate); curator then computes the taker-facing effectiveConversionRate after your manager fee. See the rate field glossary for how the fields relate.
What this means for you as a manager:
| Scenario | What happens |
|---|---|
| Positive manager rate and usable escrow floor | The higher rate applies. |
| Manager returns zero | The delegated pair is disabled, even with a positive floor. |
| Escrow floor is zero | The pair is disabled, even with a positive manager rate. |
| Manager call reverts | A usable escrow floor applies. |
| Configured oracle is stale or invalid | The pair halts; the fixed floor does not override the oracle halt. |
These rules govern new intents. An existing intent retains its snapshotted rate.
4. Fees
Your fee is deducted from the USDC released when an intent is fulfilled. It goes to your feeRecipient address.
How the fee affects the taker
The taker's all-in cost is:
takerRate = grossRate * 1e18 / (1e18 - managerFee)
Example: Gross rate is 1.00 USD/USDC, your fee is 2%.
- Taker sends 1.00 USD
- 0.98 USDC is released to the taker (2% fee deducted)
- 0.02 USDC goes to your fee recipient
- Taker's effective cost: ~1.0204 USD per USDC
Fee timing
The fee is snapshotted at intent signal time. If you change your fee between when the intent is signaled and when it's fulfilled, the original fee applies to that intent.
Fee caps
- Global protocol cap: 5%
- Your vault's cap: whatever
maxFeeyou set at creation (immutable) - Current fee: whatever you've set, up to your
maxFee
5. Strategies
How you approach rate management depends on what you and your depositors want.
Fixed floor without an oracle
A positive fixed floor supplies the escrow rate without an oracle dependency. A positive manager rate can price above it. A zero manager rate still disables the pair.
Oracle pricing with an optional fixed floor
A valid oracle rate tracks the market with the depositor's configured spread. A fixed floor can raise the minimum while the oracle is healthy. A stale or invalid configured oracle halts new orders for that pair, even when a fixed floor exists.
No usable escrow floor
Without a positive fixed floor or a usable oracle rate, the pair is inactive. A manager rate alone cannot make it tradable.
6. Monitoring your vault
Once your registered vault is live, open its direct detail route at https://app.peer.xyz/vaults/<slug>. The page shows:
- APR (7d), Delegated Amount, Volume, and PnL at a glance
- Vault info including your manager address, fee, fee recipient, and rate model
- Charts for volume, TVL, fees, and PnL over 7D / 30D / All time
- Rates tab showing the configured rates for each payment method and currency
- Delegations tab showing which deposits are delegated to you
- Order history and Rate history tabs for full audit trail
You can filter rates by platform or currency to quickly check specific pairs.
7. Register your vault
Once your vault is deployed and running, add it to the vault-list registry. Registration verifies its identity, makes it eligible to appear as an earning strategy for compatible deposits, and gives it a direct detail route at https://app.peer.xyz/vaults/<slug>.
Steps
- Fork zkp2p/vault-list
- Add your vault entry to the
vaultsarray invault-list.json - (Optional) Add a 256x256 PNG logo to
logos/<rateManagerId>/vault.png - Run validation:
npm install && npm run validate - Open a PR
Required fields
| Field | Description |
|---|---|
rateManagerId | bytes32 ID from the RateManagerV1 contract |
chainId | 8453 (Base) |
rateManagerAddress | RateManagerV1 contract address |
name | Display name for your vault |
slug | URL-safe identifier (lowercase, hyphens) |
description | Short description, max 500 characters |
strategyShort | One-line strategy summary |
manager.address | Your manager wallet address |
manager.name | Your display name |
paymentMethods | Array of supported payment method identifiers |
currencies | Array of supported fiat currencies (ISO 4217) |
Optional fields
| Field | Description |
|---|---|
strategyLong | Detailed strategy description (markdown supported) |
manager.twitter | Your Twitter/X handle |
fee | Current fee (e.g. "0.10%") |
maxFee | Maximum fee (e.g. "2.0%") |
riskLevel | low, medium, or high |
tags | Filterable tags, max 10 (e.g. ["automated", "ai-managed"]) |
links | Object with website, docs, twitter, discord, telegram URLs |
logoURI | URL to a 256x256 vault logo |
Example entry
{
"rateManagerId": "0x...",
"chainId": 8453,
"rateManagerAddress": "0xeEd7Db23e724aC4590D6dB6F78fDa6DB203535F3",
"name": "My Vault",
"slug": "my-vault",
"description": "USDC liquidity vault for European payment rails.",
"strategyShort": "Automated rate management for EUR and GBP",
"manager": {
"address": "0x...",
"name": "Your Name",
"twitter": "yourhandle"
},
"fee": "0.50%",
"maxFee": "2.0%",
"paymentMethods": ["revolut", "wise"],
"currencies": ["EUR", "GBP"],
"riskLevel": "low",
"tags": ["automated", "european"],
"links": {
"website": "https://yourvault.xyz",
"twitter": "https://x.com/yourhandle"
}
}
Dynamic data like TVL, volume, fill rate, and APY is not stored in the vault-list. This data is fetched at runtime from on-chain or the ZKP2P indexer.
Full schema and validation details: github.com/zkp2p/vault-list
8. Reference vaults
Two live vaults on prod you can use as reference:
- Delegate by USDCtoFiat — AI-powered rate engine, fully automated. delegate.usdctofiat.xyz · @usdctofiat
- J.A.R.V.I.S Fund — autonomous multi-strategy management with competitive analysis and fill-rate feedback loops. jarvis.payhumans.ai · @0xSachinK
Quick reference
| Action | Contract | Function |
|---|---|---|
| Create vault | RateManagerV1 | createRateManager(config) where config is a RateManagerConfig struct |
| Set rate (single) | RateManagerV1 | setRate(rateManagerId, paymentMethod, currencyCode, rate) |
| Set rates (batch) | RateManagerV1 | setRateBatch(rateManagerId, paymentMethods[], currencyCodes[][], rates[][]) |
| Change fee | RateManagerV1 | setFee(rateManagerId, newFee) |
| Change fee recipient | RateManagerV1 | setFeeRecipient(rateManagerId, newRecipient) |
| Depositor delegates | EscrowV2 | setRateManager(depositId, rateManager, rateManagerId) |
| Depositor exits | EscrowV2 | clearRateManager(depositId) |
| Depositor sets floor | EscrowV2 | setCurrencyMinRate(depositId, paymentMethod, currencyCode, rate) |
| Depositor sets oracle | EscrowV2 | setOracleRateConfig(depositId, paymentMethod, currencyCode, config) |
Safety guarantees
- Depositor funds never leave escrow. Delegation only controls rate management.
- Escrow enforces the floor and halt rules. A positive manager rate cannot undercut a usable depositor floor.
- A configured stale oracle halts the pair. A fixed floor does not bypass this requirement.
- Manager revert uses a usable escrow floor. A zero manager rate disables the pair instead.
- Existing intents retain their agreed rate. Later manager updates do not rewrite the conversion rate snapshotted at signal time. Payment verification still governs fulfillment.
- Depositors can clear delegation without manager approval. Funds locked in active orders remain subject to the order lifecycle.
If you have questions, reach out in the Peer Liquidity Providers Telegram group.