# nOpal Vault

## Contract Addresses & Configuration

```jsx
// nOPAL vault addresses (same on both Plume and Eth chains)
const VAULT_ADDRESS = "0x119Dd7dAFf816f29D7eE47596ae5E4bdC4299165"
const TELLER_ADDRESS = "0xA5F8e5843dd597a179453bF782844e8Bf808A90b"
const ACCOUNTANT_ADDRESS = "0x2Ed2f77a961fc92F73D1087786099c39C894Ed1D"

const INFRA = {
  predicateProxyAddress: "0x6104fe10ca937a086ba7AdbD0910A4733d380cB6",
  nativeFeeTokenAddress: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  bridgeMessageGas: 100000,
}

const PLUME_MAINNET = {
  chainId: 98866,
  layerZeroEndpointId: 30370,
  atomicQueueAddress: "0x220dc6d4569c1f406d532f9633d5be5bc86e8264",
  depositToken: {
    address: "0xdddd73f5df1f0dc31373357beac77545dc5a6f3f",
    decimals: 6,
    symbol: "pUSD",
    defaultSlippagePercentage: 0.005,
  }
}

const ETH_MAINNET = {
  chainId: 1,
  layerZeroEndpointId: 30101,
  atomicQueueAddress: "0x220dc6d4569c1f406d532f9633d5be5bc86e8264",
  depositToken: {
    address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    decimals: 6,
    symbol: "USDC",
    defaultSlippagePercentage: 0.005,
  }
}

const VAULT_DECIMALS = 6
const PUSD_ADDRESS = PLUME_MAINNET.depositToken.address
const USDC_ADDRESS = ETH_MAINNET.depositToken.address
const PLUME_ATOMIC_QUEUE_ADDRESS = PLUME_MAINNET.atomicQueueAddress
const ETH_ATOMIC_QUEUE_ADDRESS = ETH_MAINNET.atomicQueueAddress
```

***

## Summary: 6 Distinct Paths

Deposits (3 paths):

* USDC Ethereum -> nOPAL Plume (cross-chain)
* pUSD Plume -> nOPAL Plume (same-chain)
* USDC Ethereum -> nOPAL Ethereum (same-chain)

Redemptions (3 paths):

* nOPAL Plume -> USDC Ethereum (cross-chain)
* nOPAL Plume -> pUSD Plume (same-chain)
* nOPAL Ethereum -> USDC Ethereum (same-chain)

***

## DEPOSIT FLOWS

### Path 1: Ethereum -> Plume (Cross-Chain Deposit)

{% stepper %}
{% step %}

### Compliance Check (isDepositAndBridge=true)

```http
GET https://api.nest.credit/v1/user/{userAddress}/compliance?chainId=1&isDepositAndBridge=true
```

Response: `{ data: { isCompliant: boolean, predicateMessage: {...} } }`
{% endstep %}

{% step %}

### Exchange Rate Query

```solidity
// Call accountant on Ethereum
Accountant(ACCOUNTANT_ADDRESS).getRateInQuoteSafe(USDC_ADDRESS) -> rateInQuote
// Returns rate in USDC decimals (6), e.g., 1050000 = 1.05 USDC per nOPAL share
```

{% endstep %}

{% step %}

### Calculate Minimum Mint

```jsx
// Step 3a: Calculate expected vault shares
const oneShare = 10n ** BigInt(VAULT_DECIMALS) // 10^6 = 1000000n
const exchangeAmount = (depositAmount * oneShare) / rateInQuote

// Step 3b: Apply slippage protection
const slippageFactor = 1 - ETH_MAINNET.depositToken.defaultSlippagePercentage // 0.995
const slippageBigInt = BigInt(Math.floor(slippageFactor * 1e6)) // 995000n
const minimumMint = (exchangeAmount * slippageBigInt) / 1_000_000n

// Example: 1000 USDC deposit with 1.05 rate and 0.5% slippage
// exchangeAmount = (1000000000n * 1000000n) / 1050000n = 952380952n
// minimumMint = (952380952n * 995000n) / 1000000n = 947619047n
```

{% endstep %}

{% step %}

### Calculate Bridge Fee

