# Centrifuge Documentation > Centrifuge is institutional-grade infrastructure for onchain asset management. It enables asset managers, > fintechs, and DeFi protocols to tokenize, manage, and distribute real-world assets on-chain. ## Overview Centrifuge is one of the first and largest tokenization platforms, with more than $2B in real-world assets tokenized. It powers onchain strategies for institutions including Apollo, Janus Henderson, and S&P Dow Jones Indices, with tokenized assets integrating into DeFi through Sky, Aave, and Morpho. ## Architecture Hub-and-spoke design. Each pool selects a single hub chain as its source of truth, then issues tokens and vaults across any number of spoke chains. - **Hub:** Central orchestration layer handling pool management, double-entry accounting, holdings tracking, share class management, and cross-chain message coordination. - **Spoke:** Local registry on each chain managing token instances, vaults, escrows, and balance sheets. Factory-based deployment of tokens, vaults, and escrows. - **Cross-chain messaging:** Multi-adapter aggregation (LayerZero, Wormhole, Chainlink, Axelar) with automatic batching, gas subsidies, and built-in retries. ## Vault Standards - **Asynchronous vaults (ERC-7540):** Request-based deposits and redemptions processed through the Hub. - **Synchronous deposit vaults (ERC-4626 + ERC-7540):** Instant deposits via ERC-4626, async redemptions. - **Pooled vaults (ERC-7575):** Multiple investment assets per share token, single aggregated balance sheet. ## Developer Tools ### Centrifuge SDK TypeScript/JavaScript client (`@centrifuge/sdk`) for investments, redemptions, reports, and pool management. Runs client-side and server-side. Supports full investment lifecycle: quote, deposit, claim, and reporting. ### Centrifuge API Public read-only GraphQL endpoint at `https://api.centrifuge.io`. Indexed data from the multi-chain protocol deployment. No authentication required. Entities include pools, tokens, vaults, holdings, investor transactions, snapshots, and cross-chain messages. ## Deployments Protocol v3.1.0 deployed on 9 chains: Ethereum, Base, Arbitrum, Avalanche, Plume, Binance Smart Chain, Optimism, HyperEVM, and Monad. Core contracts deployed at identical addresses across all chains. 21+ audits and 4 independent security reviews. ## Resources - **Documentation:** https://docs.centrifuge.io - **SDK:** https://www.npmjs.com/package/@centrifuge/sdk - **API:** https://api.centrifuge.io - **Protocol source:** https://github.com/centrifuge/protocol --- # Centrifuge API Source: https://docs.centrifuge.io/developer/centrifuge-api # Centrifuge API The Centrifuge API is a public, read-only GraphQL endpoint for querying onchain data from the Centrifuge protocol across multiple networks. Send any GraphQL-compliant `POST` request to [`https://api.centrifuge.io`](https://api.centrifuge.io). No authentication required. For interactive exploration, open the [GraphiQL UI](https://api.centrifuge.io) and use `Ctrl + Space` to trigger autocomplete. All queries accept these standard parameters: | Parameter | Type | Description | |-----------|------|-------------| | `limit` | `Int` | Max records to return (default 100, max 1000) | | `where` | `Filter` | Entity-specific filter criteria (see each section below) | | `orderBy` | `String` | Column to sort by (default: primary key) | | `orderDirection` | `String` | `"asc"` or `"desc"` (default: `"asc"`) | ## Pools ## Tokens ## Vaults ## Holdings ## Investor transactions ## Investor positions ## Snapshots --- ## Other entities Beyond the main entities above, the API exposes a full set of indexed resources: ### Infrastructure | Entity | Description | |--------|-------------| | `Blockchain` | All blockchains the indexer is currently tracking | | `Deployment` | Protocol contracts and addresses deployed to a blockchain | | `PoolSpokeBlockchain` | Mapping of pools to their spoke chain deployments | ### Protocol hub resources | Entity | Description | |--------|-------------| | `Holding` | Pool-level asset position with quantity and valuation | | `HoldingAccount` | Categorized asset account for a pool (asset, equity, liability, etc.) | | `AssetRegistration` | Successful registration of an asset on a hub chain | | `OnOffRampManager` | Cross-chain asset movement for pools | | `OfframpRelayer` | Authorized services that process withdrawal requests | | `OnRampAsset` | Token types investors can deposit for pool shares | | `OffRampAddress` | Pre-approved withdrawal destinations | | `Policy` | Merkle tree root representing allowed operations for a strategist | | `MerkleProofManager` | Enables strategists to execute multi-call operations on behalf of pools | ### Protocol spoke resources | Entity | Description | |--------|-------------| | `Asset` | An asset on a spoke chain | | `TokenInstance` | Instance of a share class token on a spoke chain | | `Escrow` | Custodial wallet for pool assets on a spoke chain | ### Cross-chain communication | Entity | Description | |--------|-------------| | `CrosschainPayload` | Data content sent from one blockchain to another | | `CrosschainMessage` | Structured communication between blockchains | | `Adapter` | Bridges that relay payloads between networks | | `AdapterWiring` | Configuration linking adapter addresses across chains | | `AdapterParticipation` | Tracks adapter involvement in cross-chain transfers | ### Transactional data | Entity | Description | |--------|-------------| | `OutstandingInvest` | Pending invest order awaiting fulfillment | | `OutstandingRedeem` | Pending redeem order awaiting fulfillment | | `PendingInvestOrder` | Pending invest order with queued and pending amounts | | `PendingRedeemOrder` | Pending redeem order with queued and pending amounts | | `InvestOrder` | Approved invest order (tracks approval to claim) | | `RedeemOrder` | Approved redeem order (tracks approval to claim) | | `VaultInvestOrder` | Cross-chain invest order via a vault | | `VaultRedeemOrder` | Cross-chain redeem order via a vault | | `EpochOutstandingInvest` | Aggregated outstanding invest orders (epoch-level) | | `EpochOutstandingRedeem` | Aggregated outstanding redeem orders (epoch-level) | | `EpochInvestOrder` | Approved invest order at epoch level | | `EpochRedeemOrder` | Approved redeem order at epoch level | | `TokenInstancePosition` | Investor position holding a share class token | ### Actors and participants | Entity | Description | |--------|-------------| | `Account` | User-owned address (UOA) in the protocol | | `PoolManager` | Manager of a pool (hub or spoke) | | `WhitelistedInvestor` | Investor allowed to invest in a pool | --- ## Filtering, sorting, and pagination ### Filters Use the `where` argument to filter records. Filter options are available for every column: | Filter suffix | Column types | Matches records where column... | |---------------|-------------|--------------------------------| | _(none)_ | All | equals the value | | `_not` | All | does not equal the value | | `_in` | Primitives, enums | is one of the values | | `_not_in` | Primitives, enums | is not one of the values | | `_gt` / `_lt` | Numeric | is greater / less than the value | | `_gte` / `_lte` | Numeric | is greater/less than or equal to the value | | `_contains` | String | contains the substring | | `_not_contains` | String | does not contain the substring | | `_starts_with` / `_ends_with` | String | starts / ends with the substring | | `_not_starts_with` / `_not_ends_with` | String | does not start / end with the substring | | `_has` | Lists | has the value as an element | | `_not_has` | Lists | does not have the value as an element | Compose filters using the `AND` and `OR` operators, which accept an array of filter objects. ### Sorting Use `orderBy` and `orderDirection` to sort results: | Argument | Default | Options | |----------|---------|---------| | `orderBy` | Primary key | Any column name | | `orderDirection` | `"asc"` | `"asc"` or `"desc"` | ### Pagination The API uses cursor-based pagination. For details, see the [Ponder.sh pagination docs](https://ponder.sh/docs/query/graphql#pagination). ### Filter example --- ## Limits and performance | Limit | Value | |-------|-------| | Max page size | 1000 records per page | | Request timeout | 30 seconds | **Performance tips:** 1. **Limit query depth**: each nested level adds a sequential database query. Keep queries to 2 levels deep or fewer. 2. **Use pagination**: fetch records in smaller chunks with cursor-based pagination to reduce database load. ## Supported networks See the [Deployments](/developer/protocol/deployments) page for the full list of supported networks and contract addresses. ## Data freshness Data is typically available within 1-2 minutes of onchain confirmation. Historical snapshots are maintained at daily intervals, with some entities also snapshotted on specific trigger events. --- ## Token REST API The API also exposes REST endpoints for simple token lookups. All endpoints return JSON responses. ### Token price ### Token total issuance --- # Buy and sell DeFi assets Source: https://docs.centrifuge.io/developer/centrifuge-sdk/buying-or-selling-defi-assets # Buy and sell DeFi assets This guide shows how to use the Centrifuge SDK to buy and sell tokenized DeFi assets within a pool. ## Prerequisites Before you begin, make sure you have: - [Node.js](https://nodejs.org/) (v18 or later recommended) - A package manager: [pnpm](https://pnpm.io/), [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/) - A wallet or signer that can connect to Ethereum-compatible chains (e.g. MetaMask, WalletConnect, or a private key account via [Viem](https://viem.sh/)) ## Installation Install the Centrifuge SDK in your project: ```bash pnpm add @centrifuge/sdk ``` ## 1. Initialize the SDK Create a Centrifuge instance and connect it to mainnet: ```typescript const centrifuge = new Centrifuge({ environment: "mainnet", }); ``` :::info For testing purposes, you can connect to testnet instead by setting environment: `testnet`. ::: ## 2. Deploy Merkle Proof Manager ```typescript const poolId = new PoolId(1); const pool = await centrifuge.pool(poolId); const scId = ShareClassId.from(poolId, 1); const centrifugeId = 1; // Centrifuge network ID const poolNetworks = await pool.activeNetworks(); const poolNetwork = poolNetworks.filter( (activeNetwork) => activeNetwork.centrifugeId === centrifugeId ); await poolNetwork.deployMerkleProofManager(); ``` ## 3. Retrieve Merkle Proof Manager and add as balance sheet manager Retrieve the deployed Merkle Proof Manager and set it as a BalanceSheet manager: ```typescript const merkleProofManager = await poolNetwork.merkleProofManager(); await poolNetwork.updateBalanceSheetManagers([{ centrifugeId, address: merkleProofManager.address, canManage: true }]), ``` ## 4. Setup policies Policies define specific contract methods that strategists are authorized to execute for managing pool assets. The Merkle Proof Manager controls access to balance sheet functions and enables whitelisting of strategists, allowing them to perform approved operations securely: ```typescript const addresses = await centrifuge._protocolAddresses(centrifugeId); const strategist = "0xStrategistAddress"; const vaultDepositPolicy = { decoder: addresses.vaultDecoder, targetName: "Vault", target: "0xVaultAddress", name: "Deposit", selector: "function deposit(uint256,address)", valueNonZero: false, inputs: [ { parameter: "Amount", label: "Amount", input: [], }, { parameter: "Address", label: "Vault Address" input: ["0xVaultAddress"], }, ], }; const balanceSheetWithdrawPolicy = { decoder: addresses.vaultDecoder, targetName: "Balance Sheet", target: addresses.balanceSheet, name: "Withdraw", selector: "function withdraw(uint64,bytes16,address,uint256,address,uint128)", valueNonZero: false, inputs: [ { parameter: "Pool ID", label: "Pool ID", input: [poolId.toString() as HexString], }, { parameter: "Share class ID", label: "Share class ID", input: [scId.raw], }, { parameter: "Asset", label: "Asset", input: [someErc20], }, { parameter: "Token ID", label: "Token ID", input: [], }, { parameter: "Receiver", label: "Receiver Address", input: [merkleProofManager.address], }, { parameter: "Amount", label: "Amount", input: [], }, ], }; const balanceSheetDepositPolicy = { decoder: addresses.vaultDecoder, targetName: "Balance Sheet", target: addresses.balanceSheet, name: "Deposit", selector: "function deposit(uint64 poolId, bytes16 scId, address asset, uint256, uint128)", valueNonZero: false, inputs: [ { parameter: "Pool ID", label: "Pool ID", input: [poolId.toString() as HexString], }, { parameter: "Share class ID", label: "Share class ID", input: [scId.raw], }, { parameter: "Asset", label: "Asset", input: [someErc20], }, { parameter: "Token ID", label: "Token ID", input: [], }, { parameter: "Amount", label: "Amount", input: [], }, ], }; centrifuge.setSigner(fundManager); await merkleProofManager.setPolicies(strategist, [ vaultDepositPolicy, balanceSheetWithdrawPolicy, balanceSheetDepositPolicy, ]); ``` ## 5. Withdraw funds from the balance sheet to the manager and deposit (invest) into the vault Withdraw funds from the pool's balance sheet, transfer them to the manager, and then deposit (invest) those funds into the vault. This process involves executing a sequence of policy-approved transactions using the MerkleProofManager, ensuring that only authorized strategists can perform these operations: ```typescript centrifuge.setSigner(strategist); await merkleProofManager.execute([ { policy: balanceSheetWithdrawPolicy, inputs: [0, amount], }, { policy: vaultDepositPolicy, inputs: [amount], }, { policy: balanceSheetDepositPolicy, inputs: [0, amount], }, ]); ``` --- # Invest into a vault Source: https://docs.centrifuge.io/developer/centrifuge-sdk/invest-into-a-vault # Invest into a vault This guide shows how to invest into a Centrifuge vault using the Centrifuge SDK. It covers setup, connecting to mainnet or testnet, and sending your first investment transaction. ## Prerequisites Before you begin, make sure you have: - [Node.js](https://nodejs.org/) (v18 or later recommended) - A package manager: [pnpm](https://pnpm.io/), [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/) - A wallet or signer that can connect to Ethereum-compatible chains (e.g. MetaMask, WalletConnect, or a private key account via [Viem](https://viem.sh/)) - Deposit asset (e.g. USDC) on `mainnet` or `testnet` ## Installation Install the Centrifuge SDK in your project: ```bash pnpm add @centrifuge/sdk ``` ## 1. Initialize the SDK Create a Centrifuge instance and connect it to mainnet: ```typescript const centrifuge = new Centrifuge({ environment: "mainnet", }); ``` :::info For testing purposes, you can connect to testnet instead by setting environment: `testnet`. ::: ## 2. Set a signer To send transactions, attach a signer (for example, from MetaMask or another EIP-1193 compatible provider): ```typescript // Example: using a MetaMask injected provider const provider = (window as any).ethereum; await provider.request({ method: "eth_requestAccounts" }); centrifuge.setSigner(provider); ``` If you’re using a private key or server-side setup, you can also provide a [Viem LocalAccount](https://viem.sh/docs/accounts/local). ## 3. Get a pool and vault Each pool can contain multiple share classes and each share class can have multiple vaults issuing tokens against an deposit asset. You need the pool ID, share class ID, centrifuge ID, and asset address. ```typescript // Get a pool by ID const poolId = new PoolId(1); const pool = await centrifuge.pool(poolId); const scId = ShareClassId.from(poolId, 1); const assetId = AssetId.from(centId, 1); const centrifugeId = 1; // Centrifuge network ID // Get a vault const vault = await pool.vault(centrifugeId, scId, assetId); ``` ## 4. Deposit into the vault You can deposit into the vault using either synchronous or asynchronous deposit methods: ### Synchronous deposits (ERC-4626) For vaults that support synchronous deposits, use `syncDeposit()`: ```typescript const { asset } = await vault.details(); const amount = Balance.fromFloat(1000, asset.decimals); const tx = await vault.syncDeposit(amount); ``` The deposit settles immediately and shares are minted in the same transaction. ### Asynchronous deposits (ERC-7540) For vaults that support asynchronous deposits, use `asyncDeposit()`: ```typescript const { asset } = await vault.details(); const amount = Balance.fromFloat(1000, asset.decimals); const tx = await vault.asyncDeposit(amount); ``` The deposit request is queued and will be processed during the next epoch. ## 5. Claim your shares For asynchronous deposits, once the deposit request is processed during an epoch, you need to claim your shares: ```typescript const claimTx = await vault.claim(); console.log("Claim transaction hash:", claimTx.hash); ``` :::info For synchronous deposits using `syncDeposit()`, shares are minted immediately in the same transaction, so there's no need to claim separately. ::: ## 6. Check investor position You can query your current position in the vault at any time: ```typescript const investor = await vault.investment("0xYourWalletAddress"); console.log(investor.shareBalance); // current balance of shares console.log(investor.pendingDepositAssets); // assets in pending deposit requests console.log(investor.claimableDepositShares); // shares ready to be claimed ``` --- # Manage NAV & orders Source: https://docs.centrifuge.io/developer/centrifuge-sdk/manage-nav-and-orders # Manage NAV & orders This guide shows how to update share prices and process investment and redemption orders using the Centrifuge SDK. For the underlying contract-level details, see the [Manage a pool](/developer/protocol/guides/manage-a-pool/) guide. ## Prerequisites Before you begin, make sure you have: - [Node.js](https://nodejs.org/) (v18 or later recommended) - A package manager: [pnpm](https://pnpm.io/), [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/) - A wallet or signer with pool manager permissions on the pool you want to manage ## Installation Install the Centrifuge SDK in your project: ```bash pnpm add @centrifuge/sdk ``` ## 1. Initialize the SDK ```typescript const centrifuge = new Centrifuge({ environment: "mainnet" }); centrifuge.setSigner(signer); ``` :::info For testing purposes, you can connect to testnet instead by setting `environment: "testnet"`. ::: ## 2. Get the pool and share class ```typescript const poolId = new PoolId("0x..."); const pool = await centrifuge.pool(poolId); const scId = ShareClassId.from(poolId, 1); const shareClass = await pool.shareClass(scId); ``` ## 3. Update share price Call `updateSharePrice` with the new price. The SDK automatically batches the on-chain price update with `notifySharePrice` calls for every active network the share class is deployed on. ```typescript const pricePerShare = Price.fromFloat(1.05); await shareClass.updateSharePrice(pricePerShare); ``` `Price.fromFloat` takes a decimal number and converts it to an 18-decimal fixed-point integer. ### Notify share price to specific chains `updateSharePrice` notifies all active networks automatically. If you need to re-push the price to specific chains without updating it, call `notifySharePrice` per chain. To notify multiple chains, use `Promise.all`: ```typescript const centrifugeIds = [1, 2, 3]; // Centrifuge network IDs await Promise.all(centrifugeIds.map((centrifugeId) => shareClass.notifySharePrice(centrifugeId))); ``` ## 4. Approve and issue shares (deposits) When investors submit deposit requests, they sit in a pending queue. As the pool manager, you approve them and lock in a share price. The SDK combines `approveDeposits`, `issueShares`, and the per-investor `notifyDeposit` calls into a single transaction. ```typescript const assetId = AssetId.from(centrifugeId, assetAddress); await shareClass.approveDepositsAndIssueShares([ { assetId, approveAssetAmount: Balance.fromFloat(100_000, 6), // approve 100,000 USDC issuePricePerShare: Price.fromFloat(1.05), // price at which shares are issued }, ]); ``` * `approveAssetAmount`: the total asset amount to approve for the current epoch. Must not exceed the pending deposit amount. Omit to skip approval and only issue for a previously approved epoch. * `issuePricePerShare`: the price at which approved assets are converted into shares. Omit to approve without issuing yet. The SDK calculates how many per-investor notify calls fit within the available cross-chain gas budget and includes them automatically. ### Issuing across multiple epochs If shares were not issued immediately when approving (for example, because capital needed to be deployed before the price was known), multiple approved epochs can accumulate. You can issue all of them in one transaction by passing an array of prices, one per epoch in order: ```typescript await shareClass.approveDepositsAndIssueShares([ { assetId, issuePricePerShare: [ Price.fromFloat(1.04), // epoch N Price.fromFloat(1.05), // epoch N+1 ], }, ]); ``` You can also combine an approval with a multi-epoch issuance in the same call, which approves the current pending epoch and then immediately issues all accumulated approved epochs. ## 5. Approve and revoke shares (redemptions) Redemption requests follow the same pattern. Approving allows the balance sheet to start returning assets; revoking burns the shares at the given price. ```typescript await shareClass.approveRedeemsAndRevokeShares([ { assetId, approveShareAmount: Balance.fromFloat(50_000, 18), // approve 50,000 shares for redemption revokePricePerShare: Price.fromFloat(1.05), // price at which shares are converted to assets }, ]); ``` * `approveShareAmount`: the total share amount to approve for the current epoch. Omit to skip approval and only revoke for a previously approved epoch. * `revokePricePerShare`: the price applied when burning shares and returning assets. Omit to approve without revoking yet. The SDK auto-includes `notifyRedeem` calls for investors within the gas budget. ### Revoking across multiple epochs Similarly, multiple approved redemption epochs can be revoked in one transaction by passing an array of prices: ```typescript await shareClass.approveRedeemsAndRevokeShares([ { assetId, revokePricePerShare: [ Price.fromFloat(1.04), // epoch N Price.fromFloat(1.05), // epoch N+1 ], }, ]); ``` ## Processing multiple assets Both methods accept an array, so you can process several assets in one transaction: ```typescript await shareClass.approveDepositsAndIssueShares([ { assetId: usdcAssetId, approveAssetAmount: Balance.fromFloat(100_000, 6), issuePricePerShare: Price.fromFloat(1.05), }, { assetId: usdtAssetId, approveAssetAmount: Balance.fromFloat(50_000, 6), issuePricePerShare: Price.fromFloat(1.05), }, ]); ``` Each asset is processed independently with its own epoch tracking. --- # Overview Source: https://docs.centrifuge.io/developer/centrifuge-sdk/overview # Overview Welcome to the Centrifuge SDK documentation. The Centrifuge SDK is a JavaScript client for interacting with the [Centrifuge](https://centrifuge.io) ecosystem. It provides a comprehensive, fully typed library to integrate investments and redemptions, generate financial reports, manage pools, and much more. ## Installation The SDK is available as an npm package. It it is built to run both client-side and server-side. ```bash pnpm add @centrifuge/sdk ``` ## Basic setup (mainnet) ```typescript const centrifuge = new Centrifuge({ environment: "mainnet", }); ``` :::info[Testnet] To connect to the testnet instead, replace `mainnet` with `testnet`. ```typescript const centrifuge = new Centrifuge({ environment: "testnet", }); ``` ::: ## Key concepts - Pool: A collection of assets that investors can invest in or redeem from. - Vault: A mechanism for handling investments/redemptions via tokenized share classes. - Investor position: Data about an investor's balance, pending orders, and what can be claimed. - Reports: Financial views like token price. ### Query data (read-only) ```typescript const pools = await centrifuge.pools(); ``` ### Perform transactions First set a signer, e.g. a wallet provider: ```typescript centrifuge.setSigner(signer); const poolId = new PoolId(1); const pool = await centrifuge.pool(poolId); const tx = await pool.updatePoolManagers([ { address: "0xAddress", canManage: true, }, ]); console.log(tx.hash); ``` ### Generate reports ```typescript const poolId = new PoolId(1); const pool = await centrifuge.pool(poolId); const report = await pool.reports.sharePrices({ from: fromNum, to: toNum, groupBy: "day", }); console.log(report); ``` ## Example full flow (mainnet) ```typescript async function main() { const centrifuge = new Centrifuge({ environment: "mainnet" }); // set signer (wallet or provider) centrifuge.setSigner(walletProvider); // get a pool const poolId = new PoolId(1); const pool = await centrifuge.pool(poolId); const scId = ShareClassId.from(poolId, 1); const assetId = AssetId.from(centId, 1); // deposit into vault (async) const vault = await pool.vault(11155111, scId, assetId); await vault.asyncDeposit(Balance.fromFloat(1000, 18)); // once processed, claim shares await vault.claim(); // get a report const fromNum = toUTCEpoch("2025-01-01", "s"); const toNum = toUTCEpoch("2025-01-02", "s"); const report = await pool.reports.sharePrices({ from: fromNum, to: toNum, groupBy: "day", }); console.log(report); } main().catch(console.error); ``` --- # Query data of a pool Source: https://docs.centrifuge.io/developer/centrifuge-sdk/query-data-of-a-pool # Query data of a pool This guide shows how to use the Centrifuge SDK to read pool data, vaults, and investor positions. ## Prerequisites Before you begin, make sure you have: - [Node.js](https://nodejs.org/) (v18 or later recommended) - A package manager: [pnpm](https://pnpm.io/), [npm](https://www.npmjs.com/) or [yarn](https://yarnpkg.com/) - A wallet or signer that can connect to Ethereum-compatible chains (e.g. MetaMask, WalletConnect, or a private key account via [Viem](https://viem.sh/)) ## Installation Install the Centrifuge SDK in your project: ```bash pnpm add @centrifuge/sdk ``` ## 1. Initialize the SDK Create a Centrifuge instance and connect it to mainnet: ```typescript const centrifuge = new Centrifuge({ environment: "mainnet", }); ``` :::info For testing purposes, you can connect to testnet instead by setting environment: `testnet`. ::: ## 2. Set a signer To send transactions, attach a signer (for example, from MetaMask or another EIP-1193 compatible provider): ```typescript // Example: using a MetaMask injected provider const provider = (window as any).ethereum; await provider.request({ method: "eth_requestAccounts" }); centrifuge.setSigner(provider); ``` If you’re using a private key or server-side setup, you can also provide a [Viem LocalAccount](https://viem.sh/docs/accounts/local). ## 3. Get a pool Pools are the main entry point to query data: ```typescript // Get a pool by ID const poolId = new PoolId(1); const pool = await centrifuge.pool(poolId); ``` ## 4. Get a pool metadata This includes general pool information such as name, manager, and configuration: ```typescript const metadata = await pool.metadata(); console.log(metadata); ``` ## 5. Query a vault Each pool can contain multiple tokens and each token can have multiple vaults. You can query a single vault using pool ID, token ID, centrifuge ID, and asset address: ```typescript // Get tokenId based on previously defined poolId const tokenId = ShareClassId.from(poolId, 1); const assetId = AssetId.from(centId, 1); const centrifugeId = 1; // Centrifuge network ID // Get a vault const vault = await pool.vault(centrifugeId, tokenId, assetId); ``` or if you do not know token ID and asset ID you can do: ```typescript // This has information about tokens const poolDetails = await pool.details(); const shareClasses = poolDetails.shareClasses; // This will return all the networks to which pool is deployed const poolNetworks = await pool.activeNetworks(); // Now we can loop through networks and tokens to retrieve the desired token ID and call: const vaults = await poolNetwork.vaults(tokenId); // OR const vaults = await shareClass.vaults(centrifugeId); // OR if we do have asset address const vault = await poolNetwork.vault(tokenId, assetAddress); ``` ## 6. Get share token price To get the current price per share of a vault's share token, use the `shareClass.details()` method: ```typescript // Get the share class (token) details const shareClassDetails = await shareClass.details(); // Access the price per share const pricePerShare = shareClassDetails.pricePerShare; console.log(`Price per share: ${pricePerShare}`); ``` The `pricePerShare` field represents the current value of one share token in the vault's underlying currency. It is always denominated as an 18 decimals fixed point integer. ## 7. Query investor position Get the details of the investment of an investor in the vault and any pending deposits or redemptions: ```typescript const investment = await vault.investment("0xInvestorAddress"); console.log(investment.shareBalance); console.log(investment.share); console.log(investment.assetBalance); console.log(investment.assetAllowance); console.log(investment.isAllowedToDeposit); console.log(investment.isAllowedToRedeem); console.log(investment.isSyncDeposit); console.log(investment.maxDeposit); console.log(investment.claimableDepositShares); console.log(investment.claimableDepositAssetEquivalent); console.log(investment.claimableRedeemAssets); console.log(investment.claimableRedeemSharesEquivalent); console.log(investment.pendingDepositAssets); console.log(investment.pendingRedeemShares); console.log(investment.claimableCancelDepositAssets); console.log(investment.claimableCancelRedeemShares); console.log(investment.hasPendingCancelDepositRequest); console.log(investment.hasPendingCancelRedeemRequest); console.log(investment.asset); ``` ## 7. Query reports ```typescript const poolId = new PoolId(1); const pool = await centrifuge.pool(poolId); const report = await pool.reports.sharePrices({ from: fromNum, to: toNum, groupBy: "day", }); console.log(report); ``` --- # Codebase Source: https://docs.centrifuge.io/developer/legacy/centrifuge-chain/codebase # Centrifuge Chain Codebase The Centrifuge Chain functionality is grouped in 4 key groups of modules (also known as pallets). `pool-system` is at the core of everything, tieing together the investment side with the asset side. **Pools** (`pool-system`, `pool-registry`, `investments`, `pool-fees`): The core logic for managing investment pools such as bundling loans, slicing pools into tranches and controlling investment epochs, as well as charging and accruing fees incurred by pool issuers. **Assets** (`loans`, `interest-accrual`, `anchors`, `oracle-`): Managing the assets that make up the value of a pool, computing the NAV (Net Asset Value) of a pool, managing data feeds (oracles) that brings in pricing from external sources, and any other asset related features. **Liquidity Pools** (`liquidity-pools-`, `foreign-investment`, `swaps`, `order-book`, `token-mux`): Enables investment activities on EVM chains using Solidity contracts, bridging the liquidity of EVM ecosystems with the efficiency and security of the Polkadot ecosystem through the flexibly configurable LP Gateway. **Permissioning** (`restricted-(x)tokens`, `transfer-allowlist`): Extension of the token logic that enables strict permissioning, to ensure regulatory compliance for tranche tokens of pools, as well as increased operational security for stablecoins and other pool currencies. ![](./images/centrifuge-chain-pallets.png#width=120%;) ## Centrifuge Chain Pallets On top of the [Substrate FRAME](https://docs.substrate.io/reference/frame-pallets/) framework, Centrifuge Chain is composed of custom pallets which can be found inside the [pallets directory](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets). The following list gives a brief overview, and links to the corresponding documentation: - [**anchors**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/anchors) ([docs](https://reference.centrifuge.io/pallet_anchors/index.html)): Storing hashes of documents on-chain. The documents are stored in the Private Off-chain Data (POD) node network. - [**block-rewards**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/block-rewards) ([docs](https://reference.centrifuge.io/pallet_block_rewards/index.html)): Provides means of configuring and distributing block rewards to collators as well as the annual treasury inflation. - [**collator-allowlist**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/collator-allowlist) ([docs](https://reference.centrifuge.io/pallet_collator_allowlist/index.html)): Tracking active collators, and allows the root account to manage this list. - [**ethereum-transaction**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/ethereum-transaction) ([docs](https://reference.centrifuge.io/pallet_ethereum_transaction/index.html)): Wrapper around the Ethereum pallet which allows other pallets to execute EVM calls. - [**fees**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/fees) ([docs](https://reference.centrifuge.io/pallet_fees/index.html)): Taking fees from accounts and sending this to the treasury, to the author, or burning them. - [**foreign-investments**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/foreign-investments) ([docs](https://reference.centrifuge.io/pallet_foreign_investments/index.html)): Enables investing, redeeming and collecting in foreign and non-foreign currencies. Can be regarded as an extension of `pallet-investments` which provides the same toolset for pool (non-foreign) currencies. - [**interest-accrual**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/interest-accrual) ([docs](https://reference.centrifuge.io/pallet_interest_accrual/index.html)): Keeping account of the outstanding debt through interest accrual calculations. - [**investments**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/investments) ([docs](https://reference.centrifuge.io/pallet_investments/index.html)): Provides orders for assets and allows user to collect these orders. - [**keystore**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/keystore) ([docs](https://reference.centrifuge.io/pallet_keystore/index.html)): Linking public keys to accounts. Supporting the operations of the offchain document consensus layer through the Centrifuge POD (Private Offchain Data) Node. - [**liquidity-pools**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/liquidity-pools-gateway) ([docs](https://reference.centrifuge.io/pallet_liquidity_pools/index.html)): Provides the toolset to enable foreign investments on foreign domains. - [**liquidity-pools-gateway**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/liquidity-pools-gateway) ([docs](https://reference.centrifuge.io/pallet_liquidity_pools_gateway/index.html)): The main handler of incoming and outgoing Liquidity Pools messages. - [**liquidity-rewards**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/liquidity-rewards) ([docs](https://reference.centrifuge.io/pallet_liquidity_rewards/index.html)): Epoch based reward system. - [**loans**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/loans) ([docs](https://reference.centrifuge.io/pallet_loans/index.html)): Locking a collateral NFT into a pool allowing to borrow from the pool. The loans pallet is also used for bookkeeping loan values and outstanding debt. - [**oracle-collection**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/oracle-collection) ([docs](https://reference.centrifuge.io/pallet_oracle_collection/index.html)): Pallet used to collect and aggregate oracle values. - [**oracle-feed**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/oracle-feed) ([docs](https://reference.centrifuge.io/pallet_oracle_feed/index.html)): Pallet used to feed oracle values. - [**order-book**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/order-book) ([docs](https://reference.centrifuge.io/pallet_order_book/index.html)): Allows orders for currency swaps to be placed and fulfilled. - [**permissions**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/permissions) ([docs](https://reference.centrifuge.io/pallet_permissions/index.html)): Linking roles to accounts. It is adding and removing relationships between roles and accounts on chain. - [**pool-fees**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/pool-fees) ([docs](https://reference.centrifuge.io/pallet_pool_fees/index.html)): Stores all the fees related to a pool and allows for these fees to be charged. - [**pool-registry**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/pool-registry) ([docs](https://reference.centrifuge.io/pallet_pool_registry/index.html)): Used for creating, updating, and setting the metadata of pools. - [**pool-system**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/pool-system) ([docs](https://reference.centrifuge.io/pallet_pool_system/index.html)): Creating and managing investment pools. It is bundling loans, slicing pools into tranches, and controlling investment epochs. - [**restricted-tokens**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/restricted-tokens) ([docs](https://reference.centrifuge.io/pallet_restricted_tokens/index.html)): Transferring tokens and setting balances. It is wrapping `orml-tokens` with the addition of checking for permissions. - [**restricted-xtokens**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/restricted-xtokens) ([docs](https://reference.centrifuge.io/pallet_restricted_xtokens/index.html)): Wrapper pallet over `orml-xtokens` which allows the runtime to create arbitrary filters for transfers of x-chain-transfers. - [**rewards**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/rewards) ([docs](https://reference.centrifuge.io/pallet_rewards/index.html)): Implement a [scalable reward distribution](https://solmaz.io/2019/02/24/scalable-reward-changing/) mechanism that can be used for other pallets to create different rewards systems. - [**swaps**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/token-mux) ([docs](https://reference.centrifuge.io/pallet_token_mux/index.html)): Enables applying swaps independently of previous swaps in the same or opposite directions. - [**token-mux**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/token-mux) ([docs](https://reference.centrifuge.io/pallet_token_mux/index.html)): Enables proxying variants of the same foreign assets to a local asset representation. - [**transfer-allowlist**](https://github.com/centrifuge/centrifuge-chain/tree/main/pallets/transfer-allowlist) ([docs](https://reference.centrifuge.io/pallet_transfer_allowlist/index.html)): This pallet checks whether an account should be allowed to make a transfer to a receiving location with a specific currency. --- # Contributing Source: https://docs.centrifuge.io/developer/legacy/centrifuge-chain/contributing # Contributing to Centrifuge Chain The development of the Centrifuge Chain benefits greatly from the diverse perspectives and skills of our external contributors. Whether you are fixing a bug, proposing a new feature, or enhancing the existing documentation, your efforts are highly valued. Join us in further developing this open-source project by sharing your unique contributions. A practical entry point for contributors is to address issues tagged as [Q0-trivial or Q1-easy](https://github.com/centrifuge/centrifuge-chain/issues?q=is%3Aopen+is%3Aissue+label%3AQ1-easy%2CQ0-trivial+). Making corrections to typos in our Rustdocs or inline comments is equally important and greatly appreciated. The sections below provide guidance on compiling the repository, executing unit tests, operating a local chain, verifying proposed runtime versions, conducting benchmarks, and updating to newer versions of the Polkadot SDK. ## Build Install [Rust](https://www.rust-lang.org/tools/install): Initialize your Wasm Build environment: ```bash ./scripts/init.sh install-toolchain ``` Build Wasm and native code: - Prerequisites : `cmake`, `libclang-dev` ```bash cargo build --release ``` Great! You have already compiled the Centrifuge Chain! ## Tests There are two kinds of tests, one related to how the _Centrifuge Chain_ works itself and another one to verify how it works in a more real environment as a parachain. ### Chain tests The following command will run the unit and integration tests: ```bash cargo test --workspace --release --features runtime-benchmarks,try-runtime ``` ### Environment tests You can deploy a relay chain and connect a Centrifuge Chain node as parachain to it to verify how it behaves in the entire environment (end-to-end). 0. Prerequisites. You must install these tools before: - [docker](https://docs.docker.com/get-docker/) - [_jd_](https://stedolan.github.io/jq/) 1. Start a local [relay chain](https://wiki.polkadot.network/docs/learn-architecture#relay-chain). It contains two [validator](https://wiki.polkadot.network/docs/learn-validator) nodes (Alice and Bob): `bash ./scripts/init.sh start-relay-chain ` After a few seconds you can see the block production of the relay chain using the [polkadot.js (on localhost:9944)](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2Flocalhost%3A9944#/explorer) client. *Note: You can stop the relay chain using `./scripts/init.sh stop-relay-chain`* 2. Start a _Centrifuge Chain_ as [parachain](https://wiki.polkadot.network/docs/learn-parachains). It runs a [collator](https://wiki.polkadot.network/docs/learn-collator) node: `bash ./scripts/init.sh start-parachain ` _Note: the command above will show logs and block until the parachain is stopped. If you had a previous state, you can reset the node using `purge` after the command._ Similar to the relay chain, you can explore the parachain using the [polkadot.js (on localhost:11936)](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2Flocalhost%3A11936#/explorer) client. You will see the block production frozen until you connect it to the relay chain. By default, the initialized parachain will have the id `2000`. Please note that for ephemeral chains specs such as `development`, `centrifuge-local` or `altair-local`, the parachain ID is currently hardcoded. If you need to customize it, please open an issue and we will add such a feature. If choose other chain specifications, you can set the `PARA_CHAIN_SPEC` env var, e.g.: `bash PARA_CHAIN_SPEC=development ./scripts/init.sh start-parachain ` The different`PARA_CHAIN_SPEC` values can be found at [`src/command.rs`](https://github.com/centrifuge/centrifuge-chain/blob/main/node/src/command.rs) under the `load_spec()` function. 3. Onboard the parachain This step will have the targeted parachain onboarded in the relay chain. The parachain will NOT produce blocks until this step is completed successfully. ```bash ./scripts/init.sh onboard-parachain ``` When you have run the command, you should see in the relay-chain dashboard that there is a parachain that will be onboarded in one/two minutes. Once onboarded, block production should start soon after in the parachain. That's all! The environment is set. You can play with it from the parachain client, make transfers, inspect events, etc. ## Linting ### Source code Lint the source code with `cargo fmt --all`. This excludes certain paths (defined in `rustfmt.toml`) that we want to stay as close as possible to `paritytech/substrate` to simplify upgrading to new releases. ### Cargo.toml files 1. Install [taplo](https://github.com/tamasfe/taplo) with `cargo install taplo-cli`. 2. Lint the `Cargo.toml` files with `taplo fmt`. ## Verifying Runtime 1. Check out the commit at which the runtime was built. 2. Build the WASM via `cargo build --release` 3. Ensure the output from [subwasm](https://github.com/chevdor/subwasm) matches the release one. Run `subwasm info ./target/release/wbuild/centrifuge-runtime/centrifuge_runtime.compact.compressed.wasm`, which creates an output like the following one: ``` 🏋️ Runtime size: 1.819 MB (1,906,886 bytes) 🗜 Compressed: Yes, 78.50% ✨ Reserved meta: OK - [6D, 65, 74, 61] 🎁 Metadata version: V14 🔥 Core version: centrifuge-1025 (centrifuge-1.tx2.au1) 🗳️ system.setCode hash: 0x210cdf71ee5c5fbea3dc5090c596a992b148030474121b481a856433eb5720f3 🗳️ authorizeUpgrade hash: 0x319e30838e1acc9dd071261673060af687704430c45b14cdb5fc97ec561e2a12 🗳️ Blake2-256 hash: 0xb7f74401c52ee8634ad28fe91e8a6b1debb802d4d2058fdda184a6f2746477f6 📦 IPFS: https://www.ipfs.io/ipfs/QmS3GDmbGKvcmSd7ca1AN9B34BW3DuDEDQ1iSLXgkjktpG ``` 4. The `Blake2-256` hash should match the hex of the `authorizeUpgrade` call. See more [here](https://github.com/centrifuge/centrifuge-chain/blob/main/docs/runtime-upgrade.md). ## Generate new Spec and Parachain files This script will take a valid chain-spec chain_id and a flag to build new spec or not, and will output genesis spec (raw and plain), wasm and state files. ```shell ./scripts/export_parachain_files.sh demo true ``` Adapt parameters accordingly. ## Benchmarking ### Benchmarking runtimes When benchmarking pallets, we are just running the benchmarking scenarios they specify within their mocked runtime. This fails to actually benchmark said pallet in the context in which it will be actually used in production: within a specific runtime and composing with other pallets. To cover that, we run test for every pallet for a given runtime and use the output weights in production since those are the most trustworthy weights we can use. Note: This command should be run in a cloud environment that mimics closely the specs of the collator node the parachain will be running on. ```shell ./scripts/runtime_benchmarks.sh ``` ## Updating to a newer version of Polkadot When a new version of Polkadot is released, companion releases happen for the other parity projects such as the Polkadot SDK, as well as for other third-party projects such as the `ORML` pallets, `xcm-simulator`, etc. Therefore, updating this repository to a new version of Polkadot means updating all of these dependencies (internal and external to Centrifuge ) and have them all aligned on the same version of Polkadot. _Note: When we say "new version of Polkadot", we implicitly mean "Polkadot SDK, Frontier, ORML"._ The high level flow to upgrade to a newer version of Polkadot is: 1. Update all the Centrifuge-Chain dependencies to a revision that also points to the last version of Polkadot 2. Fix all the breaking changes introduced by the latest versions ### Update dependencies Since the Centrifuge Chain repository uses workspace inheritance, dependency revisions only need to be configured in the root `Cargo.toml`. 1. **Update the `patch` rules in `Cargo.toml`** The cargo patch rules ensure that we use specific revision for the Polkadot SDK, Frontier and others, by pointing to a specific git revision or branch. For each of the projects covered by these rules, look up whether there exists a git branch for the specific Polkadot SDK version to which you want to upgrade. 2. **Repeat step 1. for the other Centrifuge repositories that the Centrifuge Chain depends on** For each of those repositories, create a new branch out of the latest `polkadot-vX.Y.Z` and repeat step 1 for each of them. - [centrifuge/fudge](https://github.com/centrifuge/fudge) - [foss3/runtime-pallet-library](https://github.com/foss3/runtime-pallet-library) - [centrifuge/go-substrate-rpc-client](https://github.com/centrifuge/go-substrate-rpc-client) - NOTE: Only required in case of breaking client changes (i.e. deprecations or new host functions) 3. **Back to Centrifuge-chain, update the crates in the projects updated in step 2.** For example, if before we have a dependency on `fudge` at branch `polkadot-v1.1.1`, update it to `polkadot-v1.7.0`. Note: assuming `1.7.0` is the version we are updating to. 4. **Repeat step 3. for other third-party dependencies that also depend on Polkadot/Substrate/Cumulus** If any of the third-party projects we depend on don't yet have a branch or release for the new Polkadot version, either wait or fork said project and run step 1 for it and open a PR and point that revision. - [`orml` pallets](https://github.com/open-web3-stack/open-runtime-module-library) - [`frontier` pallets](https://github.com/moonbeam-foundation/frontier/) - [xcm-simulator](https://github.com/shaunxw/xcm-simulator) - etc 5. **Build and test the project and migrate any new introduced changes** Now that all dependencies are aligned with the latest version of Polkadot, run build and test commands and address any compilation issue. ### Troubleshooting If you face compilation errors like "type X doesn't implement trait Y", and the compiler doesn't suggest you import any particular trait, experience is that there are different versions of the Polkadot SDK being pulled; The `cargo patch` rules in `Cargo.toml` should be handling that so if this still happens it's likely because some new crate of Polkadot SDK or Frontier is being pulled directly or indirectly and we need to include that crate in the appropriate `cargo patch` rule. --- # EVM compatibility Source: https://docs.centrifuge.io/developer/legacy/centrifuge-chain/evm # Centrifuge Ethereum Compatibility Centrifuge merges the capabilities of the Ethereum and Polkadot ecosystems, enabling developers to utilize Ethereum’s established tools and infrastructure alongside Polkadot’s scalable and interoperable features. The sections that follow outline the specifics of Ethereum compatibility, underscore important elements, and delineate the primary distinctions between Centrifuge and Ethereum, providing clarity for Ethereum developers on what to expect. ## EVM compatibility Centrifuge features a fully integrated Ethereum Virtual Machine (EVM), enabling the execution of smart contracts written in Solidity or other EVM-compatible languages. ## Accounts Ethereum addresses (H160) are adapted for use on Centrifuge by expanding them to fit the Substrate address format. This involves adding some extra information to ensure compatibility with Centrifuge’s system: - **Chain ID**: The EVM chain ID is added as 8 bytes in big-endian format. - **Tag**: The address is tagged with "EVM". - **Zero** Byte: A single zero byte is appended. This enhanced format facilitates the recognition of Ethereum addresses within Centrifuge and enables their conversion back to the original. **Example**: The Ethereum address `0x7F429e2e38BDeFa7a2E797e3BEB374a3955746a4` is converted to the Centrifuge address `0x7F429e2e38BDeFa7a2E797e3BEB374a3955746a400000000000007ef45564d00` which is equivalent to `4eDcBEZ6Kor2HGLhX9XUQd9j3t69G9zs17B1E5AfYfQ2LNqY` in the Centrifuge [SS58 address format](https://docs.substrate.io/reference/glossary/#ss58-address-format). ## JSON-RPC Support Centrifuge offers full JSON-RPC compatibility with Ethereum through its default RPC endpoints. This allows developers to use familiar Ethereum tools and libraries to interact with Centrifuge nodes for tasks such as account management, transaction submission, smart contract deployment, and event monitoring. For more information, please refer to the [official Ethereum JSON-RPC API documentation](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendtransaction). ## Blocks Centrifuge uses [Frontier](https://github.com/moonbeam-foundation/frontier/) to provide Ethereum-compatible blocks. Each Ethereum block is derived from the state of the Substrate chain when the Substrate block is finalized. Ethereum block metadata is stored on-chain as part of the final Substrate block data. - The block number in Ethereum matches the block number of the corresponding Substrate block. - The parent block hash is the Ethereum block hash of the previous Substrate block. In case of a Substrate chain fork, Ethereum blocks will fork alongside it. ## Precompiles Centrifuge supports all native Ethereum precompiles, including `DELEGATECALL`. Additionally, it includes the following precompiles: - **ERC-20 Precompile**: This enables transfers of the native CFG token through an ERC-20 compatible interface. - **Custom Precompile**: Centrifuge has an Axelar gateway precompile, which handles incoming Liquidity Pools messages from the Axelar network. By supporting these precompiles, Centrifuge ensures efficient execution of smart contracts and compatibility with Ethereum's cryptographic functions. --- # Networks Source: https://docs.centrifuge.io/developer/legacy/centrifuge-chain/networks # Centrifuge Networks Centrifuge operates three networks, each tailored for specific stages of development, testing, and deployment. The native assets across all networks have 18 decimals of precision. | **Feature** | **Centrifuge** | **Altair** | **Demo** | | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | --- | | **Network Type** | Mainnet | Mainnet (Canary) | Testnet | | **Parachain ID** | 2031 | 2088 | 2031 | | **EVM Chain ID** | 2031 | 2088 | 2090 | | **Native Asset Symbol** | CFG | AIR | DEVEL | | **Fullnode** | [fullnode.centrifuge.io](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Ffullnode.centrifuge.io) | [fullnode.altair.centrifuge.io](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Ffullnode.altair.centrifuge.io) | [fullnode.demo.cntrfg.com](https://polkadot.js.org/apps/?rpc=wss://fullnode.demo.k-f.dev/public-ws/#explorer) | | **Fullnode Relay** | [polkadot.api.onfinality.io](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fpolkadot.api.onfinality.io%2Fpublic-ws) | [kusama.api.onfinality.io](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fkusama.api.onfinality.io%2Fpublic-ws) | [Moonbase Relay](https://polkadot.js.org/apps/?rpc=wss://fro-moon-rpc-1-moonbase-relay-rpc-1.moonbase.ol-infra.network) | | **Block Explorer** | [Subscan](https://centrifuge.subscan.io/) | [Subscan](https://altair.subscan.io/) | N/A | | **Governance** | [OpenSquare](https://voting.opensquare.io/space/centrifuge) | [OpenSquare](https://voting.opensquare.io/space/altair) | N/A | | **LP Router Explorers** | [Axelarscan](https://axelarscan.io/gmp/search?chain=centrifuge) | N/A | [Testnet Axelarscan](https://testnet.axelarscan.io/gmp/search?chain=centrifuge-2) | | --- # Overview Source: https://docs.centrifuge.io/developer/legacy/centrifuge-chain/overview # Centrifuge Chain Overview Centrifuge Chain is our blockchain built with Rust and the [Polkadot SDK](https://github.com/paritytech/polkadot-sdk), purpose built for real-world assets. Pools (and their assets, tranches, etc), [onchain governance](https://docs.centrifuge.io/use/governance-process/) and the Centrifuge treasury, and the CFG token live on Centrifuge Chain. Investors can invest in assets on Centrifuge from other chains via Liquidity Pools deployed on supported chains. For example, a Liquidity Pool deployed on Ethereum allows users on Ethereum to invest in a pool on Centrifuge without leaving Ethereum. ## Centrifuge Chain Efficiencies Centrifuge Chain is optimized specifically for the transactions required by our specific use case. This focus allows us to improve upon our current architecture in a few key ways: speed, cost, storage efficiencies, and privacy. Ethereum works well for low volumes of high value transactions. High volumes of privacy-requiring use-cases require a different solution. The average business user, SMBs and large enterprises alike, would be paying many times more using Centrifuge on Ethereum compared to their existing solutions. It wouldn’t be worth it for most businesses to make a switch. But what if we could lower that cost and have high throughput capabilities? ## Optimization for use cases The transactions on the Centrifuge Chain are optimized for the small subset of operations needed by our specific use case. This allows for faster execution of logic and finality of transactions. The optimization of transactions, together with our PoS architecture, is also what brings down the transaction costs dramatically. This encourages decentralization because less resources are required to run a node. Building our own chain also allows us to improve upon the user and developer experience for Centrifuge. Our users require privacy, and this is something we can build for directly — targeting the features they need from the start. For developers, we can provide custom APIs and tools that come with the blockchain node itself instead of smart contract APIs which are harder to integrate with. While there are downsides to building a single purpose chain, the advantages for our use case outweigh the costs. Integration with other Ethereum and DeFi projects becomes a bit more involved. Our experience with Ethereum development, combined with a standardized bridge to get data to/from our Parity Substrate based chain reduces the overhead substantially, while still benefiting from the upside of our own chain. --- # Centrifuge POD Source: https://docs.centrifuge.io/developer/legacy/pod # Centrifuge POD The Centrifuge POD network is built to support a new generation of applications for the financial supply chain. Centrifuge provides users with the ability to remove intermediaries and create financial business documents as Non-Fungible Tokens (NFTs) that have long-term verifiability, are censorship resistant, and are stored and processed in a decentralized fashion. The Centrifuge POD node provides a method to create, exchange, and use the data that exists in the financial supply chain. It creates transparent and shareable relationships between interacting companies. Data owners can selectively share the information with their business partners or other users of the network. Centrifuge provides a censorship resistant way to verify the authenticity of data that is transacted through and stored in it. This creates the foundation for data ownership, privacy, and transparency throughout the financial supply chain and also allows third parties to offer additional services, such as instant and decentralized financing of invoices and orders, trade credit insurance and financing supply chains multiple levels deep. The underlying Centrifuge protocol has a two layered approach. It is built on Substrate which allows businesses to transact freely on a single verifiable source of truth. The public blockchain is used for business identities, committing document status and minting business NFTs. In addition, a peer to peer network enables the exchange of business documents in a private and verifiable way. ## Substrate Substrate is a modular blockchain framework that enables creating custom blockchains. Centrifuge uses Substrate as the source of truth for document anchoring, heavily involved in the peer to peer document consensus protocol. For more information, see the [Parity Substrate Project](https://docs.substrate.io/) ## Protocol Architecture The components of the Centrifuge protocol are a collection Substrate Pallets and a peer to peer (P2P) network implemented on [libp2p](https://libp2p.io/). Substrate Pallets are used for maintaining identities, minting NFTs from off-chain Centrifuge documents, and anchoring state commitments. ![Centrifuge architecture](./images/architecture.png) ## Centrifuge PODs The Centrifuge POD provides a simple API interface to interact with the p2p network and the Centrifuge Chain. The POD operates on a “service bus” principal where plugins and outside systems can subscribe to messages about specific objects (e.g., a procurement application can subscribe to changes of order objects). The POD abstracts the events that occur on the public blockchain and P2P Layer and translates them into messages on this internal bus for other applications to consume. The POD also offers the connectivity to Centrifuge Chain for applications that build on top of the network. ### Centrifuge Identities A Centrifuge Identity (CentrifugeID) is a unique ID assigned to a participant of Centrifuge in a network. It keeps track of the different cryptographic keys in use and enforces that this data can only be modified by the creator and/or a delegate chosen by the creator. An identity has the following credentials: - Peer to Peer Messaging Encryption Keys: are used for message encryption. These keys are used to identify the nodes over the P2P network and establish an encrypted communication channel between peers. - Signing Keys: Documents in Centrifuge are signed with signing keys. These signatures are a part of the Merkle root that is anchored on the public chain and verifiable at a later time. The unique identifier of a participant in the Centrifuge protocol is equivalent to the Centrifuge Chain account ID. ## Documents within the protocol A document within the Centrifuge protocol is a structured set of fields with specific types. The protocol supports any document types as long as the formats are agreed upon and shared between the participants. E.g.: A document can be an invoice or a purchase order with agreed upon fields and line items. The structure of the document becomes important for reaching consensus by attaching signatures to the document state, as well as creating specific attestations about a document at a later point in time. Documents are exchanged encrypted, and are only accessible for parties involved in this private data exchange. Collaborators can be added and removed from a document. Different collaborators can update a document and publish new versions within the set of nodes with access. ## Centrifuge Chain In order to interact with Centrifuge Chain, you can either start your own node and sync with the network or use one of the public full nodes that Centrifuge provides: - mainnet: `wss://fullnode.centrifuge.io` - catalyst: `wss://fullnode.catalyst.cntrfg.com` ## Creating a Centrifuge Chain Account ### Install Parity Substrate Subkey Before you can create a new Centrifuge Chain account, you have to install the latest version of Parity Substrate [Subkey](https://github.com/paritytech/substrate/tree/master/bin/utils/subkey). To install, we recommend you follow the instructions found [here](https://docs.substrate.io/reference/command-line-tools/subkey/). Alternatively, you can use the docker image - `parity/subkey:latest`. ### Create a new account #### Mainnet ```bash $ subkey --sr25519 --network centrifuge generate ``` #### Testnets ```bash $ subkey --sr25519 generate ``` You can now fund the newly generated Centrifuge Chain account with CFG by making a request in our discord `#dev` channel ## POD Bootstrap ### Installation Before being able to transfer and anchor financial documents and mint NFTs, you need to spin up a Centrifuge POD on your machine and [create an account](#account-creation). Follow these steps to install the Centrifuge POD: 1. Download and install the latest [Centrifuge POD binary](https://github.com/centrifuge/pod/releases). If you want to build the node from source, follow the description in the [source code](https://github.com/centrifuge/pod/blob/main/README.md). 2. Add the Centrifuge binary to the `$PATH` or modify the command invocation to point to the correct library. ### Configuration Run `centrifuge createconfig` as seen in the example below. This command automatically creates an identity and the required key pairs. It then generates the `config.yaml` file required to run the node. ```bash $ centrifuge createconfig \\ -n mainnet \\ -t \\ -a 8082 -p 38204 \\ --centchainurl \\ --ipfsPinningServiceName pinata \\ --ipfsPinningServiceURL \\ --ipfsPinningServiceAuth \\ --podOperatorSecretSeed \\ --podAdminSecretSeed \\ ``` **NOTE**: - **The generated `config.yaml` includes sensitive information regarding the accounts used to authenticate and sign transactions. Make sure to store it in a secure environment.** - `podOperatorSecretSeed` - if this is omitted a new secret seed will be generated by the node, please see [POD operator](#pod-operator) for more information regarding this account. - `podAdminSecretSeed` - if this is omitted a new secret seed will be generated by the node, please see [POD admin](#pod-admin) and [token usage](#usage) for more information regarding this account. - For more information regarding IPFS pinning, please see [IPFS](#ipfs). #### Network Configurations Besides `mainnet`, Centrifuge has support for the `catalyst` test network. The network configuration for the different test networks is also part of the [code base](https://github.com/centrifuge/pod/blob/main/build/configs/default_config.yaml). This enables the client user to run on top of them with minimum configuration needed. Please find the most important information summarized below: ##### Catalyst Use network `-n catalyst`. This network is a test network running a version of the Centrifuge Chain modified for testing. - Client: parity - Purpose: Testnet - Bootstrap Nodes - ask our team in the DAO Slack channel. ##### Mainnet Use network `-n mainnet`. This network is the production network, the Centrifuge Chain. - Client: parity - Purpose: Mainnet - Bootstrap Nodes - ask our team in the DAO Slack channel. #### Changing the default configuration The default configuration with all available options is accessible [here](https://github.com/centrifuge/pod/blob/main/build/configs/default_config.yaml). You may adjust certain configurations according to your requirements. - Configure node under NAT If you want your node to be accessible outside your private network, you will need to manually specify the External IP of the node: ```yaml p2p: externalIP: "100.111.112.113" ``` #### Open ports for incoming P2P connections To accept the incoming P2P connections, you will need to open two ports for incoming TCP connections. - P2P Port: open ingress/egress. This port will be configured under `p2p` `port` in your config. - API Port: restrict at will, only you or your upstream systems should need to talk to it. This port will be configured as `nodeport` in your config. ### Running the Centrifuge POD after creating the configuration You can run the Centrifuge POD using the `config.yaml` file you created: ```bash $ centrifuge run -c //config.yaml ``` Replace the `PATH-TO-CONFIG-DIR` with the location of the `config.yaml` file. ### Post Install Verification To make sure that your Centrifuge POD setup was successful and is running properly you can ping your node. ```bash $ curl -X GET "http://localhost:8082/ping" -H "accept: application/json" ``` It will return (e.g. Catalyst): `{"version":"...","network":"catalyst"}` --- ## Accounts The `Accounts` section of our [swagger API docs](https://app.swaggerhub.com/apis/centrifuge.io/cent-node/3.0.0#/Accounts) provides an overview of all the endpoints available for handling accounts. --- An account is the POD representation of the user that is performing various operations. The identity of this account is used when storing documents and performing any action related to the document handling process such as - starting long-running tasks for committing or minting documents, or sending the document via the p2p layer. ### Account Data The data stored for each account has the following JSON format: ```json { "data": [ { "identity": "string", "document_signing_public_key": [0], "p2p_public_signing_key": [0], "pod_operator_account_id": [0], "precommit_enabled": true, "webhook_url": "string" } ] } ``` `identity` - hex encoded Centrifuge Chain account ID. This is the identity used for performing the operations described above. `document_signing_public_key` - read-only - public key that is used for signing documents, this is generated for each account that is created on the POD. `p2p_public_signing_key` - read-only - public key that is used for interactions on the P2P layer, this is generated during POD [configuration](#configuration). `pod_operator_account_id` - read-only - the [POD operator](#pod-operator) account ID. `precommit_enabled` - flag that enables anchoring the document prior to requesting the signatures from all collaborators. `webhook_url` - URL of the [webhook](#webhooks) that is used for sending updates regarding documents or jobs. ### Account Creation An account can be created by calling the [account creation endpoint](https://app.swaggerhub.com/apis/centrifuge.io/cent-node/3.0.0#/Accounts/generate_account_v2) with a valid admin token (see [token usage](#usage)), and providing the required information - `identity`, `precommit_enabled`, `webhook_url`. **IMPORTANT** - the `identity` must be a valid account on the Centrifuge Chain meaning that it **MUST** hold funds or have some proxies (which is the case for a pure/anonymous proxy). The successful response for the account creation operation will contain the fields mentioned above in [account data](#account-data). ### Account Boostrap **NOTE** - The following steps are required to ensure that the POD can use a newly created account. 1. Store the `document_signing_public_key` and `p2p_public_signing_key` in the `Keystore` storage of Centrifuge Chain. This can be done by submitting the `addKeys` extrinsic of the `Keystore` pallet. 2. Add the POD operator account ID as a `PodOperation` proxy to the `identity`. This can be done by submitting the `addProxy` extrinsic of the `Proxy` pallet. Example script using our [Go Substrate RPC Client](https://github.com/centrifuge/go-substrate-rpc-client): ``` func bootstrapAccount( api *gsrpc.SubstrateAPI, rv *types.RuntimeVersion, genesisHash types.Hash, meta *types.Metadata, accountInfo types.AccountInfo, krp signature.KeyringPair, ) error { addProxyCall, err := types.NewCall( meta, "Proxy.add_proxy", delegateAccountID, 10, // PodOperation types.NewU32(0), // Delay ) if err != nil { return fmt.Errorf("couldn't create addProxy call: %w", err) } discoveryKeyHash := types.NewHash(discoveryKeyBytes) documentSigningKeyHash := types.NewHash(documentSigningKeyBytes) type AddKey struct { Key types.Hash Purpose uint8 KeyType uint8 } addKeysCall, err := types.NewCall( meta, "Keystore.add_keys", []*AddKey{ { Key: discoveryKeyHash, Purpose: 0, // P2P Discovery KeyType: 0, // ECDSA }, { Key: documentSigningKeyHash, Purpose: 1, // P2P Document Signing KeyType: 0, // ECDSA }, }, ) if err != nil { return fmt.Errorf("couldn't create addKeys call: %w", err) } batchCall, err := types.NewCall( meta, "Utility.batch_all", addProxyCall, addKeysCall, ) if err != nil { return fmt.Errorf("couldn't create batch call: %w", err) } ext := types.NewExtrinsic(batchCall) opts := types.SignatureOptions{ BlockHash: genesisHash, // using genesis since we're using immortal era Era: types.ExtrinsicEra{IsMortalEra: false}, GenesisHash: genesisHash, Nonce: types.NewUCompactFromUInt(uint64(accountInfo.Nonce)), SpecVersion: rv.SpecVersion, Tip: types.NewUCompactFromUInt(0), TransactionVersion: rv.TransactionVersion, } err = ext.Sign(krp, opts) if err != nil { return fmt.Errorf("couldn't sign extrinsic: %w", err) } sub, err := api.RPC.Author.SubmitAndWatchExtrinsic(ext) if err != nil { return fmt.Errorf("couldn't submit and watch extrinsic: %w", err) } defer sub.Unsubscribe() select { case st := | ### Token The format of the JW3 token that we use is: `base_64_encoded_json_header.base_64_encoded_json_payload.base_64_encoded_signature` Where the un-encoded parts are as follows: Header: ```json { "algorithm": "sr25519", "token_type": "JW3T", "address_type": "ss58" } ``` --- Payload: ```json { "address": "delegate_address", "on_behalf_of": "delegator_address", "proxy_type": "proxy_type", "expires_at": "1663070957", "issued_at": "1662984557", "not_before": "1662984557" } ``` `address` - SS58 address of the proxy delegate (see [usage](#usage) for more info). `on_behalf_of` - SS58 address of the proxy delegator (see [usage](#usage) for more info). `proxy_type` - one of the allowed proxy types (see [usage](#usage) for more info): - `PodAdmin` - defined in the POD. - `Any` - defined in the Centrifuge Chain. - `PodOperation` - defined in the Centrifuge Chain. - `PodAuth` - defined in the Centrifuge Chain. `expires_at` - token expiration time. `issued_at` - token creation time. `not_before` - token activation time. --- Signature - the `Schnorrkel/Ristretto x25519` signature generated for `json_header.json_payload`. --- ### Usage The POD has 2 types of authentication mechanisms: 1. On-chain proxies - this is the most commonly used mechanism, and it is used to authenticate any on-chain proxies of the identity. In this case, the `address`, `on_behalf_of` and `proxy_type` should contain the information as found on-chain. Example: `Alice` - identity. `Bob` - proxy of `Alice` with type `PodAuth`. Token payload: ```json { "address": "ss58_address_of_bob", "on_behalf_of": "ss58_address_of_alice", "proxy_type": "PodAuth", "expires_at": "1663070957", "issued_at": "1662984557", "not_before": "1662984557" } ``` 2. POD admin - this is used when performing authentication for restricted endpoints. In this case, the `address` and `on_behalf_of` fields should be equal and contain the SS58 address of the POD admin, and the `proxy_type` should be `PodAdmin`. Example: ```json { "address": "pod_admin_ss58_address", "on_behalf_of": "pod_admin_ss58_address", "proxy_type": "PodAdmin", "expires_at": "1663070957", "issued_at": "1662984557", "not_before": "1662984557" } ``` --- ## REST API Example Uses Once the Centrifuge POD is up and running you are able to start submitting documents and tokenize these documents via the Rest API. Please refer to the [swagger API docs](https://app.swaggerhub.com/apis/centrifuge.io/cent-node/3.0.0) documentation for a complete list of endpoints. A short summary can be found below: ### NFTs The `NFTs` section of our [swagger API docs](https://app.swaggerhub.com/apis/centrifuge.io/cent-node/3.0.0#/NFTs) provides an overview of all the endpoints available for handling document NFTs. The NFT endpoint provides basic functionality for minting NFTs for a document and retrieving NFT specific information such as attributes, metadata, and owner. #### IPFS When minting NFTs, additional information is stored on-chain and on IPFS, as follows: - document fields that are specified in the minting request are saved on IPFS under the following format: ```json { "name": "ipfs_name", "description": "ipfs_description", "image": "ipfs_image", "properties": { "AssetIdentifier": "0x25680a49ff1b6368f7e243130ff957f9523b917c8c83d79aab97c0ef99fd3b15", "AssetValue": "100", "MaturityDate": "2022-10-13T11:07:28.128752151Z", "Originator": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", "result": "0x0000000000000000000000000000000100000000000000000000000000000064" } } ``` **NOTE** - at the moment, the only IPFS pinning service that is supported is [pinata](https://www.pinata.cloud/). - the IPFS hash of the above mentioned fields is set as metadata to the NFT on chain, in the following format - `/ipfs/QmfN7u6hMRHxL83Jboa4bHgme4PJmcS4eQFnkrXye5ctAM` - the document ID and document version are set as attributes to the NFT on chain. **NOTE** - All the above information can be found on chain by querying the related storages of the `Uniques` pallet. --- ### Documents The `Documents` section of our [swagger API docs](https://app.swaggerhub.com/apis/centrifuge.io/cent-node/3.0.0#/Documents) provides an overview of all the endpoints available for handling documents. The main purpose of the POD is to serve as a handler for documents that contain private off-chain data, as described above. --- ### Jobs The `Jobs` section of our [swagger API docs](https://app.swaggerhub.com/apis/centrifuge.io/cent-node/3.0.0#/Jobs) provides an overview of all the endpoints available for retrieving job details. --- The jobs endpoint returns detailed information for a job. A job is a long-running operation that is triggered by the POD when performing actions related to documents and/or NFTs. ### Webhooks The `Webhook` section of our [swagger API docs](https://app.swaggerhub.com/apis/centrifuge.io/cent-node/3.0.0#/Webhook) provides an overview the notification message that is sent by the POD for document or job events. --- ## Disclaimer ### Centrifuge is provided "As Is" The "Software", which includes but is not limited to the source code of components of Centrifuge, related repositories, client implementations, user interfaces, compiled or deployed binaries and smart contracts all of its components, libraries, supporting services (including, but not limited to, build pipelines, tests, deployments, "boot nodes", code samples, integrations) is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors, maintainers, operators or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software. Centrifuge and all its components are Beta Software, which might and will lead to substantial changes in the future, re-architecture, addition and removal of features, as well as unexpected behavior. Use at your own risk. ## Protocol Limitations Centrifuge is in an early stage of its development. The protocol and its first client implementation have a limited feature set compared to the end-vision. Not all features are implemented yet, and tradeoffs between security, speed, end-user features, and protocol flexibility are made continuously. Following is a list of important limitations and not yet implemented features of Centrifuge. ### The Meaning of a Signature When two Centrifuge POD exchange documents with each other, they automatically attach signatures to the transferred documents after validation of the data payload and signatures/keys. A Centrifuge POD validates the structural integrity of a received document as well as the validity of previous signatures compared to the public keys of the corresponding Centrifuge ID of the counterparty. A Centrifuge POD itself does not validate if the document data makes sense from a business point of view. A Centrifuge POD is a technical client to Centrifuge. This client exchanges and signs data in well-known formats. It does not validate document data authenticity. Data authenticity and correctness are always validated by the upstream system. E.g. the accounting system interacting with a Centrifuge POD. A signature of a collaborator on a Centrifuge document signifies the technical receipt and validation of a message. It does not signify the agreement that a document itself is valid, e.g. if an invoice amount is matching the underlying purchase order. It is possible to attach additional signatures to a document (e.g., with custom attributes) to indicate "business agreement" of a document. However, this is not part of the protocol specifications and is the responsibility of an upstream system. ### Collaborator List Visible to all Collaborators Important: Nobody outside of a document can view or deduce the parties who collaborate on a document. However, the list of collaborators on any single document is visible to all of the document's collaborators. This is part of the implementation approach where signatures are gathered from all collaborators on a document when anchoring a new state. To do this, the list of collaborators has to be known when making an update. For the initial implementation, we assume that businesses only add their already known and trusted business partners to a document as a collaborator rendering this limitation insignificant. ### No Document Forking Centrifuge does not support forking or successive merging of document state. If disagreement of document state between collaborators exist this has to be solved by the user by creating a new document. Collaborators can withhold their signature on a given document update if they choose to do so. The mitigation to this behavior is to remove the withholding/offline collaborator from the document's collaborator list and re-issue the document update and/or create a new document based on the original document data with a new set of collaborators. For the initial implementation, we assume that businesses only add their trusted business partners to a document as a collaborator. With that, the likelihood of disagreement on the protocol level is low. #### Blocking Document Updates by Malicious Collaborator It is possible for a malicious collaborator to publish a new document version that blocks other collaborators from updating the original document. This can be done by the malicious collaborator by removing all collaborators from the original document and then publishing a new version with the "next identifier," essentially preventing other collaborators from publishing a new version of the document with this identifier. Mid-term this will be mitigated by supporting document forking. Short-term the mitigation is as described above: The users can create a new document with the last benign document data and do not add the malicious actor as a collaborator to the document. This will create a new chain of document updates that the malicious collaborator can neither access nor block. For the initial implementation, we assume that businesses only add their trusted business partners to a document as a collaborator. With that, the likelihood of a malicious actor trying to block document updates is low. #### Blocking Document Updates by "Accident"/Race condition Two or more collaborators could try to update a document at the same time. The "first" update that goes through (the first version being anchored) essentially blocks the other from updating the desired document version. Mitigation is to always have "pre-commit" enabled. Mid-term this is also possible to be mitigated by supporting document forking/merging. ### No Collaborator Signatures Required to Anchor It is possible for any collaborator to anchor a new document version at any time. Previous collaborator's signatures are not required to anchor/publish a new document version. This is less of a limitation and more of a feature to prevent malicious collaborators from blocking documents by withholding signatures. Mid-term a feature could be added that requires an `x of n` signature scheme where a certain threshold of collaborator signatures is required to anchor a new state. For now, anybody can publish a new version of a document. --- # Querying V2 data Source: https://docs.centrifuge.io/developer/legacy/querying-v2-data # Querying V2 data You can follow the official GraphQL guide [here](https://graphql.org/learn/) to learn more about GraphQL, how it works, and how to use it: - There are libraries to help you implement GraphQL in your application. - For an in-depth learning experience with practical tutorials, see How to GraphQL. - Check out the free online course, Exploring GraphQL: A Query Language for APIs. ## Endpoints | **Network** | **GraphQL Endpoint** | | ----------- | ------------------------------------------------------------ | | Centrifuge | https://api.subquery.network/sq/centrifuge/pools | | Dev | https://api.subquery.network/sq/centrifuge/pools-development | ## Sample Queries Queries can be tested in a dedicated [SubQL Sandbox](https://explorer.subquery.network/subquery/embrio-tech/centrifuge-subql). The [SubQL documentation](https://academy.subquery.network/run_publish/query.html) provides some insights on how to perform queries in their SubQuery explorer. Here some important hints and common pitfalls that can save you some time when working woth our data: - Currencies and Token amounts are expressed in fixed decimal precision As pools have different reference currencies, the amount this precision can vary. For this matter we reference the Currency entity in Pools, so that the correct amount of decimals can be queried together with each pool. - Queries return a maximum of 100 entries per page by default This can be increased to a maximum of 1000 entries per page in production environments. Sanbox environments are limited to 100 results. - Entities ids are not necessarily the same as on chain ids Therefore, when querying an entity, always refer to the GraphQL data model to verify how the id is composed. ### Get net portfolio valuation and active loans for all Centrifuge Pools ```graphql { pools { nodes { id currency { id decimals } portfolioValuation sumNumberOfActiveLoans } } } ``` ### Get balances and last investor transactions for an account ```graphql { account(id: "kALNreUp6oBmtfG87fe7MakWR8BnmQ4SmKjjfG27iVd3nuTue") { id outstandingOrders { nodes { investAmount redeemAmount trancheId } } investorTransactions(last: 2) { nodes { type currencyAmount tokenAmount } } currencyBalances { nodes { amount } } trancheBalances { nodes { trancheId sumInvestOrderedAmount sumInvestCollectedAmount sumInvestUncollectedAmount } } } } ``` ### Get outstanding debt information for loans belongig to a pool ```graphql { pool(id: "2825130900") { id currency { id decimals } loans { nodes { id outstandingDebt } } } } ``` ### Get historical token price and token supply evolution for a given tranche token ```graphql { trancheSnapshots( orderBy: TIMESTAMP_ASC filter: { trancheId: { equalTo: "1873519631-0xb05f8e95eaf6ffc940ab4b4fbcb6324b" } } ) { nodes { id timestamp tokenPrice tokenSupply } } } ``` ### Get TVL for single pools or for the entire ecosystem The TVL for each pool can be obtained with the following query: ```graphql { pools { nodes { value } } } ``` The total for the entire CFG ecosystem is obtained by summing across all results. --- # Tinlake smart contracts Source: https://docs.centrifuge.io/developer/legacy/tinlake # Tinlake Smart Contracts (Legacy) For a brief description of the Tinlake protocol go to [here](/getting-started/legacy/centrifuge-v1). ### Repositories Tinlake is implemented in Solidity and deployed on Ethereum mainnet. The source-code can be found on [Github](https://github.com/centrifuge/tinlake): | Repository | Desc | | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------ | | https://github.com/centrifuge/tinlake | Main Tinlake Repository | | https://github.com/centrifuge/tinlake-math | Tinlake Math Libary. Adopted from ds-math. | | https://github.com/centrifuge/tinlake-erc20 | Tinlake ERC20 implementation. Re-uses the ERC20 implementation from DAI. | | https://github.com/centrifuge/tinlake-title | ERC721 implementation used for Tinlake. | | https://github.com/centrifuge/tinlake-deploy | Tinlake deploy scripts build with bash and seth | | https://github.com/centrifuge/tinlake-proxy | Tinlake proxy contract used for the borrower interactions | | https://github.com/centrifuge/tinlake-actions | Tinlake actions is a libary used via delegate call by the proxy | | https://github.com/centrifuge/tinlake-auth | Tinlake authentification pattern libary | | https://github.com/centrifuge/tinlake-maker-lib | Tinlake Maker adapter. MIP22 implementation | | https://github.com/centrifuge/tinlake-rpc-tests | Tinlake RPC tests for the Maker integration. Tests run against live Mainnet or Kovan deployment. | | https://github.com/centrifuge/tinlake-maker-tests | Tinlake Maker integration system tests. | | https://github.com/centrifuge/tinlake-spells-mainnet | Tinlake Mainnet spells (changes on Mainnet deployments) | | https://github.com/centrifuge/tinlake-spells-kovan | Tinlake Kovan spells (changes on Mainnet deployments) | | https://github.com/centrifuge/tinlake-pools-cli | Tinlake pool management | | https://github.com/centrifuge/tinlake-tranche-worker | Contract for cancelling all orders in a pool. (Required for contract upgrades) | #### Audits Centrifuge has performed multiple audits of its codebase: - [Trail of Bits](https://www.trailofbits.com/) (1 audit) - Centrifuge Chain - [Least Authority](https://leastauthority.com/) (3 audits) - Tinlake v0.2 - Tinlake v0.3 - Centrifuge Chain - [DappHub](https://dapphub.com/) (1 audit) - Tinlake v0.3.5 All audit reports can be found [here](https://github.com/centrifuge/security/tree/master/audits). ### Security Vulnerability Disclosure No technology is perfect or perfectly secure. Centrifuge believes that working with skilled security researchers across the globe is crucial in identifying weaknesses in any technology. We welcome the contribution of external security researchers and look forward to awarding them for their invaluable contribution to the security of all our users. [Read more about security here](https://centrifuge.io/security/). ### Deployments Each Tinlake pool is an individual deployment of the set of smart contracts. A full list of all deployed pools, including metadata, can be found [here](https://github.com/centrifuge/tinlake-pools-mainnet). ## Overview of Smart Contracts ### Introduction The design of Tinlake was inspired by the design of the MakerDAO contracts. Some design patterns and best practices like the `auth pattern` have been adopted in the Tinlake contracts. The current version of Tinlake v0.3.5 has around 15 contracts. It is important to notice that a deployment of Tinlake can only manage one pool. The current multiple pools on tinlake.centrifuge.io are re-deployments of the same source code. The reasoning behind this is to keep the logic as simple as possible. ### Module overview At its core, Tinlake has _two_ main modules: - **Borrower Module** - **Lender Module** Each of the modules consists of multiple contracts written in Solidity. The main purpose of the lender module is to handle the investor requests and maintain the pool constraints. On the other hand, the borrow module keeps track of the individual loans from the asset originators. ### Contract Transaction Diagram ![](./images/tinlake_contracts_diagram.png) ## Contract interactions ### Borrower Interactions The Borrow module contract handles the individual loans of the issuers. The main actions of an issuer are: - **open** - Opens a new loan - **lock** - Locks a NFT as collateral for loan - **borrow** - Requests an amount of to borrow from the lender module - **withdraw** - Withdraws a currency amount after the borrow requests - **repay** - Repays a loan - **close** - Close the loan after the debt has been repaid ### Lender Interactions On the lender side of Tinlake, the there are three main interactions: - Supply Order - Redeem Order - Disburse (Collect) #### Supply Order A user can create a supply order by locking DAI/stablecoin in Tinlake. ![](./images/supply_order.png#width=60%) - During an _epoch_, the locked amount of DAI can be changed - After the _epoch_ is closed, it is not possible to change the order - If the _epoch_ is executed, the disburse method must be called to collect the DROP token - If it is not possible to fulfill the entire supplyOrder, the rest is automatically resubmitted in the next epoch ``` Example: - Alice wants to invest 100 DAI into the seniorTranche (DROP). - She calls supplyOrder on the senior operator contract. - Her DAI is transferred to the seniorTranche contract. - The epoch is closed and the current DROP price of 1.5 DAI is calculated - 60% of all supplyOrders can be fulfilled - Alice calls the disburse method and she receives - (100 DAI* 0.6) / 1.5 = 40 DROP tokens on her address - The remaining 100-60 = 40 DAI are a new supplyOrder in the next epoch - Alice calls supplyOrder and changes the order to zero DAI - Alice receives the 40 DAI back ``` - The tokenPrice which Alice gets for her investment is determined when the epoch is closed. #### Redeem Order #### Redeem Order A user can redeem their DROP or TIN tokens in exchange for DAI/stablecoin with a redeem order. ![](./images/redeem_order.png#width=60%) #### Disburse Order The disburse method can be called to collect tokens from an executed supply order or DAI/stablecoin from a successfully executed redeem order. ![](./images/disburse_order.png#width=60%) For creating a new redeem or supply order, the disburse method needs to be called before. The token price of a supply or redeem order is defined by the `epoch` and not by the price. ## Lender Variables From a high level perspective, the lender contracts need to track the investments, perform the interest rates calculations, and ensure that the junior tranche takes the losses first. The following variables are needed to track the state of the these actions. #### Reserve The total currency (ERC20) locked in the reserve contract. In live Tinlake pools as of time of writing, the currency is the DAI stablecoin. #### NAV Net Asset Value of all outstanding loans. #### PoolValue The pool value is the total value in a Tinlake pool. It includes the currency in the reserve and net asset value of the outstanding loans. $$ \text{poolValue} = \text{reserve} + \text{NAV} $$ #### SeniorAsset The seniorAsset is the amount which belongs to the senior investor (_DROP_) in a pool. **Expected SeniorAsset** $$ \text{expectedSeniorAsset} = \text{seniorDebt} + \text{seniorBalance} $$ **SeniorDebt** SeniorDebt is the amount which accrues interest for the senior tranche. $$ \text{seniorDebt} = \text{seniorDebt} * \text{seniorInterestRate} $$ **SeniorBalance** SeniorBalance is the amount of the seniorTranche which is not used for interest accumulation. **Example** ``` Tinlake Pool: ------------------------------------------ | NAV: 80 DAI | juniorAsset: 10 DAI | | Reserve: 20 DAI | seniorAsset: 90 DAI | ------------------------------------------ In this pool, 80% of the pool value is used for loans. Therefore, 80% of the seniorAsset should be used for interest accumulation. seniorDebt: 90 DAI * 0.8 = 72 DAI seniorBalance: 90 DAI - 72 DAI = 18 DAI Let's say the interest rate is 10%. The seniorDebt would increase in one time period. seniorDebt: 72 DAI * 1.10 = 79.2 DAI seniorBalance: = 18.0 DAI seniorAsset: 97.2 DAI ``` The senior value represents the value of the senior/DROP tranche. It is calculated as: $$ \text{seniorAsset} = min(\text{expectedSeniorAsset}, \text{poolValue}) $$ If loans are defaulting, the juniorAsset would cover the losses. If the entire juniorAsset is lost, the poolValue could be lower than the expectedSeniorAsset. The poolValue can be also expressed as: $$ \text{poolValue} = \text{seniorAsset} + \text{juniorAsset} $$ #### JuniorAsset The juniorAsset is the amount of the poolValue which belongs to junior investors (`TIN`). The difference between the seniorAsset value poolValue is the juniorAsset. $$ \text{juniorAsset} = max(\text{poolValue} - \text{seniorAsset}, 0) $$ In case of losses, they are first covered by the junior investors. **seniorSupply** Is the total amount of minted ERC20 DROP tokens. **juniorSupply** Is the total amount of minted ERC20 TIN tokens. #### SeniorRatio The seniorAssetRatio is defined as: $$ \text{seniorRatio} = \frac{\text{seniorAsset}}{\text{poolValue}} $$ It describes the percentage of the poolValue which belongs to senior investors. $$ \text{juniorRatio} = 1 - \text{seniorRatio} = \frac{\text{juniorAsset}}{\text{poolValue}} $$ The juniorRatio is an important metric in the pool because it defines the protections of the junior investors. The percentage of loan defaults in a pool has to be higher than the juniorRatio until the senior investors are affected. In the contracts the seniorRatio is used. #### JuniorRatio Increase The following investor actions increase the juniorRatio: - TIN supply - DROP redeem #### JuniorRatio Decrease The following investor actions decrease the juniorRatio: - TIN redeem - DROP supply #### Epoch Execute: State Variable Changes The lender state variables in Tinlake are changing either because of: - Interest Accumulation - Borrow/Repay Loans - Epoch Execute In an epoch execution, the orders which can be fulfilled are changing the lender state - $\text{TIN}_{invest}$ - $\text{DROP}_{invest}$ - $\text{TIN}_{redeem}$ - $\text{DROP}_{redeem}$ #### Reserve Amount of DAI available in the reserve. $$ \text{Reserve}_{e+1} = \text{Reserve}_{e} + \text{TIN}_{invest} + \text{DROP}_{invest} - \text{TIN}_{redeem} - \text{DROP}_{redeem} $$ Notation: $\text{Reserve}_{e+1}$ new reserve in the next epoch after of the execution of the current. $\text{Reserve}_{e}$ describes the current. #### NAV Net asset value of all ongoing loans expressed in DAI. The NAV is not impacted by the orders but relevant for the constraint calculation. #### SeniorAsset $$ \text{SeniorAsset}_{e+1} = \text{SeniorAsset}_{e} + \text{DROP}_{invest} - \text{DROP}_{redeem} $$ Note: This is a simplification of the seniorAsset formula and does not contain losses. (Not relevant for the solver). #### JuniorAsset $$ \text{JuniorAsset}_{e+1} = NAV + \text{Reserve}_{e+1} - \text{SeniorAsset}_{e+1} $$ Note: This is a simplification of the juniorAsset formula. ## Lender Contracts ### Coordinator Contract ``` The coordinator contract manages the epochs for the investors. ``` #### Main purpose - Closing and executing epochs - Responsible for determining the fulfillments of supplyOrders and redeemOrders in an epoch - Maintaining the pool constraints - Allowing and handling submissions if not all orders can be fulfilled - Validating and scoring submitted solutions #### Contract Diagram ![](./images/coordinator.png) #### Coordinator State Diagram ![](./images/coordinator_state.png) #### Main Functions **closeEpoch** ```javascript function closeEpoch() external minimumEpochTimePassed ``` - An epoch can be closed after a minimum epoch time has passed - `closeEpoch` creates a snapshot of the current lender state - If all orders can be fulfilled without violating any constraints, the epoch is executed - Otherwise the submission period starts **executeEpoch** ```javascript function executeEpoch() public ``` **executeEpoch** ```javascript function submitSolution() public ``` **submitSolution** ```javascript function submitSolution(uint seniorRedeem, uint juniorRedeem, uint juniorSupply, uint seniorSupply) public returns(int) ``` ### Tranche Contract #### Tranche Module Contracts Overview A Tinlake deployment has two tranche modules deployed: - _DROP_ Tranche Module - _TIN_ Tranche Module #### Module Diagram ![](./images/tranche_modules.png) A tranche module has four contracts - **Operator Contract** - Can be directly called by an investor - **Memberlist Contracts** - Maintains a whitelisted list of investors for a specific Tinlake pool - **Tranche Contract** - Main tranche contract - **Token (ERC20) Contract** - ERC20 contract for the interest bearing token ``` The Tranche contracts maintains the orders of investors. ``` #### Main Purpose - Managing supply and redeem orders - Calculating the correct disburse amount over multiple epochs - Minting of DROP or TIN tokens - Holding DAI and tokens from locked orders and non-collected disburse amounts #### Main Functions **function supplyOrder** ```javascript function supplyOrder(address usr, uint newSupplyAmount) public auth orderAllowed(usr) ``` The `supplyOrder` function can be used to place or revoke an order. The method is called by the operator contract. The initial call is initiated by an investor. **function redeemOrder** ```javascript function redeemOrder(address usr, uint newRedeemAmount) public auth orderAllowed(usr) { ``` The `redeemOrder` function can be used to place or revoke a redeem. **function disburse** ```javascript function disburse(address usr, uint endEpoch) public auth returns (uint payoutCurrencyAmount, uint payoutTokenAmount, uint remainingSupplyCurrency, uint remainingRedeemToken) ``` The `disburse` function can be used after an epoch is over to receive currency and tokens. The collection can be used over multiple epochs. **Example** ``` Alice: supplyOrder: 100 DAI Epochs: epoch n: 40% of all orders can be fulfilled tokenPrice: 1.2 epoch n+1: 30% of all orders can be fulfilled tokenPrice: 1.5 Alice calls the disburse function in epoch n+2: epoch n: 100 DAI * 0.4 /1.2 = 33.33 DROP epoch n: supplyOrder(amountLeft) = 60 DAI epoch n+1: 60 DAI * 0.3/1.5 = 12 DROP Disburse Amount: 33.33 DROP + 12 DROP = 45.33 DROP ``` The contract maintains the supply and redeem orders for epochs. One for DROP and one for TIN. If an epoch gets executed, the tranche contract mints new token or transfers currency to reserve. At any point in time, the contract can hold tokens or the stablecoin-currency. The balances are not considered as part of the Tinlake reserve. The contract maintains the supply and redeem orders for epochs. One for DROP and one for TIN. If an epoch gets executed, the tranche contract mints new token or transfers currency to reserve. At any point in time, the contract can hold tokens or the stablecoin-currency. The balances are not considered as part of the Tinlake reserve. A locked amount for a supply or redeem order can be changed as long as the epoch is still ongoing. On the other side, users might have successfully supplied or redeemed their tokens or currency but didn't collect them. ### Assessor Contract ``` The assessor contract keeps track of the state and the constraints of lender module . ``` **changeSeniorAsset** ```javascript function changeSeniorAsset(uint seniorSupply, uint seniorRedeem) external auth ``` - Method is called by the coordinator in epoch execute - Updates the senior asset and performs the rebalancing **calcJuniorTokenPrice** ```javascript function calcJuniorTokenPrice(uint nav_, uint reserve_) public view returns (uint) ``` - Returns the current junior token price **calcSeniorTokenPrice** ```javascript function calcSeniorTokenPrice(uint nav_, uint reserve_) public view returns(uint) ``` - Returns the current senior token price ### Reserve Contract ``` The reserve contracts holds the currency and offers methods for deposit and payout. ``` **deposit** ```javascript= function deposit(uint currencyAmount) public auth ``` - Deposits currency into the reserve - Currency is transferred from `msg.sender` ### Operator Contract ``` The operator contract manages the allowances for investors. ``` ## Borrower Contracts ### Contract diagram ![](./images/borrower_contracts.png) ### IDs The loanID itself is an ERC721 NFT contract called `Title`. ### NFT's We need to distinguish between three different NFTs used in the Tinlake contracts. - Collateral NFT - A collateral NFT is used as collateral in Tinlake to borrow a loan - Title NFT - Tracks the ownership of a Tinlake loan. - Only the owner of the title NFT can repay a Tinlake loan and unlock the collateral NFT * Access NFT - The borrowers in Tinlake interact via a proxy contract with the shelf contract - The authentication in the proxy contract happens with an Access NFT - The owner of the NFT can call execute on the proxy contract ### Shelf contract ``` The Shelf contract handles all loan related actions. ``` The collateral NFTs are locked in the Shelf contract. #### Main Functions **issue** ```javascript function issue(address registry_, uint token_) external note returns (uint) ``` This is the first step in the loan process. It issues (or creates) a new loan in Tinlake. Issuing a new loan requires the ownership of a collateral NFT that will be locked in the next step of the loan creation process. It combines a collateral NFT with a loan ID. **lock** ```javascript function lock(uint loan) external owner(loan) note ``` Locks the collateral NFT in the shelf. This requires the ID of an issued loan, and the ownership of both the corresponding loan NFT and the collateral NFT. **borrow** ```javascript function borrow(uint loan, uint currencyAmount) external owner(loan) ``` This starts the borrow process of a loan. The method can only be called if the collateral NFT is locked. Calling borrow informs the system of the requested currencyAmount. This requires a `max ceiling` (~max borrow amount) for the collateral NFT to be defined by an oracle in the NAV feed. If no max ceiling has been provided in the NAV feed contract, the maximum borrow amount would be zero. **withdraw** ```javascript function withdraw(uint loan, uint currencyAmount, address usr) external owner(loan) note ``` - Transfers the requested currencyAmount to the address of the loan owner - The method triggers the reserve to ensure the shelf has enough currency **repay** ```javascript function repay(uint loan, uint currencyAmount) external owner(loan) note ``` - Repays the debt of a loan - Partial repayment is supported **unlock** ```javascript function unlock(uint loan) external owner(loan) not ``` - Unlocks the NFT and transfers it back to the msg.sender - msg.sender is the proxy contract in most cases - Rquires a debt of zero **close** ```javascript function close(uint loan) external note ``` - Closes a loan after the entire debt has been repaid. ### Pile contract ``` The pile contract manages the interest rate accumulations for loans. ``` The default implementation of the Pile allows creating of different interest rate groups and assigning each loan a rate group. Each interest rate group has an interest rate that is calculated on a per second compounding basis. Its task is to report the outstanding debt for each loan with the method `debt(uint loan) returns (uint)`. The method `accrue(uint loan)` needs to be called by the Shelf before any modification of the debt is made to update the current debt. This is to ensure that any other methods relying on that data (such as the Ceiling contract) get the most up to date debt(). Whenever `decDebt` and `incDebt` are called, first the debt is updated with the compounded interest and then the debt is increased or decreased by the specified amount. #### Main Functions **incDebt** ```javascript function incDebt(uint loan, uint currencyAmount) external auth note ``` Increases the debt of a loan by a currencyAmount. **decDebt** ```javascript function decDebt(uint loan, uint currencyAmount) external auth note ``` - Increases the debt of a loan by a currencyAmount. Decrease the loan's debt by a currencyAmount. **debt** ```javascript function debt(uint loan) external view returns (uint) ``` - Returns the current debt based on actual block.timestamp. **claim** ```javascript function claim(uint loan, address usr) public auth note ``` - Auth function to seize a loan with the collector contract - Ownership of the collateral NFT is transferred to the msg.sender ### Collector contract ``` The collector contract can seize defaulted loans. ``` #### Main Functions **seize** ```javascript function seize(uint loan) external ``` - If the loan debt is above the loan threshold, the NFT should be seized - The ownership of the nft is transferred to the collector **file price** ```javascript function file(bytes32 what, uint loan, address buyer, uint nftPrice) external auth ``` - For a seized loan, a price can be set with an auth call - If the buyer is equal to address(0), everyone can buy the default loan **collect** ```javascript function collect(uint loan, address buyer) external auth ``` - After a price has been set, a buyer can buy the loan - The collateral nft is transferred to the buyer The collector functionality is part of Tinlake, but it is not in active usage. ### NAV Feed contract The main purpose of the NAV feed is to maintain the priced values of collateral NFTs and to calculate the NAV. ##### Risk Groups The NAV feed maintains risk groups for individual collateral NFTs. A risk group has the following **properties**: - **risk group ID** - uint256 - **thresholdRatio** - uint256 - Percentage value in Fixed27 (10^27 = ONE) - If the loan debt reaches thresholdRatio \* nftValue, the loan can be seized (See collector contract) - **ceilingRatio** - uint256 - Percentage value in Fixed27 (10^27 = ONE) - Defines the maximum amount that can be borrowed - Maximum borrow: ceilingRatio \* nftValue - **interestRate** - uint256 - Interest rate per second for risk group - **recoveryRatePD** - uint256 - Percentage value in Fixed27 (10^27 = ONE) - Defines the expected return considering a default rate and loss given default ## NAV ### Introduction to Tinlake NAV For a high level introduction to the NAV in Tinlake, please visit the [Pool Valuation (NAV) documentation](https://docs.centrifuge.io/learn/pool-valuation/). In this document, the NAV formulas as seen as given. The focus of this section is how to efficiently implement the NAV calculation on-chain in Solidity. The reader should be familiar with the following financial concepts: - **NAV** (Net Asset Value) - **FV** (Future Value) - **P** (Present Value) - **D** (Discount Rate) - **PD** (Probability of Default) - **LGD** (Loss Given Default) - **EL** (Expected Loss) From a finance perspective, the current NAV implemented in Tinlake is a simple one-cash flow DCF (Discounted Cash Flow) valuation approach. The idea is to have it on-chain for full transparency on how token prices are calculated in Tinlake. The NAV contract serves two purposes in the Tinlake system. - Calculating the current NAV - Storing information about the individual collateral NFTs ### NAV Formulas/Calculation #### Future Value (FV) The future value is the expected amount of a loan repayment. In the most cases all the loans will be fully repaid. However, a certain percentage may default. The underlying collateral will be sold and a certain amount should be recoverable. In Tinlake this is expressed in the expected return factor. #### Expected Return Factor It includes a given probability of default (PD) and loss given default (LGD) per each loan risk group. This can be expressed as one variable. ``` Expected Loss = PD * LGD ``` ``` Expected Return = 1 - ExpectedLoss ``` Note, `Expected Return` is also denoted Recovery Rate in finance. In the Solidity contract, the variable is called **recoveryRatePD**. #### Future Value Calculation for the future value of a loan. ``` P.....principal (loan borrow amount) i.....interest rate per second m.....maturityDate (unixTimestamp) now...unix timestamp now ER....expected return factor (0-1) FV.... future value of a loan ``` $$ FV = P*i^{m-now}*ER $$ **Example**: Alice wants to borrow 100 DAI for 2 years with 5% interest per year. The probability of default **over two years** is 1% and loss given default is 20%. ExpectedLoss: 0.01 _ 0.2 = 0.002 ExpectedReturnFactor: 1 - 0.002 = 0.998 FV = 100 DAI _ 1.05^(2023-2021) _ 0.998 = 100 _ 1.05^2 \* 0.998 = 110.0295 DAI Note: For illustration, time and interest is in years instead of seconds. #### Present Value of a Loan ``` FV...future value of a loan d....discount rate of a loan m.....maturityDate (unixTimestamp) now...unix timestamp now PV.....present value of a loan ``` #### PV before Maturity date (Discounting) $$ PV = \frac{FV}{d^{m-now}} $$ It is important to note that the present value of the loans is depending on the block.timestamp in Solidity. **Example**: Alice loan has a future value of 110.0295 DAI. Let's assume a discount rate of 3.00%. In the year 2022 the present value would be: p = 110.0295\/(1.03^(2023-2022)) = 106.82 DAI Note: For illustration time and interest is in year instead of seconds. #### Total Discounting The total discounting is the sum over all present values of the loans before the **maturity date**. ``` td....total Discounting pv.....present value of a loan ``` $$ \text{totalDiscount} = \sum_{i=1}^{loans} PV_{i} $$ $$ \text{ For all loans where (maturityDate >= now) } $$ ### Overdue loans An overdue loan in Tinlake is defined as a loan with `now > maturityDate` and `isWrittenOff(loan) == false`. Each individual loan can be immediately written off if `maturityDate > now`. The standard write-off function is a `public` method which can be called by everyone. However, if a loan is not writtenOff, it is considered as **overdue**. In that case the presentValue should be equal to future value until the loan is written-off. $$ PV = FV $$ #### Total Overdue Loans $$ \text{overdueLoans} = \sum_{i=1}^{loans} FV_{i} $$ For all loans where `(maturityDate m$ it is a way to avoid a negative pow operation in Solidity. We can define `errTotalDiscount` as the following $$ \text{errTotalDiscount} = \sum_{i=1}^{loans} FV * d^{now-m} $$ For all loans where `maturityDate lastUpdate`. ### Total Discounting Formula $$ \text{totalDiscount}_{t+n} = \text{totalDiscount}_t * d^n - \text{errTotalDiscount} $$ ### Solidity Implementation: Total Discount In the final Solidity implementation, we don't iterate over all loans to calculate the `errTotalDiscount`. The loan are grouped in `buckets`. All loans with the same maturity date are grouped into one bucket. ```solidity= // Solidity pseudo code uint errTotalDiscount = 0; uint nnow = uniqueDayTimestamp(block.timestamp); // find all new overdue loans since the last update // calculate the discount of the overdue loans which is needed // for the total discount calculation for(uint i = lastNAVUpdate; i = \text{DROP Ratio}_{min} $$ #### Currency Available Constraint The orders are restricted by the currency available in the pool after an epoch execution. This constraint considers new investments through supply orders. $$ \text{Reserve}_{e+1} > 0 $$ ``` Example: Reserve: 5 DAI DROP supplyOrder: 10 DAI DROP redeemOrder: 15 DAI Solution: DROP supplyOrder: 10 DAI DROP redeemOrder: 15 DAI ``` #### Max Order Constraint This is a helper constraint. A submitted solution is not allowed to be higher than the total orders. $$ \text{DROP}_{invest} = 0 $$ $$ \text{TIN}_{invest} >= 0 $$ $$ \text{TIN}_{redeem}>= 0 $$ $$ \text{DROP}_{redeem} >= 0 $$ ### Decision Function The decision function in the Tinlake contracts uses different weights to achieve the following order: - 1. DROP Redeem - 2. TIN Redeem - 3. TIN Invest - 4. DROP Invest **Weights** $$ \text{DROP}_{redeem} = 1.000.000 $$ $$ \text{TIN}_{redeem} = 100.000 $$ $$ \text{TIN}_{supply} = 10.000 $$ $$ \text{DROP}_{supply} = 1.000 $$ **Max Function** $$ \max \text{DROP}_{redeem} * \text{DROP}_{redeem} + \text{TIN}_{redeem} * \text{TIN}_{redeem} + \text{TIN}_{supply} * \text{TIN}_{supply} + \text{DROP}_{supply} * \text{DROP}_{supply} $$ See More: [Centrifuge Design Doc: Solver - Decision Function](https://centrifuge.hackmd.io/qor69CIXSl6pw-hAq2ocAQ) ### State Improvements Submissions In a Tinlake pool, two different types of constraints exist - Core Constraints - Pool Constraints A submitted solution always needs to satisfy the core constraints. It is possible that a current pool state violates the pool constraints. The seniorRatio can be higher or lower than the min and max ratio. This can happen by a NAV decrease or increase. The reserve can violate the maxReserve if a high repayment happened or the asset originator changed the maxReserve. If the ratio or maxReserve violates a constraint, we define a pool as unhealthy. **Unhealthy State** In the case of a pool being in a unhealthy state, there a two possibilities in an epoch execution: - Change of the pool state from unhealthy to healthy with the orders - The pool stays unhealthy; only a potential improvement is possible It is not possible to detect on-chain in which of the two cases a pool is. ``` Example: reserve: 100k DAI maxReserve: 110 DAI seniorRedeemOrder: 20k The 20k seniorRedeem orders could fix the unhealthy state. Instead, if we only would have 5k redeemOrders, we could only improve the reserve constraint. (We are assuming here the redeemOrders would not violate any other constraints) ``` ### Improvement Score In case of an unhealthy state, the pool accepts improvements of the current situation as a solution until it sees a solution which satisfies all constraints. The highest priority is to fix the seniorRatio if it is broken. ``` Example Broken Senior Ratio: current seniorRatio: 0.95 maxSeniorRatio: 0.8 minSeniorRatio: 0.7 ``` The second priority is to fix the maxReserve constraint if it is broken too. **Ratio Improvement Score** If a submitted solution is outside of the senior Ratio range. $$ \text{minSeniorRatio} <= \text{seniorRatio} <= \text{maxSeniorRatio} $$ The score of the ratio is calculated in the following way: $$ d = |\text{seniorRatio} -\frac{\text{minSeniorRatio}+\text{maxSeniorRatio}}{2}| $$ $$ \text{ratioScore} = \text{weight} * \frac{1}{d} $$ This results in the following scoring behavior. The closer the ratio is to the minRatio or maxRatio, the higher the score. Note: The scoring function only applies if the current ratio is not within the range. **Reserve Improvement Score** If the current reserve is higher than the maxReserve, the improvement score is: $$ \text{reserveScore} = \text{weight} * \frac{1}{\text{maxReserve} - \text{reserve}} $$ **If a first improvement is submitted the score of the current state are used as a baseline** The submitted solution needs to have a higher score than the current state. If the current orders would not improve the state, no new orders are accepted. ``` Example: reserve Constraint is violated but only new supplyOrders exist ``` ### Interest Rebalancing The senior investors are getting a fixed interest rate on their investment for the currency of ongoing loans. Currency which is in the reserve does not accrue interest. Only DAI which is used for loans can accrue interest. The current NAV multiplied with the current seniorRatio is the interest bearing amount for the seniorAsset. **Rebalancing** In rebalancing, the total seniorAsset value stays the same. Only the relation between seniorDebt and seniorBalance changes. $$ \text{seniorDebt} = \text{NAV} * \text{seniorRatio} $$ $$ \text{seniorBalance} = max(\text{seniorAsset} - \text{seniorBalance}, 0); $$ With every epoch that has executed supply\/redeem transactions, the relation between senior und junior tranches changes. The rebalancing is happening as part of the epochExecute. **Loan Borrow/Repay** If a loan is repaid or borrowed, it changes the NAV and reserve. The borrow and repayment amounts are updates to the balance between `seniorDebt` and `seniorBalance`. **JuniorAsset increase** The juniorAsset is defined as the difference between the poolValue and the seniorAsset. Since the NAV is continuously increasing, it results in a juniorAsset increase. This increase of the juniorAsset is only considered in the rebalancing at the end of an epoch. ### Off-chain - LP Solver Library The solver is currently run from our Tinlake Bot, as well as manually callable from the Tinlake UI. These are both Javascript environments. However, no linear solver exists in vanilla Javascript which fits our requirements. Therefore, a while back we ported CLP, a well tested C++ based linear solver, using WebAssembly: - https://github.com/centrifuge/clp-wasm The clp-wasm library contains a C++ wrapper which makes sure the precision we need (with the usage of the fixed point arithmetic) is achieved. The actual input and constraints are then defined in tinlake.js, our JS client library for Tinlake. The code for this can be found [here](https://github.com/centrifuge/tinlake.js). ## Maker integration Some DROP tokens of specific Tinlake pools are accepted as collateral in Maker. In this case, a `Debt ceiling` is defined my Maker governance. In exchange for providing liquidity, DROP tokens are locked in a Maker vault as collateral. Some pools also include an additional over-collateralization as an additional protection. In a liquidation scenario, the overcollateralization would be paid by TIN token holders. ![](./images/maker_adapter.png) ### DROP Token as collateral Important metrics to use DROP as a collateral in Maker are: - DROP token price - current price of DROP if redeemed for DAI - tinRatio - the juniorAsset value protects drop investors in case of losses ### Maker Creditline If the DROP token of a specific Tinlake pool is accepted as collateral in Maker, a `debt ceiling` is defined my Maker governance. Tinlake itself maintains a variable called `creditline`. The asset originator can change the creditline by calling - raise - raises the creditline - sink - sink the creditline The creditline needs to be `<=` debt ceiling. The creditline defines how much an asset originator wants to borrow at maximum from the Maker vault. **Raise/Sink** A raise or sink of the creditline is not triggering a borrowing or repayment of DAI. It only indicates that it will happen soon and ensures that the pool is ready for it. The pool check if a borrow from Maker would violate any of the constraints. A borrow from Maker results in a seniorRatio increase. From the constraint perspective, the Maker DROP is already considered as part after the raise. It is not possible to redeem the required TIN for the Maker creditline. ``` Example: Raise creditline 10k DAI seniorRatioRange: 0,10 - 0,9 seniorAsset: 10k juniorAsset: 200k seniorRatio: 0,11 It is possible for nearly all TIN investors to leave the pool. raise creditline: 890k (no overcollateralization) validate constraint perspective: seniorAsset: 900k juniorAsset: 200k seniorRatio: 0,9 It would be not possible for TIN investors to redeem after the raise. ``` From the constraint perspective in the epoch, it doesn't matter if the amount is already borrowed or not. **Draw** Borrows DAI from Maker and uses DROP as collateral. ### Over-Collateralization The Maker community can define a over-collateralizaton for DROP to be accept as collateral in Maker. ``` Example: Overcollateralization of 110% dropPrice: 1.5 draw Amount: 100 DAI collateralValue: 100 DAI 1.10 = 110 DAI collateral in DROP: 110 DAI/1.5 = 73.33 DROP ``` The required over-collateralization of DROP for Maker is paid by TIN investors in case of liquidation scenario. ``` Example: State: seniorAsset: 80 DAI | 80 DROP | dropPrice: 1.0 juniorAsset: 20 DAI | 20 TIN | tinPrice: 1.0 Reserve: 100 DAI dropPrice: 1.0 seniorRatio: 0.8 -- Maker draw: draw: 10 DAI from Maker Overcollateralization: 110% Assumption: draw is not violating constraints --- drawAmount: 10 DAI mint: 11 DROP (10 * 1.1/1 = 11 DROP) transfer DROP to MKR deposit: 10 DAI into the reserve increase seniorAsset: 11 DAI State: seniorAsset: 91 DAI | 91 DROP | dropPrice: 1.0 juniorAsset: 19 DAI | 20 TIN | tinPrice: 19/20 = 0.95 Reserve: 110 DAI seniorRatio: 91/110: 0.82 ``` In case of a liquidation the TIN investors would have to pay for the additional DROP. ``` In the example above the TIN price would drop to 0.95 in case of a liquidation. ``` --- # Hub Source: https://docs.centrifuge.io/developer/protocol/architecture/hub # Hub The Hub module serves as the central orchestration layer for pool management in the Centrifuge Protocol. It coordinates all core pool operations including registration, accounting, holdings management, share class configuration, and cross-chain message handling. ![](./images/hub.png) ### Hub The central pool management contract that aggregates all core pool functions in a single interface. It handles pool administration including manager assignment, share class notifications, metadata updates, and asset price broadcasting across chains. Managers assigned to a pool have full rights over all pool actions, enabling them to configure holdings, update share classes, set price feeds, and coordinate cross-chain state synchronization. The `Hub` coordinates with `ShareClassManager`, `Holdings`, `Accounting`, and `HubRegistry` to maintain consistent pool state. It supports batched multicall operations for efficient transaction execution and integrates with an optional fee hook for custom fee logic. All cross-chain communication flows through the `Hub`'s integration with the `Gateway`'s message sender, enabling pools to notify remote chains of share class updates, price changes, and other critical state transitions. ### HubHandler Processes incoming cross-chain messages for the `Hub`, acting as the message receiver from the `Gateway`'s `MessageProcessor`. It handles asset registration from remote chains, vault request callbacks, holding amount updates, share issuance/revocation messages, and cross-chain share transfers. All handler methods are auth-protected and called exclusively by the `Gateway`. The `HubHandler` coordinates state updates across `Hub`, `Holdings`, and `ShareClassManager` based on incoming messages. It validates and routes request callbacks to appropriate `HubRequestManager`s registered per pool and destination chain, ensuring that vault operations flow correctly through the system. It also maintains snapshot state for cross-chain consistency, tracking when assets and shares are synchronized across different networks. ### Holdings The `Holdings` contract serves as the ledger for all pool holdings, tracking assets and their associated accounting IDs. Each holding is initialized with an `IValuation` contract that determines how to price the asset in the pool's currency, along with mappings to accounting IDs for integration with the double-entry bookkeeping system. Holdings can be designated as either assets or liabilities, affecting how they contribute to pool valuations. The contract tracks holding amounts per pool, share class, and asset, providing methods to increase and decrease amounts with price validation. It maintains snapshot state per chain to ensure cross-chain data consistency, tracking when the holdings on a given chain are synchronized with share issuance. Optional `ISnapshotHook` integration allows for custom logic to execute when snapshot state changes, enabling advanced pool behaviors. ### HubRegistry The global registry serves as the canonical source of truth for all pools, assets, currencies, and pool-level dependencies. It handles pool registration with initial manager and currency assignment, asset registration with decimal precision tracking, and ongoing manager updates. The registry enforces uniqueness constraints, preventing duplicate pool or asset registrations and validating existence before state updates. Beyond basic registration, `HubRegistry` manages pool metadata storage and tracks pool dependencies such as `HubRequestManager`s per destination chain. This enables pools to configure different request handling logic for different networks while maintaining centralized visibility into pool configuration. Multiple managers can be assigned to each pool, providing flexible access control for pool operations. ### Accounting A double-entry bookkeeping system that maintains financial integrity for all pool operations. Accounts are created with either debit-normal or credit-normal balances, following traditional accounting conventions. It uses a lock/unlock mechanism where a specific pool must be unlocked before journal entries can be recorded, and must be locked to commit the transaction. During the locked period, the contract enforces that total debits equal total credits, ensuring balanced transactions. Transient storage tracks the in-flight journal state, including debited and credited amounts for the current transaction. ### ShareClassManager Manages all share classes across pools and chains, handling creation, metadata, pricing, and issuance tracking. Share classes are created with unique deterministic IDs generated from the pool and an incrementing index, along with names, symbols, and salts. The contract prevents salt reuse to ensure share class uniqueness and tracks both total issuance and per-chain issuance to support cross-chain vault operations. The manager maintains share prices (price per share in pool currency) with computed-at timestamps, validating that prices cannot be set in the future. It provides methods to update share class metadata, issue and revoke shares based on cross-chain activity, and preview the next share class ID for deterministic deployment planning. Share issuance is tracked separately per chain to enable accurate accounting when shares are minted or burned on different networks. --- # Messaging scheme Source: https://docs.centrifuge.io/developer/protocol/architecture/messaging # Messaging scheme The Centrifuge Protocol routes messages between chains through its Gateway layer. To minimize cross-chain gas costs, all messages use a custom packed binary encoding rather than standard ABI encoding. ## Encoding Each message is a tightly packed byte sequence. The first byte is a `uint8` that identifies the message type. All subsequent fields are packed in a fixed order with no padding or dynamic offset tables, using Solidity's `abi.encodePacked`. Fixed-width types occupy exactly their natural byte size (e.g. `uint128` → 16 bytes, `bytes32` → 32 bytes, `bool` → 1 byte). This differs from standard ABI encoding, which zero-pads all values to 32-byte slots and prepends dynamic offset tables. For cross-chain messages that may be relayed through bridges with per-byte fees, the savings are significant. Messages with variable-length payloads (`UpdateRestriction`, `Request`, `RequestCallback`, `TrustedContractUpdate`, and `UntrustedContractUpdate`) encode the payload length as a `uint16` immediately before the payload bytes. The base message size does not include this variable portion. All pool-scoped messages (from `NotifyPool` onwards) carry a `poolId` at byte offset 1. Some messages include a `extraGasLimit` parameter, which lets the initiator reserve additional gas for the forwarded call on the destination chain. ## Core messages ### Pool-independent messages These messages are not scoped to a specific pool. #### ScheduleUpgrade Schedules a contract upgrade for the given target. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 1) | | 1 | 32 | target | `bytes32` | **Total: 33 bytes** #### CancelUpgrade Cancels a previously scheduled upgrade for the given target. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 2) | | 1 | 32 | target | `bytes32` | **Total: 33 bytes** #### RecoverTokens Recovers tokens from a target contract, sending them to a specified recipient. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 3) | | 1 | 32 | target | `bytes32` | | 33 | 32 | token | `bytes32` | | 65 | 32 | tokenId | `uint256` | | 97 | 32 | to | `bytes32` | | 129 | 32 | amount | `uint256` | **Total: 161 bytes** #### RegisterAsset Registers an asset with its identifier and decimal precision. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 4) | | 1 | 16 | assetId | `uint128` | | 17 | 1 | decimals | `uint8` | **Total: 18 bytes** #### SetPoolAdapters Sets the set of adapters for a pool, along with a confirmation threshold and a recovery index. The `adapterList` is variable-length; the 2-byte `length` field at offset 11 gives the number of entries. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 5) | | 1 | 8 | poolId | `uint64` | | 9 | 1 | threshold | `uint8` | | 10 | 1 | recoveryIndex | `uint8` | | 11 | 2 | length | `uint16` | | 13 | N×32 | adapterList | `bytes32[]` | **Total: 13 + N×32 bytes** --- ### Pool-scoped messages These messages carry a `poolId` at offset 1 and are routed to a specific pool. #### NotifyPool Notifies a remote chain that a pool exists. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 6) | | 1 | 8 | poolId | `uint64` | **Total: 9 bytes** #### NotifyShareClass Notifies a remote chain of a share class and its initial configuration. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 7) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 128 | name | fixed 128-byte UTF-8 string | | 153 | 32 | symbol | `bytes32` UTF-8 | | 185 | 1 | decimals | `uint8` | | 186 | 32 | salt | `bytes32` | | 218 | 32 | hook | `bytes32` | **Total: 250 bytes** #### NotifyPricePoolPerShare Publishes the pool-per-share price for a share class at a given timestamp. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 8) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 16 | price | `uint128` | | 41 | 8 | timestamp | `uint64` | **Total: 49 bytes** #### NotifyPricePoolPerAsset Publishes the pool-per-asset price for a specific asset in a share class. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 9) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 16 | assetId | `uint128` | | 41 | 16 | price | `uint128` | | 57 | 8 | timestamp | `uint64` | **Total: 65 bytes** #### NotifyShareMetadata Updates the name and symbol metadata for a share class on a remote chain. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 10) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 128 | name | fixed 128-byte UTF-8 string | | 153 | 32 | symbol | `bytes32` UTF-8 | **Total: 185 bytes** #### UpdateShareHook Sets the hook contract address for a share class. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 11) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 32 | hook | `bytes32` | **Total: 57 bytes** #### InitiateTransferShares Initiates a cross-chain share transfer from the originating chain. Carries separate gas limits for the local execution and the remote leg of the transfer. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 12) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 2 | centrifugeId | `uint16` | | 27 | 32 | receiver | `bytes32` | | 59 | 16 | amount | `uint128` | | 75 | 16 | remoteExtraGasLimit | `uint128` | | 91 | 16 | extraGasLimit | `uint128` | **Total: 107 bytes** #### ExecuteTransferShares Executes the receiving side of a cross-chain share transfer. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 13) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 32 | receiver | `bytes32` | | 57 | 16 | amount | `uint128` | | 73 | 16 | extraGasLimit | `uint128` | **Total: 89 bytes** #### UpdateRestriction Delivers a restriction update to a share class hook. The `payload` is a submessage encoded using `UpdateRestrictionMessageLib` (see [UpdateRestriction submessages](#updaterestriction-submessages) below). | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 14) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 16 | extraGasLimit | `uint128` | | 41 | 2 | payloadLength | `uint16` | | 43 | payloadLength | payload | `bytes` | **Base: 43 bytes + payload** #### UpdateVault Deploys, links, or unlinks a vault for a share class and asset. The `kind` field maps to `VaultUpdateKind`: `0` for DeployAndLink, `1` for Link, `2` for Unlink. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 15) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 16 | assetId | `uint128` | | 41 | 32 | vaultOrFactory | `bytes32` | | 73 | 1 | kind | `uint8` | | 74 | 16 | extraGasLimit | `uint128` | **Total: 90 bytes** #### UpdateBalanceSheetManager Grants or revokes balance sheet management rights for a pool. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 16) | | 1 | 8 | poolId | `uint64` | | 9 | 32 | who | `bytes32` | | 41 | 1 | canManage | `bool` | **Total: 42 bytes** #### UpdateGatewayManager Grants or revokes gateway management rights for a pool. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 17) | | 1 | 8 | poolId | `uint64` | | 9 | 32 | who | `bytes32` | | 41 | 1 | canManage | `bool` | **Total: 42 bytes** #### UpdateHoldingAmount Updates the recorded holding amount for a share class and asset, including price and whether the update is an increase, a snapshot, or both. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 18) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 16 | assetId | `uint128` | | 41 | 16 | amount | `uint128` | | 57 | 16 | pricePoolPerAsset | `uint128` | | 73 | 8 | timestamp | `uint64` | | 81 | 1 | isIncrease | `bool` | | 82 | 1 | isSnapshot | `bool` | | 83 | 8 | nonce | `uint64` | | 91 | 16 | extraGasLimit | `uint128` | **Total: 107 bytes** #### UpdateShares Records share issuance or revocation for a share class, with snapshot and ordering metadata. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 19) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 16 | shares | `uint128` | | 41 | 8 | timestamp | `uint64` | | 49 | 1 | isIssuance | `bool` | | 50 | 1 | isSnapshot | `bool` | | 51 | 8 | nonce | `uint64` | | 59 | 16 | extraGasLimit | `uint128` | **Total: 75 bytes** #### SetMaxAssetPriceAge Sets the maximum acceptable age of an asset price feed for a share class. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 20) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 16 | assetId | `uint128` | | 41 | 8 | maxPriceAge | `uint64` | **Total: 49 bytes** #### SetMaxSharePriceAge Sets the maximum acceptable age of a share price feed for a share class. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 21) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 8 | maxPriceAge | `uint64` | **Total: 33 bytes** #### Request Forwards an investor request (deposit, redeem, or cancel) from a spoke chain to the hub. The `payload` is a submessage encoded using `RequestMessageLib` (see [Request submessages](#request-submessages) below). | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 22) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 16 | assetId | `uint128` | | 41 | 16 | extraGasLimit | `uint128` | | 57 | 2 | payloadLength | `uint16` | | 59 | payloadLength | payload | `bytes` | **Base: 59 bytes + payload** #### RequestCallback Returns the result of a processed investor request from the hub back to the spoke. The `payload` is a submessage encoded using `RequestCallbackMessageLib` (see [RequestCallback submessages](#requestcallback-submessages) below). | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 23) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 16 | assetId | `uint128` | | 41 | 16 | extraGasLimit | `uint128` | | 57 | 2 | payloadLength | `uint16` | | 59 | payloadLength | payload | `bytes` | **Base: 59 bytes + payload** #### SetRequestManager Assigns a request manager contract for a pool on the remote chain. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 24) | | 1 | 8 | poolId | `uint64` | | 9 | 32 | manager | `bytes32` | **Total: 41 bytes** #### TrustedContractUpdate Forwards an arbitrary call to a trusted contract on a remote chain. The `payload` is standard ABI-encoded calldata for the target contract. Because the call is forwarded without a sender check, the target must already trust all messages arriving via the protocol. See [Extensibility via contract updates](#extensibility-via-contract-updates) for details. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 25) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 32 | target | `bytes32` | | 57 | 16 | extraGasLimit | `uint128` | | 73 | 2 | payloadLength | `uint16` | | 75 | payloadLength | payload | `bytes` | **Base: 75 bytes + payload** #### UntrustedContractUpdate Forwards an arbitrary call to a contract on a remote chain, including a `sender` field so the target can verify the originator. This is appropriate for contracts that do not unconditionally trust all protocol messages. See [Extensibility via contract updates](#extensibility-via-contract-updates) for details. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 26) | | 1 | 8 | poolId | `uint64` | | 9 | 16 | scId | `bytes16` | | 25 | 32 | target | `bytes32` | | 57 | 32 | sender | `bytes32` | | 89 | 16 | extraGasLimit | `uint128` | | 105 | 2 | payloadLength | `uint16` | | 107 | payloadLength | payload | `bytes` | **Base: 107 bytes + payload** --- ## Submessages Several core messages carry an inner `payload` that is itself a packed submessage. Each submessage library follows the same convention as `MessageLib`: a 1-byte type prefix followed by packed fields. The submessage encoding scheme is not enforced by the core protocol. The following schema is used by the hook and vault implementations included in the protocol periphery code, but alternative submessage schemes may also be used by integrators. ### UpdateRestriction submessages Encoded by `UpdateRestrictionMessageLib` and carried inside `UpdateRestriction.payload`. The `UpdateRestrictionType` enum governs the submessage kind. #### Member (code 1) Adds or extends a user's membership in a restricted share class, valid until the given timestamp. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 1) | | 1 | 32 | user | `bytes32` | | 33 | 8 | validUntil | `uint64` | **Total: 41 bytes** #### Freeze (code 2) Freezes a user's ability to transfer shares in a restricted share class. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 2) | | 1 | 32 | user | `bytes32` | **Total: 33 bytes** #### Unfreeze (code 3) Reverses a freeze on a user's share transfers. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 3) | | 1 | 32 | user | `bytes32` | **Total: 33 bytes** --- ### Request submessages Encoded by `RequestMessageLib` and carried inside `Request.payload`. The `RequestType` enum governs the submessage kind. #### DepositRequest (code 1) Requests a deposit of `amount` assets on behalf of `investor`. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 1) | | 1 | 32 | investor | `bytes32` | | 33 | 16 | amount | `uint128` | **Total: 49 bytes** #### RedeemRequest (code 2) Requests a redemption of `amount` shares on behalf of `investor`. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 2) | | 1 | 32 | investor | `bytes32` | | 33 | 16 | amount | `uint128` | **Total: 49 bytes** #### CancelDepositRequest (code 3) Cancels an outstanding deposit request for `investor`. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 3) | | 1 | 32 | investor | `bytes32` | **Total: 33 bytes** #### CancelRedeemRequest (code 4) Cancels an outstanding redeem request for `investor`. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 4) | | 1 | 32 | investor | `bytes32` | **Total: 33 bytes** --- ### RequestCallback submessages Encoded by `RequestCallbackMessageLib` and carried inside `RequestCallback.payload`. The `RequestCallbackType` enum governs the submessage kind. These messages flow from the hub back to spoke chains to report the outcome of processed requests. #### ApprovedDeposits (code 1) Reports that a batch of deposits has been approved at a given price. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 1) | | 1 | 16 | assetAmount | `uint128` | | 17 | 16 | pricePoolPerAsset | `uint128` | **Total: 33 bytes** #### IssuedShares (code 2) Reports that shares have been issued at a given price. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 2) | | 1 | 16 | shareAmount | `uint128` | | 17 | 16 | pricePoolPerShare | `uint128` | **Total: 33 bytes** #### RevokedShares (code 3) Reports that shares have been revoked, returning both asset and share amounts at the given price. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 3) | | 1 | 16 | assetAmount | `uint128` | | 17 | 16 | shareAmount | `uint128` | | 33 | 16 | pricePoolPerShare | `uint128` | **Total: 49 bytes** #### FulfilledDepositRequest (code 4) Notifies an investor that their deposit request has been fulfilled, including any cancelled portion. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 4) | | 1 | 32 | investor | `bytes32` | | 33 | 16 | fulfilledAssetAmount | `uint128` | | 49 | 16 | fulfilledShareAmount | `uint128` | | 65 | 16 | cancelledAssetAmount | `uint128` | **Total: 81 bytes** #### FulfilledRedeemRequest (code 5) Notifies an investor that their redeem request has been fulfilled, including any cancelled portion. | Offset | Size | Field | Type | |--------|------|-------|------| | 0 | 1 | type | `uint8` (code 5) | | 1 | 32 | investor | `bytes32` | | 33 | 16 | fulfilledAssetAmount | `uint128` | | 49 | 16 | fulfilledShareAmount | `uint128` | | 65 | 16 | cancelledShareAmount | `uint128` | **Total: 81 bytes** --- ## Extensibility via contract updates `TrustedContractUpdate` and `UntrustedContractUpdate` provide a general-purpose escape hatch for sending arbitrary cross-chain calls without adding a new message type to `MessageLib`. Both carry a `payload` of standard ABI-encoded calldata, including the 4-byte function selector. The receiving chain's `Gateway` forwards the call by invoking the `target` contract with that payload directly. The two variants serve opposite directions and trust levels: - **TrustedContractUpdate** flows hub to spoke. It is initiated by a pool's hub manager through the `Hub` contract, so the receiving spoke can trust that any message of this type was authorized at the hub level. No sender field is included, and the target contract is expected to accept calls from the Gateway without additional verification. - **UntrustedContractUpdate** flows spoke to hub. It can be triggered by anyone on a spoke chain, so the hub-side target cannot assume the caller is authorized. The `sender` field carries the originating address so the target contract can apply its own access control checks. --- # Overview Source: https://docs.centrifuge.io/developer/protocol/architecture/overview # Overview The Centrifuge Protocol is built on an immutable core protocol architecture, with a modular design that enables customized products to be built on top of it. ![](./images/architecture.png) ## Immutable core The immutable core includes: * Hub: Central orchestration layer for pool management, accounting, and share class coordination * Spoke: Local registry and integration point for cross-chain deployments ## Modular extensions Built on top of the immutable core, the protocol supports various extension points that enable customization without modifying core contracts: - Adapters: Cross-chain messaging adapters integrating with LayerZero, Wormhole, Chainlink, Axelar, and recovery mechanisms - Hooks: Transfer hook implementations (FreezeOnly, RedemptionRestrictions, FullRestrictions, FreelyTransferable) - Hub Managers: NAVManager for net asset value tracking and SimplePriceManager for single-share-class pool pricing - Spoke Managers: OnOfframpManager for asset custody, MerkleProofManager for permissioned operations, QueueManager for batched syncing - Valuations: Asset valuation implementations (IdentityValuation for 1:1 pricing, OracleValuation for oracle-based pricing) - Vaults: ERC-4626/ERC-7540 vault implementations (AsyncVault, SyncDepositVault), request managers, and router --- # Spoke Source: https://docs.centrifuge.io/developer/protocol/architecture/spoke # Spoke The Spoke module is a core component of the Centrifuge protocol's on-chain infrastructure, responsible for local management and orchestration of asset pools, share classes, vaults, and related operations. It acts as the local registry and integration hub for factory-based deployments and other pool contracts. ![](./images/spoke.png) ## Contracts ### Spoke The `Spoke` contract serves as: * A **local registry** for pools, share classes, and associated vaults. * An **integration point** for factories like `TokenFactory`, `VaultFactory`, and `PoolEscrowFactory`. * A **router** for inter-contract coordination, including dispatching messages via the `MessageDispatcher`. Key integrations: * `TokenFactory` – to deploy `ShareToken` instances. * `VaultFactory` – to deploy pool vaults. * `PoolEscrowFactory` – for creating pool-specific escrows. * `BalanceSheet` – to enable financial tracking and share/token management. ### Escrow There are two primary types of escrow contracts: * **Global Escrow** (`Escrow`) Used to hold pending requests and ensure secure settlement across the system. * **Pool Escrow** (`PoolEscrow`) Tied to individual pools, it holds the balance sheet for assets and liabilities associated with that pool. Factories such as `PoolEscrowFactory` are responsible for deterministic escrow deployments. ### BalanceSheet The `BalanceSheet` contract: * Tracks **asset and share class balances**. * Authorizes **manager contracts** to interact with share tokens and vaults. * Coordinates with `MerkleProofManager` and `OnOfframpManager` to verify and execute off-chain proofs and liquidity bridges. Vault managers like `SyncManager` and `AsyncRequestManager` interact with this module to perform vault-specific logic. ### ShareToken `ShareToken` is a custom ERC20 implementation with additional features: * **ERC1404 compatibility**: Allows restriction enforcement on transfers. * **ERC20 callbacks**: Integrates with `ITransferHook` for transfer-related hooks. * Optional **transfer restriction logic** provided by: * `FreezeOnly` * `RedemptionRestrictions` * `FullRestrictions` These hook contracts implement the `ITransferHook` interface and can be dynamically attached to `ShareToken`. Each `ShareToken` is instantiated by the `TokenFactory` and linked to a specific pool and share class. ## Factories * **TokenFactory**: Creates ERC20-compliant `ShareToken` contracts. * **VaultFactory**: Spawns vaults that conform to `IVault`. * **PoolEscrowFactory**: Deploys dedicated `PoolEscrow` instances per pool. ## Vaults and Managers Vaults manage capital allocation strategies. The Spoke coordinates with multiple vault interfaces: * `SyncManager` (synchronous interactions) * `AsyncRequestManager` (handles delayed execution flows) Managers are special contracts allowed to interact with vaults and balance sheets: * `OnOfframpManager`: Restricts on-offramp transactions. * `MerkleProofManager`: Integrates with external DeFi protocols. --- # Vaults Source: https://docs.centrifuge.io/developer/protocol/architecture/vaults # Vaults The Vaults module in the Centrifuge protocol enables flexible, secure, and standards-compliant tokenized asset management through both synchronous and asynchronous vault implementations. It leverages a combination of Ethereum token standards such as ERC-4626, ERC-7540, and ERC-7575 to provide support for token deposits and redemptions, including multicall entry points and support for multiple input/output assets. It builds on: * **ERC-4626**: Tokenized vaults for yield-bearing assets (synchronous behavior). * **ERC-7540**: Standard for asynchronous deposits/redemptions. * **ERC-7575**: Multitoken vaults - a single share token can be issued in exchange for multiple different underlying assets. ## Contract Structure ![](./images/vaults.png) * **`IVault`**: Root interface for all vault functionality. * **`IBaseVault`**: Common interface implemented by all vaults. * **`IAsyncRedeemVault`**: Interface for vaults supporting asynchronous redemptions. * **`IAsyncVault`**: Interface for vaults supporting full asynchronous behavior (deposits and redemptions). * **`BaseVault`**: Abstract contract providing base implementation shared across vault types. * **`BaseAsyncRedeemVault`**: Extends `BaseVault`, adds asynchronous redemption support. * **`BaseSyncDepositVault`**: Extends `BaseVault`, adds ERC-4626 deposit logic. ### Vault implementations * **`AsyncVault`** * Implements `IAsyncVault` * Fully asynchronous vault (ERC-7540 compliant) * Useful for real-world asset (RWA) use cases requiring delayed fulfillment of deposits/redemptions. * **`SyncDepositVault`** * Combines synchronous deposits (ERC-4626) with asynchronous redemptions (ERC-7540) * Hybrid vault that caters to different liquidity needs for depositing vs. redeeming. ## Manager contracts * **`AsyncRequestManager`** * Core engine for handling ERC-7540 asynchronous operations * Queues, executes, and manages state for async requests. * **`SyncManager`** * Handles standard ERC-4626 operations * Ensures consistent accounting and exchange rate logic. ## Periphery * **`VaultRouter`** * Multicall-enabled entry point for externally owned accounts (EOAs) * Routes deposit/redeem requests to appropriate vaults * Simplifies user interaction by abstracting vault-specific logic. ## ERC-7575 Support Vaults in this module support **ERC-7575**, enabling: * A single share token can be exchanged for multiple accepted collateral assets. * Greater composability and flexibility in portfolio management. * Enhanced user experience for diversified asset deposits. --- # Composability Source: https://docs.centrifuge.io/developer/protocol/composability # Composability The Centrifuge protocol has been designed to enable integration with the broader DeFi ecosystem. Standards that are supported by the protocol include: * **ERC-20**: issued share tokens, as well as holdings on the balance sheet of a pool. * **ERC-1404**: standardized compliance checks for share tokens. * **ERC-2612**: `permit` functionality built in to share tokens. * **ERC-4626**: tokenized vault standard, used for synchronous deposit vaults. * **ERC-6909**: holdings of multi-tokens on the balance sheet of a pool. * **ERC-7540**: asynchronous vault standard, used for asynchronous vault logic. * **ERC-7575**: multi-asset vault standard, to allow multiple investment assets per share token. --- # Deployments Source: https://docs.centrifuge.io/developer/protocol/deployments # Deployments ## Smart contracts ### Mainnet The protocol is currently deployed on Ethereum, Base, Arbitrum, Avalanche, Plume, Binance Smart Chain, Optimism, HyperEVM, Monad and Pharos. The deployed code commit for v3.1.0 is `6b9d36eabee48728486f377ea2766a5cd233c555`. This commit has been reviewed by 4 independent security reviews. The deployment of the protocol has been [verified by Sherlock in this report](https://github.com/centrifuge/protocol/blob/main/docs/audits/2026-02-Sherlock-deployment.pdf). ](https://etherscan.io/address/0xA4A7Bb3831958463b3FE3E27A6a160F764341953) [](https://basescan.org/address/0xA4A7Bb3831958463b3FE3E27A6a160F764341953) [](https://arbiscan.io/address/0xA4A7Bb3831958463b3FE3E27A6a160F764341953) [](https://snowscan.xyz/address/0xA4A7Bb3831958463b3FE3E27A6a160F764341953) [](https://explorer.plume.org/address/0xA4A7Bb3831958463b3FE3E27A6a160F764341953) [](https://bscscan.com/address/0xA4A7Bb3831958463b3FE3E27A6a160F764341953) [](https://optimistic.etherscan.io/address/0xA4A7Bb3831958463b3FE3E27A6a160F764341953) [](https://hyperevmscan.io/address/0xA4A7Bb3831958463b3FE3E27A6a160F764341953) [](https://monadscan.com/address/0xA4A7Bb3831958463b3FE3E27A6a160F764341953) [](https://pharosscan.xyz/address/0xA4A7Bb3831958463b3FE3E27A6a160F764341953) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/hub/Hub.sol) | | Hub Registry | `0x19f46D8130e610C6C0f0116EA40Fb781dEFaDE93` | [](https://etherscan.io/address/0x19f46D8130e610C6C0f0116EA40Fb781dEFaDE93) [](https://basescan.org/address/0x19f46D8130e610C6C0f0116EA40Fb781dEFaDE93) [](https://arbiscan.io/address/0x19f46D8130e610C6C0f0116EA40Fb781dEFaDE93) [](https://snowscan.xyz/address/0x19f46D8130e610C6C0f0116EA40Fb781dEFaDE93) [](https://explorer.plume.org/address/0x19f46D8130e610C6C0f0116EA40Fb781dEFaDE93) [](https://bscscan.com/address/0x19f46D8130e610C6C0f0116EA40Fb781dEFaDE93) [](https://optimistic.etherscan.io/address/0x19f46D8130e610C6C0f0116EA40Fb781dEFaDE93) [](https://hyperevmscan.io/address/0x19f46D8130e610C6C0f0116EA40Fb781dEFaDE93) [](https://monadscan.com/address/0x19f46D8130e610C6C0f0116EA40Fb781dEFaDE93) [](https://pharosscan.xyz/address/0x19f46D8130e610C6C0f0116EA40Fb781dEFaDE93) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/hub/HubRegistry.sol) | | Accounting | `0x050206c38f06e4710C4a37D39F75Ddc5c16a7396` | [](https://etherscan.io/address/0x050206c38f06e4710C4a37D39F75Ddc5c16a7396) [](https://basescan.org/address/0x050206c38f06e4710C4a37D39F75Ddc5c16a7396) [](https://arbiscan.io/address/0x050206c38f06e4710C4a37D39F75Ddc5c16a7396) [](https://snowscan.xyz/address/0x050206c38f06e4710C4a37D39F75Ddc5c16a7396) [](https://explorer.plume.org/address/0x050206c38f06e4710C4a37D39F75Ddc5c16a7396) [](https://bscscan.com/address/0x050206c38f06e4710C4a37D39F75Ddc5c16a7396) [](https://optimistic.etherscan.io/address/0x050206c38f06e4710C4a37D39F75Ddc5c16a7396) [](https://hyperevmscan.io/address/0x050206c38f06e4710C4a37D39F75Ddc5c16a7396) [](https://monadscan.com/address/0x050206c38f06e4710C4a37D39F75Ddc5c16a7396) [](https://pharosscan.xyz/address/0x050206c38f06e4710C4a37D39F75Ddc5c16a7396) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/hub/Accounting.sol) | | Holdings | `0x3f0c8D8d2637881c3f6d8531F51a47c2094C918d` | [](https://etherscan.io/address/0x3f0c8D8d2637881c3f6d8531F51a47c2094C918d) [](https://basescan.org/address/0x3f0c8D8d2637881c3f6d8531F51a47c2094C918d) [](https://arbiscan.io/address/0x3f0c8D8d2637881c3f6d8531F51a47c2094C918d) [](https://snowscan.xyz/address/0x3f0c8D8d2637881c3f6d8531F51a47c2094C918d) [](https://explorer.plume.org/address/0x3f0c8D8d2637881c3f6d8531F51a47c2094C918d) [](https://bscscan.com/address/0x3f0c8D8d2637881c3f6d8531F51a47c2094C918d) [](https://optimistic.etherscan.io/address/0x3f0c8D8d2637881c3f6d8531F51a47c2094C918d) [](https://hyperevmscan.io/address/0x3f0c8D8d2637881c3f6d8531F51a47c2094C918d) [](https://monadscan.com/address/0x3f0c8D8d2637881c3f6d8531F51a47c2094C918d) [](https://pharosscan.xyz/address/0x3f0c8D8d2637881c3f6d8531F51a47c2094C918d) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/hub/Holdings.sol) | | Share Class Manager | `0xaFFC269c8fe18EE9C7DDb22301AC2c2507d69BEf` | [](https://etherscan.io/address/0xaFFC269c8fe18EE9C7DDb22301AC2c2507d69BEf) [](https://basescan.org/address/0xaFFC269c8fe18EE9C7DDb22301AC2c2507d69BEf) [](https://arbiscan.io/address/0xaFFC269c8fe18EE9C7DDb22301AC2c2507d69BEf) [](https://snowscan.xyz/address/0xaFFC269c8fe18EE9C7DDb22301AC2c2507d69BEf) [](https://explorer.plume.org/address/0xaFFC269c8fe18EE9C7DDb22301AC2c2507d69BEf) [](https://bscscan.com/address/0xaFFC269c8fe18EE9C7DDb22301AC2c2507d69BEf) [](https://optimistic.etherscan.io/address/0xaFFC269c8fe18EE9C7DDb22301AC2c2507d69BEf) [](https://hyperevmscan.io/address/0xaFFC269c8fe18EE9C7DDb22301AC2c2507d69BEf) [](https://monadscan.com/address/0xaFFC269c8fe18EE9C7DDb22301AC2c2507d69BEf) [](https://pharosscan.xyz/address/0xaFFC269c8fe18EE9C7DDb22301AC2c2507d69BEf) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/hub/ShareClassManager.sol) | | Hub Handler | `0x0dEFb429B1663698da4bAe3278393F6844c3392C` | [](https://etherscan.io/address/0x0dEFb429B1663698da4bAe3278393F6844c3392C) [](https://basescan.org/address/0x0dEFb429B1663698da4bAe3278393F6844c3392C) [](https://arbiscan.io/address/0x0dEFb429B1663698da4bAe3278393F6844c3392C) [](https://snowscan.xyz/address/0x0dEFb429B1663698da4bAe3278393F6844c3392C) [](https://explorer.plume.org/address/0x0dEFb429B1663698da4bAe3278393F6844c3392C) [](https://bscscan.com/address/0x0dEFb429B1663698da4bAe3278393F6844c3392C) [](https://optimistic.etherscan.io/address/0x0dEFb429B1663698da4bAe3278393F6844c3392C) [](https://hyperevmscan.io/address/0x0dEFb429B1663698da4bAe3278393F6844c3392C) [](https://monadscan.com/address/0x0dEFb429B1663698da4bAe3278393F6844c3392C) [](https://pharosscan.xyz/address/0x0dEFb429B1663698da4bAe3278393F6844c3392C) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/hub/HubHandler.sol) | | Spoke | `0xEC3582fcDc34078a4B7a8c75a5a3AE46f48525aB` | [](https://etherscan.io/address/0xEC3582fcDc34078a4B7a8c75a5a3AE46f48525aB) [](https://basescan.org/address/0xEC3582fcDc34078a4B7a8c75a5a3AE46f48525aB) [](https://arbiscan.io/address/0xEC3582fcDc34078a4B7a8c75a5a3AE46f48525aB) [](https://snowscan.xyz/address/0xEC3582fcDc34078a4B7a8c75a5a3AE46f48525aB) [](https://explorer.plume.org/address/0xEC3582fcDc34078a4B7a8c75a5a3AE46f48525aB) [](https://bscscan.com/address/0xEC3582fcDc34078a4B7a8c75a5a3AE46f48525aB) [](https://optimistic.etherscan.io/address/0xEC3582fcDc34078a4B7a8c75a5a3AE46f48525aB) [](https://hyperevmscan.io/address/0xEC3582fcDc34078a4B7a8c75a5a3AE46f48525aB) [](https://monadscan.com/address/0xEC3582fcDc34078a4B7a8c75a5a3AE46f48525aB) [](https://pharosscan.xyz/address/0xEC3582fcDc34078a4B7a8c75a5a3AE46f48525aB) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/spoke/Spoke.sol) | | Balance Sheet | `0x12a110cE5f0FC871cC72Bc7ECaF35cf39DD0f43e` | [](https://etherscan.io/address/0x12a110cE5f0FC871cC72Bc7ECaF35cf39DD0f43e) [](https://basescan.org/address/0x12a110cE5f0FC871cC72Bc7ECaF35cf39DD0f43e) [](https://arbiscan.io/address/0x12a110cE5f0FC871cC72Bc7ECaF35cf39DD0f43e) [](https://snowscan.xyz/address/0x12a110cE5f0FC871cC72Bc7ECaF35cf39DD0f43e) [](https://explorer.plume.org/address/0x12a110cE5f0FC871cC72Bc7ECaF35cf39DD0f43e) [](https://bscscan.com/address/0x12a110cE5f0FC871cC72Bc7ECaF35cf39DD0f43e) [](https://optimistic.etherscan.io/address/0x12a110cE5f0FC871cC72Bc7ECaF35cf39DD0f43e) [](https://hyperevmscan.io/address/0x12a110cE5f0FC871cC72Bc7ECaF35cf39DD0f43e) [](https://monadscan.com/address/0x12a110cE5f0FC871cC72Bc7ECaF35cf39DD0f43e) [](https://pharosscan.xyz/address/0x12a110cE5f0FC871cC72Bc7ECaF35cf39DD0f43e) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/spoke/BalanceSheet.sol) | | Token Factory | `0xcE1616505F93215751FBb41Efac618b631997c38` | [](https://etherscan.io/address/0xcE1616505F93215751FBb41Efac618b631997c38) [](https://basescan.org/address/0xcE1616505F93215751FBb41Efac618b631997c38) [](https://arbiscan.io/address/0xcE1616505F93215751FBb41Efac618b631997c38) [](https://snowscan.xyz/address/0xcE1616505F93215751FBb41Efac618b631997c38) [](https://explorer.plume.org/address/0xcE1616505F93215751FBb41Efac618b631997c38) [](https://bscscan.com/address/0xcE1616505F93215751FBb41Efac618b631997c38) [](https://optimistic.etherscan.io/address/0xcE1616505F93215751FBb41Efac618b631997c38) [](https://hyperevmscan.io/address/0xcE1616505F93215751FBb41Efac618b631997c38) [](https://monadscan.com/address/0xcE1616505F93215751FBb41Efac618b631997c38) [](https://pharosscan.xyz/address/0xcE1616505F93215751FBb41Efac618b631997c38) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/spoke/factories/TokenFactory.sol) | | Vault Registry | `0xd9531AC47928c3386346f82d9A2478960bf2CA7B` | [](https://etherscan.io/address/0xd9531AC47928c3386346f82d9A2478960bf2CA7B) [](https://basescan.org/address/0xd9531AC47928c3386346f82d9A2478960bf2CA7B) [](https://arbiscan.io/address/0xd9531AC47928c3386346f82d9A2478960bf2CA7B) [](https://snowscan.xyz/address/0xd9531AC47928c3386346f82d9A2478960bf2CA7B) [](https://explorer.plume.org/address/0xd9531AC47928c3386346f82d9A2478960bf2CA7B) [](https://bscscan.com/address/0xd9531AC47928c3386346f82d9A2478960bf2CA7B) [](https://optimistic.etherscan.io/address/0xd9531AC47928c3386346f82d9A2478960bf2CA7B) [](https://hyperevmscan.io/address/0xd9531AC47928c3386346f82d9A2478960bf2CA7B) [](https://monadscan.com/address/0xd9531AC47928c3386346f82d9A2478960bf2CA7B) [](https://pharosscan.xyz/address/0xd9531AC47928c3386346f82d9A2478960bf2CA7B) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/spoke/VaultRegistry.sol) | | Pool Escrow Factory | `0x5187A505c485E22f0b8a5FBdF69eF1c29C478CE3` | [](https://etherscan.io/address/0x5187A505c485E22f0b8a5FBdF69eF1c29C478CE3) [](https://basescan.org/address/0x5187A505c485E22f0b8a5FBdF69eF1c29C478CE3) [](https://arbiscan.io/address/0x5187A505c485E22f0b8a5FBdF69eF1c29C478CE3) [](https://snowscan.xyz/address/0x5187A505c485E22f0b8a5FBdF69eF1c29C478CE3) [](https://explorer.plume.org/address/0x5187A505c485E22f0b8a5FBdF69eF1c29C478CE3) [](https://bscscan.com/address/0x5187A505c485E22f0b8a5FBdF69eF1c29C478CE3) [](https://optimistic.etherscan.io/address/0x5187A505c485E22f0b8a5FBdF69eF1c29C478CE3) [](https://hyperevmscan.io/address/0x5187A505c485E22f0b8a5FBdF69eF1c29C478CE3) [](https://monadscan.com/address/0x5187A505c485E22f0b8a5FBdF69eF1c29C478CE3) [](https://pharosscan.xyz/address/0x5187A505c485E22f0b8a5FBdF69eF1c29C478CE3) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/spoke/factories/PoolEscrowFactory.sol) | | Gateway | `0x19a524D03aA94ECEe41a80341537BCFCb47D3172` | [](https://etherscan.io/address/0x19a524D03aA94ECEe41a80341537BCFCb47D3172) [](https://basescan.org/address/0x19a524D03aA94ECEe41a80341537BCFCb47D3172) [](https://arbiscan.io/address/0x19a524D03aA94ECEe41a80341537BCFCb47D3172) [](https://snowscan.xyz/address/0x19a524D03aA94ECEe41a80341537BCFCb47D3172) [](https://explorer.plume.org/address/0x19a524D03aA94ECEe41a80341537BCFCb47D3172) [](https://bscscan.com/address/0x19a524D03aA94ECEe41a80341537BCFCb47D3172) [](https://optimistic.etherscan.io/address/0x19a524D03aA94ECEe41a80341537BCFCb47D3172) [](https://hyperevmscan.io/address/0x19a524D03aA94ECEe41a80341537BCFCb47D3172) [](https://monadscan.com/address/0x19a524D03aA94ECEe41a80341537BCFCb47D3172) [](https://pharosscan.xyz/address/0x19a524D03aA94ECEe41a80341537BCFCb47D3172) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/messaging/Gateway.sol) | | Multi Adapter | `0x35C837F0A54B715a23D193E1476BFC9BC30073BE` | [](https://etherscan.io/address/0x35C837F0A54B715a23D193E1476BFC9BC30073BE) [](https://basescan.org/address/0x35C837F0A54B715a23D193E1476BFC9BC30073BE) [](https://arbiscan.io/address/0x35C837F0A54B715a23D193E1476BFC9BC30073BE) [](https://snowscan.xyz/address/0x35C837F0A54B715a23D193E1476BFC9BC30073BE) [](https://explorer.plume.org/address/0x35C837F0A54B715a23D193E1476BFC9BC30073BE) [](https://bscscan.com/address/0x35C837F0A54B715a23D193E1476BFC9BC30073BE) [](https://optimistic.etherscan.io/address/0x35C837F0A54B715a23D193E1476BFC9BC30073BE) [](https://hyperevmscan.io/address/0x35C837F0A54B715a23D193E1476BFC9BC30073BE) [](https://monadscan.com/address/0x35C837F0A54B715a23D193E1476BFC9BC30073BE) [](https://pharosscan.xyz/address/0x35C837F0A54B715a23D193E1476BFC9BC30073BE) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/messaging/MultiAdapter.sol) | | Message Processor | `0x97cc7e9Dafdd725Cc23B25eeBC93c4384B4Fe30A` | [](https://etherscan.io/address/0x97cc7e9Dafdd725Cc23B25eeBC93c4384B4Fe30A) [](https://basescan.org/address/0x97cc7e9Dafdd725Cc23B25eeBC93c4384B4Fe30A) [](https://arbiscan.io/address/0x97cc7e9Dafdd725Cc23B25eeBC93c4384B4Fe30A) [](https://snowscan.xyz/address/0x97cc7e9Dafdd725Cc23B25eeBC93c4384B4Fe30A) [](https://explorer.plume.org/address/0x97cc7e9Dafdd725Cc23B25eeBC93c4384B4Fe30A) [](https://bscscan.com/address/0x97cc7e9Dafdd725Cc23B25eeBC93c4384B4Fe30A) [](https://optimistic.etherscan.io/address/0x97cc7e9Dafdd725Cc23B25eeBC93c4384B4Fe30A) [](https://hyperevmscan.io/address/0x97cc7e9Dafdd725Cc23B25eeBC93c4384B4Fe30A) [](https://monadscan.com/address/0x97cc7e9Dafdd725Cc23B25eeBC93c4384B4Fe30A) [](https://pharosscan.xyz/address/0x97cc7e9Dafdd725Cc23B25eeBC93c4384B4Fe30A) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/messaging/MessageProcessor.sol) | | Message Dispatcher | `0xf837a22883e004f705E0D7e1deE08e295Df30B27` | [](https://etherscan.io/address/0xf837a22883e004f705E0D7e1deE08e295Df30B27) [](https://basescan.org/address/0xf837a22883e004f705E0D7e1deE08e295Df30B27) [](https://arbiscan.io/address/0xf837a22883e004f705E0D7e1deE08e295Df30B27) [](https://snowscan.xyz/address/0xf837a22883e004f705E0D7e1deE08e295Df30B27) [](https://explorer.plume.org/address/0xf837a22883e004f705E0D7e1deE08e295Df30B27) [](https://bscscan.com/address/0xf837a22883e004f705E0D7e1deE08e295Df30B27) [](https://optimistic.etherscan.io/address/0xf837a22883e004f705E0D7e1deE08e295Df30B27) [](https://hyperevmscan.io/address/0xf837a22883e004f705E0D7e1deE08e295Df30B27) [](https://monadscan.com/address/0xf837a22883e004f705E0D7e1deE08e295Df30B27) [](https://pharosscan.xyz/address/0xf837a22883e004f705E0D7e1deE08e295Df30B27) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/messaging/MessageDispatcher.sol) | | Gas Service | `0xbEbef21D686a957decECCe6a58455fA0F16754Be` | [](https://etherscan.io/address/0xbEbef21D686a957decECCe6a58455fA0F16754Be) [](https://basescan.org/address/0xbEbef21D686a957decECCe6a58455fA0F16754Be) [](https://arbiscan.io/address/0xbEbef21D686a957decECCe6a58455fA0F16754Be) [](https://snowscan.xyz/address/0xbEbef21D686a957decECCe6a58455fA0F16754Be) [](https://explorer.plume.org/address/0xbEbef21D686a957decECCe6a58455fA0F16754Be) [](https://bscscan.com/address/0xbEbef21D686a957decECCe6a58455fA0F16754Be) [](https://optimistic.etherscan.io/address/0xbEbef21D686a957decECCe6a58455fA0F16754Be) [](https://hyperevmscan.io/address/0xbEbef21D686a957decECCe6a58455fA0F16754Be) [](https://monadscan.com/address/0xbEbef21D686a957decECCe6a58455fA0F16754Be) [](https://pharosscan.xyz/address/0xbEbef21D686a957decECCe6a58455fA0F16754Be) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/messaging/GasService.sol) | | Contract Updater | `0x3B150B19245D2C366bc8f18c775b725DFB298F71` | [](https://etherscan.io/address/0x3B150B19245D2C366bc8f18c775b725DFB298F71) [](https://basescan.org/address/0x3B150B19245D2C366bc8f18c775b725DFB298F71) [](https://arbiscan.io/address/0x3B150B19245D2C366bc8f18c775b725DFB298F71) [](https://snowscan.xyz/address/0x3B150B19245D2C366bc8f18c775b725DFB298F71) [](https://explorer.plume.org/address/0x3B150B19245D2C366bc8f18c775b725DFB298F71) [](https://bscscan.com/address/0x3B150B19245D2C366bc8f18c775b725DFB298F71) [](https://optimistic.etherscan.io/address/0x3B150B19245D2C366bc8f18c775b725DFB298F71) [](https://hyperevmscan.io/address/0x3B150B19245D2C366bc8f18c775b725DFB298F71) [](https://monadscan.com/address/0x3B150B19245D2C366bc8f18c775b725DFB298F71) [](https://pharosscan.xyz/address/0x3B150B19245D2C366bc8f18c775b725DFB298F71) | [Code](https://github.com/centrifuge/protocol/blob/main/src/core/utils/ContractUpdater.sol) | ](https://etherscan.io/address/0xD517BC7ba17271a8D87BE7355B2523bF5c750295) [](https://basescan.org/address/0xD517BC7ba17271a8D87BE7355B2523bF5c750295) [](https://arbiscan.io/address/0xD517BC7ba17271a8D87BE7355B2523bF5c750295) [](https://snowscan.xyz/address/0xD517BC7ba17271a8D87BE7355B2523bF5c750295) [](https://explorer.plume.org/address/0xD517BC7ba17271a8D87BE7355B2523bF5c750295) [](https://bscscan.com/address/0xD517BC7ba17271a8D87BE7355B2523bF5c750295) [](https://optimistic.etherscan.io/address/0xD517BC7ba17271a8D87BE7355B2523bF5c750295) [](https://hyperevmscan.io/address/0xD517BC7ba17271a8D87BE7355B2523bF5c750295) [](https://monadscan.com/address/0xD517BC7ba17271a8D87BE7355B2523bF5c750295) [](https://pharosscan.xyz/address/0xD517BC7ba17271a8D87BE7355B2523bF5c750295) | [Code](https://github.com/centrifuge/protocol/blob/main/src/adapters/LayerZeroAdapter.sol) | | Chainlink Adapter | `0x39CF679Eb0Ac9075CFb5f94930A367Ba1557D955` | [](https://etherscan.io/address/0x39CF679Eb0Ac9075CFb5f94930A367Ba1557D955) [](https://basescan.org/address/0x39CF679Eb0Ac9075CFb5f94930A367Ba1557D955) [](https://arbiscan.io/address/0x39CF679Eb0Ac9075CFb5f94930A367Ba1557D955) [](https://snowscan.xyz/address/0x39CF679Eb0Ac9075CFb5f94930A367Ba1557D955) [](https://explorer.plume.org/address/0x39CF679Eb0Ac9075CFb5f94930A367Ba1557D955) [](https://bscscan.com/address/0x39CF679Eb0Ac9075CFb5f94930A367Ba1557D955) [](https://optimistic.etherscan.io/address/0x39CF679Eb0Ac9075CFb5f94930A367Ba1557D955) [](https://hyperevmscan.io/address/0x39CF679Eb0Ac9075CFb5f94930A367Ba1557D955) [](https://monadscan.com/address/0x39CF679Eb0Ac9075CFb5f94930A367Ba1557D955) [](https://pharosscan.xyz/address/0x39CF679Eb0Ac9075CFb5f94930A367Ba1557D955) | [Code](https://github.com/centrifuge/protocol/blob/main/src/adapters/ChainlinkAdapter.sol) | | Axelar Adapter | `0x34e904237341C3de02D4447C3fF0ca8880ca6484` | [](https://etherscan.io/address/0x34e904237341C3de02D4447C3fF0ca8880ca6484) [](https://basescan.org/address/0x34e904237341C3de02D4447C3fF0ca8880ca6484) [](https://arbiscan.io/address/0x34e904237341C3de02D4447C3fF0ca8880ca6484) [](https://snowscan.xyz/address/0x34e904237341C3de02D4447C3fF0ca8880ca6484) [](https://explorer.plume.org/address/0x34e904237341C3de02D4447C3fF0ca8880ca6484) [](https://bscscan.com/address/0x34e904237341C3de02D4447C3fF0ca8880ca6484) [](https://optimistic.etherscan.io/address/0x34e904237341C3de02D4447C3fF0ca8880ca6484) [](https://hyperevmscan.io/address/0x34e904237341C3de02D4447C3fF0ca8880ca6484) [](https://monadscan.com/address/0x34e904237341C3de02D4447C3fF0ca8880ca6484) [](https://pharosscan.xyz/address/0x34e904237341C3de02D4447C3fF0ca8880ca6484) | [Code](https://github.com/centrifuge/protocol/blob/main/src/adapters/AxelarAdapter.sol) | | Wormhole Adapter | `0x4BE430401760075315E931dD34b892DFdfc706A7` | [](https://etherscan.io/address/0x4BE430401760075315E931dD34b892DFdfc706A7) [](https://basescan.org/address/0x4BE430401760075315E931dD34b892DFdfc706A7) [](https://arbiscan.io/address/0x4BE430401760075315E931dD34b892DFdfc706A7) [](https://snowscan.xyz/address/0x4BE430401760075315E931dD34b892DFdfc706A7) [](https://explorer.plume.org/address/0x4BE430401760075315E931dD34b892DFdfc706A7) [](https://bscscan.com/address/0x4BE430401760075315E931dD34b892DFdfc706A7) | [Code](https://github.com/centrifuge/protocol/blob/main/src/adapters/WormholeAdapter.sol) | ](https://etherscan.io/address/0x7Ed48C31f2fdC40d37407cBaBf0870B2b688368f) [](https://basescan.org/address/0x7Ed48C31f2fdC40d37407cBaBf0870B2b688368f) [](https://arbiscan.io/address/0x7Ed48C31f2fdC40d37407cBaBf0870B2b688368f) [](https://snowscan.xyz/address/0x7Ed48C31f2fdC40d37407cBaBf0870B2b688368f) [](https://explorer.plume.org/address/0x7Ed48C31f2fdC40d37407cBaBf0870B2b688368f) [](https://bscscan.com/address/0x7Ed48C31f2fdC40d37407cBaBf0870B2b688368f) [](https://optimistic.etherscan.io/address/0xdc9456e7e20f15029C8231Ec433a20F404b7235E) [](https://hyperevmscan.io/address/0xdc9456e7e20f15029C8231Ec433a20F404b7235E) [](https://monadscan.com/address/0xdc9456e7e20f15029C8231Ec433a20F404b7235E) [](https://pharosscan.xyz/address/0xdc9456e7e20f15029C8231Ec433a20F404b7235E) | [Code](https://github.com/centrifuge/protocol/blob/main/src/admin/Root.sol) | | Protocol Guardian | `0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6` | [](https://etherscan.io/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) [](https://basescan.org/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) [](https://arbiscan.io/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) [](https://snowscan.xyz/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) [](https://explorer.plume.org/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) [](https://bscscan.com/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) [](https://optimistic.etherscan.io/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) [](https://hyperevmscan.io/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) [](https://monadscan.com/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) [](https://pharosscan.xyz/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) | [Code](https://github.com/centrifuge/protocol/blob/main/src/admin/ProtocolGuardian.sol) | | Ops Guardian | `0x055589229506Ee89645EF08ebE9B9a863486d0dE` | [](https://etherscan.io/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) [](https://basescan.org/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) [](https://arbiscan.io/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) [](https://snowscan.xyz/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) [](https://explorer.plume.org/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) [](https://bscscan.com/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) [](https://optimistic.etherscan.io/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) [](https://hyperevmscan.io/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) [](https://monadscan.com/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) [](https://pharosscan.xyz/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) | [Code](https://github.com/centrifuge/protocol/blob/main/src/admin/OpsGuardian.sol) | | Token Recoverer | `0x1E70530e9555711f8DF4838Ab940b97c039B4037` | [](https://etherscan.io/address/0x1E70530e9555711f8DF4838Ab940b97c039B4037) [](https://basescan.org/address/0x1E70530e9555711f8DF4838Ab940b97c039B4037) [](https://arbiscan.io/address/0x1E70530e9555711f8DF4838Ab940b97c039B4037) [](https://snowscan.xyz/address/0x1E70530e9555711f8DF4838Ab940b97c039B4037) [](https://explorer.plume.org/address/0x1E70530e9555711f8DF4838Ab940b97c039B4037) [](https://bscscan.com/address/0x1E70530e9555711f8DF4838Ab940b97c039B4037) [](https://optimistic.etherscan.io/address/0x1E70530e9555711f8DF4838Ab940b97c039B4037) [](https://hyperevmscan.io/address/0x1E70530e9555711f8DF4838Ab940b97c039B4037) [](https://monadscan.com/address/0x1E70530e9555711f8DF4838Ab940b97c039B4037) [](https://pharosscan.xyz/address/0x1E70530e9555711f8DF4838Ab940b97c039B4037) | [Code](https://github.com/centrifuge/protocol/blob/main/src/admin/TokenRecoverer.sol) | ](https://etherscan.io/address/0xd5B243F05b2906F1f6C80c6096945faADa0731C1) [](https://basescan.org/address/0xd5B243F05b2906F1f6C80c6096945faADa0731C1) [](https://arbiscan.io/address/0xd5B243F05b2906F1f6C80c6096945faADa0731C1) [](https://snowscan.xyz/address/0xd5B243F05b2906F1f6C80c6096945faADa0731C1) [](https://explorer.plume.org/address/0xd5B243F05b2906F1f6C80c6096945faADa0731C1) [](https://bscscan.com/address/0xd5B243F05b2906F1f6C80c6096945faADa0731C1) [](https://optimistic.etherscan.io/address/0xd5B243F05b2906F1f6C80c6096945faADa0731C1) [](https://hyperevmscan.io/address/0xd5B243F05b2906F1f6C80c6096945faADa0731C1) [](https://monadscan.com/address/0xd5B243F05b2906F1f6C80c6096945faADa0731C1) [](https://pharosscan.xyz/address/0xd5B243F05b2906F1f6C80c6096945faADa0731C1) | [Code](https://github.com/centrifuge/protocol/blob/main/src/hooks/FreezeOnly.sol) | | Full Restrictions Hook | `0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4` | [](https://etherscan.io/address/0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4) [](https://basescan.org/address/0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4) [](https://arbiscan.io/address/0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4) [](https://snowscan.xyz/address/0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4) [](https://explorer.plume.org/address/0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4) [](https://bscscan.com/address/0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4) [](https://optimistic.etherscan.io/address/0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4) [](https://hyperevmscan.io/address/0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4) [](https://monadscan.com/address/0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4) [](https://pharosscan.xyz/address/0x8E680873b4C77e6088b4Ba0aBD59d100c3D224a4) | [Code](https://github.com/centrifuge/protocol/blob/main/src/hooks/FullRestrictions.sol) | | Freely Transferable Hook | `0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61` | [](https://etherscan.io/address/0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61) [](https://basescan.org/address/0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61) [](https://arbiscan.io/address/0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61) [](https://snowscan.xyz/address/0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61) [](https://explorer.plume.org/address/0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61) [](https://bscscan.com/address/0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61) [](https://optimistic.etherscan.io/address/0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61) [](https://hyperevmscan.io/address/0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61) [](https://monadscan.com/address/0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61) [](https://pharosscan.xyz/address/0x2a9B9C14851Baf7AD19f26607C9171CA1E7a1A61) | [Code](https://github.com/centrifuge/protocol/blob/main/src/hooks/FreelyTransferable.sol) | | Redemption Restrictions Hook | `0xE5423eD8602Fa0F263e17b6212d88Efe42317f06` | [](https://etherscan.io/address/0xE5423eD8602Fa0F263e17b6212d88Efe42317f06) [](https://basescan.org/address/0xE5423eD8602Fa0F263e17b6212d88Efe42317f06) [](https://arbiscan.io/address/0xE5423eD8602Fa0F263e17b6212d88Efe42317f06) [](https://snowscan.xyz/address/0xE5423eD8602Fa0F263e17b6212d88Efe42317f06) [](https://explorer.plume.org/address/0xE5423eD8602Fa0F263e17b6212d88Efe42317f06) [](https://bscscan.com/address/0xE5423eD8602Fa0F263e17b6212d88Efe42317f06) [](https://optimistic.etherscan.io/address/0xE5423eD8602Fa0F263e17b6212d88Efe42317f06) [](https://hyperevmscan.io/address/0xE5423eD8602Fa0F263e17b6212d88Efe42317f06) [](https://monadscan.com/address/0xE5423eD8602Fa0F263e17b6212d88Efe42317f06) [](https://pharosscan.xyz/address/0xE5423eD8602Fa0F263e17b6212d88Efe42317f06) | [Code](https://github.com/centrifuge/protocol/blob/main/src/hooks/RedemptionRestrictions.sol) | ](https://etherscan.io/address/0x493b6C8ccC7BfD43c5a20C4F2C648701f74E9130) [](https://basescan.org/address/0x493b6C8ccC7BfD43c5a20C4F2C648701f74E9130) [](https://arbiscan.io/address/0x493b6C8ccC7BfD43c5a20C4F2C648701f74E9130) [](https://snowscan.xyz/address/0x493b6C8ccC7BfD43c5a20C4F2C648701f74E9130) [](https://explorer.plume.org/address/0x493b6C8ccC7BfD43c5a20C4F2C648701f74E9130) [](https://bscscan.com/address/0x493b6C8ccC7BfD43c5a20C4F2C648701f74E9130) [](https://optimistic.etherscan.io/address/0x493b6C8ccC7BfD43c5a20C4F2C648701f74E9130) [](https://hyperevmscan.io/address/0x493b6C8ccC7BfD43c5a20C4F2C648701f74E9130) [](https://monadscan.com/address/0x493b6C8ccC7BfD43c5a20C4F2C648701f74E9130) [](https://pharosscan.xyz/address/0x493b6C8ccC7BfD43c5a20C4F2C648701f74E9130) | [Code](https://github.com/centrifuge/protocol/blob/main/src/managers/hub/NAVManager.sol) | | Simple Price Manager | `0x280C94eB440A8a75c2F8f6cA8c6FaFf907000823` | [](https://etherscan.io/address/0x280C94eB440A8a75c2F8f6cA8c6FaFf907000823) [](https://basescan.org/address/0x280C94eB440A8a75c2F8f6cA8c6FaFf907000823) [](https://arbiscan.io/address/0x280C94eB440A8a75c2F8f6cA8c6FaFf907000823) [](https://snowscan.xyz/address/0x280C94eB440A8a75c2F8f6cA8c6FaFf907000823) [](https://explorer.plume.org/address/0x280C94eB440A8a75c2F8f6cA8c6FaFf907000823) [](https://bscscan.com/address/0x280C94eB440A8a75c2F8f6cA8c6FaFf907000823) [](https://optimistic.etherscan.io/address/0x280C94eB440A8a75c2F8f6cA8c6FaFf907000823) [](https://hyperevmscan.io/address/0x280C94eB440A8a75c2F8f6cA8c6FaFf907000823) [](https://monadscan.com/address/0x280C94eB440A8a75c2F8f6cA8c6FaFf907000823) [](https://pharosscan.xyz/address/0x280C94eB440A8a75c2F8f6cA8c6FaFf907000823) | [Code](https://github.com/centrifuge/protocol/blob/main/src/managers/hub/SimplePriceManager.sol) | ](https://etherscan.io/address/0x2539e60B0B50D7BD004A09e9D2b7E8c86EB0AaF6) [](https://basescan.org/address/0x2539e60B0B50D7BD004A09e9D2b7E8c86EB0AaF6) [](https://arbiscan.io/address/0x2539e60B0B50D7BD004A09e9D2b7E8c86EB0AaF6) [](https://snowscan.xyz/address/0x2539e60B0B50D7BD004A09e9D2b7E8c86EB0AaF6) [](https://explorer.plume.org/address/0x2539e60B0B50D7BD004A09e9D2b7E8c86EB0AaF6) [](https://bscscan.com/address/0x2539e60B0B50D7BD004A09e9D2b7E8c86EB0AaF6) [](https://optimistic.etherscan.io/address/0x2539e60B0B50D7BD004A09e9D2b7E8c86EB0AaF6) [](https://hyperevmscan.io/address/0x2539e60B0B50D7BD004A09e9D2b7E8c86EB0AaF6) [](https://monadscan.com/address/0x2539e60B0B50D7BD004A09e9D2b7E8c86EB0AaF6) [](https://pharosscan.xyz/address/0x2539e60B0B50D7BD004A09e9D2b7E8c86EB0AaF6) | [Code](https://github.com/centrifuge/protocol/blob/main/src/managers/spoke/OnOfframpManager.sol) | | Merkle Proof Manager Factory | `0xc5243bdEa2d86eA7541AC69084dF3EDdc137a18b` | [](https://etherscan.io/address/0xc5243bdEa2d86eA7541AC69084dF3EDdc137a18b) [](https://basescan.org/address/0xc5243bdEa2d86eA7541AC69084dF3EDdc137a18b) [](https://arbiscan.io/address/0xc5243bdEa2d86eA7541AC69084dF3EDdc137a18b) [](https://snowscan.xyz/address/0xc5243bdEa2d86eA7541AC69084dF3EDdc137a18b) [](https://explorer.plume.org/address/0xc5243bdEa2d86eA7541AC69084dF3EDdc137a18b) [](https://bscscan.com/address/0xc5243bdEa2d86eA7541AC69084dF3EDdc137a18b) [](https://optimistic.etherscan.io/address/0xc5243bdEa2d86eA7541AC69084dF3EDdc137a18b) [](https://hyperevmscan.io/address/0xc5243bdEa2d86eA7541AC69084dF3EDdc137a18b) [](https://monadscan.com/address/0xc5243bdEa2d86eA7541AC69084dF3EDdc137a18b) [](https://pharosscan.xyz/address/0xc5243bdEa2d86eA7541AC69084dF3EDdc137a18b) | [Code](https://github.com/centrifuge/protocol/blob/main/src/managers/spoke/MerkleProofManager.sol) | | Queue Manager | `0x971ACA9b4AB4895F400bA042Fd10A31c7918D220` | [](https://etherscan.io/address/0x971ACA9b4AB4895F400bA042Fd10A31c7918D220) [](https://basescan.org/address/0x971ACA9b4AB4895F400bA042Fd10A31c7918D220) [](https://arbiscan.io/address/0x971ACA9b4AB4895F400bA042Fd10A31c7918D220) [](https://snowscan.xyz/address/0x971ACA9b4AB4895F400bA042Fd10A31c7918D220) [](https://explorer.plume.org/address/0x971ACA9b4AB4895F400bA042Fd10A31c7918D220) [](https://bscscan.com/address/0x971ACA9b4AB4895F400bA042Fd10A31c7918D220) [](https://optimistic.etherscan.io/address/0x971ACA9b4AB4895F400bA042Fd10A31c7918D220) [](https://hyperevmscan.io/address/0x971ACA9b4AB4895F400bA042Fd10A31c7918D220) [](https://monadscan.com/address/0x971ACA9b4AB4895F400bA042Fd10A31c7918D220) [](https://pharosscan.xyz/address/0x971ACA9b4AB4895F400bA042Fd10A31c7918D220) | [Code](https://github.com/centrifuge/protocol/blob/main/src/managers/spoke/QueueManager.sol) | | Vault Decoder | `0x8Ca5372A5613A6Df75fD5fbC43216e68c1bE6D38` | [](https://etherscan.io/address/0x8Ca5372A5613A6Df75fD5fbC43216e68c1bE6D38) [](https://basescan.org/address/0x8Ca5372A5613A6Df75fD5fbC43216e68c1bE6D38) [](https://arbiscan.io/address/0x8Ca5372A5613A6Df75fD5fbC43216e68c1bE6D38) [](https://snowscan.xyz/address/0x8Ca5372A5613A6Df75fD5fbC43216e68c1bE6D38) [](https://explorer.plume.org/address/0x8Ca5372A5613A6Df75fD5fbC43216e68c1bE6D38) [](https://bscscan.com/address/0x8Ca5372A5613A6Df75fD5fbC43216e68c1bE6D38) [](https://optimistic.etherscan.io/address/0x8Ca5372A5613A6Df75fD5fbC43216e68c1bE6D38) [](https://hyperevmscan.io/address/0x8Ca5372A5613A6Df75fD5fbC43216e68c1bE6D38) [](https://monadscan.com/address/0x8Ca5372A5613A6Df75fD5fbC43216e68c1bE6D38) [](https://pharosscan.xyz/address/0x8Ca5372A5613A6Df75fD5fbC43216e68c1bE6D38) | [Code](https://github.com/centrifuge/protocol/blob/main/src/managers/spoke/decoders/VaultDecoder.sol) | | Circle Decoder | `0xd40871a6336fD19A25a7bd96c0C0dd66Ed60931D` | [](https://etherscan.io/address/0xd40871a6336fD19A25a7bd96c0C0dd66Ed60931D) [](https://basescan.org/address/0xd40871a6336fD19A25a7bd96c0C0dd66Ed60931D) [](https://arbiscan.io/address/0xd40871a6336fD19A25a7bd96c0C0dd66Ed60931D) [](https://snowscan.xyz/address/0xd40871a6336fD19A25a7bd96c0C0dd66Ed60931D) [](https://explorer.plume.org/address/0xd40871a6336fD19A25a7bd96c0C0dd66Ed60931D) [](https://bscscan.com/address/0xd40871a6336fD19A25a7bd96c0C0dd66Ed60931D) [](https://optimistic.etherscan.io/address/0xd40871a6336fD19A25a7bd96c0C0dd66Ed60931D) [](https://hyperevmscan.io/address/0xd40871a6336fD19A25a7bd96c0C0dd66Ed60931D) [](https://monadscan.com/address/0xd40871a6336fD19A25a7bd96c0C0dd66Ed60931D) [](https://pharosscan.xyz/address/0xd40871a6336fD19A25a7bd96c0C0dd66Ed60931D) | [Code](https://github.com/centrifuge/protocol/blob/main/src/managers/spoke/decoders/CircleDecoder.sol) | ](https://etherscan.io/address/0x05e22C20B21c1314a0c93d34855358B9B96133CF) [](https://basescan.org/address/0x05e22C20B21c1314a0c93d34855358B9B96133CF) [](https://arbiscan.io/address/0x05e22C20B21c1314a0c93d34855358B9B96133CF) [](https://snowscan.xyz/address/0x05e22C20B21c1314a0c93d34855358B9B96133CF) [](https://explorer.plume.org/address/0x05e22C20B21c1314a0c93d34855358B9B96133CF) [](https://bscscan.com/address/0x05e22C20B21c1314a0c93d34855358B9B96133CF) [](https://optimistic.etherscan.io/address/0x05e22C20B21c1314a0c93d34855358B9B96133CF) [](https://hyperevmscan.io/address/0x05e22C20B21c1314a0c93d34855358B9B96133CF) [](https://monadscan.com/address/0x05e22C20B21c1314a0c93d34855358B9B96133CF) [](https://pharosscan.xyz/address/0x05e22C20B21c1314a0c93d34855358B9B96133CF) | [Code](https://github.com/centrifuge/protocol/blob/main/src/valuations/IdentityValuation.sol) | | Oracle Valuation | `0xCBdb6EFFC9b954D05dF89c747eCaa8A143c26E6D` | [](https://etherscan.io/address/0xCBdb6EFFC9b954D05dF89c747eCaa8A143c26E6D) [](https://basescan.org/address/0xCBdb6EFFC9b954D05dF89c747eCaa8A143c26E6D) [](https://arbiscan.io/address/0xCBdb6EFFC9b954D05dF89c747eCaa8A143c26E6D) [](https://snowscan.xyz/address/0xCBdb6EFFC9b954D05dF89c747eCaa8A143c26E6D) [](https://explorer.plume.org/address/0xCBdb6EFFC9b954D05dF89c747eCaa8A143c26E6D) [](https://bscscan.com/address/0xCBdb6EFFC9b954D05dF89c747eCaa8A143c26E6D) [](https://optimistic.etherscan.io/address/0xCBdb6EFFC9b954D05dF89c747eCaa8A143c26E6D) [](https://hyperevmscan.io/address/0xCBdb6EFFC9b954D05dF89c747eCaa8A143c26E6D) [](https://monadscan.com/address/0xCBdb6EFFC9b954D05dF89c747eCaa8A143c26E6D) [](https://pharosscan.xyz/address/0xCBdb6EFFC9b954D05dF89c747eCaa8A143c26E6D) | [Code](https://github.com/centrifuge/protocol/blob/main/src/valuations/OracleValuation.sol) | ](https://etherscan.io/address/0x55cde53B7dbc24336E34FFE233AF8DF10f72F0Be) [](https://basescan.org/address/0x55cde53B7dbc24336E34FFE233AF8DF10f72F0Be) [](https://arbiscan.io/address/0x55cde53B7dbc24336E34FFE233AF8DF10f72F0Be) [](https://snowscan.xyz/address/0x55cde53B7dbc24336E34FFE233AF8DF10f72F0Be) [](https://explorer.plume.org/address/0x55cde53B7dbc24336E34FFE233AF8DF10f72F0Be) [](https://bscscan.com/address/0x55cde53B7dbc24336E34FFE233AF8DF10f72F0Be) [](https://optimistic.etherscan.io/address/0x55cde53B7dbc24336E34FFE233AF8DF10f72F0Be) [](https://hyperevmscan.io/address/0x55cde53B7dbc24336E34FFE233AF8DF10f72F0Be) [](https://monadscan.com/address/0x55cde53B7dbc24336E34FFE233AF8DF10f72F0Be) [](https://pharosscan.xyz/address/0x55cde53B7dbc24336E34FFE233AF8DF10f72F0Be) | [Code](https://github.com/centrifuge/protocol/blob/main/src/vaults/factories/AsyncVaultFactory.sol) | | Sync Deposit Vault Factory | `0xdb9C27762045Addd713182521C0580C68BDF700A` | [](https://etherscan.io/address/0xdb9C27762045Addd713182521C0580C68BDF700A) [](https://basescan.org/address/0xdb9C27762045Addd713182521C0580C68BDF700A) [](https://arbiscan.io/address/0xdb9C27762045Addd713182521C0580C68BDF700A) [](https://snowscan.xyz/address/0xdb9C27762045Addd713182521C0580C68BDF700A) [](https://explorer.plume.org/address/0xdb9C27762045Addd713182521C0580C68BDF700A) [](https://bscscan.com/address/0xdb9C27762045Addd713182521C0580C68BDF700A) [](https://optimistic.etherscan.io/address/0xdb9C27762045Addd713182521C0580C68BDF700A) [](https://hyperevmscan.io/address/0xdb9C27762045Addd713182521C0580C68BDF700A) [](https://monadscan.com/address/0xdb9C27762045Addd713182521C0580C68BDF700A) [](https://pharosscan.xyz/address/0xdb9C27762045Addd713182521C0580C68BDF700A) | [Code](https://github.com/centrifuge/protocol/blob/main/src/vaults/factories/SyncDepositVaultFactory.sol) | | Vault Router | `0xF684014771C01e50B8B526968B3a1e33acDA63f6` | [](https://etherscan.io/address/0xF684014771C01e50B8B526968B3a1e33acDA63f6) [](https://basescan.org/address/0xF684014771C01e50B8B526968B3a1e33acDA63f6) [](https://arbiscan.io/address/0xF684014771C01e50B8B526968B3a1e33acDA63f6) [](https://snowscan.xyz/address/0xF684014771C01e50B8B526968B3a1e33acDA63f6) [](https://explorer.plume.org/address/0xF684014771C01e50B8B526968B3a1e33acDA63f6) [](https://bscscan.com/address/0xF684014771C01e50B8B526968B3a1e33acDA63f6) [](https://optimistic.etherscan.io/address/0xF684014771C01e50B8B526968B3a1e33acDA63f6) [](https://hyperevmscan.io/address/0xF684014771C01e50B8B526968B3a1e33acDA63f6) [](https://monadscan.com/address/0xF684014771C01e50B8B526968B3a1e33acDA63f6) [](https://pharosscan.xyz/address/0xF684014771C01e50B8B526968B3a1e33acDA63f6) | [Code](https://github.com/centrifuge/protocol/blob/main/src/vaults/VaultRouter.sol) | | Async Request Manager | `0xF48256AbDDf96EcDDc4B3DbD23E8C1921f9761Ae` | [](https://etherscan.io/address/0xF48256AbDDf96EcDDc4B3DbD23E8C1921f9761Ae) [](https://basescan.org/address/0xF48256AbDDf96EcDDc4B3DbD23E8C1921f9761Ae) [](https://arbiscan.io/address/0xF48256AbDDf96EcDDc4B3DbD23E8C1921f9761Ae) [](https://snowscan.xyz/address/0xF48256AbDDf96EcDDc4B3DbD23E8C1921f9761Ae) [](https://explorer.plume.org/address/0xF48256AbDDf96EcDDc4B3DbD23E8C1921f9761Ae) [](https://bscscan.com/address/0xF48256AbDDf96EcDDc4B3DbD23E8C1921f9761Ae) [](https://optimistic.etherscan.io/address/0xF48256AbDDf96EcDDc4B3DbD23E8C1921f9761Ae) [](https://hyperevmscan.io/address/0xF48256AbDDf96EcDDc4B3DbD23E8C1921f9761Ae) [](https://monadscan.com/address/0xF48256AbDDf96EcDDc4B3DbD23E8C1921f9761Ae) [](https://pharosscan.xyz/address/0xF48256AbDDf96EcDDc4B3DbD23E8C1921f9761Ae) | [Code](https://github.com/centrifuge/protocol/blob/main/src/vaults/AsyncRequestManager.sol) | | Sync Manager | `0xFf8Ed1862f6aC3a8e89B81C75507c225E36e273D` | [](https://etherscan.io/address/0xFf8Ed1862f6aC3a8e89B81C75507c225E36e273D) [](https://basescan.org/address/0xFf8Ed1862f6aC3a8e89B81C75507c225E36e273D) [](https://arbiscan.io/address/0xFf8Ed1862f6aC3a8e89B81C75507c225E36e273D) [](https://snowscan.xyz/address/0xFf8Ed1862f6aC3a8e89B81C75507c225E36e273D) [](https://explorer.plume.org/address/0xFf8Ed1862f6aC3a8e89B81C75507c225E36e273D) [](https://bscscan.com/address/0xFf8Ed1862f6aC3a8e89B81C75507c225E36e273D) [](https://optimistic.etherscan.io/address/0xFf8Ed1862f6aC3a8e89B81C75507c225E36e273D) [](https://hyperevmscan.io/address/0xFf8Ed1862f6aC3a8e89B81C75507c225E36e273D) [](https://monadscan.com/address/0xFf8Ed1862f6aC3a8e89B81C75507c225E36e273D) [](https://pharosscan.xyz/address/0xFf8Ed1862f6aC3a8e89B81C75507c225E36e273D) | [Code](https://github.com/centrifuge/protocol/blob/main/src/vaults/SyncManager.sol) | | Batch Request Manager | `0xc52Bd1Bdfa0135147D3F01a0B6D6cd0A831dFe77` | [](https://etherscan.io/address/0xc52Bd1Bdfa0135147D3F01a0B6D6cd0A831dFe77) [](https://basescan.org/address/0xc52Bd1Bdfa0135147D3F01a0B6D6cd0A831dFe77) [](https://arbiscan.io/address/0xc52Bd1Bdfa0135147D3F01a0B6D6cd0A831dFe77) [](https://snowscan.xyz/address/0xc52Bd1Bdfa0135147D3F01a0B6D6cd0A831dFe77) [](https://explorer.plume.org/address/0xc52Bd1Bdfa0135147D3F01a0B6D6cd0A831dFe77) [](https://bscscan.com/address/0xc52Bd1Bdfa0135147D3F01a0B6D6cd0A831dFe77) [](https://optimistic.etherscan.io/address/0xc52Bd1Bdfa0135147D3F01a0B6D6cd0A831dFe77) [](https://hyperevmscan.io/address/0xc52Bd1Bdfa0135147D3F01a0B6D6cd0A831dFe77) [](https://monadscan.com/address/0xc52Bd1Bdfa0135147D3F01a0B6D6cd0A831dFe77) [](https://pharosscan.xyz/address/0xc52Bd1Bdfa0135147D3F01a0B6D6cd0A831dFe77) | [Code](https://github.com/centrifuge/protocol/blob/main/src/vaults/BatchRequestManager.sol) | | Refund Escrow Factory | `0xC4f9A1DCF2E05eb55AbB30BaA7070838D3Fd3D5B` | [](https://etherscan.io/address/0xC4f9A1DCF2E05eb55AbB30BaA7070838D3Fd3D5B) [](https://basescan.org/address/0xC4f9A1DCF2E05eb55AbB30BaA7070838D3Fd3D5B) [](https://arbiscan.io/address/0xC4f9A1DCF2E05eb55AbB30BaA7070838D3Fd3D5B) [](https://snowscan.xyz/address/0xC4f9A1DCF2E05eb55AbB30BaA7070838D3Fd3D5B) [](https://explorer.plume.org/address/0xC4f9A1DCF2E05eb55AbB30BaA7070838D3Fd3D5B) [](https://bscscan.com/address/0xC4f9A1DCF2E05eb55AbB30BaA7070838D3Fd3D5B) [](https://optimistic.etherscan.io/address/0xC4f9A1DCF2E05eb55AbB30BaA7070838D3Fd3D5B) [](https://hyperevmscan.io/address/0xC4f9A1DCF2E05eb55AbB30BaA7070838D3Fd3D5B) [](https://monadscan.com/address/0xC4f9A1DCF2E05eb55AbB30BaA7070838D3Fd3D5B) [](https://pharosscan.xyz/address/0xC4f9A1DCF2E05eb55AbB30BaA7070838D3Fd3D5B) | [Code](https://github.com/centrifuge/protocol/blob/main/src/utils/RefundEscrowFactory.sol) | | Subsidy Manager | `0xBFC7B60684880457030C08AceE2E675CbcB9d646` | [](https://etherscan.io/address/0xBFC7B60684880457030C08AceE2E675CbcB9d646) [](https://basescan.org/address/0xBFC7B60684880457030C08AceE2E675CbcB9d646) [](https://arbiscan.io/address/0xBFC7B60684880457030C08AceE2E675CbcB9d646) [](https://snowscan.xyz/address/0xBFC7B60684880457030C08AceE2E675CbcB9d646) [](https://explorer.plume.org/address/0xBFC7B60684880457030C08AceE2E675CbcB9d646) [](https://bscscan.com/address/0xBFC7B60684880457030C08AceE2E675CbcB9d646) [](https://optimistic.etherscan.io/address/0xBFC7B60684880457030C08AceE2E675CbcB9d646) [](https://hyperevmscan.io/address/0xBFC7B60684880457030C08AceE2E675CbcB9d646) [](https://monadscan.com/address/0xBFC7B60684880457030C08AceE2E675CbcB9d646) [](https://pharosscan.xyz/address/0xBFC7B60684880457030C08AceE2E675CbcB9d646) | [Code](https://github.com/centrifuge/protocol/blob/main/src/utils/SubsidyManager.sol) | ### Testnet The latest testnet deployments can be found here: https://github.com/centrifuge/protocol/tree/main/env ## Centrifuge IDs The protocol uses `centrifugeId` (`uint16`) as an identifier of the network. The following IDs are used: ### Mainnet | Network | `centrifugeId` | |------------------|----| | Ethereum Mainnet | 1 | | Base | 2 | | Arbitrum | 3 | | Plume | 4 | | Avalanche | 5 | | BNB Smart Chain | 6 | | Solana | 7 | | Stellar | 8 | | HyperEVM | 9 | | Optimism | 10 | | Monad | 11 | | Pharos | 12 | ### Testnet | Network | `centrifugeId` | |------------------|----| | Ethereum Sepolia | 1 | | Base Sepolia | 2 | | Arbitrum Sepolia | 3 | ## Tokens #### JTRSY | Network | Address | |------------------|---------| | Ethereum Mainnet | [`0x8c213ee79581ff4984583c6a801e5263418c4b86`](https://etherscan.io/address/0x8c213ee79581ff4984583c6a801e5263418c4b86) | | Base | [`0x8c213ee79581ff4984583c6a801e5263418c4b86`](https://basescan.org/address/0x8c213ee79581ff4984583c6a801e5263418c4b86) | | Arbitrum | [`0x8c213ee79581ff4984583c6a801e5263418c4b86`](https://arbiscan.io/address/0x8c213ee79581ff4984583c6a801e5263418c4b86) | | Avalanche | [`0xa5d465251fbcc907f5dd6bb2145488dfc6a2627b`](https://snowtrace.io/address/0xa5d465251fbcc907f5dd6bb2145488dfc6a2627b) | | Plume | [`0xa5d465251fbcc907f5dd6bb2145488dfc6a2627b`](https://explorer.plume.org/address/0xa5d465251fbcc907f5dd6bb2145488dfc6a2627b) | | BNB | [`0xa5d465251fBCc907f5Dd6bB2145488DFC6a2627b`](https://bscscan.com/token/0xa5d465251fBCc907f5Dd6bB2145488DFC6a2627b) | | Solana | [`JTRu97Z4oduVwfVBWdf1fSAz8h7CBBPqEo4Jco9fZPj`](https://solscan.io/token/JTRu97Z4oduVwfVBWdf1fSAz8h7CBBPqEo4Jco9fZPj) | | Monad | [`0xc18e6f730896971a79d748e8dea61067a9bc6040`](https://monadscan.com/address/0xc18e6f730896971a79d748e8dea61067a9bc6040) | #### JAAA | Network | Address | |------------------|---------| | Ethereum Mainnet | [`0x5a0f93d040de44e78f251b03c43be9cf317dcf64`](https://etherscan.io/address/0x5a0f93d040de44e78f251b03c43be9cf317dcf64) | | Base | [`0x5a0F93D040De44e78F251b03c43be9CF317Dcf64`](https://basescan.org/address/0x5a0f93d040de44e78f251b03c43be9cf317dcf64) | | Avalanche | [`0x58f93d6b1ef2f44ec379cb975657c132cbed3b6b`](https://snowtrace.io/address/0x58f93d6b1ef2f44ec379cb975657c132cbed3b6b) | | BNB | [`0x58F93d6b1EF2F44eC379Cb975657C132CBeD3B6b`](https://bscscan.com/token/0x58f93d6b1ef2f44ec379cb975657c132cbed3b6b) | | Solana | [`AAAJXeGjpKu7W3X4QTSU4pm1Wbj4G2LPcdg7A6xJLLyG`](https://solscan.io/token/AAAJXeGjpKu7W3X4QTSU4pm1Wbj4G2LPcdg7A6xJLLyG) | | Monad | [`0xad48f183e586e92a591a610397ebf534609df797`](https://monadscan.com/address/0xad48f183e586e92a591a610397ebf534609df797) | #### ACRDX | Network | Address | |------------------|---------| | Ethereum Mainnet | [`0x9477724Bb54AD5417de8Baff29e59DF3fB4DA74f`](https://etherscan.io/address/0x9477724Bb54AD5417de8Baff29e59DF3fB4DA74f) | | Base | [`0x9477724Bb54AD5417de8Baff29e59DF3fB4DA74f`](https://basescan.org/address/0x9477724Bb54AD5417de8Baff29e59DF3fB4DA74f) | | Plume | [`0x9477724bb54ad5417de8baff29e59df3fb4da74f`](https://explorer.plume.org/address/0x9477724bb54ad5417de8baff29e59df3fb4da74f) | | Solana | [`ACDR3LGFrMuDZSDRyJjncFCzo5c8xkQxhWx4im4Vmq8G`](https://solscan.io/token/ACDR3LGFrMuDZSDRyJjncFCzo5c8xkQxhWx4im4Vmq8G) | | Monad | [`0x2fabf1c784b8583d63c00c5c9c0377d8cf1a3245`](https://monadscan.com/address/0x2fabf1c784b8583d63c00c5c9c0377d8cf1a3245) | #### SPXA | Network | Address | |------------------|---------| | Base | [`0x09b61343097c1f9b159a3ae7151298efd10f0db2`](https://basescan.org/address/0x09b61343097c1f9b159a3ae7151298efd10f0db2) | #### deJTRSY | Network | Address | |------------------|---------| | Ethereum Mainnet | [`0xa6233014b9b7aaa74f38fa1977ffc7a89642dc72`](https://etherscan.io/address/0xa6233014b9b7aaa74f38fa1977ffc7a89642dc72) | | Base | [`0xa6233014b9b7aaa74f38fa1977ffc7a89642dc72`](https://basescan.org/address/0xa6233014b9b7aaa74f38fa1977ffc7a89642dc72) | | Arbitrum | [`0xa6233014b9b7aaa74f38fa1977ffc7a89642dc72`](https://arbiscan.io/address/0xa6233014b9b7aaa74f38fa1977ffc7a89642dc72) | | Avalanche | [`0xa6233014b9b7aaa74f38fa1977ffc7a89642dc72`](https://snowtrace.io/address/0xa6233014b9b7aaa74f38fa1977ffc7a89642dc72) | | Solana | [`DeJXZwShCZYJnRX2ruVASfhUhsC44qPW1pacbxRFuGLR`](https://solscan.io/token/DeJXZwShCZYJnRX2ruVASfhUhsC44qPW1pacbxRFuGLR) | | Stellar | [`CBI7UCH5KGSVQRO5H4SUCZUTZABCITZLRHQQZTWL2TK4RZ72TAR6IHRV`](https://stellar.expert/explorer/public/contract/CBI7UCH5KGSVQRO5H4SUCZUTZABCITZLRHQQZTWL2TK4RZ72TAR6IHRV) | #### deJAAA | Network | Address | |------------------|---------| | Ethereum Mainnet | [`0xaaa0008c8cf3a7dca931adaf04336a5d808c82cc`](https://etherscan.io/address/0xaaa0008c8cf3a7dca931adaf04336a5d808c82cc) | | Base | [`0xaaa0008c8cf3a7dca931adaf04336a5d808c82cc`](https://basescan.org/address/0xaaa0008c8cf3a7dca931adaf04336a5d808c82cc) | | Arbitrum | [`0xaaa0008c8cf3a7dca931adaf04336a5d808c82cc`](https://arbiscan.io/address/0xaaa0008c8cf3a7dca931adaf04336a5d808c82cc) | | Avalanche | [`0xaaa0008c8cf3a7dca931adaf04336a5d808c82cc`](https://snowtrace.io/address/0xaaa0008c8cf3a7dca931adaf04336a5d808c82cc) | | Solana | [`AAASV2sCaykNGdEQzJ1mMVNfJMsgEtR7PJaqrCuk4bq6`](https://solscan.io/token/AAASV2sCaykNGdEQzJ1mMVNfJMsgEtR7PJaqrCuk4bq6) | | Stellar | [`CC64WBDGS6QQP22QTTIACYIXT3WF7BBQEYOQPLTP7GTKYY7PZ74QYGSL`](https://stellar.expert/explorer/public/contract/CC64WBDGS6QQP22QTTIACYIXT3WF7BBQEYOQPLTP7GTKYY7PZ74QYGSL) | #### deCRDX | Network | Address | |------------------|---------| | Optimism | [`0x9e2679eabff131b8b1b48ff7566140794e0eedc4`](https://optimistic.etherscan.io//token/0x9e2679eabff131b8b1b48ff7566140794e0eedc4) | #### deSPXA | Network | Asset | Address | |------------------|-------|---------| | Base | [`0x9c5C365e764829876243d0b289733B9D2b729685`](https://basescan.org/address/0x2da40f061536c2f3a8f95f23a5f4c133d07d393a) | ## Vaults #### JTRSY | Network | Asset | Address | |------------------|-------|---------| | Ethereum Mainnet | USDC | [`0xfe6920eb6c421f1179ca8c8d4170530cdbdfd77a`](https://etherscan.io/address/0xfe6920eb6c421f1179ca8c8d4170530cdbdfd77a) | | Ethereum Mainnet | USDS | [`0x381f4f3b43c30b78c1f7777553236e57bb8ae9ff`](https://etherscan.io/address/0x381f4f3b43c30b78c1f7777553236e57bb8ae9ff) | | Avalanche | USDC | [`0xfe6920eb6c421f1179ca8c8d4170530cdbdfd77a`](https://snowtrace.io/address/0xfe6920eb6c421f1179ca8c8d4170530cdbdfd77a) | | Plume | USDC | [`0x818a3593340622c1d6a51b039e191f2f8c99a1f2`](https://explorer.plume.org/address/0x818A3593340622c1D6A51B039e191F2f8C99A1F2) | | Arbitrum | USDC | [`0x04ffdbd63626942d5cabf12120805465b7a17547`](https://arbiscan.io/address/0x04ffdbd63626942d5cabf12120805465b7a17547) | | BSC | USDC | [`0x5aa84705a2cb2054ed303565336f188e6bffbaf5`](https://bscscan.com/address/0x5aa84705a2cb2054ed303565336f188e6bffbaf5) | | BSC | USDT | [`0x6e6b8498415083a4386be83dd59edd4366402ffa`](https://bscscan.com/address/0x6e6b8498415083a4386be83dd59edd4366402ffa) | | Monad | USDC | [`0x796ba8a2f2d80340ddb6ca8e43e7883812f13cd5`](https://monadscan.com/address/0x796ba8a2f2d80340ddb6ca8e43e7883812f13cd5) | #### JAAA | Network | Asset | Address | |------------------|-------|---------| | Ethereum Mainnet | USDC | [`0x4880799ee5200fc58da299e965df644fbf46780b`](https://etherscan.io/address/0x4880799ee5200fc58da299e965df644fbf46780b) | | Base | USDC | [`0x2AEf271F00A9d1b0DA8065D396f4E601dBD0Ef0b`](https://basescan.org/address/0x4880799ee5200fc58da299e965df644fbf46780b) | | Avalanche | USDC | [`0x1121f4e21ed8b9bc1bb9a2952cdd8639ac897784`](https://snowtrace.io/address/0x1121f4e21ed8b9bc1bb9a2952cdd8639ac897784) | | BSC | USDC | [`0x9effaa5614c689fa12892379e097b3acad239961`](https://bscscan.com/address/0x9effaa5614c689fa12892379e097b3acad239961) | | BSC | USDT | [`0xcbafe61d84c6fb88252a6adf1c9cb0b9d029cb99`](https://bscscan.com/address/0xcbafe61d84c6fb88252a6adf1c9cb0b9d029cb99) | | Monad | USDC | [`0x926030b9912bd42b092151cfb2396499b967df3a`](https://monadscan.com/address/0x926030b9912bd42b092151cfb2396499b967df3a) | #### ACRDX | Network | Asset | Address | |------------------|-------|---------| | Ethereum Mainnet | USDC | [`0x74a739ea1dc67c5a0179ebad665d1d3c4b80b712`](https://etherscan.io/address/0x74a739ea1dc67c5a0179ebad665d1d3c4b80b712) | | Base | USDC | [`0x096dc8ce2fb630b2728c9a088a04b13bbbb5b4f4`](https://basescan.org/address/0x096dc8ce2fb630b2728c9a088a04b13bbbb5b4f4) | | Plume | USDC | [`0x354a9222571259457b2e98b2285b62e6a9bf4ed3`](https://explorer.plume.org/address/0x354a9222571259457b2e98b2285b62e6a9bf4ed3) | | Monad | USDC | [`0x082c62088669facc1fa9f056c5efc8cbccda39b2`](https://monadscan.com/address/0x082c62088669facc1fa9f056c5efc8cbccda39b2) | #### SPXA | Network | Asset | Address | |------------------|-------|---------| | Base | USDC | [`0x99e9092bae6d4394e54034ecb1e45441678323b9`](https://basescan.org/address/0x99e9092bae6d4394e54034ecb1e45441678323b9) | #### deJTRSY | Network | Asset | Address | |------------------|-------|---------| | Ethereum Mainnet | USDC | [`0x18ab9fc0b2e4fef9e0e03c8ec63ba287a3238257`](https://etherscan.io/address/0x18ab9fc0b2e4fef9e0e03c8ec63ba287a3238257) | | Ethereum Mainnet | JTRSY | [`0x1ad3644a0834e7c9ed4aec2660b0ee2ea18a1f36`](https://etherscan.io/address/0x1ad3644a0834e7c9ed4aec2660b0ee2ea18a1f36) | | Avalanche | USDC | [`0x5b9b6070c517be849ad79fc49d95e02084826f77`](https://snowtrace.io/address/0x5b9b6070c517be849ad79fc49d95e02084826f77) | #### deJAAA | Network | Asset | Address | |------------------|-------|---------| | Ethereum Mainnet | USDC | [`0x4865bc9701fbd1207a7b50e2af442c7daf154c9c`](https://etherscan.io/address/0x4865bc9701fbd1207a7b50e2af442c7daf154c9c) | | Ethereum Mainnet | JAAA | [`0x559907981ed375b2d7eea6108273d181216a10cc`](https://etherscan.io/address/0x559907981ed375b2d7eea6108273d181216a10cc) | | Base | USDC | [`0x9183dbe074a61cebf82525c907458cabb984f9da`](https://basescan.org/address/0x9183dbe074a61cebf82525c907458cabb984f9da) | | Avalanche | USDC | [`0x498b6394b778a75ed9b0148e379778070b4621d2`](https://snowtrace.io/address/0x498b6394b778a75ed9b0148e379778070b4621d2) | | Arbitrum | USDC | [`0xe897e7f16e8f4ed568a62955b17744bcb3207d6e`](https://snowtrace.io/address/0x498b6394b778a75ed9b0148e379778070b4621d2) | #### deCRDX | Network | Asset | Address | |------------------|-------|---------| | Optimism | USDC | [`0x67fDa49952Cd0b059d019E51B58e742F9592bB8f`](https://optimistic.etherscan.io/address/0x67fda49952cd0b059d019e51b58e742f9592bb8f) | #### deSPXA | Network | Asset | Address | |------------------|-------|---------| | Base | USDC | [`0x2da40f061536c2f3a8f95f23a5f4c133d07d393a`](https://basescan.org/address/0x2da40f061536c2f3a8f95f23a5f4c133d07d393a) | --- # Multi-chain asset management Source: https://docs.centrifuge.io/developer/protocol/features/chain-abstraction # Multi-chain asset management The Centrifuge protocol is designed to scale tokenized assets across multiple blockchains using a hub-and-spoke architecture. This model solves a fundamental limitation of traditional tokenization protocols: the inability to efficiently operate across chains. ## The problem with isolated multi-chain tokenization In legacy designs, each token is issued on each network, fully isolated. That means if a pool wants to make its asset available across Ethereum, Base, and Arbitrum, it must issue three separate tokens - one on each chain. Each version of the token has to be deployed, managed, and reconciled separately. This model introduces operational overhead: * Each chain requires its own custodian setup or self-custody wallet. * Gas tokens must be managed on every chain. * Blockchain endpoints must be monitored and maintained. * Token accounting becomes fragmented and complex. This approach does not scale in a multi-chain world, where asset issuers increasingly seek access to liquidity across many L1s and L2s. Managing each chain as a silo creates complexity and risk, making cross-chain tokenization inefficient and brittle. ## Centrifuge’s hub-and-spoke solution Centrifuge solves this challenge with a hub-and-spoke architecture. Each pool selects a single hub chain, its source of truth and control, and issues assets and vaults across any number of spoke chains. ![](./images/hub-and-spoke.png) The hub chain serves as the control layer for the pool. It holds the authoritative state of the pool, its vaults, and the overall accounting. The spoke chains are where tokens are deployed and liquidity is accessed. These include Ethereum, Base, Arbitrum, and other supported networks. ## Scalable interoperability To make this possible, Centrifuge includes a robust messaging system between the hub and spoke chains. This system is designed to handle large-scale cross-chain activity with minimal overhead and maximum security. ### Multi-message aggregation To increase the security of cross-chain communication, the protocol integrates multiple interoperability providers. Each message is sent along with multiple independent proofs, allowing verification through more than one interoperability provider. This reduces the risk of a single interoperability provider impacting the security of the system. ### Automatic batching of messages Centrifuge automatically groups multiple messages together whenever possible, into a single payload and set of proofs. This reduces the total number of relayed messages and significantly lowers gas costs. Batching is handled by the protocol itself, requiring no configuration from pool deployers. ### Automatic gas subsidies The protocol also includes automatic gas subsidies for users of a pool, such that: * Gas payments can be subsidized or reimbursed by the pool manager * Pool users do not need to hold native gas tokens on each spoke chain * Centrifuge relays gas tokens automatically between chains to fund transactions These features remove the burden of managing gas on multiple chains and create a seamless user experience, even when interacting with vaults across different networks. ### Built-in retries and repayments Message execution across multiple chains can fail for several issues. To reduce the overhead, the protocol implements: * Built-in repayment method for messages that were underfunded with gas * Automatic retry logic for failed messages This ensures that cross-chain actions are reliably executed without requiring manual intervention from the pool manager or end user. --- # Modular extensions Source: https://docs.centrifuge.io/developer/protocol/features/modularity # Modular extensions The Centrifuge Protocol is designed as a modular, extensible system to support complex financial products. This architecture empowers builders to plug into various components independently or collectively, enabling permissioned tokenization, cross-chain deployment, custom pricing logic, and diverse collateral management. Below is an overview of the smart contracts of the Centrifuge Protocol and where builders can plug in, with some examples: ![](./images/modularity.png) ## Transfer Hooks Transfer Hooks are customizable restrictions and checks that can be applied to ERC20 tokens. They serve as plug-and-play extensions for enforcing: * Permissioned tokenization: Ensure compliance and access control. * Freely transferable tokenization: Used for deRWA tokens to integrate into DeFi. * Minimum investment thresholds: Limit the minimum investment amount. These hooks integrate directly with ERC20 token logic and enable regulatory and operational controls without changing the core token. ## Hook Managers Hook Managers are smart contracts deployed on Spoke chains that build on top of the Transfer Hooks system to provide automated account management capabilities. These managers interact with the underlying transfer restriction hooks to enable: * **Automated whitelisting**: Programmatically add or remove addresses from whitelists based on custom logic or external events * **Freezing and unfreezing accounts**: Dynamically control which accounts can transfer tokens in response to compliance requirements or risk events * **Custom access control logic**: Implement sophisticated rules for managing who can hold and transfer share tokens Hook Managers enable builders to create permissioning systems that respond to onchain or offchain events, integrate with KYC/AML providers, or implement time-based restrictions - all without requiring manual intervention or modifications to the core token contracts. This provides a flexible layer for managing compliance and access control at scale. ## Hub Managers The `Hub` is the central smart contract responsible for managing Net Asset Value (NAV) calculations, investor share class logic, and fund-level mechanics. Using Hub Managers, developers can define custom logic for: * Automated NAV and token pricing * Waterfall structures and tranching * Custom share class management * Request fulfillment logic This module provides fine-grained control over fund architecture and investor dynamics, enabling fully customizable on-chain fund operations. ## Balance Sheet Managers Balance Sheet Managers enable the protocol to support and manage any form of collateral. Whether dealing with tokenized real-world assets (RWAs), stablecoins, crypto-native tokens, or other DeFi protocols - Balance Sheet Managers abstract the logic required to: * On/off ramping for assets * Integrating DeFi protocols * Tracking performance and management fees * Integrating onchain loans This makes Centrifuge ideal for bridging traditional finance and DeFi through modular asset management. ## Request Managers Request Managers can be implemented to enable custom investment and redemption logic cross-chain. The image below shows the primary vaults implementation in the protocol, and how it leverages the immutable core as the base for the cross-chain request handling logic. The orange contracts can be fully customized. ![](./images/request-managers.png) ## Adapters Adapters are the interoperability layer of the protocol. They connect pools on Centrifuge to any blockchain, enabling cross-chain vault deployment and communication. Supported adapters include: * LayerZero adapter * Wormhole adapter * Chainlink adapter * Axelar adapter Adapters route cross-chain messages via the `Gateway` contract, maintaining a 1-to-many relationship between a single hub on one chain and many vaults. --- # Onchain accounting Source: https://docs.centrifuge.io/developer/protocol/features/onchain-accounting # Onchain accounting The Centrifuge Protocol implements fully onchain and automated accounting of tokenized assets. The system provides automated Net Asset Value (NAV) calculations, share pricing, and oracle updates across all deployed networks, leveraging an onchain double-entry bookkeeping system. Below is an overview of how the onchain accounting system works: ![](./images/onchain-accounting.png) ## Investment processing Investments are submitted on each chain (Spokes), minting new shares and depositing ERC20 tokens into the balance sheet. The Queue Manager smart contract enables automatically submitting batches of investment updates cross-chain to the pool manager on the Hub chain. This batch processing ensures efficient cross-chain communication while maintaining accurate state synchronization across all networks. ## Hub state management The Hub chain serves as the source of truth for the protocol's accounting state. When it receives investment updates, it: * Increases holdings for the invested assets * Stores the updated token issuance for each Spoke network * Increases the total issuance across all networks This centralized state management ensures consistency while still supporting multi-chain deployments. ## Double-entry bookkeeping The Hub maintains an onchain double-entry bookkeeping system that records all financial transactions. When investments are processed, the system: * Debits the asset account (per invested asset per chain) * Credits the equity account (per chain) This traditional accounting approach, implemented onchain, provides transparency and auditability for all participants. ## NAV Manager The NAV Manager smart contract recomputes the Net Asset Value based on the latest state in the bookkeeping system. It calculates: ``` NAV = equity + gain - loss - liability ``` This automated calculation ensures that the fund's value is always up-to-date and verifiable onchain, eliminating the need for manual NAV calculations or off-chain oracle inputs. ## Price Manager & oracle updates The Simple Price Manager smart contract calculates the updated share price based on: ``` Share price = NAV / total issuance ``` Once calculated, it automatically submits oracle updates to all networks where the token is deployed, ensuring price consistency across chains. ## Benefits Together, this onchain accounting system delivers: * **Transparency**: Full visibility into pricing logic and accounting state * **Automation**: Automated NAV and pricing calculations without manual intervention * **True onchain accounting**: Complete double-entry bookkeeping maintained onchain * **Cross-chain consistency**: Automated oracle updates ensure synchronized pricing across all networks * **Builder-friendly**: Any builder on the Centrifuge Protocol can leverage this system in their products --- # Token compliance Source: https://docs.centrifuge.io/developer/protocol/features/token-compliance # Token compliance Centrifuge share tokens are ERC-20 tokens extended with ERC-1404 (Simple Restricted Token) and a modular transfer hook system. Together, these provide the compliance capabilities that institutional investors require: identity management, transfer restrictions, freeze and clawback controls. The design keeps gas costs low enough for full DeFi composability. ## Architecture: share token + transfer hook Every Centrifuge share token references an optional transfer hook contract. The hook is called on every token transfer, mint, and burn, and can enforce arbitrary compliance rules. The base hook provides built-in support for: - Investor whitelisting - a memberlist with per-address validity timestamps, managed by pool managers or via multichain messages from the hub - Account freezing - frozen addresses cannot send or receive tokens - Action-aware rules - the hook distinguishes between deposits, redemptions, multichain transfers, and peer-to-peer transfers, enabling fine-grained policies per action type ## Compliance capabilities | Capability | Implementation | | --- | --- | | Issue tokens | Restricted to balance sheet managers | | Transfer tokens with compliance checks | Hook enforces rules on every `transfer` / `transferFrom` | | Limit token bridging | Hook is invoked on outgoing and incoming cross-chain transfers, allowing bridging to be blocked per address or per destination chain | | Whitelist / blacklist addresses | Memberlist with per-address validity timestamps | | Multiple trusted issuers | Multiple managers per token, each with independent authority | | Freeze / unfreeze an address | Per-address freeze flag in token storage | | Burn (revoke) tokens | Restricted to balance sheet managers | | Recover tokens from a lost wallet | Forced transfer via balance sheet `transferSharesFrom()` | The protocol ships with four pre-built hooks plus the option to set no hook at all. Each share class can use a different hook, and the hook can be changed without redeploying the token. - **Full Restrictions** - users must be on the memberlist for deposits, redemptions, and transfers. Frozen users are blocked from all operations. The most restrictive configuration. - **Freely Transferable** - users must be on the memberlist for deposits and redemptions, but tokens can be freely transferred to any non-frozen address. Suitable for tokens that should trade on secondary markets. - **Redemption Restrictions** - users must be on the memberlist only for redemption requests. All other operations are unrestricted for non-frozen users. - **Freeze Only** - no memberlist requirements. The only restriction is the ability to freeze individual addresses, blocking all their transfers. - **No hook** - fully permissionless. The token behaves as a standard ERC-20 with no transfer restrictions. Beyond these, custom hooks can enforce additional rules such as maximum holder counts, minimum investment sizes, lockup periods, and jurisdiction-based restrictions. The hook is upgradeable per share class, allowing compliance rules to evolve as requirements change. ## Securing vault actions The hook is invoked on every token movement with an action-specific context, so each ERC-7540 vault action can be gated independently: deposit requests, redemption requests, deposit claims, and redemption claims. The same applies to multichain transfers and peer-to-peer transfers. Issuers can, for example, require KYC on deposit requests while allowing free secondary market transfers, freeze an address for redemptions without blocking incoming transfers, or disable bridging for specific users without affecting their vault activity. ## Multichain management All compliance operations (whitelisting, freezing, unfreezing) can be managed centrally from the hub chain. A single transaction on the hub can whitelist a batch of investors across all spoke chains where a token is deployed. This eliminates the need to submit separate transactions on each chain and ensures consistent compliance state across the entire multichain deployment. ## Design advantages **Gas-efficient.** Each address's token balance and compliance data are packed into a single 256-bit storage slot. A compliance check on transfer reads the same slot as the balance check, with no external contract calls or additional storage lookups. Other approaches such as ERC-3643 require multiple external contract calls (identity registry, compliance contract, post-transfer callbacks) on every transfer, adding significant gas overhead that limits DeFi composability. ![](./images/restriction-data.png) **Flexible.** Issuers choose from pre-built restriction profiles or deploy custom hooks with arbitrary logic. The hook system is composable and not tied to any specific compliance vendor or module framework. **DeFi-native.** The hook system is aware of vault interactions (ERC-7540) and can enforce different rules for deposits, redemptions, and peer transfers. For example, requiring KYC for vault interactions while allowing free secondary market transfers. ## Standards Centrifuge share tokens implement ERC-20, ERC-1404 (transfer restriction detection), ERC-2612 (permit / gasless approvals), and ERC-7575 (share token for tokenized vaults). The protocol's vault contracts implement ERC-7540 (async deposits and redemptions) and are backward-compatible with ERC-4626. --- # Standards-based composability Source: https://docs.centrifuge.io/developer/protocol/features/vaults # Standards-based composability The Centrifuge protocol leverages tokenized vault standard for investments and redemptions of share tokens. ## Vault types The protocol supports asynchronous and synchronous vaults for investments. ### Asynchronous vaults Asynchronous vaults are fully request-based and follow the ERC-7540 standard. They allow both deposit and redemption actions to be handled through an asynchronous workflow, using the Centrifuge Hub to manage requests. ### Synchronous deposit vaults These vaults follow a hybrid model using both ERC-4626 and ERC-7540. Deposits are executed instantly using ERC-4626 behavior, allowing users to receive shares immediately. However, redemptions are handled asynchronously through ERC-7540, using the Hub to queue and manage the withdrawal requests. ## Pooled vaults It is possible to support multiple investment assets per share token, using the ERC-7575 standard. Liquidity from all vaults is aggregated into a single balance sheet, to be allocated efficiently across all assets. ![](./images/pooled-vaults.png) --- # Bridge share tokens Source: https://docs.centrifuge.io/developer/protocol/guides/bridge-share-tokens # Bridge share tokens Centrifuge supports native mint-and-burn bridging for any share token across any supported network. This guide explains how to use the bridge. ## Bridging To bridge share tokens across chains, you must call the `crosschainTransferShares` function on the `Spoke` contract. ```solidity spoke.crosschainTransferShares{value: gas}(centrifugeId, poolId, scId, receiver, amount, extraGasLimit, remoteExtraGasLimit, msg.sender); ``` * `centrifugeId` The identifier of the target network. You can look up valid IDs on the [deployments](https://docs.centrifuge.io/developer/protocol/deployments/#centrifuge-ids) page. * `poolId` The pool identifier from which the shares will be transferred. * `scId` The ID of the share class within the pool. * `receiver` The address of the recipient on the destination chain. :::info EVM addresses must be right-padded with zeros to 32 bytes. E.g. `0x7Ed48C31f2fdC40d37407cBaBf0870B2b688368f` becomes `0x7Ed48C31f2fdC40d37407cBaBf0870B2b688368f000000000000000000000000`. ::: * `amount` The amount of share tokens to transfer, expressed in the token’s decimals. The most common share token decimal configuration is 18. * `extraGasLimit` Additional gas for the cross-chain message processing on the Hub. This can be set to `0` in most cases. * `remoteExtraGasLimit` Additional gas forwarded to the remote execution on the destination chain. This can be set to `0` in most cases. * `msg.sender` Address to receive any excess gas refund. The `gas` value passed to the transaction is used to pay for the cross-chain transfer. You can overestimate this value as any excess gas will be refunded in the same transaction. ## Share token restrictions You must ensure that the `receiver` on the target network can receive share tokens. If the share token has a whitelist or other forms of restrictions, the receiver must be whitelisted before the transfer is executed on the target chain. If the receiver is not whitelisted, the minting transaction on the destination network will fail, and will need to be manually re-executed once the receiver is whitelisted. ## Monitoring To follow the progress of the transaction, you can find the status on [Centrifugescan](https://centrifugescan.io/). If the minting of the share tokens on the destination network fails for some reason, you can also use Centrifugescan to re-execute the transaction. --- # Create a pool Source: https://docs.centrifuge.io/developer/protocol/guides/create-a-pool # Create a pool Centrifuge enables the tokenization and management of pools on multiple chains. This guide walks you through the process of creating a pool, launching share classes, and deploying share tokens using the Centrifuge protocol. ## Core concepts #### Pool A Pool represents a distinct investment product or strategy. Each pool can exist across multiple chains, identified globally by a unique `poolId`. #### Share class Each pool can have multiple share classes, each with its own share token. These tokens represent claims to the underlying assets or yield and can be permissioned or permissionless. #### Share token Each share class is deployed as a token (ERC-20 compatible) on every supported network. These tokens have a transfer hook to enable permission logic. ## Pool parameters When creating a pool, you need to choose two key parameters that cannot be changed after creation: the hub chain and the denomination currency. ### Hub chain The hub chain is the primary network where your pool is created and managed. All administrative transactions (such as deploying tokens and vaults, updating permissions, and managing requests) are submitted on the hub chain and then bridged to all other spoke chains where the pool operates. Choosing the right hub chain is important because: * It cannot be changed: Once a pool is created on a hub chain, migrating to a different hub chain would require creating an entirely new pool and migrating all users. * Transaction costs: All management operations incur gas costs on the hub chain. For more details on how the hub and spoke architecture works, read more [about the architecture here](/developer/protocol/features/chain-abstraction#centrifuges-hub-and-spoke-solution). ### Denomination currency The denomination currency is the accounting unit for your pool. It does not restrict which assets investors can deposit or redeem, those are determined by which [vaults you deploy](/developer/protocol/guides/deploy-vaults). Instead, it serves as an intermediate unit that the protocol uses internally for pricing and accounting. #### How pricing works The protocol uses two prices to convert between deposit assets and shares: - **`pricePoolPerAsset`**: The value of one asset unit in pool currency units (e.g., 1 USDC = 1.0 USD) - **`pricePoolPerShare`**: The value of one share unit in pool currency units (e.g., 1 share = 1.05 USD) When an investor deposits assets, the conversion flows through the pool currency: ``` Asset amount → Pool currency amount → Share amount ``` For redemptions, the flow reverses: shares convert to pool currency via `pricePoolPerShare`, then to payout assets via `pricePoolPerAsset`. #### Where the denomination currency matters - **Asset price oracles**: If you accept deposit assets that are not pegged 1:1 to the pool currency (e.g., ETH deposits into a USD-denominated pool), you need to configure a price oracle on the hub chain to provide the `pricePoolPerAsset` for those assets - **Onchain accounting**: If you use [onchain accounting](/developer/protocol/features/onchain-accounting), all calculations are expressed in this currency Note that shares and pool currency always use the same number of decimals, so no decimal conversion is needed between them. #### Choosing a denomination currency You can use: - **Fiat currencies**: Use `newAssetId(isoCode)` where `isoCode` is the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) numeric code (e.g., `840` for USD, `978` for EUR) - **Registered assets**: Any ERC20 token that has been registered as an asset on any chain ## Step-by-step: creating a pool ### 1. Derive the unique pool ID To deploy a pool across different networks, choose which network you want to use as the Hub network. This will be where you create the pool, manage permissions, and control all other networks. Use the `centrifugeId` as the network identifier of the Hub chain and derive a network-specific `PoolId`. ```solidity PoolId poolId = hubRegistry.poolId(centrifugeId, 1); // Derive a unique PoolId using centrifugeId and a local identifier ``` * `centrifugeId`: [View all possible centrifuge IDs here](/developer/protocol/deployments/#centrifuge-ids) * `1`: A local identifier. ### 2. Create the pool Call the `createPool` function with the derived `PoolId`, the pool manager, and the [denomination currency](#denomination-currency). ```solidity hub.createPool(poolId, msg.sender, newAssetId(840)); ``` :::info Currently, pool creation is still permissioned, while the protocol is in its initial rollout. Pool creation will be opened up permissionlessly in the following months. ::: ### 3. Set metadata Optionally set metadata to describe the pool: ```solidity hub.setPoolMetadata(poolId, bytes("Testing pool")); ``` This can include information to be shown in the UI. ### 4. Set adapters Before notifying other networks, configure the cross-chain messaging adapters for each network where the pool will operate. Adapters handle message routing between the hub chain and spoke chains via the `Gateway` contract. ```solidity address[] memory localAdapters = new address[](1); localAdapters[0] = 0xD517BC7ba17271a8D87BE7355B2523bF5c750295; // LayerZero Adapter bytes32[] memory remoteAdapters = new bytes32[](1); remoteAdapters[0] = bytes32(bytes20(0xD517BC7ba17271a8D87BE7355B2523bF5c750295)); // LayerZero Adapter on remote chain uint8 threshold = 1; // Number of adapters required to process a message uint8 recoveryIndex = 1; // Adapters from this index onward are recovery-only hub.setAdapters{value: gas}(poolId, centrifugeId, localAdapters, remoteAdapters, threshold, recoveryIndex, msg.sender); ``` * `localAdapters`: Adapter contract addresses on the hub chain * `remoteAdapters`: Corresponding adapter addresses on the remote chain (as `bytes32`) * `threshold`: Minimum number of adapters required to process a message * `recoveryIndex`: Index in the adapters array from which adapters are considered recovery adapters * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund Adapter addresses can be found on the [deployments page](/developer/protocol/deployments). Call this for every `centrifugeId` where the pool will be launched. ### 5. Notify pool registration Once created, the pool must notify the other networks of its existence. This should be called for every `centrifugeId` where the pool is going to be launched. ```solidity hub.notifyPool{value: gas}(poolId, centrifugeId, msg.sender); ``` * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund ## Adding share classes Each pool can support multiple share classes, e.g., for different tranches or investor types. ### 1. Preview share class ID Before creating a share class, preview its ID to use for further configuration. ```solidity ShareClassId scId = shareClassManager.previewNextShareClassId(poolId); ``` ### 2. Add the share class Add a new share class with a name, symbol, and optional metadata: ```solidity hub.addShareClass(poolId, "Tokenized MMF", "MMF", bytes32(bytes("1"))); ``` * `"Tokenized MMF"`: ERC20 token name * `"MMF"`: ERC20 token symbol * `bytes32(bytes("1"))`: Salt, to be used for deterministic deployments and vanity addresses. Needs to be globally unique. ### 3. Deploy the share token(s) Once created, the pool must notify the other networks of each share class. This should be called for every `centrifugeId` where the share token is going to be launched. For each token, choose the hook that you want: - FullRestrictions: any user needs to be added to the memberlist for every deposit/redeem request as well as transfers. - FreelyTransferable: any user needs to be added to the memberlist for every deposit/redeem request, while transfers can be made to anyone that is not frozen. - RedemptionRestrictions: any user needs to be added to the memberlist only for redeem requests as well as transfers. - FreezeOnly: users don't need to be added for requests, but it is possible to freeze users. - `address(0)`: token is fully permissionless. ```solidity hub.notifyShareClass{value: gas}(poolId, scId, centrifugeId, bytes32(bytes20(hook)), msg.sender); ``` * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund This will deploy the ERC20 share token. --- # Deploy vaults Source: https://docs.centrifuge.io/developer/protocol/guides/deploy-vaults # Deploy vaults The Centrifuge protocol supports deploying and managing different types of vaults that facilitate investment into real-world assets. Vaults are associated with a specific pool (`poolId`), share class (`scId`), asset (`assetId`), and network (`centrifugeId`). The protocol supports both asynchronous and synchronous vault configurations, and relies on standardized interfaces to ensure cross-chain operability and compatibility with multiple asset types. ## Deploying vaults Before deploying a vault, the associated share token must be deployed on the target network. This token represents the share class (`scId`) that investors receive when interacting with the vault. ### Setting up the vault contracts #### Asynchronous vaults For asynchronous vaults, the `AsyncRequestManager` contract must be configured in two roles: 1. As the Spoke Request Manager, so it can handle investment actions via the Hub. 2. As the Balance Sheet Manager, so it can move assets in and out of the balance sheet. The `BatchRequestManager` also needs to be configure as the Hub Request Manager. The configuration is done using the following commands: ```solidity hub.setRequestManager{value: gas}(poolId, centrifugeId, hubRequestManagerContract, bytes32(bytes20(address(batchRequestManager))), bytes32(bytes20(address(asyncRequestManager))), msg.sender); hub.updateBalanceSheetManager{value: gas}(poolId, centrifugeId, bytes32(bytes20(address(asyncRequestManager))), true, msg.sender); ``` * `batchRequestManager`: The Hub-side request manager contract instance * `asyncRequestManager`: The Spoke-side request manager contract instance * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund After setup, the asynchronous vault can be deployed with: ```solidity hub.updateVault{value: gas}(poolId, scId, assetId, bytes32(bytes20(address(asyncVaultFactory))), VaultUpdateKind.DeployAndLink, 0, msg.sender); ``` * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund #### Synchronous deposit vaults For synchronous vaults, the `SyncManager` must be set as a Balance Sheet Manager, which allows it to handle asset transfers as part of deposit execution: ```solidity hub.updateBalanceSheetManager{value: gas}(poolId, centrifugeId, bytes32(bytes20(address(syncManager))), true, msg.sender); ``` * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund Once the manager is configured, the vault is deployed using: ```solidity hub.updateVault{value: gas}(poolId, scId, assetId, bytes32(bytes20(address(syncDepositVaultFactory))), VaultUpdateKind.DeployAndLink, 0, msg.sender); ``` * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund ### Price initialization After the vault is deployed, it cannot accept investments until the share and asset prices has been updated at least once. This ensures that the value of shares can be accurately calculated based on the current value of underlying assets. This is documented on the [manage a pool page](/developer/protocol/guides/manage-a-pool/#pushing-to-price-oracles). --- # Invest into a vault Source: https://docs.centrifuge.io/developer/protocol/guides/invest-into-a-vault # Invest into a vault This guide explains how to invest in and redeem from Centrifuge vaults, using both asynchronous and synchronous vault types. ## Asynchronous vaults Asynchronous vaults batch and process deposits at set intervals. Deposits and redemptions are split into a request phase and a claim phase, with fulfillment by the pool issuer in between. ![](./images/async-flow.png) ### Deposit Approve the vault to spend your tokens, then submit a deposit request: ```solidity asset.approve(address(vault), assets); vault.requestDeposit(assets, user, user); ``` * `assets`: amount of underlying asset to request a deposit for. * The second and third `user` values specify ownership and destination. The request is queued and processed by the pool issuer. After fulfillment, claim the shares: ```solidity vault.mint(vault.maxMint(user), user); ``` * `vault.maxMint(user)` returns the number of shares available to mint based on the fulfilled request. ### Redemption Submit a redemption request for the shares you want to redeem: ```solidity vault.requestRedeem(shares, user, user); ``` After fulfillment, withdraw the underlying asset: ```solidity vault.withdraw(vault.maxWithdraw(user), receiver, user); ``` * `vault.maxWithdraw(user)` computes the maximum amount of assets that can now be withdrawn. * `receiver`: address to receive the underlying asset (e.g., USDC). ## Synchronous deposit vaults Synchronous vaults process deposits immediately within a single transaction, but redemptions still settle asynchronously. ### Deposit Approve the vault, then deposit assets to receive vault shares in the same transaction: ```solidity asset.approve(address(vault), assets); vault.deposit(assets, receiver); ``` * `assets`: amount of underlying asset to deposit (e.g., 1000 \* 1e6 for 1000 USDC). * `receiver`: address to receive the vault shares. ### Redemption Synchronous vaults use the same redemption flow as [asynchronous vaults](#redemption): submit `requestRedeem`, wait for fulfillment, then call `withdraw`. ## Acting on behalf of another user The ERC-7540 standard allows a user to submit deposit and redemption requests on behalf of another address. The `requestDeposit` and `requestRedeem` functions take separate `controller` and `owner` parameters: ```solidity vault.requestDeposit(assets, controller, owner); vault.requestRedeem(shares, controller, owner); ``` * `owner`: the source of the assets (for deposits) or shares (for redemptions). Must equal `msg.sender` unless the owner has approved `msg.sender` as an operator. * `controller`: the address that controls the request. This address can later claim the resulting shares or assets, cancel the request, and manage the request lifecycle. ### Operator approvals An owner can approve another address as an operator using `setOperator`: ```solidity vault.setOperator(operator, true); ``` Once approved, the operator can call `requestDeposit` or `requestRedeem` with the owner's address as the `owner` parameter, cancel requests, and claim on behalf of the controller. :::warning Approving an operator grants it control over both the assets and shares associated with the vault. Only approve trusted addresses. ::: ### Smart contract integrations If you are building a smart contract that wraps vault interactions (e.g., an aggregator or routing contract): * Your contract is typically both `msg.sender` and `owner`: users transfer assets to your contract first, and your contract calls `requestDeposit` with itself as the `owner`. Alternatively, users can approve the vault directly and your contract passes the user's address as `owner` (if the user has approved your contract as an operator). * Track which address is the `controller` for each request, since that address controls the claim and cancellation lifecycle. * When claiming on behalf of users, ensure the `controller` parameter matches the address that submitted the original request. :::info For smart contract integrations, call `vault.isPermissioned(address(yourContract))` before submitting any requests. If your contract cannot hold shares, deposit and claim operations will revert. ::: ## FAQ ### How long does it take for requests to be processed? Asynchronous requests are fulfilled by the pool issuer. The time to fulfillment depends on the specific product and its subscription or redemption settlement cycle. ### How do I get the current share price? Use `vault.convertToAssets()` to convert a share amount to its current value in the investment asset: ```solidity uint8 shareDecimals = vault.share().decimals(); uint256 oneShare = 10 ** shareDecimals; uint256 assetValue = vault.convertToAssets(oneShare); ``` The result is denominated in the investment asset and uses the asset's decimals (e.g., 6 for USDC). :::info The price returned by `convertToAssets()` may not be the exact price at which a request is settled, since there can be a time lag between querying and fulfillment. ::: ### Who pays for cross-chain gas? Investments are processed locally. The vault mints share tokens natively on the chain where it is deployed and holds the deposited assets in a local escrow on the same chain. You only pay gas for the local transaction. Each vault interaction also emits a cross-chain message to the hub chain, so the pool issuer can keep bookkeeping consistent across every chain where the share token is deployed. The vault contracts estimate the cost of this message during the investment transaction, and the actual cost is drawn from the pool's subsidy balance held by the [Subsidy Manager](https://github.com/centrifuge/protocol/blob/main/src/utils/SubsidyManager.sol). The issuer tops this balance up per pool, so investors do not pay cross-chain fees. --- # Manage a pool Source: https://docs.centrifuge.io/developer/protocol/guides/manage-a-pool # Manage a pool This guide outlines how to manage a pool on the Centrifuge protocol. ## Price updates Each pool on the Centrifuge protocol can have multiple share classes. The price of each share token must be maintained and updated to reflect its current value. This price is managed at the protocol level through the Hub contract. To update the token price for a specific share class in a pool, call the following function on the Hub: ```solidity hub.updateSharePrice(poolId, scId, sharePrice, uint64(block.timestamp)); ``` * `poolId`: the unique identifier of the pool * `scId`: the identifier of the share class within the pool * `sharePrice`: the new price of the share token (18 decimal fixed point integer) * `uint64(block.timestamp)`: timestamp when the price was computed :::info[On-chain pricing] Currently, the pricing mechanism is intended to be provided by an off-chain computation. In the future, on-chain price calculations will be implemented, using the holdings and double-entry bookkeeping accounting mechanism of the Hub. ::: ### Pushing to price oracles After updating the share token price, it must be pushed to the price oracle on each deployed network. This ensures that other contracts and off-chain components can retrieve the latest share price. To notify the price oracle, call the following function: ```solidity hub.notifySharePrice{value: gas}(poolId, scId, centrifugeId, msg.sender); ``` * `poolId`: the identifier of the pool whose share price was updated * `scId`: the share class identifier for which the price was updated * `centrifugeId`: the network identifier where the oracle should receive the updated price * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund ### Updating and pushing asset prices Since multiple assets can be used to invest in the same pool, the price of the asset denominated in the currency of the pool (e.g. USD) also needs to be set. By default, this price is assumed to be `1.0`, implying a 1-to-1 peg between all assets. This needs to be pushed to the asset price oracle on each network once: ```solidity hub.notifyAssetPrice{value: gas}(poolId, scId, assetId, msg.sender); ``` * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund ## Managing investment requests Investment requests, whether deposits or redemptions, are submitted by users through vaults operating on various chains. Despite being initiated on different networks, these requests are managed centrally on the Hub chain. This ensures consistent processing and coordination across the entire protocol. Each request is tracked in a central contract called the `BatchRequestManager`. ### Lifecycle of a request Deposit and redeem requests move through six stages: * **Queued**: An optional stage that requests go through if there was non-zero approved or issued/revoked but not yet fulfilled requests for this user. * **Pending**: The initial state after submission by the user. The request has not yet been approved. * **Approved**: The Hub manager approves the request. For deposits, this allows Balance Sheet Managers to withdraw the requested assets and allocate them as needed. At this stage, the request is not yet priced. The request can not be cancelled anymore once approved. * **Issued/revoked**: A share price is assigned. For deposits, shares are issued to the user; for redemptions, shares are revoked in return for assets. * **Fulfilled**: The corresponding vault is informed that the request has been processed. The user can now claim their shares (for deposits) or assets (for redemptions). * **Claimed**: The user has successfully claimed their resulting assets or shares. ![](./images/request-stages.png) The separation of approval and issuance/revocation is to be used for cases where the price of the execution depends on buying or selling underlying assets, which can only happen after the request is fulfilled and the assets can be withdrawn and the request cannot be cancelled anymore. :::info[ERC-4626 vaults] For synchronous deposit vaults using ERC-4626, asset deposits skip all six stages, and shares are immediately minted into the user's wallet. ::: ### Request batching and partial fulfillment The `BatchRequestManager` maintains First-In-First-Out (FIFO) batch-based logic for all pending requests. Batches are created when the fund manager triggers an approval, all requests that are pending at that moment become part of that batch. When a batch is approved with a specific total amount, the `BatchRequestManager` processes all requests in that batch. If the approved amount is insufficient to fulfill all requests in the batch, each request is fulfilled proportionally based on its share of the total requested amount. As an example, consider three deposit requests submitted in this order: 1. User A requests 10 shares 2. User B requests 5 shares 3. User A requests another 10 shares **Case 1: Approving after each request** If the fund manager approves 15 shares after the second request is submitted: - **Batch 1** (requests 1-2, approved for 15 shares): - First request (User A, 10 shares): Fully approved ✓ - Second request (User B, 5 shares): Fully approved ✓ Then if the fund manager approves 5 shares after the third request: - **Batch 2** (request 3, approved for 5 shares): - Third request (User A, 10 shares): Partially approved for 5 shares After both batches: - User A has 10 shares fully approved from first request, and 5 shares partially approved from second request - User B has 5 shares fully approved **Case 2: Approving after all three requests** If the fund manager waits and approves 20 shares only after all three requests are submitted: - **Batch 1** (requests 1-3, approved for 20 shares total out of 25 requested): - First request (User A, 10 shares): Partially approved for 8 shares (10 / 25 × 20 = 8) - Second request (User B, 5 shares): Partially approved for 4 shares (5 / 25 × 20 = 4) - Third request (User A, 10 shares): Partially approved for 8 shares (10 / 25 × 20 = 8) After this batch: - User A has 8 shares approved from first request and 8 shares approved from second request (16 total) - User B has 4 shares approved This batch-based FIFO approach ensures fair and predictable processing across all investors, with the timing of approvals determining which requests are grouped together for processing. ### Approving a request Once a request is pending, the Hub manager must approve it. This is done by calling the appropriate function on the `BatchRequestManager`: ```solidity batchRequestManager.approveDeposits{value: gas}(poolId, scId, assetId, batchRequestManager.nowDepositEpoch(poolId, scId, assetId), approvedAssetAmount, pricePoolPerAsset, msg.sender); batchRequestManager.approveRedeems{value: gas}(poolId, scId, assetId, batchRequestManager.nowRedeemEpoch(poolId, scId, assetId), approvedShareAmount, pricePoolPerAsset); ``` * `approvedAssetAmount` / `approvedShareAmount`: The total amount being approved for this batch * `pricePoolPerAsset`: The price of the asset denominated in the pool currency (18 decimal fixed point integer) * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund (deposits only) Approving a deposit allows any authorized Balance Sheet Manager to withdraw the approved amount of assets. These assets can be invested before the share price is determined. ### Issuing or revoking shares After approval, the next step is to finalize the share price and process the request. This is done by issuing shares (for deposits) or revoking shares (for redemptions): ```solidity batchRequestManager.issueShares{value: gas}(poolId, scId, assetId, batchRequestManager.nowIssueEpoch(poolId, scId, assetId), pricePoolPerShare, extraGasLimit, msg.sender); batchRequestManager.revokeShares{value: gas}(poolId, scId, assetId, batchRequestManager.nowRevokeEpoch(poolId, scId, assetId), pricePoolPerShare, extraGasLimit, msg.sender); ``` * `pricePoolPerShare`: The share price in pool currency (18 decimal fixed point integer) * `extraGasLimit`: Additional gas limit for cross-chain message execution (uint128, can usually be set to 0) * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund This step locks in the value of the transaction by applying the calculated share price. ### Notifying vaults for fulfillment Once shares are issued or revoked, each vault must be notified so that individual users can claim their resulting assets or shares. This is done per user using the following calls: ```solidity uint32 maxClaims = batchRequestManager.maxDepositClaims(poolId, scId, bytes32(bytes20(user)), assetId); hub.notifyDeposit(poolId, scId, assetId, bytes32(bytes20(user)), maxClaims); uint32 maxClaims = batchRequestManager.maxRedeemClaims(poolId, scId, bytes32(bytes20(user)), assetId); hub.notifyRedeem(poolId, scId, assetId, bytes32(bytes20(user)), maxClaims) ``` After this, the request is marked as fulfilled, and the user can proceed to claim. --- # Manage assets Source: https://docs.centrifuge.io/developer/protocol/guides/manage-assets # Manage assets This guide walks through how to configure a balance sheet manager, and how to perform asset deposits and withdrawals from the protocol. ## Configuring a Balance Sheet Manager Before assets can be deposited or withdrawn, a balance sheet manager must be assigned for the pool. This manager controls access to the pool’s assets. Call the following method on the Hub, to set a balance sheet manager, for the `centrifugeId` where you want to manage assets: ```solidity hub.updateBalanceSheetManager{value: gas}(poolId, centrifugeId, bytes32(bytes20(manager)), true, msg.sender); ``` #### Parameters * `poolId`: The pool ID. * `centrifugeId`: The network identifier. * `bytes32(bytes20(manager))`: The manager’s address, converted to `bytes32`. * `true`: Indicates that this manager is being added (use `false` to remove a manager). * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded). * `msg.sender`: Address to receive any excess gas refund. #### Supported manager types * **Gnosis Safe** or **Fireblocks wallet**: For direct control by the manager of the pool. * [**On/Off Ramp Manager**](/developer/protocol/managers/on-offramp-manager/): Restricts asset flows to a set of predefined whitelisted addresses. * [**Merkle Proof Manager**](/developer/protocol/managers/merkle-proof-manager/): Enables integration with third-party protocols. ## Withdrawing assets Once a balance sheet manager is configured, assets can be withdrawn manually. Here’s an example for withdrawing USDC: ```solidity balanceSheet.withdraw(poolId, scId, address(usdc), 0, receiver, amount); ``` #### Parameters * `poolId`: ID of the pool from which to withdraw. * `scId`: ID of the share class on which to represent the withdrawn assets. * `address(usdc)`: Token contract address. * `0`: Token type identifier. * `receiver`: Address that will receive the withdrawn funds. * `amount`: Amount of the token to withdraw. :::info[Token support] The balance sheet supports both ERC20 and ERC6909 tokens. The token type is specified using the numeric identifier - use `0` for ERC20 and a non-zero `tokenId` for ERC6909. ::: ## Depositing assets To deposit assets into a pool, you must first approve the `balanceSheet` contract to spend the tokens. Then call the `deposit` function. ```solidity usdc.approve(balanceSheet, amount); balanceSheet.deposit(poolId, scId, address(usdc), 0, amount); ``` #### Parameters * `poolId`: ID of the destination pool. * `scId`: ID of the share class on which to represent the deposited assets. * `address(usdc)`: Token contract address. * `0`: Token type (`0` for ERC20). * `amount`: Amount of the token to deposit. --- # Set up onchain valuation Source: https://docs.centrifuge.io/developer/protocol/guides/setup-onchain-valuation # Set up onchain valuation This guide walks through how to set up fully onchain NAV calculations and share pricing for a pool using the Centrifuge Protocol's [onchain accounting](/developer/protocol/features/onchain-accounting) system. By the end of this guide, your pool will automatically compute the Net Asset Value based on onchain holdings and update the share price on the Hub. ![](../../features/onchain-accounting/images/onchain-accounting.png) This guide uses a pool with two asset types as an example: * **USDC**: An ERC20 stablecoin (priced 1:1 to USD) * **LoanNFT**: An ERC6909 token representing a collection of loans, valued by a custom valuation contract ## Step 1: Deposit assets into the balance sheet First, deposit the assets that the pool holds into its balance sheet. This registers them in the protocol's accounting system. For the ERC20 token (USDC): ```solidity usdc.approve(address(balanceSheet), usdcAmount); balanceSheet.deposit(poolId, scId, address(usdc), 0, usdcAmount); ``` For the ERC6909 token (LoanNFT): ```solidity loanNFT.setOperator(address(balanceSheet), true); balanceSheet.deposit(poolId, scId, address(loanNFT), tokenId, 1); ``` :::info[Token support] The balance sheet supports both ERC20 and ERC6909 tokens. Use token type `0` for ERC20 tokens and a non-zero `tokenId` for ERC6909 tokens. ERC721 NFTs can be supported by wrapping into ERC6909. ::: ## Step 2: Enable Hub Managers The NAV Manager and Simple Price Manager need to be added as [Hub Managers](/developer/protocol/features/modularity#hub-managers) for the pool. This grants them permission to make accounting and price updates. ```solidity hub.updateManager(poolId, address(navManager), true); hub.updateManager(poolId, address(simplePriceManager), true); ``` ## Step 3: Enable the Queue Manager Enable the [`QueueManager`](https://github.com/centrifuge/protocol/blob/main/src/managers/spoke/QueueManager.sol) for the pool. The Queue Manager allows permissionless syncing of queued asset and share updates to the Hub, which triggers NAV recomputation through the snapshot hook. ```solidity hub.updateBalanceSheetManager(poolId, address(queueManager), true); ``` ## Step 4: Initialize the accounting network Initialize the NAV Manager for the network where the pool's assets are held. This sets up the double-entry bookkeeping accounts needed for NAV computation. ```solidity navManager.initializeNetwork(poolId, scId, centrifugeId); ``` * `centrifugeId`: The network identifier where the assets are deposited ## Step 5: Initialize holdings with valuation Each holding needs a valuation strategy that determines how the asset is priced. Initialize each holding with the appropriate valuation contract. ### Fixed-price asset (USDC) For assets with a stable value pegged 1:1 to the pool currency, use the `IdentityValuation` contract: ```solidity navManager.initializeHolding(poolId, scId, usdcAssetId, address(identityValuation)); ``` The `IdentityValuation` contract returns the holding quantity as its value, effectively pricing the asset at 1.0 in the pool currency. ### Custom-valued asset (LoanNFT) For assets that require custom pricing logic, such as a portfolio of loans, deploy a contract that implements the `IValuation` interface: ```solidity navManager.initializeHolding(poolId, scId, loanNFTAssetId, address(loanValuation)); ``` ### Implementing IValuation Your custom valuation contract must implement the [`IValuation`](https://github.com/centrifuge/protocol/blob/main/src/core/hub/interfaces/IValuation.sol) interface, which has two functions — `getPrice` and `getQuote`. For a loan portfolio, the contract should return the total accrued value of all underlying loans: ```solidity contract LoanValuation is IValuation { function getPrice(PoolId poolId, ShareClassId scId, AssetId assetId) external view returns (D18) { // Return the price of the asset as a fixed point number with 18 decimals } function getQuote(PoolId poolId, ShareClassId scId, AssetId assetId, uint128 baseAmount) external view returns (uint128 quoteAmount) { // Calculate and return the value of baseAmount of the asset // in the pool's denomination currency } } ``` ## Step 6: Set up the snapshot hook Connect the NAV Manager as a snapshot hook on the Hub. This ensures that whenever the balance sheet state is updated (e.g. through `submitQueuedAssets`, `submitQueuedShares`, or `updateHoldingValue`), the NAV Manager is automatically triggered to recompute the NAV. ```solidity hub.setSnapshotHook(poolId, address(navManager)); ``` ## Step 7: Set the NAV hook Set the Simple Price Manager as the NAV hook on the NAV Manager. When the NAV Manager recomputes the NAV (triggered by the snapshot hook), it forwards the result to the Simple Price Manager, which then calculates the share price as `NAV / total issuance` and calls `hub.updateSharePrice`. ```solidity navManager.setNAVHook(poolId, address(simplePriceManager)); ``` ## Pushing prices to oracles After the share price is updated on the Hub, it must be pushed to the price oracle on each network where the pool's share token is deployed. This is done by calling `notifySharePrice`: ```solidity hub.notifySharePrice{value: gas}(poolId, scId, centrifugeId, msg.sender); ``` * `gas`: The amount of native currency to cover cross-chain messaging costs (excess will be refunded) * `msg.sender`: Address to receive any excess gas refund This call must be made for each network where the share token is deployed. It can be called after each NAV update or batched at a regular cadence. --- # Onchain Portfolio Manager Source: https://docs.centrifuge.io/developer/protocol/managers/merkle-proof-manager # Onchain Portfolio Manager The Onchain Portfolio Manager lets pool operators and strategists execute pre-approved DeFi workflows on behalf of a Centrifuge pool. Workflows are multi-step sequences: withdraw from the balance sheet, deploy into protocols like Aave or Morpho, bridge across chains, and deposit received tokens back, all in a single atomic transaction. Workflows are Weiroll scripts: ordered command sequences where each step can consume outputs from previous ones, enabling reactive strategies that read live onchain data (prices, balances) at execution time. Every script is authorized by the pool's Hub managers through a Merkle proof policy. Strategists can only execute scripts whose hash is included in their approved policy, and Hub managers can pin specific parameters so strategists cannot substitute addresses or modify amounts at execution time. :::info Credits The execution model builds on [Weiroll](https://github.com/weiroll/weiroll), originally developed by [@DeanEigenmann](https://x.com/deanpierce), [@matthewdif](https://x.com/matthewdif), and [@nicksdjohnson](https://x.com/nicksdjohnson). Script-level authorization was further developed and audited by [Enso](https://www.enso.finance/). The policy leaf architecture for address-level filtering is inspired by [Boring Vault](https://github.com/Se7en-Seas/boring-vault) by Se7en-Seas. ::: ## Flow of funds Assets invested into a Centrifuge pool go through the standard ERC-7540 async vault lifecycle: investors deposit, the pool operator approves deposits, and shares are issued. Once deposits are approved, the underlying assets sit on the pool's balance sheet, a non-custodial smart contract that holds all pool assets. From there, Onchain PM workflows can batch multiple operations atomically: 1. Withdraw assets from the balance sheet 2. Deploy into underlying protocols (lending, staking, liquidity pools) or bridge to other chains 3. Deposit any received tokens (LP tokens, receipt tokens, aTokens) back to the balance sheet No single party has unilateral access to withdraw funds outside of the approved Hub manager and workflow framework. Assets moving across chains or sitting in async queues are tracked as ERC-6909 accounting tokens. When an asset leaves the balance sheet, a receipt token is minted in its place; when it arrives or settles, a corresponding liability token is recorded. Both are valued identically to the underlying asset, so NAV stays accurate throughout. ## Available workflows A library of over 860 ready-to-use workflow templates covers lending and withdrawals on Aave V3 and Morpho, ERC-4626 and ERC-7540 vault interactions on Centrifuge, cross-chain USDC transfers via Circle CCTP, and USDT0 bridging. New templates for additional protocols, assets, and chains can be added. The workflow format is open and extensible. If your use case requires a workflow that is not yet listed, reach out to the Centrifuge team or contribute a template directly. ## Security model Every workflow execution is protected by multiple layers: **Merkle proof policy**: only scripts approved in the strategist's policy tree can execute. The policy is a Merkle root assigned per strategist by Hub managers via cross-chain trusted calls, and can be updated or revoked at any time. **Fixed state slots**: Hub managers can pin specific parameters (addresses, amounts) in a workflow script. Changing a pinned value invalidates the Merkle proof, preventing strategists from substituting addresses or redirecting funds at execution time. **Slippage guard**: enforces per-script slippage bounds and cumulative period loss limits across all touched assets. **Circuit breaker**: rolling-window throughput limits and per-update value deviation caps, providing a hard ceiling on capital exposure per execution window. --- # On/Off Ramp Manager Source: https://docs.centrifuge.io/developer/protocol/managers/on-offramp-manager # On/Off Ramp Manager The On/Off Ramp Manager in the Centrifuge Protocol is a smart contract responsible for managing the flow of ERC20 assets between external wallets and the pool's internal balance sheet. It ensures secure, efficient and compliant asset movement via well-defined onramping and offramping mechanisms. ## Onramping (deposits) Anyone can initiate the onramp process, provided the following conditions are met: * The asset is on the approved token list. * ERC20 tokens have already been transferred to the On/Off Ramp Manager's address. #### How it works 1. A user transfers approved ERC20 tokens to the On/Off Ramp Manager contract. 2. Anyone (including the user or a third party) can then call the contract to finalize the onramp. 3. The tokens are credited to the internal balance sheet of the pool. ## Offramping (withdrawals) Offramping is restricted to enhance security and compliance. Only predefined relayers can initiate a withdrawal to a predefined recipient (offramp) account. #### Key features * Only authorized relayers can execute withdrawals. * Offramp accounts must be preconfigured and recognized by the pool. * Relayers act as regulated bridges between the pool and external accounts. --- # Overview Source: https://docs.centrifuge.io/developer/protocol/overview # Overview The Centrifuge Protocol is an open-source, decentralized protocol for tokenizing and distributing financial products across multiple blockchain networks. Built on immutable smart contracts, it provides the infrastructure for creating customizable asset management products with seamless multi-chain deployment. The protocol is designed to be non-opinionated and extensible, empowering builders to create diverse financial products, from permissioned investment funds to freely tradable tokenized assets, onchain loans, and custom financial structures. The protocol has had [24 security reviews](/developer/security/audits) to date, and is currently live on [9 blockchains](/developer/protocol/deployments/). ![](./images/overview.png) ## Key features ### Multi-chain asset management Using protocol-level [chain abstraction](/developer/protocol/features/chain-abstraction/), issuers can access and manage liquidity across any supported network while maintaining unified control from a single hub chain. The protocol operates on a hub-and-spoke model where one hub chain handles operational management, accounting, pricing, and investment request processing, while multiple spoke chains act as separate balance sheets where tokens can be issued, transferred, and redeemed. Cross-chain transfers use a secure burn-and-mint mechanism for 1:1 token movement between chains. ### Standards-based composability The protocol provides [vault implementations](/developer/protocol/features/vaults/) that integrate seamlessly with the broader DeFi ecosystem. Share tokens are issued as ERC-20 tokens. ERC-4626 vaults offer the standard tokenized vault interface for synchronous deposits and redemptions, while ERC-7540 vaults provide an asynchronous standard for request-based investment flows. The protocol also supports ERC-7575, enabling multi-asset vaults where a single share token can be exchanged for different accepted assets. These standards enable easy integration with existing DeFi protocols, aggregators, and tools. ### Immutable core, modular extensions The protocol combines an [immutable core](/developer/protocol/architecture/overview/) with a [modular set of extensions](/developer/protocol/features/modularity/) for customization. The protocol allows customizing investment vaults, transfer hooks for compliance logic, balance sheet managers for asset allocation strategies, hub managers for automated pricing and order management, valuation contracts for asset pricing, per-pool cross-chain adapters, and much more. This architecture enables builders to innovate at the extension layer while maintaining the security guarantees of the immutable core. ### Onchain accounting The protocol implements [fully onchain and automated accounting](/developer/protocol/features/onchain-accounting/) of tokenized assets across all chains. The Hub maintains a complete double-entry bookkeeping system that records all financial transactions, as well as a ledger for all pool holdings. An automated cross-chain synchronization mechanism ensures that data across many chains can be aggregated in a single smart contract. The usage of the accounting system is optional, allowing issuers to automate processes according to their needs. ### One-click deployment The protocol uses monolithic contracts for the immutable core, with automated deployment of share tokens, escrows for pool holdings, vaults and much more. This enables tokenizing financial products and launching tokens and vaults on new chains in a single click. ## Protocol architecture The Centrifuge Protocol operates on a [hub-and-spoke model](/developer/protocol/features/chain-abstraction/). Each pool selects a single hub chain for management and can tokenize and distribute liquidity on many spoke chains. ### Centrifuge Hub The hub chain serves as the central control and accounting layer for the entire pool. From a single chain of your choice, you can manage all tokens and vaults across every deployment. The hub maintains consolidated accounting using double-entry bookkeeping, tracking all vault balances and holdings in one place. NAV calculations are performed on the hub, with price oracle updates pushed to all networks ### Centrifuge Spoke Spoke chains provide the tokenization and distribution layer where end users interact with the protocol. Each spoke deploys ERC-20 share tokens that are customizable with transfer hooks for compliance and restrictions. Both ERC-4626 and ERC-7540 vaults can be deployed for seamless DeFi integration, with multiple vaults supported per share class to accept different payment assets. --- # Application security Source: https://docs.centrifuge.io/developer/security/application-security # Application security Centrifuge applications are built to meet institutional requirements for access control, infrastructure isolation, and real-time monitoring. ## Access controls * All administrative access requires hardware-based two-factor authentication. Hardware security keys are phishing-resistant and prevent the most common account hijacking attacks. * Sensitive operations such as deployments and configuration changes require multi-signature authorization, preventing any single point of failure. * Application deployments are authenticated with strict role-based access controls. Infrastructure platform access is limited under least-privilege principles. ## Infrastructure * Centrifuge web applications run on a fully serverless architecture, minimizing the attack surface by eliminating persistent servers. * Containerized services undergo security scans before publication, with regular base image and dependency updates. * Static code analysis runs across the development pipeline, catching vulnerabilities before they reach production. ## Network and application protection All web traffic is proxied through an enterprise CDN and security platform, providing layered protection from network edge to application layer: * Full strict SSL/TLS with TLS 1.3, automatic HTTPS redirection, and HSTS enforcement to prevent downgrade attacks. * DNSSEC enabled on all managed zones, preventing DNS spoofing and cache poisoning. * Web application firewall with managed rulesets, DDoS mitigation, and rate limiting. * Strict Content Security Policy, Permissions-Policy, and other security headers enforced across all applications. * Client-side script monitoring to detect tampering. * Fail-closed architecture: if edge services hit their limits, requests are rejected rather than bypassing security controls. * Hardware-based two-factor authentication enforced for all platform account users. ## Continuous security monitoring Security monitoring spans multiple layers: cloud infrastructure, CDN, application code, and onchain activity. Using a combination of managed security platforms and automated scanners, in many cases multiple layers and scanners are in place at differnt stages. Includes: * Static application security testing (SAST) and source code scans. * Vulnerability detection and license compliance checks across all open-source dependencies. * Known vulnerability scanning for all published container images. * Cloud infrastructure configuration audits and posture management. * Surface monitoring for public-facing domains. * Infrastructure-as-code misconfiguration detection before deployment. ## Onchain monitoring * Dedicated monitoring tools track sensitive onchain operations: liquidity pool activity, cross-chain message relaying, and code deployment verification against published releases. * Real-time alerting flags anomalous transactions and state changes. --- # Audits Source: https://docs.centrifuge.io/developer/security/audits # Audits ### Protocol | Auditor | Scope | Date | Engagement | Report | | ---------------------------------------------------- | --------------- | --------------- | :------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [BurraSec](https://www.burrasec.com/) | Onchain PM | Apr 2026 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2026-04-burraSec-onchain-pm.pdf) | | [Sherlock](https://www.sherlock.xyz/) | Onchain PM | Apr 2026 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2026-04-Sherlock-onchain-pm.pdf) | | [xmxanuel](https://x.com/xmxanuel) | Onchain PM | Mar 2026 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2026-03-xmxanuel-onchain-pm.pdf) | | [Sherlock](https://www.sherlock.xyz/) | V3.1 | Feb 2026 | Deployment verification | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2026-02-Sherlock-deployment.pdf) | | [yAudit](https://yaudit.dev/) | V3.1 | Jan 2026 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2026-01-yAudit.pdf) | | [Sherlock](https://www.sherlock.xyz/), [Blackthorn](https://www.blackthorn.xyz/) | V3.1 | Nov-Dec 2025 | Audit competition | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-12-Sherlock-Blackthorn.pdf) | | [xmxanuel](https://x.com/xmxanuel) | V3.1 | Dec 2025 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-12-xmxanuel.pdf) | | [yAudit](https://yaudit.dev/) | V3.1 | Oct 2025 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-10-yAudit.pdf) | | [BurraSec](https://www.burrasec.com/) | V3.1 | Oct 2025 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-10-burraSec.pdf) | | [BurraSec](https://www.burrasec.com/) | V3.1 | Sep 2025 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-09-burraSec.pdf) | | [BurraSec](https://www.burrasec.com/) | LayerZero adapter | Aug 2025 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-08-burraSec.pdf) | | [Spearbit](https://spearbit.com/) | V3.0 | July 2025 | Security review | [`Report`](https://cantina.xyz/portfolio/5feee047-ded1-4e15-b3a8-0e05afa62ddb) | | [xmxanuel](https://x.com/xmxanuel) | V3.0 | May-July 2025 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-07-xmxanuel.pdf) | | [Macro](https://0xmacro.com/) | Merkle Proof Manager | June 2025 | Security review | [`Report`](https://0xmacro.com/library/audits/centrifuge-1.html) | | [yAudit](https://yaudit.dev/) | Spoke/Vaults | June 2025 | Security review | [`Report`](https://reports.yaudit.dev/2025-07-centrifuge) | | [Spearbit](https://spearbit.com/) | V3.0 | May 2025 | Security review | [`Report`](https://cantina.xyz/portfolio/8b98604d-b303-42ee-95bf-50c9c6eb7b47) | | [BurraSec](https://www.burrasec.com/) | Gateway | May 2025 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-05-burraSec.pdf) | | [Alex the Entreprenerd](https://x.com/gallodasballo) | V3.0 | Apr 2025 | Review + invariant testing | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-04-Recon.pdf) | | [BurraSec](https://www.burrasec.com/) | Gateway | Apr 2025 | Security review | [`Part 1`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-04-burraSec-1.pdf) [`Part 2`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-04-burraSec-2.pdf) | | [xmxanuel](https://x.com/xmxanuel) | V3.0 | Mar 2025 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-03-xmxanuel.pdf) | | [Spearbit](https://spearbit.com/) | V2.1 | Feb 2025 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2025-02-Cantina.pdf) | | [Recon](https://getrecon.xyz/) | V2.0 | Jan 2025 | Invariant testing | [`Report`](https://getrecon.substack.com/p/never-stop-improving-your-invariant) | | [Spearbit](https://spearbit.com/) | V2.0 | July 2024 | Security review | [`Report`](https://cantina.xyz/portfolio/8c15e83a-08fc-48b9-8cc1-4f9ca76bb064) | | [Spearbit](https://spearbit.com/) | Morpho integration | June 2024 | Security review | [`Report`](https://cantina.xyz/portfolio/cf6f801f-5c05-488c-a387-3836606600e7) | | [Alex the Entreprenerd](https://x.com/gallodasballo) | V2.0 | Mar - Apr 2024 | Review + invariant testing | [`Part 1`](https://getrecon.substack.com/p/lessons-learned-from-fuzzing-centrifuge) [`Part 2`](https://getrecon.substack.com/p/lessons-learned-from-fuzzing-centrifuge-059) | | [Spearbit](https://spearbit.com/) | V1.0 | Oct 2023 | Security review | [`Report`](https://cantina.xyz/portfolio/693b6f24-6e47-4194-97b0-356d10dc1df6) | | [Code4rena](https://code4rena.com/) | V1.0 | Sep 2023 | Audit competition | [`Report`](https://code4rena.com/reports/2023-09-centrifuge) | | [SRLabs](https://www.srlabs.de/) | V1.0 | Sep 2023 | Security review | [`Report`](https://github.com/centrifuge/protocol/blob/main/docs/audits/2023-09-SRLabs.pdf) | --- # Guardian Source: https://docs.centrifuge.io/developer/security/guardian # Guardian The protocol is controlled by the `Root` contract, an immutable smart contract that holds admin (ward) access on all other contracts. `Root` has no direct external access. The only way to interact with it is through the guardian contracts, which mediate between governance multisigs and `Root`. The two guardian contracts each have distinct responsibilities and separate Safe multisigs. ## Timelock All privilege escalation through `Root` goes through a 48-hour timelock. Changes must be scheduled first, then can only be executed after the delay has passed. This gives the community visibility into upcoming changes and time to react before they take effect. ## Guardian roles The `ProtocolGuardian` is controlled by the Protocol Safe multisig and handles emergency response and protocol-level changes, including pausing, upgrades, and cross-chain operations. Every transaction is verified by third-party signers from [Cantina](https://cantina.xyz/solutions/multisig-security). The protocol can respond to emergencies instantly, with full resumption requiring multisig consensus. The `OpsGuardian` is controlled by a separate Ops Safe multisig and handles day-to-day operational tasks such as adapter initialization, network wiring, and pool creation. It has no pause authority. ## Protocol guardian addresses The protocol guardian is deployed at `0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6` on all networks. | Network | Address | |------------------|---------| | Ethereum | [0xCEb7...35c6](https://app.safe.global/home?safe=eth:0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) | | Base | [0xCEb7...35c6](https://app.safe.global/home?safe=base:0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) | | Arbitrum | [0xCEb7...35c6](https://app.safe.global/transactions/history?safe=arb1:0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) | | Avalanche | [0xCEb7...35c6](https://app.safe.global/home?safe=avax:0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) | | Plume | [0xCEb7...35c6](https://safe.onchainden.com/home?safe=plume:0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) | | BNB Smart Chain | [0xCEb7...35c6](https://app.safe.global/home?safe=bnb:0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) | | Optimism | [0xCEb7...35c6](https://optimistic.etherscan.io/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) | | HyperEVM | [0xCEb7...35c6](https://hyperevmscan.io/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) | | Monad | [0xCEb7...35c6](https://monadscan.com/address/0xCEb7eD5d5B3bAD3088f6A1697738B60d829635c6) | ## Ops guardian addresses The ops guardian is deployed at `0x055589229506Ee89645EF08ebE9B9a863486d0dE` on all networks. | Network | Address | |------------------|---------| | Ethereum | [0x0555...0dE](https://etherscan.io/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) | | Base | [0x0555...0dE](https://basescan.org/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) | | Arbitrum | [0x0555...0dE](https://arbiscan.io/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) | | Avalanche | [0x0555...0dE](https://snowscan.xyz/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) | | Plume | [0x0555...0dE](https://explorer.plume.org/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) | | BNB Smart Chain | [0x0555...0dE](https://bscscan.com/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) | | Optimism | [0x0555...0dE](https://optimistic.etherscan.io/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) | | HyperEVM | [0x0555...0dE](https://hyperevmscan.io/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) | | Monad | [0x0555...0dE](https://monadscan.com/address/0x055589229506Ee89645EF08ebE9B9a863486d0dE) | ## Pause mechanism When activated, the global pause halts all cross-chain messaging, both inbound and outbound. Local vault operations (deposit, redeem, withdraw) are not affected, as they don't involve cross-chain coordination. The protocol also supports granular blocking of outgoing messages per destination chain, allowing targeted response without a full protocol pause. ## Access control Every protocol contract inherits the `Auth` mixin, which implements the ward pattern, a role-based access control framework where each contract maintains a mapping of authorized addresses. `Root` sits at the top of the hierarchy and can manage permissions on any contract. --- # Operational security Source: https://docs.centrifuge.io/developer/security/operational-security # Operational security The Centrifuge team maintains production-grade security standards across development and operational workflows. ## Team standards * Every team member enforces hardware based 2-factor authentication. No SMS codes or cloud-based passkeys are permitted for critical accounts. * All team members run endpoint firewalls configured to company baselines, blocking unauthorized outbound network connections. * Credentials are managed through Password managers and Cloud Secrets like KMS. Passwords are never shared or reused across services. * Full-disk encryption and automatic screen lock are enforced on all devices. * The team completes regular security trainings, including phishing awareness exercises. * The Centrifuge security team performs regular controlled attacks against core contributors (phising, smithing, social engineering, etc.) and launches monthly easy-to-digest trainings for all employees. * The core team completed a third-party operational security review with [OPSEK](https://www.opsek.io/) in 2025. ## Operational security * Administrative accounts are separated from day-to-day accounts and have stricter security policies. No single person holds all keys to any critical system. * All system access follows the principle of least privilege, with quarterly access reviews and credential rotation. * All code changes require peer review before merge. The development pipeline includes unit, integration, fuzz, and invariant testing. Most critical repositories require GPG signing to commit code. --- # Overview Source: https://docs.centrifuge.io/developer/security/overview # Overview Centrifuge security highlights: * 24 security reviews to date for the Centrifuge protocol, including tier-1 audit firms Spearbit and Blackthorn. * Launched on mainnet in 2019, 0 exploits. * $250,000 bug bounty program live on [Cantina](https://cantina.xyz/bounties/6cc9d51a-ac1e-4385-a88a-a3924e40c00e). The protocol codebase is fully immutable, and any emergency functions are locked behind a 48-hour timelock. ![](../images/auditors.png) ## Explore Audits Full list of protocol security reviews, audit competitions, and invariant testing engagements. Pool access levels Granular pool-level permissioning: hub managers, balance sheet managers, relayers, strategists, and gateway controls. Guardian Protocol and ops guardian roles, multisig structure, and the global pause mechanism. Application security Access controls, infrastructure isolation, network protection, and continuous monitoring. Operational security Team security standards, credential management, and development workflow controls. --- # Pool access levels Source: https://docs.centrifuge.io/developer/security/pool-access-levels # Pool access levels The protocol supports granular permissioning at the pool level. Rather than relying on a single admin key, each pool can assign distinct roles with scoped authority, limiting what each actor can do and where they can operate. ## Hub manager The hub manager is the top-level role for a pool. It controls pool configuration, share class management, accounting, and cross-chain deployment. The hub manager role is assigned during [pool creation](/developer/protocol/guides/create-a-pool/) and can be granted to additional addresses by calling `updateHubManager` on the [`Hub`](https://github.com/centrifuge/protocol/blob/main/src/core/hub/interfaces/IHub.sol) contract. Hub managers can: * Add and configure share classes * Set share and asset prices * Manage onchain accounting (journal entries, holdings, valuations) * Deploy vaults and notify remote chains * Assign balance sheet managers, [request managers](/developer/protocol/guides/manage-a-pool/), and gateway managers * Configure cross-chain adapter sets per destination chain Multiple addresses can hold the hub manager role simultaneously, enabling multisig workflows or delegation to operational tooling. :::warning Any hub manager can grant or revoke the hub manager role for other addresses. Pools should carefully control which addresses hold this role. ::: ## Balance sheet manager Balance sheet managers control asset movement into and out of a pool on a specific chain. The hub manager grants this role per chain by calling `updateBalanceSheetManager`, and the assignment is propagated cross-chain to the [`BalanceSheet`](https://github.com/centrifuge/protocol/blob/main/src/core/spoke/interfaces/IBalanceSheet.sol) contract on the target chain. Balance sheet managers can: * Deposit and withdraw assets from the pool escrow * Reserve and unreserve assets for pending operations * Issue and revoke share tokens * Submit queued asset and share updates to the hub This separation means asset custody operations are independent from pool configuration. A balance sheet manager cannot modify share classes, change prices, or alter the pool's accounting structure. ## On/off ramp manager The [`OnOfframpManager`](https://github.com/centrifuge/protocol/blob/main/src/managers/spoke/OnOfframpManager.sol) is a specialized balance sheet manager for fiat on/off ramp flows. It introduces a relayer model that further restricts who can trigger withdrawals. * Onramping is permissionless. Once an asset is enabled for onramp, anyone can trigger a deposit into the pool after transferring tokens to the manager. * Offramping is permissioned. Only designated relayers can trigger withdrawals, and only to pre-approved offramp addresses. The pool admin configures three independent permission sets through the hub: * Onramp assets, controlling which tokens can be deposited * Relayers, controlling which addresses can trigger withdrawals * Offramp destinations, controlling which asset-receiver pairs are valid withdrawal targets All three must align for a withdrawal to succeed: the caller must be a relayer, the destination must be an approved offramp, and the asset must be registered. This layered design prevents unauthorized fund movement even if a single key is compromised. See the [on/off ramp manager](/developer/protocol/managers/on-offramp-manager/) documentation for implementation details. ## Merkle proof manager The [`MerkleProofManager`](https://github.com/centrifuge/protocol/blob/main/src/managers/spoke/MerkleProofManager.sol) enables programmable allocation policies enforced through merkle proofs. Instead of granting broad balance sheet access, the pool admin assigns per-strategist policies that define exactly which operations a strategist can perform. Each strategist is assigned a policy root, a merkle root that encodes the set of allowed calls. When a strategist executes an operation, they provide a merkle proof demonstrating that the call matches their policy. The contract verifies the proof against the stored root before executing. This means: * Different strategists can have different permissions on the same pool * Policies can restrict by target contract, function selector, and parameter values * Updating a strategist's policy requires only a single root update from the hub, with no per-function permission changes The merkle proof approach supports delegation structures where multiple parties manage different aspects of a pool's assets, each operating within strictly defined boundaries. See the [merkle proof manager](/developer/protocol/managers/merkle-proof-manager/) documentation for implementation details. ## Gateway manager The gateway manager controls cross-chain message flow for a specific pool. The hub manager grants this role per chain by calling `updateGatewayManager`, and the assignment is propagated to the [`Gateway`](https://github.com/centrifuge/protocol/blob/main/src/core/messaging/Gateway.sol) contract on the target chain. Gateway managers can block outgoing messages for their pool on a specific destination chain. This provides a pool-level circuit breaker: if suspicious activity is detected, the pool's gateway manager can halt cross-chain operations for that pool without affecting other pools or requiring a protocol-wide pause. This complements the protocol-level pause mechanism (controlled by the protocol guardian) with a pool-specific control that the pool operator themselves can activate. ## Multi-adapter security Pools can configure their own cross-chain security by choosing which general message passing (GMP) adapters to use and how many must confirm each message. The hub manager calls `setAdapters` to configure per-chain adapter sets with: * Multiple adapters, sending messages through several independent GMP providers (LayerZero, Wormhole, Chainlink, Axelar) * A confirmation threshold, setting the minimum number of adapters that must deliver a message before it is processed * Recovery adapters, providing backup adapters that can be used if a primary adapter fails This [multi-message aggregation](/developer/protocol/features/chain-abstraction/#multi-message-aggregation) model means no single interoperability provider can compromise a pool's cross-chain operations. A pool handling high-value transactions can require three out of four adapters to confirm, while a pool with lower risk tolerance might operate with a single adapter for cost efficiency. Each pool makes this tradeoff independently. ## Transfer hooks [Transfer hooks](https://github.com/centrifuge/protocol/blob/main/src/core/spoke/interfaces/ITransferHook.sol) enable custom compliance rules per share token. Every share token transfer triggers a callback to the token's configured hook contract, which can approve or block the transfer based on custom logic. Hooks can enforce: * Investor whitelisting and transfer restrictions * Jurisdiction-based compliance rules * Holding period requirements * Any custom validation logic the pool requires The hook is set per share class during [pool creation](/developer/protocol/guides/create-a-pool/) and can be updated by the hub manager through `updateShareHook`. This makes the compliance layer fully upgradeable. If regulatory requirements change, the pool can deploy a new hook contract and switch to it without redeploying the share token itself. The hook contract receives contextual information about the transfer type (deposit, redemption, cross-chain transfer) through standardized address patterns, enabling different rules for different operation types. The `updateRestriction` function allows the hub to push restriction updates to hooks on remote chains, so investor permissions can be managed centrally from the hub and propagated across all chains where the token is deployed. --- # Centrifuge Governance Source: https://docs.centrifuge.io/getting-started/cfg-governance # The Centrifuge Governance Centrifuge has evolved its structure to accelerate RWA adoption, boost transparency, and drive CFG value accrual. Per [CP171](https://github.com/centrifuge/cps/blob/main/cps/cp171.md) (approved by CFG DAO token holders on Nov 3rd, 2025), active DAO governance has paused, shifting execution to a streamlined model under Centrifuge Network Foundation supervision. This change enables agility in the fast-evolving RWA space while keeping CFG holders in control of the protocol. The DAO tools remain paused but available for potential reactivation. ### Roles & Focus | | Role | Focus | |---|---|---| | **Centrifuge Network Foundation (CNF)** | Board-led oversight; manages treasury. Ensures all actions grow protocol adoption and CFG value | Long-term value accrual and protocol sustainability | | **Centrifuge Labs** | CNF service provider handling operations, development, growth and adoption. | Development, growth and adoption | | **CFG Holders & Community** | Community driving engagement through local activations, events, and grassroots initiatives to spread Centrifuge's mission and awareness. | Support protocol growth and adoption, strengthen CFG Value alignment, maintain oversight and transparency | ## Centrifuge Network Foundation (CNF) The Centrifuge Network Foundation (CNF) is a non-profit entity dedicated to stewarding the Centrifuge ecosystem's growth, funding core infrastructure, and ensuring long-term sustainability, RWA growth, and CFG value accrual. CNF maintains transparency via public dashboards and reports and aligns operations with CFG holder interests. ![](./images/CNFn.png) Bhaji Illuminati, Lucas Vogelsang, Kevin Chan, Nick Cherney, Glenn Kennedy, Laura Birrell ### Board Structure Centrifuge Foundation's board ensures diverse expertise in finance, tech, and governance: - Bhaji Illuminati — CEO & Co-Founder of Centrifuge Labs; expertise in scaling organisations and go-to-market strategies. - Lucas Vogelsang — Co-Founder of Centrifuge; led engineering at DeinDeal and Taulia. - Kevin Chan — Co-founder at Grove Labs; focuses on RWA adoption; previously at BlockTower and Deloitte. - Nick Cherney — Head of Innovation at Janus Henderson; ETF expert from VelocityShares and Barclays. - Glenn Kennedy — Founder of Leeward Management, advises on governance for Cayman foundations. - Laura Birrell — Independent Director at Leeward; legal/governance leader and early Cayman digital asset founder; former GC at a FTSE 250 company and regulated VASP builder. ### Get Involved - Contact: [info@centrifuge.foundation](mailto:info@centrifuge.foundation) - Learn more: https://centrifuge.foundation - Community: Engage via Discord ([link](https://discord.gg/xHT2t2eTd7)) or Telegram ([link](https://t.me/centrifuge_chat)) ## Centrifuge Labs ![](./images/CFGlabs.png) Bhaji Illuminati (CEO), Anil Sood (CSO & CGO), Martin Quensel (President), Jeroen Offerijns (CTO), Eli Cohen (CLO), Juergen Blumberg (COO) Centrifuge Labs serves as the primary development and execution engine for the Centrifuge Protocol. Centrifuge Labs builds and maintains the open-source infrastructure powering Centrifuge. Centrifuge Labs is responsible for the growth and adoption of Centrifuge. **Centrifuge Labs Structure** Centrifuge Labs is a fully remote team emphasising diversity and shared values. - **Bhaji Illuminati**, CEO and Co-Founder, brings extensive experience in scaling marketplace tech companies and leading go-to-market strategies in fintech, having previously held roles at Dragonboat, Surkus, Social Native, and Taulia. - **Anil Sood**, Chief Strategy Officer and Co-Founder, is a leader in ETFs, capital markets, and asset management with expertise in crypto, having co-founded Anemoy and held roles at Knight Capital, Cantor Fitzgerald, Goldman Sachs, and Barclays. - **Martin Quensel**, President and Co-Founder, has built multiple startups, including Taulia and Ebydos, with expertise in financial supply chain management and developing regulated DeFi products. - **Jeroen Offerijns**, Chief Technology Officer and Co-Founder, leads technology for on-chain finance and protocol development, specialising in engineering security into large-scale systems. - **Eli Cohen**, Chief Legal Officer, has over 25 years of experience in financial services law, including at Baker & McKenzie, Euroclear Bank, Singapore Exchange, CME Group, and Block.One/Bullish. - **Jürgen Blumberg**, Chief Operating Officer, is an ETF trading veteran with over two decades of experience, having worked at BlackRock, Invesco, Goldman Sachs, and leading institutional adoption of tokenised assets. ### Role and Responsibilities of Centrifuge Labs - Execution & Operations: Handles strategy, product development, institutional partnerships, marketing, distribution, and revenue generation in the RWA space. - Growth & Adoption: by building and launching innovative and market-ready RWA tokenisation products, onboarding institutional capital and asset originators, and integrating with leading DeFi and TradFi partners. - Protocol Contributions: Provides core infrastructure, collaborates with users for adoption, and maintains open-source tools powering Centrifuge's ecosystem. - Alignment to CFG: All activities prioritise growing protocol TVL, revenue, and CFG token value. ### Get Involved - Careers: Explore opportunities at [centrifuge.io](https://centrifuge.io/contributors#careers). - Contribute: Join development via GitHub ([link](https://github.com/centrifuge)) or Discord ([link](https://discord.gg/xHT2t2eTd7)). ## Reinstatement Process Active DAO governance is paused. Execution now follows a streamlined model under the supervision of the Centrifuge Network Foundation. Token holders retain ultimate control of the protocol and can propose reinstating the DAO to transfer power back. **Process for reactivating the DAO:** - Create a forum post with a complete proposal description following the [CP0](https://github.com/centrifuge/cps/tree/main/cps/CP0) template. - After 14 days of discussion, submit the final proposal on [GitHub](https://github.com/centrifuge/cps/tree/main/cps). - Initiate a 7-day Snapshot vote. - Notify all token holders through available media channels. - The off-chain vote requires a quorum of at least 4,000,000 CFG. --- # Glossary Source: https://docs.centrifuge.io/getting-started/glossary # Glossary A reference guide to key terms and concepts used across the Centrifuge documentation. ## Centrifuge-specific terms **Centrifuge V3** The third version of the Centrifuge protocol, designed for scalable, permissionless, and multi-chain asset tokenization. **RWA (Real-World Asset)** A physical or offchain financial asset such as bonds, real estate, or private credit that is tokenized onchain. **RWA Launchpad** The no-code interface for issuers to configure, deploy, and manage tokenized financial products using Centrifuge contracts. **Hub chain** The central coordination chain for a pool. Manages accounting, permissions, share prices, and controls interactions across spoke chains. **Spoke chain** A network where user interaction occurs. Vaults are deployed on spoke chains, and investors deposit or redeem assets there. **Hub-and-spoke model** Centrifuge's multi-chain architecture, where one hub coordinates activity across many spokes. **Centrifuge ID (centrifugeId)** A unique identifier for a supported chain in the Centrifuge protocol. Used to direct communication and actions across chains. **Pool** A unique investment product deployed on Centrifuge, consisting of vaults, share classes, and tokens. Identified by a `poolId`. **Pool ID (poolId)** A globally unique identifier for a pool, derived from the hub chain and local pool index. **Share class** A distinct investment tranche within a pool. Each share class can have different rules, permissions, and associated tokens. **Share class ID (scId)** The identifier for a specific share class within a pool. **Share token** An ERC-20 token representing user ownership in a specific share class. Issued when users invest and burned upon redemption. **Vault** A smart contract that manages deposits, redemptions, and asset allocations for a specific strategy. Vaults can be synchronous or asynchronous. **Synchronous vault (ERC-4626)** A vault where deposits are fulfilled immediately. Shares are minted on deposit; redemptions are typically processed asynchronously. **Asynchronous vault (ERC-7540)** A vault where deposits and redemptions are request-based and processed in batches. Useful for offchain or delayed asset management. **Pooled vault (ERC-7575)** A share token that collects value across multiple vaults. Enables strategies with multiple currencies or layered structures. **Asset ID (assetId)** The identifier for a specific investment currency (ERC-20 or ERC-6909) used in a vault, scoped to a given chain. **NAV (Net Asset Value)** The total value of a vault or share class, representing its current worth based on asset prices and liabilities. ## Token standards **ERC-20** Ethereum's base token standard. Used for share tokens and supported investment currencies. **ERC-1404** An extension of ERC-20 for permissioned tokens. Allows restriction on transfers and redemptions. **ERC-4626** A standard for yield-bearing vaults. Used for synchronous vaults in Centrifuge. **ERC-7540** A request-based vault interface used for asynchronous investing and redeeming. **ERC-7575** A standard that enables a single token to represent positions across multiple vaults. Used for pooled vault strategies. ## User roles **Issuer** Deploys a new pool using the RWA Launchpad. Responsible for onboarding assets and configuring structure and compliance. **Curator** Designs and manages tokenized strategies by composing assets, vaults, and rules. May or may not involve RWAs. **Investor** Deposits capital into vaults and receives share tokens. Can redeem tokens for the underlying assets or exit the strategy. ## Concepts in practice **Tokenization** The process of representing an asset or strategy as an onchain token. **Redemption** The act of converting share tokens back into the original asset or currency, usually through a vault. **Composability** The ability of Centrifuge assets to integrate into DeFi protocols and strategies. --- # Welcome to Centrifuge Source: https://docs.centrifuge.io/getting-started/introduction # Welcome to Centrifuge Centrifuge is institutional-grade infrastructure for the future of finance, bringing efficiency, liquidity, and composability to onchain asset management. As one of the first and largest tokenization platforms, it connects traditional capital markets to onchain rails, enabling asset managers, fintechs, and DeFi protocols to launch compliant tokenized funds with ease. Today, Centrifuge powers onchain strategies for leading institutions including Apollo, Janus Henderson, and S&P Dow Jones Indices, while its tokenized assets integrate deeply across DeFi through protocols such as Sky, Aave, and Morpho. More than $2B in real-world assets have already been tokenized through the platform, supported by 21+ audits, deployments on 7 chains, and a proven security record. Centrifuge is designed to make launching and operating tokenized funds simple, fast, and the highest quality: * Accelerate deployment with pre-built modules for tokenization, NAV, investor management, and multi-chain distribution, leapfrogging months of development work. * Build and scale with our SDK and API to customize your own frontend, automate operations, and integrate Centrifuge data into your systems. * Ensure seamless DeFi interoperability with ERC-4626 and ERC-7540 assets that plug into Sky, Aave Horizon, Morpho, Pendle, and more. In these docs, you'll find everything you need to know about Centrifuge; from basic concepts to user guides to developer documentation. Dive in: User Documentation Explore core concepts and learn how to interact with the Centrifuge protocol. Build and use tokenized financial products across chains. Developer Documentation Understand how Centrifuge works under the hood. Integrate contracts and interfaces into your application or strategy. --- # Mission and History Source: https://docs.centrifuge.io/getting-started/introduction/mission-and-history # Centrifuge's Mission and History ## Mission In traditional finance, asset financing requires many intermediaries to function. For example, the shortlist of parties involved in a securitization can include a lead manager, managers, lawyers, paying agents, fiscal agents, auditors, registrars, transfer agents, calculation agents, listing agents, rating agents, process agents. All of these intermediaries add to the upfront and ongoing costs, increasing the barriers for small and medium enterprises compared to large corporations. Centrifuge brings finance onchain and allows multiple parties to achieve agreement on shared information without a trusted intermediary. Furthermore, reliance on intermediaries can be greatly reduced, leading to a more open, transparent, and efficient access to finance. Representing real-world assets (RWAs) onchain is known as **tokenization**. Tokenization brings more efficiency and lower costs — these lower costs reflect the power of automation and blockchain-enabled efficiencies, in contrast to the high labor and operational costs of traditional finance. Looking ahead: we're building Centrifuge to be a comprehensive platform to take these initial benefits of tokenization and build powerful, automated infrastructure for accessibility, liquidity, and interoperability of digital assets. Users will be able to access onchain capital markets on a global scale, and developers will be able to build cohesive, customized financial infrastructure tailored to their specific use cases. ## History ### Pioneering real-world assets in decentralized finance Centrifuge was founded in 2017 with the mission to solve inefficiencies in the financial system with blockchain technology. The first iteration of our platform, built on Ethereum, was called Tinlake. With Tinlake, we pioneered multiple tranches and revolving pools that financed assets in the real world — both firsts for decentralized finance. In mid-2021, [Centrifuge integrated the first real-world asset pool with MakerDAO](https://medium.com/centrifuge/defi-2-0-first-real-world-loan-is-financed-on-maker-fbe24675428f). This was the first loan to use MakerDAO as a credit facility as well as the first instance of a stablecoin (DAI) being backed by real-world assets. Later in 2021, we launched the [RWA Market](https://medium.com/centrifuge/rwa-market-the-aave-market-for-real-world-assets-goes-live-48976b984dde), making use of the Aave protocol to bring liquidity to RWAs on Centrifuge. ### Bringing the first credit fund onchain In 2022, BlockTower Credit became the [first institutional credit fund to bring their collateralized lending operations onchain](https://centrifuge.mirror.xyz/yGWnk8ar_iXuoML-ZKF79NfKa9Q43o5SKgu13qR4uIg) by funding \$220M of real-world assets through Centrifuge — with MakerDAO providing \$150M of senior capital to BlockTower's Centrifuge pools. This marked the largest onchain investment in real-world assets. ### Building the RWA Ecosystem In 2023, Centrifuge launched [Centrifuge Prime](https://centrifuge.mirror.xyz/KyrMWLKMccFCNfSlvjxe7uyhba7oLrUzlBuZ7GQTn6s): our product that helps DeFi-native organizations, like DAOs, stablecoins, and protocols, onboard and scale a portfolio of real-world assets. Customers include Frax and Gnosis. Also in 2023, Centrifuge held the first [Real-World Asset Summit](https://www.rwasummit.io/), where we brought together leaders from both traditional and decentralized finance to accelerate the future of the tokenized asset industry. Additionally, Centrifuge was one of the founding members of the [Tokenized Asset Coalition](https://www.rwa.xyz/tokenized-asset-coalition) alongside industry leaders like Circle, Coinbase, and more, with the mission of working together to bring the next trillion dollars of assets onchain. ### Cross-chain expansion and looking ahead Alongside launching pools on Centrifuge Chain in late-2023, Centrifuge also released Liquidity Pools: smart contracts that can be deployed on any EVM-based chain that allow users on these chains to invest in pools on Centrifuge. We're building Centrifuge to be the best place for onchain capital to allocate to real-world assets — and for the best protocol for institutional issuers and investors to take advantage of the benefits of onchain finance. --- # Centrifuge V1 (aka Tinlake) Source: https://docs.centrifuge.io/getting-started/legacy/centrifuge-v1 # Tinlake ## Overview [Tinlake](https://legacy.tinlake.centrifuge.io/) is Centrifuge's initial Ethereum-based, open, decentralized smart-contract based platform of asset pools bringing together pool issuers looking for financing and investors who seek to utilize the full potential of decentralized finance (DeFi). **Note that Tinlake has been replaced by the [Centrifuge App](https://app.centrifuge.io/), however _legacy_ Tinlake pools as described below are still live on the App. Newer pools do not use the below smart contracts, but rather are launched on Centrifuge Chain.** ### Tinlake from a borrower perspective Issuers can responsibly bridge real-world assets, such as invoices, mortgages or streaming royalties into DeFi and access bankless liquidity. They do this by tokenizing their financial assets into Non-Fungible Tokens (“NFTs”), using these NFTs as collateral in Tinlake Pools to draw funding. Borrowers (the issuers) have individual assets with varying terms and varying durations drawn against their collateral. The collateral is represented as an NFT, which needs to be locked in the Tinlake contracts to draw a loan from it. Pooling the individual assets removes the cumbersome need of p2p financing for matching maturities, risk, and interest rates, and it allows investors to invest in a diversified portfolio of real world assets. ![](./images/tinlake_flow.png) ### Tinlake from the investor perspective Investors earn yield on [TIN and DROP](https://medium.com/centrifuge/a-tale-of-two-tokens-introducing-tin-drop-our-two-investment-tokens-d4c7342c799a), Tinlake’s two investment tokens that are minted in exchange. TIN, known as the “risk token,” takes the risk of defaults first but also receives higher returns. DROP, known as the “yield token,” is protected against defaults by the TIN token and receives stable (but usually lower) returns. This is similar to junior/senior investment structures common in traditional finance. --- # Centrifuge V2 Source: https://docs.centrifuge.io/getting-started/legacy/centrifuge-v2 ## Built on Centrifuge Chain Centrifuge is built on Centrifuge Chain, a layer 1 blockchain custom built for financing real-world assets(RWAs). Real-world assets are tokenized as NFTs (Non-Fungible Tokens) to create an on-chain representation, and are linked to detailed off-chain data. The assets are pooled together and securitized by the issuer. Liquidity Pools can be used to invest from other blockchains. Integrations with DeFi protocols such as Maker, Aave, and more are already set up across the ecosystem. The advantages of a blockchain custom-built for RWA include: 1. Lower transaction costs and increased scalability, as the functionality is natively built into the runtime logic of the blockchain, which enables optimization for dedicated transactions and use cases. 2. The flexibility to develop features that are not possible within a general-purpose smart-contract blockchain such as Ethereum. 3. Dedicated blockspace for real-world asset transactions: if there's a hugely popular NFT drop tomorrow on Ethereum, this won't block borrowers from repaying or investors from redeeming. 4. The ability to define transaction ordering, e.g. ensuring that redemption orders can always be submitted, even in highly contested blocks. ![](./images/CentrifugeProtocol.png#width=70%;) ### Governed by CFG holders The native token of Centrifuge Chain, CFG, is used as an on-chain governance mechanism that empowers CFG holders to manage the development of the Centrifuge Protocol. Centrifuge's formalized governance system enables onchain voting mechanisms for binding and transparent governance by CFG token holders. Beyond the use of the Centrifuge token (CFG) for governance of the blockchain, CFG is also used to pay transaction fees. ## Integrated with DeFi Currently, most DeFi (decentralized finance) applications are limited to their respective blockchain ecosystem. Bridges between chains exist but using them is cumbersome and expensive with the need to set up different tools and swap several tokens. Centrifuge Protocol provides Liquidity pools that allow for direct integration with any general purpose EVM blockchain. By creating a standard layer to invest in Centrifuge pools this minimizes the effort required to integrate a new EVM based chain. ### Liquidity Pools Using Liquidity pools, integrations could be used to enable protocols to invest in real world assets. Stablecoin protocols such as MakerDAO could invest into the pools that they want. This could allow issuers to access deeper liquidity without having to integrate all these DeFi sources of liquidity and new end user markets themselves. While the Centrifuge Protocol is built on Substrate, interacting with the Centrifuge Protocol would be possible from multiple chains creating a true multi-chain ecosystem. ![](./images/ecosystem.png#width=70%;) ## Onchain Securitization By nature, real-world assets are often illiquid and can have maturities up to several years. This makes investing in individual assets extremely difficult. A way to solve this is by pooling multiple assets together, and allowing investors to provide financing for this pool instead of each asset individually. This is called a securitization and is a well established concept in traditional financial markets. After an asset is tokenized and an NFT is minted onchain, this NFT is used as a representation of the offchain collateral for an asset linked to an investment pool, as visualized below. The asset is priced and the issuer borrows liquidity from the pool. Over time, the accruing debt per asset is repaid by the issuer including interest payments and principal repayments. ![](./images/pooling.png#width=60%;) Together, this creates onchain, asset-level transparency: an investor can see at a glance what assets (NFTs) a pool contains, what has been borrowed against and repaid, what is overdue, and so on. This creates an immutable, transparent track record of financial transactions that can be publicly verified. Compare this to the current state of traditional finance, where historical financial data is often hidden and locked in private, siloed databases unavailable to the public, and financial analysis is done based on spreadsheets. ### Revolving pools In traditional finance, many securitizations are static: a group of investors provides capital to the issuer, the issuer finances debt, and then repays interest and principal of the assets over time as they mature. At the end, the investors get their capital back plus the yield. Instead of being a good deal for investors, this situation creates unnecessary overhead, because they have to reinvest after the pools mature. This also makes it harder for other DeFi protocols to integrate with the Centrifuge, as they will have to invest in new pools constantly. To solve this, pools on Centrifuge are revolving: investment and redemption (the withdrawal of invested capital) orders can come in at any time, and assets can be financed and repaid continuously. This has multiple advantages for both issuers and investors: - Issuers can finance assets at any time given liquidity in the pools; - Investors, including DeFi protocols, can make flexible portfolio allocation decisions without the need to constantly reinvest; - The overhead of setting up and operating the underlying legal structure multiple times is removed. Two fundamental components are needed to make this work: an epoch mechanism and an on-chain NAV (Net Asset Value) calculation. ### Epoch mechanism A decentralized pool where investors of different tranches can invest and redeem at any time needs a decentralized mechanism to coordinate the inflow of investments and outflow of redemptions. To address this, each pool is managed using “Epochs”: sessions with a fixed minimum time (e.g. 24 hours) over which investment and redemption orders can be submitted. At the end of the epoch, a decentralized solver mechanism considers the pool state and executes the orders according to seniority of the tranches (e.g. senior tranche redemptions take priority over junior tranche redemptions) and available liquidity. ### Onchain NAV The second component to enable revolving pools is an onchain NAV (Net Asset Value) calculation: to support continuous investments and redemptions, accurate pricing for the pool tokens is required. In traditional finance, pricing for such illiquid assets is usually done using Discounted Cash Flow (DCF) models: expected cash flows (e.g. principal payments of assets in the pool at maturity) are discounted to their present value. Centrifuge brings these calculations onchain and calculates the new NAV on an ongoing basis. The NAV should also account for different kinds of loans, to ensure accurate pricing: financing for real-world assets can vary from simple bullet loans (borrow now and repay principal plus interest at maturity) to complex amortization schedules (repayment of principal plus interest at specific intervals). The NAV also needs to account for defaults of assets: if an asset fails to be repaid, the NAV should represent this. Centrifuge supports this through onchain representation of write offs of assets. The Protocol will show written off assets on a predefined write-off schedule (e.g. when an asset is 30 days overdue, 25% of the asset value should be written off, and a penalty interest rate of 3% should apply). This enables fairer pricing of overdue assets. Assets can also be written off manually by a third party. ### Tranching Investors often want different kinds of risk exposure and yield on the same asset class. In the traditional finance world, one way to achieve this is by introducing a tiered investment structure or in other words, different tranches. This means that investors can invest in the same group of assets through different classes of debt with different risk/return profiles. A standard example is shown below. ![](./images/tranching.png#width=60%;) At its most common form, a pool can have a junior and senior tranche, with the junior tranche tracking the first loss position and receiving the excess yield, while the senior position receives a lower, fixed yield, but is protected from losses by junior. A key advantage of this structure is that it allows the issuer of the pool to invest in the junior tranche and thus take a first loss position in the pool, ensuring skin in the game. Another example includes a three tranche structure, where a super senior lender such as MakerDAO invests in the most senior tranche, other investors invest in a mezzanine tranche, and the pool issuer invests in the junior tranche. For each tranche, tokens are issued that investors receive, representing interests in the tranches of the pool. These tokens are considered securities, thus can only be held by and transferred to KYCed accounts. The pool issuer can specify which accounts are allowed to hold which tranche tokens. --- # Legacy Source: https://docs.centrifuge.io/getting-started/legacy # Legacy Protocols Centrifuge was founded with the vision of bringing real-world financial products on-chain, and its journey can be traced through three major protocol generations, with [Centrifuge V3](/getting-started/protocol-summary) being the latest one. **2017 – The first on-chain tokenized credit system** The team’s initial milestone was building one of the earliest tokenized financing systems on Ethereum. Even in its earliest form, the protocol showed how off-chain receivables—such as invoices or loan agreements—could be represented by ERC-20 tokens, opening traditional credit markets to the emerging world of DeFi. **2020 – Centrifuge V1 (“Tinlake”)** Launched as an end-to-end securitization engine, Tinlake combined: * **On-chain smart contracts:** a two-tranche structure (“TIN” for junior / first-loss investors and “DROP” for senior investors) that automated interest accrual, repayments, and waterfall distributions. * **Off-chain P2P deal room:** parties could negotiate and digitally sign legal agreements; once finalized, the resulting claims were “minted” as non-fungible asset NFTs on Ethereum. * **Dual-token approach:** every real-world asset produced two sets of tokens—asset tokens that tracked principal/interest and share tokens that represented senior or junior exposure. Tinlake’s most notable achievement was its integration with MakerDAO’s credit line. Approved issuers could lock DROP tokens as collateral and mint DAI, proving that real-world assets could back a major stablecoin. Yet Tinlake also exposed Ethereum’s limitations: gas prices made large-scale credit operations costly and slow. **2021 – Centrifuge V2 on Substrate** To overcome scalability constraints, development pivoted to a dedicated L2 built with the Substrate framework (and later connected to Polkadot as a parachain). Goals for V2 included: * **Greater flexibility:** developers could model classic multi-tranche securitizations or simpler single-share funds within the same contract set. * **Higher throughput & lower fees:** Substrate’s modular architecture provided block-level customization and a path to sub-second finality—crucial for high-volume asset minting and frequent interest calculations. **2023 – Solving fragmented liquidity** While V2 handled throughput, liquidity was still scattered across Ethereum, Polygon, and other networks. Centrifuge answered with *Liquidity Pools*—EVM-compatible bridge contracts that let capital flow into Centrifuge regardless of its origin chain. Investors could commit funds on their preferred network, while issuers tapped unified liquidity on the Centrifuge parachain. **Today - Centrifuge V3 - An open, multi-chain tokenization product** The latest iteration of Centrifuge is a multi-chain tokenization product, deployable on any EVM chain, connected between all deployments - Read more [here](/getting-started/protocol-summary). --- # Resources Source: https://docs.centrifuge.io/getting-started/resources # Resources This page gathers many resources to help you explore, research, and participate with Centrifuge — from sources of data and analytics to our official communication channels to articles and reports on real-world assets. ## Data and Analytics These sites allow you to explore the data underpinning Centrifuge (as well as the broader RWA ecosystem), such as information on TVL and assets issued on Centrifuge. - [Dune Analytics](https://dune.com/centrifuge/centrifuge) - [RWA.xyz](https://rwa.xyz/) - [DeFiLlama](https://defillama.com/protocol/centrifuge) (Check _include borrows_ at the top right) - [Centrifugescan](https://centrifugescan.io/) ### CFG token exchanges and market data - [CoinGecko](https://www.coingecko.com/en/coins/centrifuge) - [CoinMarketCap](https://coinmarketcap.com/currencies/centrifuge/) ## News and Social Media These are all of our public communication channels in one place. Our Twitter and LinkedIn pages as well as our blog are great ways to keep up on Centrifuge. We also have podcasts, interviews, and panels uploading to our YouTube and Spotify pages. - [Twitter](https://twitter.com/centrifuge) - [LinkedIn](https://www.linkedin.com/company/centrifugehq/) - [Blog](https://centrifuge.mirror.xyz/) - [YouTube](https://www.youtube.com/channel/UCfNkoq7YLrr8MeSJ3a6jVcA) - [Podcast](https://open.spotify.com/show/3mcy2eIFO9qUFlxhZeYMV4) - [Telegram Feed](https://t.me/centrifuge_chat) ## Events and Campaigns The Real-World Asset Summit, hosted by Centrifuge, brings together the brightest minds from both decentralized and traditional finance. Fixing Finance is our campaign to tell the story of what problems we're solving here at Centrifuge. Check them both out below. - [Real-World Asset Summit](https://www.rwasummit.io/) - [Fixing Finance](https://fixing.finance/) ## Governance and Community Get involved with the Centrifuge community. Our forum is for governance RFCs (Request for comments) and votes as well as general discussion about the platform and protocol. The Discord channel is more casual, and a great place to reach out if you need technical support or are looking to connect with the team for a partnership. - [Centrifuge Forum](https://gov.centrifuge.io/) - [Community Discord](https://discord.com/invite/yEzyUq5gxF) ## Essential reading These are a few handpicked articles and reports diving into the thriving real-world asset ecosystem onchain. Give them a read to learn more about why we're building Centrifuge and the problems we're looking to solve! - [Not Boring by Packy McCormick: Everything is Broken](https://www.notboring.co/p/everything-is-broken) - [The Role of Securitization in Tokenized Assets](https://assets-global.website-files.com/651206970b81ddcea1edf1ea/6536e1b1436cbc683707a4f4_securization-in-tokenized-assets.pdf) - [Federal Reserve: Tokenization: Overview and Financial Stability Implications](https://www.federalreserve.gov/econres/feds/files/2023060pap.pdf) - [Galaxy: Real World Assets (RWAs) and Their Impact on DeFi](https://www.galaxy.com/insights/research/rwas-and-their-impact-on-defi/) - [Centrifuge Credit Group: US Treasuries Asset Primer](https://gov.centrifuge.io/t/asset-primer-overview-us-treasurys/5991) - [Binance Research: Real World Assets: The Bridge Between TradFi and DeFi](https://www.binance.com/en/research/analysis/real-world-assets) ## For Developers Centrifuge is open source software. You can explore our source code as well as professional audits of our protocol in the links below. - [Github](https://github.com/centrifuge/) - [Audits](https://github.com/centrifuge/security/tree/main/audits) - [Security](https://centrifuge.io/security) - [Brand Guidelines](https://centrifuge.io/brand) --- # The CFG Token Source: https://docs.centrifuge.io/getting-started/token-summary # Centrifuge Tokenomics Overview ## CFG Token Migration On March 20, 2025, the CFG governance proposal [CP 149](https://github.com/centrifuge/cps/blob/main/cps/CP149/CP149.md) was passed, which introduced a new, [V3 CFG token](https://etherscan.io/token/0xcccccccccc33d538dbc2ee4feab0a7a1ff4e8a94) that consolidated: - [Legacy CFG from the deprecated Centrifuge Chain](https://centrifuge.subscan.io/) - [Wrapped CFG (wCFG) on Ethereum](https://etherscan.io/token/0xc221b7e65ffc80de234bbb6667abdd46593d34f0) into a single, Ethereum-native ERC-20 token: CFG. The migration built on [CP 141](https://github.com/centrifuge/cps/blob/main/cps/CP141/CP141.md), which introduced Centrifuge V3 and migrated the Centrifuge protocol itself to a native EVM execution environment. ## V3 CFG Token Contract - [0xcccCCCcCCC33D538DBC2EE4fEab0a7A1FF4e8A94](https://etherscan.io/address/0xcccccccccc33d538dbc2ee4feab0a7a1ff4e8a94) ### Migration Overview The V3 CFG migration took place between **May 20, 2025 - December 03, 2025**. As part of this migration: - Legacy CFG and wCFG tokens were swapped at a **1:1** ratio for V3 CFG - Following completion, the bridge between Ethereum and the legacy Centrifuge Chain was fully deprecated For more details, see the [Token Migration](https://docs.centrifuge.io/getting-started/token-summary/token-migration/) docs. ## CFG Governance Updates Following the V3 protocol and token migrations initiatives, [CP 171](https://github.com/centrifuge/cps/blob/main/cps/cp171.md) was approved by the Centrifuge DAO on November 3, 2025. Key outcomes of this proposal include: - The **Centrifuge Network Foundation (CNF)** inherits governance and oversight responsibilities - The **Centrifuge DAO retains the ability to reassume governance operations** at a future date These updates to the CFG governance structure eliminate execution bottlenecks and risks associated with decentralized governance structures, allowing Centrifuge to: - **Reduce coordination challenges** in a hyper-competitive, burgeoning RWA market - **Maximize value accrual to CFG** by streamlining decision-making and execution As part of CNF’s commitment to transparency, progress and results will be reported publicly through [centrifuge.foundation](https://www.centrifuge.foundation/) and Centrifuge’s official social channels. For more details, see [Centrifuge Governance](https://docs.centrifuge.io/getting-started/cfg-governance/). ## CFG Tokenomics ### Token Supply As of January 2026, the total supply of CFG is **691,800,000** tokens. This includes 115,000,000 tokens minted as part of CP 149. - 15,000,000 of the CP 149 tokens unlocked upon minting in May 2025 to support near-term operational and early stage strategic initiatives - The remaining 100,000,000 tokens vest linearly through April 2029 - All CP 149 tokens are part of an ecosystem **Incentives program** designed to promote long-term protocol growth ## Token Allocations As of January 2026, allocations for the CFG token supply are as follows: ![](./images/tokenallocation.png) ![](./images/tokenallocation2.png) ### Ecosystem The Centrifuge ecosystem allocation of 24% includes the Centrifuge Treasury and is allocated for long-term ecosystem growth initiatives. All CP 149 incentives tokens vested as of January 2026 are included in this allocation. ### Team 14% of the total supply is allocated to the Centrifuge team to align long-term ecosystem incentives with the contributors actively building and growing the protocol. The allocation vests gradually through March 2030. ### Incentives This allocation consists of the newly-minted CP 149 tokens that are still locked. These incentives are allocated for sole use in ecosystem initiatives to grow and develop the Centrifuge protocol. As of January 2026, 12% of the total supply has been allocated to these incentives and is still locked. These locked tokens will vest linearly into the Centrifuge Treasury through April 2029. ### Other Stakeholders The majority of other stakeholder CFG allocations have already vested; the remaining 0.1% of the total supply that was allocated to other stakeholders will vest over 3 months, through March 2026. ### Released Supply 50% of the CFG supply is considered “released” as of January 2026; these tokens are freely circulating and exclude the specific allocations mentioned above for team, incentives, ecosystem and other stakeholders. ### CFG Emissions CFG has a 3% annual inflation rate based on the total token supply, resulting in a modest increase in total supply over time. All inflationary tokens accrue to the Centrifuge Treasury. ![](./images/cfgemission.png) This release schedule represents the current intended strategy for CFG emissions through June 2029. It is subject to change, with any significant modifications or actions to be communicated to CFG stakeholders. --- # Legacy CFG/WCFG Migration to Ethereum Source: https://docs.centrifuge.io/getting-started/token-summary/token-migration # Legacy CFG/WCFG Migration to Ethereum With the launch of **Centrifuge V3**, a multi-chain, EVM-based protocol, the legacy Centrifuge Chain and its associated CFG token will be deprecated, along with the Wrapped CFG (WCFG) token on Ethereum. This guide outlines how users can migrate their legacy CFG and WCFG tokens to the new EVM-based CFG token on Ethereum. The migration process started on **May 20th 2025**, following a successful [governance vote](https://gov.centrifuge.io/t/cp149-migration-of-centrifuge-governance-token-cfg-to-evm/6810). The existing Centrifuge Chain is expected to be discontinued in **Q4 2025**. Detailed instructions and demo videos are provided below to assist token holders. ## Migration Overview - **Token Swap Ratio:** 1:1 (1 legacy CFG or WCFG = 1 new CFG) - **New CFG Token Address on Ethereum:** `0xcccCCCcCCC33D538DBC2EE4fEab0a7A1FF4e8A94` - **WCFG/CFG Swap Contract on Ethereum:** `0xacf3c07bebd65d5f7d86bc0bc716026a0c523069` - **Total Supply:** 680,000,000 CFG - **Start Date:** May 20, 2025 - **End Date:** November 30, 2025 ## How to Migrate Your Tokens ### Use the Centrifuge App #### Legacy CFG (CFG on Centrifuge Chain) 1. Navigate to [https://app.centrifuge.io/migrate](https://migrate.centrifuge.io/#/migrate/cent). 2. Connect your wallet that holds legacy CFG. 3. Follow the on-screen instructions: - Connect an EVM-based address and sign a message proving ownership of your Ethereum wallet. ![CFG migration](../images/cfg_migration_1.png#width=60%) - Submit a transaction on the Centrifuge Chain to request migration. ![CFG migration](../images/cfg_migration_3.png#width=60%) 4. Upon completion, the new CFG tokens will be sent to your EVM wallet. > **IMPORTANT:** The process can take up to 1 hour to complete. **Demo Video**: [Watch the video on Youtube](https://www.youtube.com/watch?v=xaoM5qMgnT0) #### WCFG (Wrapped CFG on Ethereum) 1. Navigate to [https://app.centrifuge.io/migrate](https://migrate.centrifuge.io/#/migrate/eth). 2. Connect your Ethereum wallet holding WCFG. 3. Follow the on-screen instructions to complete the WCFG-to-CFG conversion. ![wCFG migration](../images/wcfg_migration_1.png#width=60%) 4. Receive the new CFG tokens in your Ethereum wallet. > **IMPORTANT:** The process is atomic, and you will receive your new CFG tokens immediately. **Demo Video**: [Watch the video on Youtube](https://www.youtube.com/watch?v=f63OraAZJu8) --- ## Additional Notes - **FAQ:** [Centrifuge Token Migration](https://gov.centrifuge.io/t/centrifuge-token-migration-update/6860/4) - **Support:** For assistance, reach out to the Centrifuge team via official channels. ## Next Steps For more information on the CFG token and its role in the Centrifuge ecosystem, see [The CFG Token](../). --- --- # Concepts Source: https://docs.centrifuge.io/user/concepts # Concepts Centrifuge introduces several core concepts that define how the protocol works. These concepts form the foundation for understanding tokenized strategies, vault behavior, and multi-chain asset management. Explore these topics to get a clear overview of how Centrifuge fits together. ## Explore Tokenization Turn real-world or onchain assets into programmable share tokens, backed by smart contract logic. Multi-Chain Protocol Access liquidity and interact with vaults on any supported chain, coordinated through a single hub. Pools Create and manage investment products using share classes, vaults, and permissioned logic. Share Tokens Understand share tokens, permissioning rules, and how ownership is tracked across chains. Vaults Deposit, redeem, and allocate assets through vaults that support synchronous or asynchronous flows. --- # Multi-Chain Protocol Source: https://docs.centrifuge.io/user/concepts/multi-chain # Multi-Chain Protocol Centrifuge is designed to scale tokenized asset management across blockchains. Using a **hub-and-spoke architecture**, each pool is managed from a single hub chain while operating vaults and issuing tokens on multiple spoke chains. This makes Centrifuge truly multi-chain, giving investors and managers a consistent experience wherever they operate. ## What does this solve? In most protocols, issuing tokens on multiple chains means maintaining separate contracts, price feeds, wallets, and accounting per chain. This creates silos, increases overhead, and fragments liquidity. ![](./images/hub-and-spoke.png) Centrifuge avoids this by keeping one unified source of truth on the hub chain, while letting vaults and tokens live across networks like Ethereum, Base, or Arbitrum. ## How it works Each pool selects a **hub chain**, which acts as the central control point. This is where: - Investment requests are approved - Vault pricing and NAV are updated - Tokens and strategies are registered - Accounting is consolidated From there, the pool can launch vaults and share tokens on any number of **spoke chains**. These are the chains where users interact with the product and capital is deployed. ## As an investor - You can invest on any supported chain where the vault is deployed - You interact locally, while pricing and approvals are handled on the hub - You never need to manage gas across multiple networks - You receive the same experience no matter which chain you choose ## As a manager - You choose a single hub chain to coordinate your pool - You deploy vaults on the spoke chains that match your liquidity goals - You do not need to replicate logic or infrastructure across chains - You stay in control of share prices, NAV, and asset flows from one place ## What the hub does The **hub chain** is where all logic and coordination happens: - Maintains share class metadata and pricing - Tracks NAV and accounting across all vaults - Approves or denies deposit and redemption requests - Pushes share and asset prices to oracles on each network ## What the spokes do **Spoke chains** are where users interact and assets are moved: - Vaults accept deposits and redemptions - Share tokens are minted or burned - Assets are allocated into external strategies - Vault logic can include ERC-4626 or ERC-7540 flows Vaults can be synchronous, asynchronous, or hybrid depending on how the manager configures them. ## Cross-chain coordination Behind the scenes, Centrifuge handles messaging between the hub and spokes with built-in reliability and efficiency: ### Message aggregation Each cross-chain message is verified using multiple interoperability providers to improve security. ### Batching Messages are bundled together to reduce gas usage and keep processing fast. ### Gas abstraction Users do not need to hold gas tokens across chains. Pools can cover or subsidize transaction costs. ### Retry and repayment If a message fails or runs out of gas, it is retried automatically or repaid without manual work. ## Summary Centrifuge’s multi-chain protocol gives you: - Unified control of your pool from a single hub - Vault access on any supported network - Secure, automated cross-chain messaging - A consistent experience for both investors and managers This design eliminates the need to duplicate logic or infrastructure and removes the complexity of scaling across chains. > Want to learn more about how the messaging system works? > See the [developer documentation on chain abstraction](/developer/protocol/chain-abstraction/) --- # Pools Source: https://docs.centrifuge.io/user/concepts/pools # Pools A pool is the core structure that represents an investment product on Centrifuge. Each pool can contain one or more vaults, serve one or multiple investor groups, and be deployed across multiple chains. Pools are created and managed by issuers or curators, who configure how capital is accepted, tracked, and distributed. ## What is a pool? A pool brings together everything needed to run a tokenized investment strategy: - Share tokens for investors - Vaults to manage deposits and redemptions - Pricing logic and valuation updates - Permissions and investor rules Each pool has a unique ID and is anchored on a single hub chain. From there, it can operate across any number of supported spoke chains. ## Pools can include: - **Multiple vaults**, each with its own logic or currency - **Multiple share classes**, such as junior/senior tranches - **Custom permissions**, including investor whitelisting and redemption restrictions ## Share classes and share tokens Each pool contains one or more **share classes**, which define how tokens are issued and what claims investors have. - Every share class issues its own token - Tokens follow the ERC-20 standard, with optional compliance rules - Tokens can be permissioned or fully open depending on the configuration For example, a pool might include: - A permissioned senior share class for institutional investors - A permissionless junior share class for open access ## Vaults within a pool Each share class can be connected to one or more vaults. Vaults are deployed to spoke chains where users invest and redeem. - Vaults define how assets flow into and out of the pool - Vaults can use synchronous or asynchronous flows - Assets are tracked and settled across chains through the Hub For more, see the [Vaults](/user/concept/vaults) section. ## Creating a pool When creating a pool, managers choose: - A hub chain (for central control) - A base currency (e.g. USD, USDC) - Share classes and their names, symbols, and metadata - Permissioning rules for each share class After setup, the pool is registered across selected networks and ready to accept deposits. ## Managing a pool Pool managers are responsible for maintaining and updating: - **Share prices**, which determine how much each token is worth - **Asset prices**, especially in multi-asset vaults - **Investment requests**, including approval, issuance, and redemption flows All of this is coordinated through the Hub chain, even if users are interacting from other chains. For asynchronous vaults, requests go through a lifecycle that includes: 1. **Pending**: The user submits a deposit or redemption 2. **Approved**: The request is confirmed and capital is unlocked 3. **Issued or revoked**: The share price is applied 4. **Fulfilled**: The vault is updated 5. **Claimed**: The user receives their shares or assets Vaults using synchronous deposits (ERC-4626) skip this process, shares are minted immediately upon deposit. ## Summary Pools are the foundation for every product in Centrifuge. They allow managers to launch customizable, multi-chain investment strategies with: - Multiple share classes and investor types - Vaults tailored to capital flow and liquidity needs - Cross-chain operations, all coordinated through one hub Whether you're launching a tokenized fund or investing in a diversified strategy, everything starts with a pool. --- # Share Tokens Source: https://docs.centrifuge.io/user/concepts/share-tokens # Share Tokens In Centrifuge, tokens represent ownership in a vault or pool. They are the interface for investors to hold, redeem, or interact with tokenized strategies. Each token follows standard Ethereum formats, but can also include additional logic for permissioning, compliance, or cross-chain behavior. ## Share tokens Every share class in a pool has its own **share token**. These tokens: - Represent a claim on the pool’s assets or yield - Follow the ERC-20 standard (so they work in most wallets and DeFi apps) - Are issued when a user deposits, and burned when they redeem - Are **price-accruing**, meaning their value increases over time rather than their balance increasing Share tokens are deployed on **spoke chains** where users interact with vaults. ### Example A pool might include: - A senior share token: permissioned for institutional investors - A junior share token: open to all users Both tokens exist across the same spoke chains, but have different rules and risk profiles. ## Permissioning Not all share tokens are open to everyone. Depending on the pool configuration, tokens can include **transfer restrictions**. Centrifuge supports multiple permissioning options: - **Fully permissionless**: anyone can invest, redeem, or transfer - **Restricted redemptions**: deposits are open, but redemptions require approval - **Full restrictions**: all transfers require whitelisting - **Freeze only**: users are unrestricted unless explicitly frozen This is enforced using smart contract hooks that control who can interact with the token. ## Token behavior across chains Although a token may be visible on multiple chains, it behaves as **one unified asset**. The Hub coordinates: - Total supply - Valuation - Redemption logic Users can invest or redeem on any chain where the vault is deployed. All state changes are synced through Centrifuge’s messaging system. ## How token pricing works Each token has a **price per share** that is updated by the pool manager on the Hub. This price reflects the value of the vault’s underlying assets and is used to: - Issue the correct number of shares when someone deposits - Calculate how much someone receives when redeeming Prices are pushed to local oracles on each chain so external contracts and apps can retrieve the current value. Because tokens are price-accruing, the token balance in your wallet stays constant, but its value increases over time as the strategy yields returns. ## Summary Tokens in Centrifuge give users access to onchain investment strategies in a familiar ERC-20 format, but with built-in support for: - Compliance and permissioning - Accurate cross-chain accounting - Price-accruing logic that reflects value growth - Integration with wallets, DeFi protocols, and oracles Whether you’re holding tokens in your wallet or using them in DeFi, they represent real positions in tokenized, managed assets. --- # Tokenization Source: https://docs.centrifuge.io/user/concepts/tokenization # Tokenization Tokenization on Centrifuge is the process of representing assets or strategies as onchain tokens. These tokens are issued through smart contracts and reflect ownership in a vault, governed by rules set at the pool and share class level. Each tokenized asset is created and managed through a **pool**, which contains one or more **vaults**, and each vault issues **share tokens** to investors. These tokens may be held, transferred, and used across supported EVM-compatible networks, depending on how the pool is configured. > Learn more: > - [What is a pool?](/user/concepts/pools) > - [How vaults work](/user/concepts/vaults) > - [Understanding share tokens](/user/concepts/tokens) ## Why tokenization? Tokenization enables assets to become: - **Transferable** *(when permitted)*: Users may hold or move share tokens based on pool-level permissions - **Composable**: Tokens can integrate into DeFi if designed to follow standards like ERC-4626 or deRWA - **Auditable**: All transactions, balances, and ownership records are transparently recorded onchain - **Programmable**: Rules and strategies are enforced using smart contracts :::info Not all tokenized assets are freely transferable. Pools define permissioning rules that control who can hold, invest in, or redeem share tokens. ::: ## What gets tokenized? Centrifuge can tokenize many types of assets and strategies, including: - **Real world assets**: US Treasuries, real estate, credit, carbon, or consumer finance - **Onchain assets**: Tokens, staked positions, or DeFi strategies - **Structured products**: Combinations of both onchain and offchain exposures Tokenization is flexible. Products can range from direct credit exposure to complex multi-asset vaults or portfolio strategies. ## How tokenization works in Centrifuge 1. **Create a pool** A manager launches a pool on a selected hub chain. The pool defines capital flow, share classes, and permissioning. 2. **Configure vaults** Vaults are deployed to accept specific currencies and invest in target assets. They define whether flows are synchronous or asynchronous. 3. **Deploy tokens** Each share class issues a share token (ERC-20 compatible), optionally including modules for compliance, multi-asset handling, or async workflows. 4. **Distribute to investors** Investors deposit into vaults and receive share tokens that represent their claim on the strategy. ## Token standards used Centrifuge builds on Ethereum token standards to support flexible, compliant tokenization: - **ERC-20**: Base standard for share tokens and vault balances - **ERC-1404**: Enables permissioning and compliance rules - **ERC-4626**: Vault standard for synchronous investing - **ERC-7540**: Request-based standard for asynchronous flows - **ERC-7575**: Allows pooled vaults with multiple investment assets ## Examples - A tokenized real estate fund issues permissioned share tokens to accredited investors - A DeFi-native strategy wraps staked ETH and LSTs into a vault, offering a token to DeFi protocols - A curated portfolio combines several Centrifuge vaults into one share class backed by diverse assets ## Key takeaways - Tokenization connects assets and strategies to onchain liquidity using programmable, standard-based tokens - Pools define the rules and permissions, vaults define the investment flows, and tokens represent the user’s ownership - Once issued, share tokens can be held, redeemed, or integrated across DeFi and institutional platforms --- # Vaults Source: https://docs.centrifuge.io/user/concepts/vaults # Vaults Vaults are how users interact with pools in Centrifuge. They define how assets are deposited, redeemed, and allocated across strategies. Each share token is backed by one or more vaults. These vaults live on spoke chains, allowing users to invest directly from the chain of their choice. Vaults are configured to support either synchronous or asynchronous flows. ## Vault types Centrifuge supports two types of vaults. Pool managers choose the right type depending on their product needs. ### Synchronous vaults Synchronous vaults use the ERC-4626 standard. When users deposit, they immediately receive share tokens. These vaults offer real-time minting and are best suited for highly liquid strategies where on-demand issuance is feasible. - Real-time minting - Easy DeFi integration - Ideal for liquid, onchain strategies ### Asynchronous vaults Asynchronous vaults follow the ERC-7540 standard. In this model, deposits and redemptions are processed through a request lifecycle managed by the hub chain. Users submit a request and receive or redeem tokens after it’s been approved and priced. - Request-based flow (invest and redeem) - Suited for RWA strategies with offchain components - Requests are queued, approved, priced, and fulfilled > Learn more: [Managing investment requests](/user/concepts/onchain-accounting) ## Multi-asset support A single share token can be backed by **multiple vaults**, each accepting a different asset. For example, one share class might accept both USDC and DAI by deploying two separate vaults—each linked to the same token. This setup is supported by [ERC-7575], allowing flexible entry points while consolidating capital into one balance sheet. - Users can deposit different currencies - Capital is aggregated and managed centrally - Investors all receive the same share token regardless of entry asset ## How vaults connect to the protocol Each vault is deployed to a spoke chain and connected to a pool via: - A defined share class and share token - Asset configuration (what currencies are accepted) - Logic for investing, pricing, and redemption Vaults can interact with external DeFi protocols or offchain strategies, depending on how the balance sheet and permissions are configured. ## Summary Vaults are how users enter and exit investment strategies in Centrifuge. They are: - Deployed on spoke chains - Backed by pool-defined share tokens - Configured for real-time or request-based flows - Flexible enough to support multiple assets through distinct vaults Whether users are depositing stablecoins into a treasury vault or redeeming from an offchain credit strategy, vaults are the interface that coordinates capital across the protocol. --- # Curator Source: https://docs.centrifuge.io/user/curator # Curators Curators are strategy designers within the Centrifuge protocol. They create and manage tokenized investment products by configuring vaults, allocating capital, and managing performance. Centrifuge serves as the strategy execution layer—giving curators a programmable, chain-abstracted platform for building custom structured products. Unlike issuers, curators do not originate RWAs. Instead, they compose strategies using existing assets—including RWA vault tokens, DeFi primitives, or other Centrifuge pools. ## Two types of curator strategies ### 1. Direct-to-user strategies In this model, curators configure vaults that users can deposit into directly. These vaults are typically structured around a single asset strategy and may use: - A stablecoin yield vault - A Centrifuge pool token (e.g. Anemoy RWA vault) - A fixed-income DeFi position Users interact directly with the vault. The curator manages allocation, fees, and performance reporting. **Example**: A curator creates a USDC-denominated vault that allocates into a leveraged LRT/ETH strategy. Users deposit directly into the vault. ### 2. Multi-layered strategies via vault tokens Curators can also build **meta-vaults** by depositing vault tokens (ERC-20) from underlying strategies into a higher-level structure. This enables feeder-fund-style products and simplifies downstream integrations. All capital flows into the base Centrifuge strategy vault. The curator only needs to manage one vault at the execution layer—simplifying rebalancing, reporting, and risk. **Example**: - Vault A (e.g. Anemoy) holds RWAs - Vault B (e.g. LRT strategy) holds staked ETH positions - Curator creates Vault C that deposits into both A and B - Vault C tokens are deposited into an external aggregator (e.g. Morpho) This pattern supports composability and abstraction while maintaining a single point of capital execution. ## Why build strategies on Centrifuge? - **Multi-currency vault infrastructure** Native support for multiple investment assets and ERC-7575 pooled vaults. - **Composable DeFi & RWA integration** Combine yield-bearing DeFi tokens and tokenized RWAs in a single strategy. - **Protocol-level abstraction** Manage investment operations cross-chain from a single Hub deployment. - **Custom vault configuration** Choose between synchronous (ERC-4626) and asynchronous (ERC-7540) flows—or mix both. - **Fine-grained control via Merkle Proof Manager** Restrict interactions, define strategy rules, and automate execution. ## NAV management Curators are responsible for managing the Net Asset Value (NAV) of their pools. This includes: - Tracking underlying asset prices - Updating NAV onchain - Ensuring accurate pricing for deposits and redemptions ## Custom UI and integrations Curators can build custom user interfaces to: - Streamline investor UX - Control branding and messaging - Build analytics or dashboards ## Merkle Proof Manager > Learn more: [Merkle Proof Manager →](/developer/protocol/managers/merkle-proof-manager/) The Merkle Proof Manager allows curators to enforce programmable strategies by limiting allowed contract calls. ### Benefits - Enforces only whitelisted interactions with external protocols - Locks vault logic into predefined flows - Enables secure integration with external DeFi protocols ## Summary Centrifuge gives curators a robust foundation to define, launch, and scale onchain strategies. Whether building direct-to-user vaults or composing layered feeder products, curators benefit from composable infrastructure, chain abstraction, and secure capital management. --- # Investor Source: https://docs.centrifuge.io/user/investor # Investor Investors participate in Centrifuge pools by investing in share tokens, gaining exposure to tokenized strategies offered by issuers or curators. Each pool is customizable, so access, currency, and liquidity terms can vary. ## Access requirements Each pool defines its own permissioning logic. Before investing, ensure you meet the eligibility requirements. ### Whitelisting Some pools require whitelisting before investment or redemption. This may include: - KYC/AML verification - Jurisdictional restrictions - Wallet pre-approval Whitelisting can apply to: - **Investing only** - **Redeeming only** - **Both investing and redeeming** You’ll be notified during the process if your address must be whitelisted before interacting with the pool. ## Supported currencies The currencies accepted for investment are defined per pool. Common options include: - USDC - Other pool assets - Other ERC-20 tokens approved by the pool’s issuer or curator Each vault supports one or more investment assets depending on configuration. ## Available chains Centrifuge operates on a **hub-and-spoke** model. Investors interact with pools on the **spoke chains** where the vaults are deployed. You can invest directly from any supported chain selected by the issuer or curator, without needing to use the Hub chain yourself. ## Investing To invest in a pool: 1. Navigate to the pool’s page 2. Review the investment terms 3. Choose the amount and asset to deposit 4. Follow the onchain flow ### Vault behavior Each pool uses one of the following vault types: #### Synchronous vaults - Follow the ERC-4626 standard - Deposits are instant - Shares are minted immediately to the investor - Redemptions are processed through a request-based flow #### Asynchronous vaults - Follow the ERC-7540 standard - Both deposits and redemptions happen in two steps: - You submit a `requestDeposit` or `requestRedeem` - Later, you complete the action using `claimDeposit` or `claimRedeem` - Timing depends on the pool’s processing schedule Make sure to check whether the pool uses a synchronous or asynchronous flow before investing. ## What to consider - **Access control**: Ensure your wallet is allowed to invest or redeem - **Redemption timing**: Async vaults introduce a delay before funds are claimable - **Chain experience**: Transactions must be performed on the same chain as the pool Investors are encouraged to read the pool’s documentation or terms carefully before participating. --- # Issuer Source: https://docs.centrifuge.io/user/issuer # Issuers This guide helps issuers tokenize real-world assets (RWA) using Centrifuge's RWA Launchpad. It walks through the end-to-end process of launching an asset-backed issuance, from setup to deployment. ## Overview The RWA Launchpad is a modular suite of institutional-grade smart contracts for launching tokenized financial products. Issuers can configure asset types, fund structures, and operational logic without writing custom code. Supported use cases include: - Tokenized bonds, equity, credit, real estate, and indices - Fund structures like single-fund, fund-of-funds, or structured credit - Operational logic such as on/off-ramping, fee distribution, waterfalls, and performance reporting ## Issuance workflow ### 1. Configure your product Use the Launchpad interface to configure: - All pool details (type, issuer info, providers, ratings etc) Show pool details UI ![Pool Details](./images/page1.png) - Asset type (e.g. bond, equity, real estate) - Share class structure (e.g. junior/senior tranches) Asset type & Share class structure UI ![Share Class Details](./images/page2.png) - Compliance rules (e.g. allowlist, jurisdictional controls) - Manager access controls Access Control UI ![Access Control](./images/page3.png) ### 2. Deploy your pool Launchpad deploys a suite of protocol-native contracts: - ERC-20 share tokens with optional ERC-1404 restrictions - Vaults using ERC-4626 (for synchronous deposits) or ERC-7540 (for asynchronous flows) - Pooled vaults using ERC-7575 to aggregate capital across supported assets - On/Off Ramp Manager to control asset movements - Fee, accounting, and reporting modules All contracts are upgrade-free and immutable once deployed. ### 3. Set up on/off-ramping Configure on-chain and off-chain capital flows: - **Onramp**: any user can deposit approved ERC20 tokens into the pool - **Offramp**: only authorized relayers can initiate withdrawals to predefined recipient addresses This ensures compliance and control over fund flows. ### 4. Launch your issuance Once contracts are deployed and configured: - Begin accepting deposits from whitelisted or open users (based on your setup) - Mint and distribute share tokens - Fund vaults with capital or asset-backed flows Deposits and redemptions will follow the configured vault logic: - **Synchronous deposits**: users receive shares immediately (ERC-4626) - **Asynchronous redemptions**: requests are queued and processed via the Hub (ERC-7540) ## Vault logic Centrifuge supports two primary vault configurations: - **Asynchronous vaults (ERC-7540)** Deposits and redemptions are request-based, coordinated through the Hub. This is ideal for RWAs with delayed settlement or valuation updates. - **Synchronous deposit vaults** Deposits are executed immediately using ERC-4626. Redemptions are still handled asynchronously via ERC-7540. This is ideal for liquid, onchain assets. Each share token can be backed by multiple vaults—each accepting a different asset—using the ERC-7575 standard. This allows issuers to consolidate liquidity across asset types while managing them independently. ## Post-launch operations Track the performance and operations of your issuance: - Issuer Dashboard ![Issuer Dashboard](./images/page5.png) - Live reporting on NAV, share price, and token supply ![Update Dashboard](./images/page4.png) ## Extensibility Launchpad products are fully modular. Issuers can integrate: - Custom relayers and compliance agents - Fee structures and waterfall models - Automated yield strategies - Interoperability with DeFi protocols ## Benefits for issuers - **Fast time to market**: Launch in days, not months - **Secure and immutable**: Smart contracts are non-upgradeable and decentralized - **Composability**: Plug into the broader DeFi ecosystem - **Customizability**: Tailor every aspect of your product to fit your asset and investor needs --- # Overview Source: https://docs.centrifuge.io/user/overview # User Documentation Centrifuge is the open infrastructure for onchain asset management. This section helps users understand how to interact with the protocol depending on who they are and what they want to do. Whether you're issuing a pool, curating assets, investing, or testing the system, this guide will get you started. ## Explore Concepts Core ideas behind the Centrifuge Protocol: hub-and-spoke architecture, vaults, assets, and token flows. Issuer Tokenize real-world assets using configurable onchain vaults through the RWA Launchpad. Curator Structure and manage pools, set investment logic, and configure vault permissions. Investors Invest in tokenized assets across networks. Understand access controls and redemption flows.