@pbg.io/dvp-contract-v2
v1.1.1
Published
The DVP Contract has 3 parts:
Readme
DVP Contract
The DVP Contract has 3 parts:
- Registry (only two instances will ever exist on Cardano)
- Portfolio (one instance per DVP on Cardano)
- Vault (a Portfolio can have many Vaults in many different venues)
Registry
Principally contains portfolio UTxOs with NFTs with Portfolio: name prefix.
Designed for quick lookup of Portfolios. So portfolio key is part of token name. Token name has Portfolio: as UTF-8 encoded string (so 10 chars), and the remaining 22 chars are the first 22 bytes of the Portfolio Cardano policy.
There are also User: tokens, with the suffix being the first 27 bytes of the users PubKeyHash. The datum contains the full pubKeyHash:
- pkh: ByteArray
There are also Asset:<ticker> tokens, with an optional secondary lookup token Asset:<fingerprint>, where the fingerprint hash is calculated using the network name as a prefix and the assetclass as a suffic (format tbd). Datum:
- venue: String (eg. "Cardano")
- policy: ByteArray
- (optional) token_name: ByteArray
- price: { top: Int, bottom: Int, timestamp: Int }
Actions:
- Register portfolio
- Deregister portfolio
- Register user
- Deregister user
- Register asset
- Deregister asset
- Update asset price
With only two instances of the Registry (one for mainnet and one for preprod), it is feasible to hard-code the Registry policy in the oracle code.
Portfolio
Each portfolio contract acts as a registry for many vault contract instances.
Registered Vault UTxOs contain an NFT with Vault:<network-name> as the UTF-8 encoded name, adding a second token with Vault allows easy lookup of all vaults.
Each Vault UTxO datum has the following fields:
- policy: ByteArray
- value: { top: Int, bottom: Int, timestamp: Int }
- (optional) supply: Int (there can be Vaults that don't permit token minting)
There's a Fees NFT containing the DVP fee config in its datum
- mint: { min: Int, rel: Int }
- burn: { min: Int, rel: Int }
- management: { period: Int, rel: Int }
- (optional) success: {}
There's also an aggregate UTxO with Total NFT and following datum:
- supply: Int
- value: {top: Int, bottom: Int, timestamp: Int}
Summing requires iterating over all UTxOs at Portfolio address, and filtering UTxOs containing a Vault token.
Actions:
- Add vault
- Remove vault
- Set fees (if it's missing, create it in the same tx)
- Update total (if it's missing, create it in the same tx)
- Update vault (used for updating supply/value in the same tx)
Vault
Cardano
Works similar to Registry and Portfolio, a single contract instance which allows both minting/burning/depositing/withdrawing/swapping.
Has following token types:
- the DVP participation token itself, with Cip68 (333) with empty suffix (integrations should rely on ticker in metadata instead)
Feestoken, which accumulates the fee as participation tokens in the same UTxO, with the datum being a copy of theFeesdatum in the Portfolio contract, with the addition of a last: Int timestamp for the management fee.Pricetoken, containing the simply token price (synced by the oracle). Datum:- top: Int
- bottom: Int
- timestamp: Int
Supplytoken, datum:- current: Int (incremented/decremented whenever minting/burning in this venue only)
- total: Int (synced by oracle)
- (optional) max: Int
- Cip68 (100) Metadata token, with datum:
- name: String
- ticker: String
- logo: String
- url: String
- decimals: Int
- description: String
- portfolio: ByteArray (backlink needed by oracle)
- (optional)
User:<shortened-pubKeyHash>token, with datum:- pkh: ByteArray
- TODO: minimal structure to track accumulation of success fee
Actions:
- Initialize (create fees NFT/UTxO, create price NFT/UTxO, create supply NFT/UTxO, create metadata NFT/UTxO)]
- Set fees
- Set price
- Set max supply (adding max field to datum if it didn't exist before)
- Update metadata (any field can be changed except
portfolio) - Add user
- Remove user
- Deposit value (any asset that doesn't have the same minting policy as the vault) and mint proportional DVP tokens in return. Check value of deposited assets using
Asset:tokens in registry. Charge mint fee updating which consumes/pays fee UTxO back to self (without changing its datum). Part of this function input is the wallet utxos available for picking, and the value that will be deposited. - Withdraw chosen value by burning a number of DVP participation tokens, paying burn fee in the process (thus consuming/paying fee UTxO to self).
- Allow value for value swap (value in and value out are function args and must be compared using
Asset:prices in registry). One input arg is a list of UTxO which can be picked from for the input - Mint management fee, checking that period has passed since
last, and that the minted amount doesn't surpass configured rel fraction of thetotalsupply. Minting management fee should be disabled in other venues.
Purpose
This repository defines the Cardano-side contract model for a multichain DVP.
The model is split into three parts:
Registryfor discovery, user registration, and asset pricingPortfoliofor DVP-level fees, aggregate supply/value, and vault discoveryVaultfor venue-specific custody, minting, burning, and local accounting
The split is intentional:
Registryis shared lookup statePortfoliois product stateVaultis execution state
Relationship Between Parts
The three parts reference each other in one direction:
Registrypoints toPortfolioinstancesPortfoliopoints toVaultinstancesVaultpoints back to its owningPortfolio
This keeps lookup simple:
- start from
Registryto discover a portfolio - read the
Portfolioto discover its vaults and fee structure - read a specific
Vaultto perform venue-local actions
Synchronization Model
The contracts are synchronized in the following way:
Registrystores the latest asset prices- each
Portfoliostores the latest aggregatesupplyandvalue - each
Vaultstores its latest localsupplyandvalue
Data typically flows upward like this:
- asset prices are updated in
Registry - a
Vaultuses those prices or local inventory to determine its state - the
Portfolioupdates its aggregatesupplyandvaluefrom its vaults
Configuration typically flows downward like this:
- a
Portfoliodefines the canonical fee structure for its vaults - a
Vaultmirrors or enforces the relevant fee settings locally
Typical Actions
Registry
- register portfolio
- deregister portfolio
- register user
- deregister user
- register asset
- deregister asset
- update asset price
Portfolio
- add vault
- remove vault
- set fees
- update total
- update vault
Vault
- deposit
- withdraw
- mint
- burn
- swap
- sync fees from portfolio
- sync local supply/value back to portfolio
Invariants
The following should remain true:
- Only two
Registryinstances exist: one for mainnet and one for preprod. - Every registered
Portfoliotoken inRegistryresolves to exactly one portfolio contract instance. - Every
Vault:<network-name>entry in aPortfolioresolves to exactly one vault contract instance for that venue. - Every
Vaultpoints back to exactly one owningPortfolio. - A
Portfolioaggregatesupplyequals the sum ofsupplyvalues of vaults that support minting. - A
Portfolioaggregatevalueequals the sum of all active vaultvalueentries. - The
Feesdatum in aPortfoliois the canonical fee source for its vaults. - Asset prices in
Registryare the canonical on-chain prices used by portfolio and vault logic.
Initialization
A deployment starts in this order:
- Create or identify the correct
Registryinstance for the network. - Register the new
PortfolioinRegistry. - Create the initial
FeesandTotalstate for thePortfolio. - Create the first
Vault, usuallyVault:Cardano. - Register additional vaults as new venues are added.
Actors
User
A user interacts with a Vault by depositing, withdrawing, minting, burning, or swapping.
To do this safely, the user-facing flow needs access to:
- vault-local state
- portfolio fee state
- registry asset prices
Manager
A manager updates operational state across the system:
- registers portfolios, users, and assets in
Registry - adds or removes vaults in
Portfolio - updates fee configuration in
Portfolio - performs or authorizes vault-level maintenance actions
- updates asset prices or coordinates oracle-driven updates
Design Goal
The design goal is to keep lookup, product state, and execution state separate.
That separation avoids mixing:
- global discovery concerns
- DVP-level accounting concerns
- venue-specific operational concerns
Without this split, every change to asset pricing, fees, aggregate totals, and vault-local execution would contend in the same place.
Why This Split Is Good
This structure keeps each part narrow:
Registryis optimized for shared lookup and pricing dataPortfoliois optimized for DVP-level aggregation and configurationVaultis optimized for venue-local execution
It also makes growth straightforward:
- new portfolios only add more
Portfolio:entries toRegistry - new venues only add more
Vault:<network-name>entries to aPortfolio - new assets only add more
Asset:<ticker>entries toRegistry
Practical Summary
The model in this repository is:
- one shared
Registryper Cardano network - one
Portfolioper DVP - many
Vaultinstances perPortfolio
In short:
Registrytells you which portfolios, users, and assets existPortfoliotells you which vaults belong to a DVP and what the aggregate state isVaultperforms the actual venue-specific token and custody logic