```solidity
// Call teller to preview LayerZero bridge fee using expected share amount
const bridgeData = {
  chainSelector: PLUME_MAINNET.layerZeroEndpointId,
  destinationChainReceiver: userAddress,
  bridgeFeeToken: INFRA.nativeFeeTokenAddress,
  messageGas: INFRA.bridgeMessageGas,
  data: "0x"
}
Teller(TELLER_ADDRESS).previewFee(exchangeAmount, bridgeData) -> bridgeFee
```

{% endstep %}

{% step %}

### Token Approval

```solidity
// Approve USDC to predicate proxy on Ethereum
USDC.approve(INFRA.predicateProxyAddress, depositAmount)
```

{% endstep %}

{% step %}

### Cross-Chain Deposit

```solidity
// Execute deposit and bridge transaction on Ethereum
PredicateProxy(INFRA.predicateProxyAddress).depositAndBridge(
  USDC_ADDRESS,
  depositAmount,
  minimumMint,
  bridgeData,
  TELLER_ADDRESS,
  predicateMessage
) { value: bridgeFee }
```

Result: USDC deposited on Ethereum, nOPAL minted on Plume
{% endstep %}
{% endstepper %}

***

### Path 2: Plume -> Plume (Same-Chain Deposit)

{% stepper %}
{% step %}

### Compliance Check

```http
GET https://api.nest.credit/v1/user/{userAddress}/compliance?chainId=98866&isDepositAndBridge=false
```

Response: `{ data: { isCompliant: boolean, predicateMessage: {...} } }`
{% endstep %}

{% step %}

### Exchange Rate Query

```solidity
// Call accountant on Plume
Accountant(ACCOUNTANT_ADDRESS).getRateInQuoteSafe(PUSD_ADDRESS) -> rateInQuote
// Returns rate in pUSD decimals (6), e.g., 1050000 = 1.05 pUSD per nOPAL share
```

{% endstep %}

{% step %}

### Calculate Minimum Mint

```jsx
// Step 3a: Calculate expected vault shares
const oneShare = 10n ** BigInt(VAULT_DECIMALS) // 10^6 = 1000000n
const exchangeAmount = (depositAmount * oneShare) / rateInQuote

// Step 3b: Apply slippage protection
const slippageFactor = 1 - PLUME_MAINNET.depositToken.defaultSlippagePercentage // 0.995
const slippageBigInt = BigInt(Math.floor(slippageFactor * 1e6)) // 995000n
const minimumMint = (exchangeAmount * slippageBigInt) / 1_000_000n
```

{% endstep %}

{% step %}

### Token Approval

```solidity
// Approve pUSD to predicate proxy on Plume
pUSD.approve(INFRA.predicateProxyAddress, depositAmount)
```

{% endstep %}

{% step %}

### Direct Deposit

```solidity
// Execute direct deposit on Plume
PredicateProxy(INFRA.predicateProxyAddress).deposit(
  PUSD_ADDRESS,
  depositAmount,
  minimumMint,
  userAddress,
  TELLER_ADDRESS,
  predicateMessage
)
```

Result: pUSD deposited on Plume, nOPAL minted on Plume
{% endstep %}
{% endstepper %}

***

### Path 3: Ethereum -> Ethereum (Same-Chain Deposit)

{% stepper %}
{% step %}

### Compliance Check

```http
GET https://api.nest.credit/v1/user/{userAddress}/compliance?chainId=1&isDepositAndBridge=false
```

Response: `{ data: { isCompliant: boolean, predicateMessage: {...} } }`
{% endstep %}

{% step %}

### Exchange Rate Query

```solidity
// Call accountant on Ethereum
Accountant(ACCOUNTANT_ADDRESS).getRateInQuoteSafe(USDC_ADDRESS) -> rateInQuote
// Returns rate in USDC decimals (6), e.g., 1050000 = 1.05 USDC per nOPAL share
```

{% endstep %}

{% step %}

### Calculate Minimum Mint

