Skip to main content

Overview

Bridging from an EVM Privacy Cash pool is a three-step process:
  1. Quote — Request a bridge quote and a one-time deposit address from the relayer.
  2. Private Withdrawal — Execute a Privacy Cash withdrawal where the recipient is the relayer’s deposit address.
  3. Notify — Tell the relayer your transaction hash so it can release funds on the destination chain.
The EVM bridge API endpoint is: https://evm.privacycash.org/bridge

Supported Output Chains

Output ChainTokens
SolanaSOL, USDT, USDC
EthereumETH, USDC, USDT, DAI
BNB ChainBNB, USDC, USDT
PolygonPOL, USDC, USDT
BitcoinBTC
Supported input pools are Base ETH/USDC and Ethereum ETH/USDT.

Prerequisites

npm install privacycash-evm ethers --save

Implementation

The example below bridges ETH. For ERC-20 inputs, set inputTokenName and token to the supported token for the selected network: usdc on Base or usdt on Ethereum.
import { BASE_NETWORK, ETH_NETWORK, withdraw } from 'privacycash-evm'

const BRIDGE_URL = 'https://evm.privacycash.org/bridge'
const MINIMUM_BRIDGE_AMOUNT = 0.005 // ETH
const RENT_FEE = 0.00025
const FEE_RATE = 35 // basis points, 0.35%

/**
 * Calculate how much ETH the relayer will actually receive after
 * Privacy Cash withdrawal fees are deducted from the user's balance.
 */
function calculateWithdrawableAmount(totalAmount: number): number {
    const flatFee = RENT_FEE                              // 0.00025 ETH
    const rateFee = (totalAmount * FEE_RATE) / 10000      // 0.35%
    return totalAmount - flatFee - rateFee
}

async function runBridge(address: string, signature: string) {
    const network = ETH_NETWORK // or BASE_NETWORK
    const inputChain = network.chainKey === 'eth' ? 'ethereum' : 'base'
    const inputTokenName = 'eth' as const

    const totalWithdrawAmount = 0.1   // ETH you will deduct from your private balance
    const recipientAddress   = '0xRECIPIENT_ADDRESS'  // destination address on output chain

    if (totalWithdrawAmount < MINIMUM_BRIDGE_AMOUNT) {
        throw new Error(`Minimum bridge amount is ${MINIMUM_BRIDGE_AMOUNT} ETH`)
    }

    // Net amount the relayer receives (after Privacy Cash fees)
    const withdrawableAmount = calculateWithdrawableAmount(totalWithdrawAmount)

    const params = {
        inputChain,
        outputChain: 'ethereum',           // see supported chains above
        inputTokenName,
        outputTokenName: 'eth',            // token on the destination chain
        inputTokenAmount: withdrawableAmount.toFixed(8),
        refundAddress: address,            // your EVM address for refunds
        recipientAddress,                  // destination address
        quoteWaitingTimeMs: 3000,          // recommended delay for best routing
    }

    // STEP 1: Get quote and relayer deposit address
    const quoteResponse = await fetch(BRIDGE_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...params, step: 'quote' }),
    })

    const quoteData = await quoteResponse.json()
    if (!quoteData.success) throw new Error(quoteData.error || 'Failed to get quote')

    const depositAddress: string = quoteData.quote.depositAddress
    console.log('Relayer deposit address:', depositAddress)
    console.log('Estimated output:', quoteData.quote.amountOutFormatted)

    // STEP 2: Withdraw to the relayer's deposit address on the selected EVM network
    const txHash = await withdraw({
        withdrawAmountInput: totalWithdrawAmount,
        recipient: depositAddress,           // relayer's one-time EVM address
        keyBasePath: '/circuit',
        signature,
        address,
        token: inputTokenName,
        network,
    })

    console.log('Withdrawal tx:', txHash)

    // STEP 3: Notify the relayer of the transaction hash
    const notifyResponse = await fetch(BRIDGE_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            txHash,
            depositAddress,
            step: 'send_tx',
        }),
    })

    const notifyData = await notifyResponse.json()
    if (notifyData.success) {
        console.log('Bridge successfully initiated!')
    } else {
        console.error('Relayer notification failed:', notifyData.error)
    }
}

Key Considerations

Fee Calculation

Two types of fees are deducted from withdrawAmountInput:
  1. Privacy Cash flat feeRENT_FEE (0.00025 ETH)
  2. Privacy Cash rate feeFEE_RATE (0.35% of withdrawal amount)
Pass the net amount (withdrawableAmount) to the quote API so the relayer knows exactly how much input token it will receive.

Address Validation

Validate the recipient address format before calling the quote API:
  • EVM chains (Ethereum, BNB, Polygon): 0x + 40 hex characters
  • Solana: Base58 string (32–44 characters)
  • Bitcoin: Bech32 (bc1...) or legacy format

Minimum Amount

ETH input uses a 0.005 ETH minimum. Stable-token inputs should use the relayer minimum returned by the quote flow.

Quote Freshness

Quotes expire quickly due to market price movement. Re-fetch the quote if more than ~5 seconds have passed before submitting the withdrawal.