> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getpara.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Centrifuge Integration

> Use a Para-powered wallet to deposit into and redeem from Centrifuge vaults on Base and X Layer.

This walkthrough explains how to integrate Centrifuge vaults on Base and X Layer with a Para-powered EVM wallet. It is a step-by-step guide for depositing into and redeeming from Centrifuge vaults in a TypeScript web application.

The vault sits in front of the Centrifuge protocol so integrators don't have to deal with its complexity: you deposit USDC into the vault and receive the freely transferable deRWA token.

The examples below use five live vaults across two different chains, Base and X Layer. They are worked examples rather than an exhaustive list. The same contract interface applies to every Centrifuge vault, so an integration written against one of these works against the others by changing the chain and the addresses. More products and chains are live already, and new ones are added regularly. The current list is published at [docs.centrifuge.io](https://docs.centrifuge.io/developer/protocol/deployments/).

## Overview

The vaults in this guide provide a small contract interface for investing in tokenized real-world asset products. Users deposit an accepted asset, such as USDC, and receive product shares. Depending on the vault configuration, a deposit either completes immediately or enters a settlement queue.

Redemptions are always asynchronous. The user submits a redemption request, waits for settlement, and then claims the resulting asset.

The integration must therefore distinguish between:

* **Synchronous deposit:** one transaction deposits the asset and returns deRWA tokens.
* **Asynchronous deposit:** one transaction creates the request and a later transaction claims deRWA tokens.
* **Asynchronous redemption:** one transaction creates the request and a later transaction claims the asset.

## Reference Deployments

The code in this guide uses the vaults below. Base and X Layer are two independent chains, each with its own RPC endpoint, gas token, and USDC contract, so a deployment on one is unrelated to a deployment on the other even when the product is the same.

### Base (Chain ID 8453, Gas in ETH)

| Product              | Deposit mode                                 | Vault address                                | Accepted asset    | Asset address                                | deRWA token                                  |
| -------------------- | -------------------------------------------- | -------------------------------------------- | ----------------- | -------------------------------------------- | -------------------------------------------- |
| deSPXA (18 decimals) | Fully asynchronous                           | `0x86faaBE66124Fe9027BEC5d920AdF7aF0590cECC` | USDC (6 decimals) | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | `0x9c5C365e764829876243d0b289733B9D2b729685` |
| deJAAA (18 decimals) | Synchronous deposit, asynchronous redemption | `0x3f24925123deAcec58CD122BFD329907B8038712` | USDC (6 decimals) | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | `0xAAA0008C8CF3A7Dca931adaF04336A5D808C82Cc` |

### X Layer (Chain ID 196, Gas in OKB)

The three X Layer vaults accept the same USDC contract and all use synchronous deposits with asynchronous redemption, the same mode as deJAAA on Base.

| Product               | Deposit mode                                 | Vault address                                | Accepted asset    | Asset address                                | deRWA token                                  |
| --------------------- | -------------------------------------------- | -------------------------------------------- | ----------------- | -------------------------------------------- | -------------------------------------------- |
| deJAAA (18 decimals)  | Synchronous deposit, asynchronous redemption | `0xddD4E1F19DA8CAF2702840784D8D930d661d51c4` | USDC (6 decimals) | `0xB6CEceAB302E2E4948951eE7843FC24E92933061` | `0x5F8a1C74C112865BD05dbe4752C7608332719062` |
| deHYB (18 decimals)   | Synchronous deposit, asynchronous redemption | `0xD55716089C722e8086A53AF180D528207AC0E753` | USDC (6 decimals) | `0xB6CEceAB302E2E4948951eE7843FC24E92933061` | `0xc5A9F6EdB48160eD9d9FB156A23c39d7140457eE` |
| deJTRSY (18 decimals) | Synchronous deposit, asynchronous redemption | `0x61506f58f12ff371b0ea88764cc09fe7d86af1d6` | USDC (6 decimals) | `0xB6CEceAB302E2E4948951eE7843FC24E92933061` | `0x8DE0F3295B9e42b29E7617BAdA7C603277420451` |

Other products and chains are live beyond these five vaults, and the set keeps growing. Look up the current addresses in the [Centrifuge deployments reference](https://docs.centrifuge.io/developer/protocol/deployments/) rather than hardcoding this table into a long-lived integration.

## How It Works

Across these deployments, deposits are **synchronous** for deJAAA, deHYB and deJTRSY, and **asynchronous** for deSPXA. Redemptions are **asynchronous** (ERC-7540 style) everywhere: you submit a request, the issuer fulfills it during their settlement cycle (this can take hours or days), and then you claim the result.

Deposit mode is a property of the vault, not of the chain or product. Read `asyncDeposit()` from the vault instead of assuming the mode from its name.

Three things to know before writing any code:

1. **There is no `claim()` function.** Claiming a fulfilled deposit is done by calling `deposit(...)`, and claiming a fulfilled redemption by calling `redeem(...)`.
2. **Requests are self-directed.** The `controller` and `owner` parameters in the request functions must be your own address.
3. **`mint()` and `withdraw()` don't exist.** Only `deposit()` and `redeem()` are supported, so don't point generic ERC-4626/7540 tooling at this vault.

<Note>
  These deployments allow anyone to claim a controller's entire settled balance on their behalf, but the receiver must be that same controller. Partial third-party claims are not supported.
</Note>

## What You Need

* Node.js 18 or later.
* A package manager such as npm, pnpm, or yarn.
* A Para API key, a completed [Para authentication flow](/v3/react/guides/custom-ui-web-sdk), and an EVM wallet for the authenticated user.
* The Para Web SDK, Para's viem integration, and viem.
* A mainnet RPC endpoint for the chain you target, Base or X Layer in this guide.
* The gas token of that chain in the signing wallet, ETH on Base or OKB on X Layer.
* USDC on that same chain for deposits. The USDC contract is different on each chain.
* The authoritative vault ABI and a standard ERC-20 ABI.
* A user interface that represents pending and claimable states separately.
* Para handles signing without exposing or storing the user's private key in your application.

### Install Dependencies

```bash theme={null}
npm install @getpara/web-sdk @getpara/viem-v2-integration viem
```

***

## Step-by-Step Integration (TypeScript + Para + viem)

All the code in this guide lives in a single file and uses Para's Web SDK, Para's viem integration, and [viem](https://viem.sh).

### Setup

Start with the Para and viem imports:

```ts theme={null}
import Para from '@getpara/web-sdk'
import {
  createParaViemAccount,
  createParaViemClient,
} from '@getpara/viem-v2-integration'
import {
  createPublicClient,
  http,
  parseAbi,
  parseUnits,
  maxUint256,
} from 'viem'
import { base, xLayer } from 'viem/chains'
```

Define the supported deployments in one place. This walkthrough selects Base by default; change `selectedNetwork` to `deployments.xLayer` to use an X Layer sync-deposit or redemption flow.

```ts theme={null}
const deployments = {
  base: {
    chain: base,
    rpcUrl: 'YOUR_BASE_RPC_URL',
    usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
    products: {
      deSPXA: {
        vault: '0x86faaBE66124Fe9027BEC5d920AdF7aF0590cECC',
        share: '0x9c5C365e764829876243d0b289733B9D2b729685',
      },
      deJAAA: {
        vault: '0x3f24925123deAcec58CD122BFD329907B8038712',
        share: '0xAAA0008C8CF3A7Dca931adaF04336A5D808C82Cc',
      },
    },
  },
  xLayer: {
    chain: xLayer,
    rpcUrl: 'YOUR_X_LAYER_RPC_URL',
    usdc: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
    products: {
      deJAAA: {
        vault: '0xddD4E1F19DA8CAF2702840784D8D930d661d51c4',
        share: '0x5F8a1C74C112865BD05dbe4752C7608332719062',
      },
      deHYB: {
        vault: '0xD55716089C722e8086A53AF180D528207AC0E753',
        share: '0xc5A9F6EdB48160eD9d9FB156A23c39d7140457eE',
      },
      deJTRSY: {
        vault: '0x61506f58f12ff371b0ea88764cc09fe7d86af1d6',
        share: '0x8DE0F3295B9e42b29E7617BAdA7C603277420451',
      },
    },
  },
} as const

const selectedNetwork = deployments.base
const syncProduct = selectedNetwork.products.deJAAA
const redemptionProduct = selectedNetwork.products.deJAAA
const asyncProduct = deployments.base.products.deSPXA
```

We only need `approve` and `balanceOf` from the ERC-20s:

```ts theme={null}
const erc20Abi = parseAbi([
  'function approve(address spender, uint256 amount) returns (bool)',
  'function balanceOf(address owner) view returns (uint256)',
])
```

And from the vault, the request/claim functions plus the two views that track a request's lifecycle in each direction:

```ts theme={null}
const vaultAbi = parseAbi([
  'function asyncDeposit() view returns (bool)',
  // deposit flow
  'function requestDeposit(uint256 assets, address controller, address owner) returns (uint256)',
  'function deposit(uint256 assets, address receiver) returns (uint256 shares)',
  'function deposit(uint256 assets, address receiver, address controller) returns (uint256 shares)',
  'function pendingDepositRequest(uint256, address controller) view returns (uint256)',
  'function claimableDepositRequest(uint256, address controller) view returns (uint256)',
  // redeem flow
  'function requestRedeem(uint256 shares, address controller, address owner) returns (uint256)',
  'function redeem(uint256 shares, address receiver, address controller) returns (uint256 assets)',
  'function pendingRedeemRequest(uint256, address controller) view returns (uint256)',
  'function claimableRedeemRequest(uint256, address controller) view returns (uint256)',
])
```

Complete Para authentication before creating the account and clients. `createParaViemAccount` selects the first available EVM wallet unless you pass a specific `address` or `walletId`.

```ts theme={null}
const para = new Para('YOUR_PARA_API_KEY')

if (!(await para.isFullyLoggedIn())) {
  throw new Error('Authenticate with Para before signing transactions')
}

const account = createParaViemAccount({ para })

const publicClient = createPublicClient({
  chain: selectedNetwork.chain,
  transport: http(selectedNetwork.rpcUrl),
})

const walletClient = createParaViemClient({
  para,
  walletClientConfig: {
    account,
    chain: selectedNetwork.chain,
    transport: http(selectedNetwork.rpcUrl),
  },
})
```

Finally, a small helper so every write waits for inclusion and fails loudly on revert:

```ts theme={null}
async function sendAndWait(txPromise: Promise<`0x${string}`>) {
  const hash = await txPromise
  const receipt = await publicClient.waitForTransactionReceipt({ hash })
  if (receipt.status !== 'success') throw new Error(`Tx reverted: ${hash}`)
  return receipt
}
```

That's all the setup. Everything below uses these constants, clients, and the helper directly.

### Case 1: Sync Deposit

In sync mode there is no request and no claim: approve USDC, call `deposit`, and the shares arrive in the same transaction.

```ts theme={null}
async function syncDeposit(usdcAmount: string) {
  const assets = parseUnits(usdcAmount, 6)

  await sendAndWait(walletClient.writeContract({
    address: selectedNetwork.usdc, abi: erc20Abi, functionName: 'approve',
    args: [syncProduct.vault, assets],
  }))

  await sendAndWait(walletClient.writeContract({
    address: syncProduct.vault, abi: vaultAbi, functionName: 'deposit',
    args: [assets, account.address],
  }))
}
```

### Case 2: Async Deposit

deSPXA on Base is the only asynchronous-deposit product in this guide. This case requires `selectedNetwork = deployments.base` and uses `asyncProduct` from that deployment.

#### Step 1: Approve USDC

The vault pulls your USDC when you submit the request, so it needs an allowance first. USDC uses **6 decimals**:

```ts theme={null}
const assets = parseUnits('1000', 6) // 1,000 USDC

await sendAndWait(walletClient.writeContract({
  address: selectedNetwork.usdc, abi: erc20Abi, functionName: 'approve',
  args: [asyncProduct.vault, assets],
}))
```

#### Step 2: Submit the Request

`requestDeposit` takes the amount plus a `controller` and an `owner`. Both must be **your own address** (the caller). Your USDC is transferred immediately and the request enters the queue:

```ts theme={null}
await sendAndWait(walletClient.writeContract({
  address: asyncProduct.vault, abi: vaultAbi, functionName: 'requestDeposit',
  args: [assets, account.address, account.address],
}))
```

#### Step 3: Wait for Fulfillment

The issuer settles requests in epochs, so fulfillment is not instant. Two views, both denominated in USDC, tell you where your request stands: `pending` is what's still queued and `claimable` is what's already settled and ready.

```ts theme={null}
async function getDepositStatus(controller = account.address) {
  const [pending, claimable] = await Promise.all([
    publicClient.readContract({
      address: asyncProduct.vault, abi: vaultAbi,
      functionName: 'pendingDepositRequest', args: [0n, controller],
    }),
    publicClient.readContract({
      address: asyncProduct.vault, abi: vaultAbi,
      functionName: 'claimableDepositRequest', args: [0n, controller],
    }),
  ])
  return { pending, claimable }
}
```

Fulfillment can also be **partial**: requests settle in order across all investors, so `claimable` may cover only part of your request for a while. You can claim partial amounts as they become available.

<Note>
  In a real service, poll these views from a background job or cron. Fulfillment can take hours or days depending on the product's settlement cycle.
</Note>

<Warning>
  Do not use `pending === 0` as the only terminal signal. Rounding dust can leave a small pending amount that is not claimable, and these vaults do not expose request cancellation.
</Warning>

#### Step 4: Claim Your Shares

Once `claimable > 0`, claim by calling `deposit`. Passing `maxUint256` as the amount claims everything that's ready, so you don't have to compute it first:

```ts theme={null}
async function claimDeposit() {
  const { claimable } = await getDepositStatus()
  if (claimable === 0n) throw new Error('Nothing claimable yet')

  await sendAndWait(walletClient.writeContract({
    address: asyncProduct.vault, abi: vaultAbi, functionName: 'deposit',
    args: [maxUint256, account.address],
  }))
}
```

The deSPXA shares (18 decimals) are now in your wallet:

```ts theme={null}
const deSpxaBalance = await publicClient.readContract({
  address: asyncProduct.share, abi: erc20Abi, functionName: 'balanceOf',
  args: [account.address],
})
```

One nice property: if you submit a **new** `requestDeposit` while you still have a claimable balance from a previous one, the vault auto-claims the old one to you first. You'll never strand settled funds by re-requesting.

### Case 3: Async Redemption

The redemption flow mirrors the asynchronous deposit flow: request, wait, and claim, but moving shares in and USDC out. The selected `redemptionProduct` can be any of the five products listed above as long as it belongs to `selectedNetwork`.

#### Step 1: Approve Shares

`requestRedeem` pulls the selected product's shares via `transferFrom`, so approve the vault first. All share tokens listed in this guide use **18 decimals**:

```ts theme={null}
const sharesToRedeem = parseUnits('500', 18)

await sendAndWait(walletClient.writeContract({
  address: redemptionProduct.share, abi: erc20Abi, functionName: 'approve',
  args: [redemptionProduct.vault, sharesToRedeem],
}))
```

#### Step 2: Submit the Request

Same rule as deposits: `controller` and `owner` must be your own address.

```ts theme={null}
await sendAndWait(walletClient.writeContract({
  address: redemptionProduct.vault, abi: vaultAbi, functionName: 'requestRedeem',
  args: [sharesToRedeem, account.address, account.address],
}))
```

#### Step 3: Wait for Fulfillment

Same pattern as deposits, with the redeem-side views. Note these are denominated in **shares**, not USDC:

```ts theme={null}
async function getRedeemStatus(controller = account.address) {
  const [pending, claimable] = await Promise.all([
    publicClient.readContract({
      address: redemptionProduct.vault, abi: vaultAbi,
      functionName: 'pendingRedeemRequest', args: [0n, controller],
    }),
    publicClient.readContract({
      address: redemptionProduct.vault, abi: vaultAbi,
      functionName: 'claimableRedeemRequest', args: [0n, controller],
    }),
  ])
  return { pending, claimable }
}
```

#### Step 4: Claim Your USDC

Claim by calling `redeem`. Again, `maxUint256` claims everything that's settled:

```ts theme={null}
async function claimRedeem() {
  const { claimable } = await getRedeemStatus()
  if (claimable === 0n) throw new Error('Nothing claimable yet')

  await sendAndWait(walletClient.writeContract({
    address: redemptionProduct.vault, abi: vaultAbi, functionName: 'redeem',
    args: [maxUint256, account.address, account.address],
  }))
}
```

<Note>
  The example selects deJAAA. On Base, set `redemptionProduct` to `selectedNetwork.products.deSPXA` to redeem deSPXA. On X Layer, select `deJAAA`, `deHYB`, or `deJTRSY` from `selectedNetwork.products`.
</Note>

## Conclusions

These vaults require direct contract integration for deposits and redemptions, giving enough flexibility to be integrated into many different applications. Synchronous deposits complete atomically, while deSPXA deposits use a request-and-claim lifecycle. Redemptions are asynchronous for every listed product. Para supplies the authenticated viem account and wallet client used to sign each transaction.

The five vaults covered here, two on Base and three on X Layer, are examples of the pattern rather than the full catalogue. Once these deposit and redemption flows are implemented, supporting another product or another chain is a matter of pointing the same code at different addresses. The [Centrifuge deployments reference](https://docs.centrifuge.io/developer/protocol/deployments/) is the place to check as new ones ship.