```jsx
// Step 3a: Calculate expected vault shares
const oneShare = 10n ** BigInt(VAULT_DECIMALS) // 10^6 = 1000000n
const exchangeAmount = (depositAmount * oneShare) / rateInQuote

// Step 3b: Apply slippage protection
const slippageFactor = 1 - ETH_MAINNET.depositToken.defaultSlippagePercentage // 0.995
const slippageBigInt = BigInt(Math.floor(slippageFactor * 1e6)) // 995000n
const minimumMint = (exchangeAmount * slippageBigInt) / 1_000_000n
```

{% endstep %}

{% step %}

### Token Approval

```solidity
// Approve USDC to predicate proxy on Ethereum
USDC.approve(INFRA.predicateProxyAddress, depositAmount)
```

{% endstep %}

{% step %}

### Direct Deposit

```solidity
// Execute direct deposit on Ethereum
PredicateProxy(INFRA.predicateProxyAddress).deposit(
  USDC_ADDRESS,
  depositAmount,
  minimumMint,
  userAddress,
  TELLER_ADDRESS,
  predicateMessage
)
```

Result: USDC deposited on Ethereum, nOPAL minted on Ethereum
{% endstep %}
{% endstepper %}

***

## REDEMPTION FLOWS

### Path 1: Plume -> Ethereum (Cross-Chain Redemption)

{% stepper %}
{% step %}

### Preview Bridge Fee

```solidity
// Quote the Plume -> Ethereum share bridge fee
const bridgeData = {
  chainSelector: ETH_MAINNET.layerZeroEndpointId,
  destinationChainReceiver: userAddress,
  bridgeFeeToken: INFRA.nativeFeeTokenAddress,
  messageGas: INFRA.bridgeMessageGas,
  data: "0x"
}
Teller(TELLER_ADDRESS).previewFee(shareAmount, bridgeData) -> bridgeFee
```

{% endstep %}

{% step %}

### Bridge Shares to Ethereum

```solidity
// Bridge nOPAL shares from Plume to Ethereum
Teller(TELLER_ADDRESS).bridge(
  shareAmount,
  bridgeData
) { value: bridgeFee }
```

{% endstep %}

{% step %}

### Exchange Rate Query for Withdrawal

```solidity
// After the bridge completes, get the current exchange rate on Ethereum
Accountant(ACCOUNTANT_ADDRESS).getRateInQuoteSafe(USDC_ADDRESS) -> rateInQuote
```

{% endstep %}

{% step %}

### Calculate Atomic Price with Slippage

```jsx
// Calculate atomic price (exchange rate with slippage protection)
const slippageFactor = 1 - ETH_MAINNET.depositToken.defaultSlippagePercentage // 0.995
const slippageBigInt = BigInt(Math.floor(slippageFactor * 1e6)) // 995000n
const atomicPrice = (rateInQuote * slippageBigInt) / 1_000_000n

// Example: If rate is 1050000 (1.05 USDC per share) with 0.5% slippage
// atomicPrice = (1050000n * 995000n) / 1000000n = 1044750n
```

{% endstep %}

{% step %}

### Vault Token Approval

```solidity
// Approve nOPAL shares to the Ethereum atomic queue
nOPAL.approve(ETH_ATOMIC_QUEUE_ADDRESS, shareAmount)
```

{% endstep %}

{% step %}

### Create Cross-Chain Withdrawal Request

```solidity
// Create atomic request for USDC on Ethereum
AtomicQueue(ETH_ATOMIC_QUEUE_ADDRESS).updateAtomicRequest(
  VAULT_ADDRESS,
  USDC_ADDRESS,
  {
    deadline: timestamp + (10 * 24 * 60 * 60),
    atomicPrice: atomicPrice,
    offerAmount: shareAmount,
    inSolve: false
  }
)
```

{% endstep %}

{% step %}

### Monitor Cross-Chain Withdrawal

```http
GET https://api.nest.credit/v1/vaults/nest-opal-vault/pending-redemptions?chainId=1&user={userAddress}
```

Result: nOPAL bridged from Plume to Ethereum, then queued for USDC redemption on Ethereum
{% endstep %}
{% endstepper %}

***

### Path 2: Plume -> Plume (Same-Chain Redemption)

{% stepper %}
{% step %}

### Exchange Rate Query for Withdrawal

```solidity
// Get current exchange rate on Plume
Accountant(ACCOUNTANT_ADDRESS).getRateInQuoteSafe(PUSD_ADDRESS) -> rateInQuote
```

