Back to projects

ZyphBridge

Cross-chain transfers that preserve privacy.

$500
Total awarded
ZyphBridge
Kshitij
Builder
Next.js Docker SQLite Rust

Awards

The problem it solves

Bridging Privacy-Preserving Blockchains

Zcash users have access to industry-leading privacy through shielded transactions, but their assets are siloed within the Zcash ecosystem. Meanwhile, Miden offers a new paradigm of private, scalable computation through zero-knowledge proofs. ZyphBridge solves the critical problem of moving value between these two privacy-focused chains.

Key Use Cases:

  • Private DeFi Access: Zcash holders can now access Miden’s private smart contract ecosystem without sacrificing privacy at the bridge layer
  • Cross-Chain Privacy Preservation: Unlike most bridges that expose transaction details, ZyphBridge maintains privacy guarantees on both sides—shielded ZEC transactions on Zcash, private notes on Miden
  • Unified Liquidity: Enables ZEC liquidity to flow into Miden dApps while maintaining the privacy properties users expect

How It Works:

  1. Deposit (ZEC → wZEC): Users send shielded ZEC to the bridge’s Zcash address. Once confirmed, the bridge mints equivalent wZEC tokens on Miden as private notes
  2. Withdraw (wZEC → ZEC): Users burn wZEC on Miden, then claim their ZEC which is sent to their shielded Zcash address

The bridge operator monitors both chains, verifying deposits/burns and executing the corresponding mint/send operations—all while preserving the privacy guarantees of both networks.

Challenges we ran into

1. Miden Wallet Address Format Mismatch

The Bug: The bridge UI showed 0 wZEC balance even after successfully minting tokens via the faucet.

Root Cause: The Miden wallet adapter returns asset faucet IDs in bech32m format (e.g., mtst1azwuvc3clmzrqgz988cg09clssuq4jsq_qruqqypuyph), but our code was comparing against hex format (0x9dc66238fec430204539f087971f84).

Solution: Implemented bech32m decoding in the frontend to convert wallet-returned addresses to hex for comparison:

const decoded = bech32m.decode(addressPart, 90);
const bytes = bech32m.fromWords(decoded.words);
const hex = '0x' + bytes.map(b => b.toString(16).padStart(2, '0')).join('');

2. Wallet Transaction API Confusion

The Bug: Burn transactions failed with INVALID_PARAMS: Invalid CustomTransaction payload.

Root Cause: The Miden wallet adapter has multiple transaction methods. Using requestTransaction() with a generic Transaction wrapper didn’t work for send operations.

Solution: Used adapter.requestSend() directly with a properly typed MidenSendTransaction object, and discovered all address fields must be in bech32 format (not hex):

const sendTransaction: MidenSendTransaction = {
  senderAddress: address,  // bech32
  recipientAddress: WZEC_FAUCET_ADDRESS,  // bech32
  faucetId: WZEC_FAUCET_ADDRESS,  // bech32
  noteType: 'private',
  amount: amountInSmallestUnits,
};
await adapter.requestSend(sendTransaction);

3. Zcash Shielded Balance Timing

The Bug: After claiming a withdrawal, z_getbalance returned 0 even though the transaction was sent.

Root Cause: Zcash shielded (Sapling) transactions require block confirmations before the balance is spendable and visible.

Solution: Mine at least one block after the withdrawal transaction before checking balance. In production, the UI should poll or wait for confirmations.

Gallery