Skip to main content

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 (v18 or later recommended)
  • A package manager: pnpm, npm or yarn
  • A wallet or signer that can connect to Ethereum-compatible chains (e.g. MetaMask, WalletConnect, or a private key account via Viem)

Installation

Install the Centrifuge SDK in your project:

pnpm add @centrifuge/sdk

1. Initialize the SDK

Create a Centrifuge instance and connect it to mainnet:

import Centrifuge from "@centrifuge/sdk";

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

const poolId = new PoolId(1);
const pool = await centrifuge.pool(poolId);
const scId = ShareClassId.from(poolId, 1);
const chainId = 11155111; // Ethereum Sepolia

const poolNetworks = await pool.activeNetworks();

const poolNetwork = poolNetworks.filter(
(activeNetwork) => activeNetwork.chainId === chainId
);

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:

const merkleProofManager = await poolNetwork.merkleProofManager();
await poolNetwork.updateBalanceSheetManagers([{ chainId, 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:

const addresses = await centrifuge._protocolAddresses(chainId);
const strategist = "0xStrategistAddress";

const vaultDepositPolicy = {
assetId: assetId.toString(),
decoder: addresses.vaultDecoder,
target: "0xVaultAddress",
abi: "function deposit(uint256,address)",
valueNonZero: false,
args: [null, strategist],
argsEncoded: encodePacked(["address"], ["0xVaultAddress"]),
};

const balanceSheetWithdrawPolicy = {
assetId: assetId.toString(),
decoder: addresses.vaultDecoder,
target: addresses.balanceSheet,
abi: "function withdraw(uint64,bytes16,address,uint256,address,uint128)",
valueNonZero: false,
args: [
poolId.toString(),
scId,
erc20,
null,
merkleProofManager.address,
null,
],
argsEncoded: encodePacked(
["uint64", "bytes16", "address", "address"],
[poolId, scId, erc20, merkleProofManager.address]
),
};

const balanceSheetDepositPolicy = {
assetId: assetId.toString(),
decoder: addresses.vaultDecoder,
target: addresses.balanceSheet,
abi: "function deposit(uint64 poolId, bytes16 scId, address asset, uint256, uint128)",
valueNonZero: false,
args: [poolId.toString(), scId.raw, someErc20, null, null],
argsEncoded: encodePacked(
["uint64", "bytes16", "address"],
[poolId.raw, scId.raw, someErc20]
),
};

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:

centrifuge.setSigner(strategist);
await merkleProofManager.execute([
{
policy: balanceSheetWithdrawPolicy,
inputs: [0, amount],
},
{
policy: vaultDepositPolicy,
inputs: [amount],
},
{
policy: balanceSheetDepositPolicy,
inputs: [0, amount],
},
]);