{% endstep %}

{% step %}

### Calculate Atomic Price with Slippage

```jsx
// Calculate atomic price (exchange rate with slippage protection)
const slippageFactor = 1 - PLUME_MAINNET.depositToken.defaultSlippagePercentage // 0.995
const slippageBigInt = BigInt(Math.floor(slippageFactor * 1e6)) // 995000n
const atomicPrice = (rateInQuote * slippageBigInt) / 1_000_000n
```

{% endstep %}

{% step %}

### Vault Token Approval

```solidity
// Approve nOPAL shares to the Plume atomic queue
nOPAL.approve(PLUME_ATOMIC_QUEUE_ADDRESS, shareAmount)
```

{% endstep %}

{% step %}

### Create Same-Chain Withdrawal Request

```solidity
// Create atomic request for pUSD on Plume
AtomicQueue(PLUME_ATOMIC_QUEUE_ADDRESS).updateAtomicRequest(
  VAULT_ADDRESS,
  PUSD_ADDRESS,
  {
    deadline: timestamp + (10 * 24 * 60 * 60),
    atomicPrice: atomicPrice,
    offerAmount: shareAmount,
    inSolve: false
  }
)
```

{% endstep %}

{% step %}

### Monitor Same-Chain Withdrawal

```http
GET https://api.nest.credit/v1/vaults/nest-opal-vault/pending-redemptions?chainId=98866&user={userAddress}
```

Result: nOPAL queued for pUSD redemption on Plume
{% endstep %}
{% endstepper %}

***

### Path 3: Ethereum -> Ethereum (Same-Chain Redemption)

{% stepper %}
{% step %}

### Exchange Rate Query for Withdrawal

```solidity
// Get current exchange rate on Ethereum
Accountant(ACCOUNTANT_ADDRESS).getRateInQuoteSafe(USDC_ADDRESS) -> rateInQuote
```

{% endstep %}

{% step %}

### Calculate Atomic Price with Slippage

```jsx
// Calculate atomic price (exchange rate with slippage protection)
const slippageFactor = 1 - ETH_MAINNET.depositToken.defaultSlippagePercentage // 0.995
const slippageBigInt = BigInt(Math.floor(slippageFactor * 1e6)) // 995000n
const atomicPrice = (rateInQuote * slippageBigInt) / 1_000_000n
```

{% endstep %}

{% step %}

### Vault Token Approval

```solidity
// Approve nOPAL shares to the Ethereum atomic queue
nOPAL.approve(ETH_ATOMIC_QUEUE_ADDRESS, shareAmount)
```

{% endstep %}

{% step %}

### Create Same-Chain Withdrawal Request

```solidity
// Create atomic request for USDC on Ethereum
AtomicQueue(ETH_ATOMIC_QUEUE_ADDRESS).updateAtomicRequest(
  VAULT_ADDRESS,
  USDC_ADDRESS,
  {
    deadline: timestamp + (10 * 24 * 60 * 60),
    atomicPrice: atomicPrice,
    offerAmount: shareAmount,
    inSolve: false
  }
)
```

{% endstep %}

{% step %}

### Monitor Same-Chain Withdrawal

```http
GET https://api.nest.credit/v1/vaults/nest-opal-vault/pending-redemptions?chainId=1&user={userAddress}
```

Result: nOPAL queued for USDC redemption on Ethereum
{% endstep %}
{% endstepper %}

***

## Key Calculation Formulas

**Minimum Mint (Deposits):**

```jsx
const oneShare = 10n ** BigInt(VAULT_DECIMALS)
const exchangeAmount = (depositAmount * oneShare) / rateInQuote
const slippageMultiplier = BigInt(Math.floor((1 - slippagePercentage) * 1e6))
const minimumMint = (exchangeAmount * slippageMultiplier) / 1_000_000n
```

**Atomic Price (Withdrawals):**

```jsx
const slippageMultiplier = BigInt(Math.floor((1 - slippagePercentage) * 1e6))
const atomicPrice = (rateInQuote * slippageMultiplier) / 1_000_000n
```

Both pUSD on Plume and USDC on Ethereum use 6-decimal quote units in this guide.
