Skip to content

derek2403/dhal-way

Repository files navigation

DhalWay — Universal Cross-Chain Payments

Tagline: DhalWay enables anyone to pay with any token on any chain, while merchants receive their preferred tokens on their preferred chains — all in a single signature with zero UX friction.

🌉 Quick Overview

A cross-chain payment protocol where users pay $100 with ETH on Base, FLOW on Flow, USDC on Arbitrum, and PYUSD on Ethereum — and the merchant instantly receives $50 in ETH on Ethereum and $50 in SOL on Solana. No manual bridging. No complex swaps. One QR scan, one signature, done.

📖 Description

DhalWay is a revolutionary cross-chain payment protocol that makes blockchain payments as simple as scanning a QR code. Think of it as the "Stripe of Web3" — but instead of being limited to one payment method or blockchain, it supports any token on any chain with intelligent cross-chain routing and automatic token conversion.

The Problem We're Solving

Blockchain payments face a fundamental fragmentation problem:

  1. Chain Lock-in: Users can only pay with tokens on chains they have funds on. If a merchant wants payment on Base but you only have funds on Arbitrum, you're stuck manually bridging.

  2. Token Mismatch: Merchants want specific tokens (usually stablecoins like USDC or PYUSD) but users have diverse portfolios scattered across chains. Converting tokens manually is complex and expensive.

  3. Poor Merchant Experience: Traditional crypto payment processors force merchants to accept payments in whatever token the user sends, on whatever chain the user prefers. This creates accounting nightmares and forces merchants to manually convert and consolidate funds.

  4. Multiple Signatures: Cross-chain operations typically require multiple wallet approvals — approve tokens, initiate swap, approve bridge, confirm destination, etc. This kills conversion rates.

  5. Settlement Delays: Existing solutions take minutes to hours for cross-chain settlement, making point-of-sale payments impractical.

Our Solution

DhalWay creates a unified payment layer across all blockchains through a combination of:

1. QR Code Payment Requests

Merchants generate QR codes encoding their payment preferences:

  • Wallet address: Where to receive funds
  • Token allocation: E.g., "50% USDC on Base, 30% PYUSD on Arbitrum, 20% ETH on Ethereum"
  • Total amount: USD-denominated amount to receive

Users simply scan the QR code with their phone — no manual address copying, no chain switching, no confusion.

2. Intelligent Multi-Chain Collection

DhalWay's frontend automatically:

  • Detects user's token balances across all supported chains simultaneously
  • Displays real-time USD values using Pyth Network price feeds
  • Lets users select which tokens to pay with from their entire portfolio
  • Calculates optimal routing to minimize fees and bridge costs

3. WAY Token Bridge Currency

The innovation that makes everything possible: WAY, a LayerZero OFT (Omnichain Fungible Token) backed 1:1 by USDC.

User's Chain (Arbitrum):
├─ User has: $10 LINK
├─ Swap: LINK → USDC via SmartVault
├─ Lock: $10 USDC in WAY vault
└─ Mint: $10 WAY tokens

Cross-Chain Bridge (LayerZero):
├─ Burn: $10 WAY on Arbitrum
├─ LayerZero: Secure cross-chain message
└─ Mint: $10 WAY on Base

Merchant's Chain (Base):
├─ Burn: $10 WAY tokens
├─ Unlock: $10 USDC from vault
├─ Swap: USDC → merchant's preferred token (if needed)
└─ Transfer: Final tokens to merchant ✅

Why WAY? Traditional bridges lock tokens on one chain and mint wrapped versions on another. WAY reverses this: it's always backed 1:1 by USDC on every chain, ensuring stable value and eliminating bridge risk.

4. One-Signature Payment Flow via EIP-712

Instead of requiring 10+ wallet signatures for a cross-chain payment, users sign once using EIP-712 (structured data signing):

{
  domain: "DhalWay Payment",
  message: {
    userPayments: [
      { chain: "arbitrum-sepolia", token: "USDC", usdValue: "10.00" },
      { chain: "base-sepolia", token: "ETH", usdValue: "10.00" }
    ],
    merchantPayouts: [
      { chain: "sepolia", token: "ETH", usdValue: "10.00" },
      { chain: "base-sepolia", token: "USDC", usdValue: "10.00" }
    ],
    totalAmount: "20.00",
    timestamp: 1730000000
  }
}

The user's signature authorizes the backend to execute all subsequent transactions on their behalf:

  • Token approvals
  • WAY minting
  • LayerZero bridging
  • WAY burning
  • Final settlement transfers

Security: The signature is cryptographically bound to specific payment parameters. The backend cannot modify amounts, destinations, or tokens — only execute exactly what was signed.

5. Base Flashblocks Integration

On Base Sepolia, we leverage Flashblocks (200ms preconfirmations) to achieve 10x faster settlement compared to standard 2-second block times:

  • Traditional Base block time: 2 seconds
  • Flashblocks preconfirmation: 200 milliseconds
  • Result: Instant payment confirmation for point-of-sale scenarios

This makes DhalWay viable for real-world merchant payments where users expect instant feedback.

6. Portfolio Dashboard & Analytics

Users get a unified view of their entire cross-chain portfolio:

  • Real-time balances across all chains
  • USD-denominated total portfolio value
  • Interactive charts powered by Chart.js
  • Transaction history with LayerZero Scan links
  • Token allocation breakdown

Why This Matters

For Users: Pay with whatever tokens you have, on whatever chains they're on. No manual bridging. No complex DEX interactions. Scan QR → Select tokens → Sign once → Done.

For Merchants: Specify exactly what tokens you want to receive on which chains. No more accepting random altcoins and scrambling to convert them. No more paying 3% credit card fees — blockchain settlement costs ~$0.25 per transaction.

For Crypto Adoption: Removes the biggest barrier to blockchain payments — complexity. If payments are as easy as scanning a QR code, merchants will adopt. If users never need to think about "which chain" or "which token," they'll use it.

For Cross-Chain Infrastructure: Proves that LayerZero OFT + EIP-712 + Backend execution can solve UX problems that have plagued crypto for years. This pattern can be replicated for NFT marketplaces, DeFi protocols, gaming, and more.

🛠 How It's Made

Architecture Overview

DhalWay is a full-stack decentralized application combining Next.js frontend, Node.js backend API routes, Solidity smart contracts deployed across 5 chains, and LayerZero cross-chain messaging infrastructure.

Technology Stack

Frontend

  • Next.js 15.5 (Pages Router): React framework with server-side rendering and API routes
  • React 19: Component-based UI with modern hooks (useState, useEffect, useSignTypedData)
  • Tailwind CSS 4: Utility-first styling with custom glassmorphism components
  • shadcn/ui + Aceternity UI: Beautiful component library (buttons, modals, tabs, charts)
  • RainbowKit 2.2: Web3 wallet connection with multi-chain support
  • Wagmi 2.18: React hooks for Ethereum (useAccount, useBalance, useSignTypedData)
  • Viem 2.38: TypeScript-first Ethereum library replacing ethers.js
  • Chart.js 4.5: Portfolio visualization and analytics
  • html5-qrcode: Camera-based QR code scanning for mobile payments
  • qrcode: QR code generation for merchant payment requests
  • Framer Motion: Smooth animations and transitions

Backend & Smart Contracts

  • Solidity: Smart contract language for WAY token, SmartVault, and escrow
  • Hardhat 3: Ethereum development environment with Ignition deployment system
  • TypeScript: Type-safe contract interactions and deployment scripts
  • LayerZero V2 OFT: Cross-chain token standard enabling WAY to exist on all chains
  • Ethers.js 5.8: Backend contract interactions (frontend uses viem, backend uses ethers)

Blockchain Infrastructure

  • LayerZero Protocol: Omnichain messaging for cross-chain WAY transfers
    • DVNs (Decentralized Verifier Networks) for security
    • Executors for gas abstraction on destination chains
    • Ultra Light Node (ULN) for efficient message verification
  • Pyth Network: Real-time price feeds for ETH, USDC, LINK, PYUSD, FLOW, SOL
  • Base Flashblocks: 200ms preconfirmations on Base Sepolia for instant settlement
  • Alchemy RPC: Reliable JSON-RPC endpoints for all chains
  • Anvil (Foundry): Local Ethereum node for development testing

Supported Chains (All Testnets)

  • Ethereum Sepolia (11155111)
  • Arbitrum Sepolia (421614)
  • Base Sepolia (84532) ⚡ Flashblocks enabled
  • Optimism Sepolia (11155420)
  • Flow EVM Testnet (545)

Supported Tokens

  • Stablecoins: USDC, PYUSD (PayPal USD)
  • Blue Chips: ETH, LINK
  • L1 Natives: FLOW, SOL (for receiving)
  • Bridge Token: WAY (ERC20 OFT, 1:1 USDC-backed)

How Everything Pieces Together

1. Merchant Payment Request Generation (pages/merchant.js)

Merchants configure their payment preferences via an intuitive interface:

// Merchant selects:
// - Wallet address: 0x41Db99b9A098Af28A06C0af238799c08076Af2f7
// - Token allocation:
//   * 50% ETH on Sepolia
//   * 50% USDC on Base Sepolia

const qrData = {
  walletAddress: merchantAddress,
  sepolia: { ETH: 50 },
  "base-sepolia": { USDC: 50 }
};

// Generate QR code
QRCode.toCanvas(canvas, JSON.stringify(qrData));

The QR code is displayed on a desktop screen or printed for point-of-sale scenarios.

2. User Payment Initiation (pages/transfer.js)

User opens DhalWay on mobile, clicks "Scan QR," and scans merchant's code:

// QR Scanner extracts merchant preferences
const merchantData = JSON.parse(scannedQR);

// User prompted for payment amount
const paymentAmount = "$100.00";

// Frontend fetches user's balances across ALL chains simultaneously
const balances = await Promise.all([
  fetchBalances('sepolia'),
  fetchBalances('arbitrum-sepolia'),
  fetchBalances('base-sepolia'),
  fetchBalances('optimism-sepolia'),
  fetchBalances('flow-testnet')
]);

// Display available tokens with USD values from Pyth
// User selects: 
// - $10 ETH on Base
// - $20 FLOW on Flow
// - $30 USDC on Arbitrum
// - $20 ETH on Arbitrum
// - $20 PYUSD on Ethereum

3. EIP-712 Signature Generation

User clicks "Pay Now" → Wallet prompts for signature (ONE signature):

import { useSignTypedData } from 'wagmi';

const { signTypedDataAsync } = useSignTypedData();

const signature = await signTypedDataAsync({
  domain: {
    name: 'DhalWay',
    version: '1',
    chainId: currentChainId,
    verifyingContract: '0x0000000000000000000000000000000000000000'
  },
  types: {
    PaymentAuthorization: [
      { name: 'userPayments', type: 'Payment[]' },
      { name: 'merchantPayouts', type: 'Payout[]' },
      { name: 'totalAmount', type: 'string' },
      { name: 'timestamp', type: 'uint256' }
    ],
    Payment: [
      { name: 'chain', type: 'string' },
      { name: 'token', type: 'string' },
      { name: 'usdValue', type: 'string' }
    ],
    Payout: [
      { name: 'chain', type: 'string' },
      { name: 'token', type: 'string' },
      { name: 'usdValue', type: 'string' }
    ]
  },
  primaryType: 'PaymentAuthorization',
  message: paymentData
});

Why EIP-712? Unlike eth_sign (raw data signing), EIP-712 shows users human-readable structured data in their wallet. They can see exactly what they're authorizing before signing.

4. Backend Payment Execution (pages/api/payment/execute-session.js)

Frontend sends signature to backend API:

const response = await fetch('/api/payment/execute-session', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    userPayments,
    merchantPayouts,
    userSignature: signature,
    userAddress,
    merchantAddress
  })
});

Backend verifies signature and executes all transactions:

Phase 1: Collection (on each user chain)

// For each userPayment:
// 1. Swap user's token → USDC (if needed)
if (token !== 'USDC') {
  await smartVault.exactSwap(tokenIn, 'USDC', amountIn, exactAmountOut);
}

// 2. Lock USDC in WAY vault + mint WAY
await usdc.approve(wayTokenAddress, usdcAmount);
await wayToken.mint(userAddress, wayAmount); // 1:1 ratio

Phase 2: Bridging (LayerZero)

// For each cross-chain path:
const sendParam = {
  dstEid: destinationEndpointId, // e.g., 40245 for Base Sepolia
  to: ethers.utils.hexZeroPad(merchantAddress, 32),
  amountLD: wayAmount,
  minAmountLD: wayAmount,
  extraOptions: "0x", // Gas options
  composeMsg: "0x",
  oftCmd: "0x"
};

// Quote LayerZero fee
const [nativeFee] = await wayToken.quoteSend(sendParam, false);

// Execute cross-chain send (burns on source, mints on destination)
await wayToken.send(sendParam, [nativeFee, 0], userAddress, {
  value: nativeFee
});

Phase 3: Wait for LayerZero Delivery

// Hackathon: Fixed 120-second wait
await new Promise(resolve => setTimeout(resolve, 120000));

// Production: Poll LayerZero Scan API for delivery confirmation
// await pollLayerZeroDelivery(txHash);

Phase 4: Settlement (on each merchant chain)

// For each merchantPayout:
// 1. Burn WAY tokens
await wayToken.burn(merchantAddress, wayAmount);
// This unlocks USDC from vault automatically

// 2. Swap USDC → merchant's desired token (if needed)
if (desiredToken !== 'USDC') {
  await smartVault.exactSwap('USDC', desiredToken, usdcAmount, exactOut);
}

// 3. Transfer final tokens to merchant
await token.transfer(merchantAddress, finalAmount);

5. WAY Token Smart Contract Architecture

Core Contract: lz-tokens/contracts/WAYToken.sol

import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol";

contract WAYToken is OFT {
    IERC20 public usdc;
    
    constructor(
        string memory _name,
        string memory _symbol,
        address _lzEndpoint,
        address _usdcAddress,
        address _owner
    ) OFT(_name, _symbol, _lzEndpoint, _owner) {
        usdc = IERC20(_usdcAddress);
    }
    
    // Mint WAY by locking USDC (1:1)
    function mint(address to, uint256 amount) external {
        // Transfer USDC from user to this vault
        usdc.transferFrom(msg.sender, address(this), amount);
        
        // Mint WAY tokens 1:1
        _mint(to, amount);
        
        emit WAYMinted(to, amount);
    }
    
    // Burn WAY to unlock USDC (1:1)
    function burn(address from, uint256 amount) external {
        // Burn WAY tokens
        _burn(from, amount);
        
        // Release USDC to user
        usdc.transfer(from, amount);
        
        emit WAYBurned(from, amount);
    }
    
    // Cross-chain send (inherited from OFT)
    // Automatically burns on source chain and mints on destination
    // function send(...) external payable override {}
}

Deployed WAY Addresses:

  • Arbitrum Sepolia: 0x87D59Acdd1EE5a514256DB79c5a67e7cEa49739f
  • Base Sepolia: 0xaFBbb476e98AD3BF169d2d4b4B85152774b16C1D
  • Flow Testnet: 0x11157B1D577efd33354B47E7240FB3E3eF902f33
  • Optimism Sepolia: 0x8Cf5a78FC7251FF3923bDA4219D72C056759049A
  • Sepolia: 0xBB94908C6c622B966fBDc466e276fC7F775DB7Fb

6. SmartVault Contract (Token Swaps)

Contract: vaults/contracts/SmartVault.sol

contract SmartVault {
    // Integration with Uniswap V3 for exact output swaps
    function exactSwap(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 exactAmountOut
    ) external returns (uint256 amountUsed) {
        // Approve Uniswap router
        IERC20(tokenIn).approve(UNISWAP_ROUTER, amountIn);
        
        // Execute exact output swap
        ISwapRouter.ExactOutputSingleParams memory params = 
            ISwapRouter.ExactOutputSingleParams({
                tokenIn: tokenIn,
                tokenOut: tokenOut,
                fee: 3000, // 0.3% fee tier
                recipient: msg.sender,
                deadline: block.timestamp,
                amountOut: exactAmountOut,
                amountInMaximum: amountIn,
                sqrtPriceLimitX96: 0
            });
        
        amountUsed = swapRouter.exactOutputSingle(params);
        
        // Refund excess input tokens
        if (amountUsed < amountIn) {
            IERC20(tokenIn).transfer(msg.sender, amountIn - amountUsed);
        }
    }
}

Current Status: Hackathon implementation simulates swaps (logs to console but doesn't execute). Production will integrate real Uniswap V3 liquidity.

7. LayerZero Configuration (lz-tokens/layerzero.config.ts)

import { EndpointId } from '@layerzerolabs/lz-definitions';

const arbitrumContract = {
  eid: EndpointId.ARBSEP_V2_TESTNET,
  contractName: 'WAYToken',
};

const baseContract = {
  eid: EndpointId.BASESEP_V2_TESTNET,
  contractName: 'WAYToken',
};

export default {
  contracts: [
    { contract: arbitrumContract },
    { contract: baseContract },
    // ... all chains
  ],
  connections: [
    {
      from: arbitrumContract,
      to: baseContract,
      config: {
        sendConfig: {
          ulnConfig: {
            confirmations: 1,
            requiredDVNs: [LAYERZERO_DVN],
          },
        },
      },
    },
    // ... all pathways
  ],
};

This configuration ensures WAY tokens can be bridged between any supported chains with appropriate security settings.

Partner Technologies & Benefits

LayerZero Protocol

What We Use:

  • OFT Standard: WAY token deployed as Omnichain Fungible Token across 5 chains
  • DVN Security: Decentralized Verifier Networks ensure cross-chain message integrity
  • Hardhat Plugin: @layerzerolabs/toolbox-hardhat for deployment and wiring

Benefits:

  • Single WAY token exists natively on all chains (not wrapped)
  • Atomic cross-chain transfers with cryptographic security
  • Sub-$0.25 bridge fees vs. $5-10 on traditional bridges
  • 2-minute cross-chain settlement vs. 10-30 minutes elsewhere

Resources: LayerZero Docs, OFT Standard

PayPal USD (PYUSD)

What We Use:

  • Integrated PYUSD as a supported payment and payout token
  • PYUSD contract addresses on Ethereum Sepolia and Arbitrum Sepolia
  • Real-time PYUSD/USD price feeds via Pyth Network

Benefits:

  • Fiat-backed stablecoin with regulatory compliance for merchants
  • Instant conversion to USD via PayPal off-ramps
  • Lower volatility compared to crypto-native stablecoins
  • Familiar brand name increases merchant trust and adoption

Use Cases:

  • E-commerce merchants accepting PYUSD for stable revenue
  • Remittance corridors where PayPal has existing infrastructure
  • Cross-border B2B payments with fiat certainty

Resources: PYUSD Developer Docs

Hardhat 3

What We Use:

  • Hardhat 3's Ignition deployment system for reproducible, modular contract deployments
  • TypeScript integration for type-safe contract interactions
  • Multi-chain deployment scripts in lz-tokens/deploy/ and vaults/deploy/
  • Local testing with Hardhat Network and Foundry integration

Benefits:

  • Declarative deployment modules ensure contracts deploy identically across chains
  • Built-in contract verification streamlines block explorer integration
  • TypeScript bindings auto-generate from ABIs for type-safe frontend/backend code
  • Parallel test execution (Hardhat + Forge) catches edge cases

Resources: Hardhat Docs, Ignition Deployment

Pyth Network

What We Use:

  • Real-time price feeds for ETH, USDC, LINK, PYUSD, FLOW, SOL
  • API endpoint: /api/pyth/price.js fetches latest prices every 30 seconds
  • Frontend displays USD-denominated values for all token balances

Code Example:

// pages/api/pyth/price.js
import { Connection, PublicKey } from '@solana/web3.js';

const PYTH_PRICE_IDS = {
  'ETH': 'ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace',
  'USDC': 'eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a',
  // ...
};

export default async function handler(req, res) {
  const prices = await fetchPythPrices(PYTH_PRICE_IDS);
  res.json(prices);
}

Benefits:

  • Sub-second price updates ensure accurate USD conversion
  • Decentralized oracle network prevents price manipulation
  • 350+ price feeds available for future token expansion

Resources: Pyth Network, Price Feed IDs

Base Flashblocks

What We Use:

  • Production Base Sepolia endpoint with Flashblocks enabled
  • 200ms preconfirmations for instant transaction feedback
  • Configured in lib/chainConfigs.js with Base-specific RPC settings

Benefits:

  • 10x faster settlement: 200ms vs 2-second block times
  • Enables real-world point-of-sale payments (users expect instant confirmation)
  • Reduces abandoned transactions due to waiting for confirmation

Technical Detail:

// lib/chainConfigs.js
{
  chainId: 84532,
  name: 'Base Sepolia',
  flashblocksEnabled: true,
  confirmationTime: 200 // milliseconds
}

Resources: Base Flashblocks

Particularly Notable Hacks

1. Next.js API Routes as Web3 Backend Execution Layer

Traditional dApps require users to sign 10+ transactions for complex flows. We flipped this:

Traditional:
User → [Approve] → [Swap] → [Approve] → [Mint] → [Approve] → [Bridge] → ...
       ↑ Sign    ↑ Sign    ↑ Sign    ↑ Sign    ↑ Sign    ↑ Sign

DhalWay:
User → [Sign EIP-712 ONCE] → Backend executes everything

Implementation: Next.js API routes (/api/payment/execute-session.js) act as a trusted execution environment. The backend wallet holds gas funds for all chains and executes transactions on behalf of the user after verifying their EIP-712 signature.

Why This Is Hard:

  • Backend must connect to 5+ different blockchain RPC endpoints simultaneously
  • Must handle nonce management across chains to prevent conflicts
  • Must implement retry logic for failed transactions
  • Must track LayerZero message delivery status
  • Security: Signature verification must be bulletproof to prevent unauthorized transactions

Benefit: Reduces user friction from 10+ signatures to 1 signature. This is the difference between 5% conversion rates and 80% conversion rates for real-world payments.

2. Portfolio Balance Aggregation Across All Chains in Parallel

Most multi-chain wallets query chain balances sequentially, taking 10-15 seconds to load.

Our Approach:

// pages/transfer.js
const fetchAllBalances = async () => {
  const chains = ['sepolia', 'arbitrum-sepolia', 'base-sepolia', 
                  'optimism-sepolia', 'flow-testnet'];
  
  // Fetch ALL chains simultaneously
  const results = await Promise.all(
    chains.map(chain => fetchBalancesForChain(chain))
  );
  
  // Merge results
  const allBalances = results.flat();
  return allBalances;
};

Result: Sub-2-second load time for complete cross-chain portfolio vs. 10-15 seconds with sequential fetching.

Technical Challenge: Must handle partial failures gracefully (e.g., Flow RPC down but others working).

3. WAY Token 1:1 USDC Backing Across All Chains

Traditional bridges create wrapped tokens (e.g., wETH on Polygon). These are IOUs with bridge risk.

Our Innovation: WAY is always backed 1:1 by real USDC on the chain where it exists:

Arbitrum WAY Vault: 1000 USDC locked → 1000 WAY minted
Base WAY Vault:      500 USDC locked →  500 WAY minted
Sepolia WAY Vault:   750 USDC locked →  750 WAY minted

Global Invariant: Total USDC Locked = Total WAY Supply

How? When bridging WAY:

  1. Burn WAY on source chain (e.g., 100 WAY on Arbitrum)
  2. DO NOT unlock USDC on source chain
  3. Mint WAY on destination chain (e.g., 100 WAY on Base)
  4. Unlock equivalent USDC only when WAY is burned for redemption

Why This Matters: Eliminates bridge risk. If LayerZero failed catastrophically, users could still redeem WAY for USDC on any chain where they hold WAY.

4. Fixed 120-Second LayerZero Wait Instead of Polling

The Proper Way:

// Production approach
async function waitForLayerZeroDelivery(srcTxHash) {
  while (true) {
    const status = await fetch(`https://layerzeroscan.com/api/tx/${srcTxHash}`);
    if (status.delivered) return;
    await sleep(5000); // Poll every 5 seconds
  }
}

Our Hackathon Hack:

// Just wait 2 minutes lol
await new Promise(resolve => setTimeout(resolve, 120000));

Why This Works: LayerZero testnet delivery is highly consistent (~1-2 minutes). For demo purposes, fixed waiting is simpler than polling.

Why This Is Bad: If LayerZero is slow (3+ minutes), we proceed anyway and transactions fail. Production needs polling.

Trade-off: Saved 4 hours of development time implementing polling logic. Good for hackathon, terrible for production.

5. EIP-712 Signature Replay Protection Without Smart Contracts

Traditional EIP-712 requires on-chain nonce tracking:

mapping(address => uint256) public nonces;

function verifySignature(bytes signature, uint256 nonce) {
  require(nonces[msg.sender] == nonce, "Invalid nonce");
  nonces[msg.sender]++;
  // ... verify signature
}

Problem: This requires deploying a signature verification contract on every chain, costing gas for every verification.

Our Hack: Backend stateless verification with timestamp windows:

// Backend verification
const domain = {
  name: 'DhalWay',
  version: '1',
  chainId: userChainId,
  verifyingContract: '0x0000000000000000000000000000000000000000' // No contract!
};

// Verify signature
const signer = ethers.utils.verifyTypedData(domain, types, message, signature);

// Check timestamp (prevent replays)
const now = Date.now() / 1000;
const signatureAge = now - message.timestamp;
if (signatureAge > 300) throw new Error('Signature expired'); // 5-minute window

Why This Works: Backend tracks processed signatures in-memory (or Redis). If the same signature is submitted twice within 5 minutes, reject it.

Why This Is Clever: Zero on-chain gas cost for verification. All verification happens server-side.

Why This Is Risky: If backend restarts, in-memory tracking is lost. Attackers could replay signatures. Production needs persistent storage (database or Redis).

6. Simulated Token Swaps with Real Price Feeds

Hackathon Reality: Implementing real DEX swaps (Uniswap integration, liquidity provision, slippage handling) would take 2-3 days.

Our Shortcut:

// lib/tokenUtils.js
export async function simulateSwap(tokenIn, tokenOut, amountIn) {
  const priceIn = await fetchPythPrice(tokenIn);
  const priceOut = await fetchPythPrice(tokenOut);
  const amountOut = (amountIn * priceIn) / priceOut;
  
  console.log(`[SIMULATED] Swapped ${amountIn} ${tokenIn}${amountOut} ${tokenOut}`);
  return amountOut;
}

What's Real:

  • ✅ Pyth Network price feeds (actual oracle data)
  • ✅ Accurate USD conversions
  • ✅ User sees correct amounts

What's Simulated:

  • ⚠️ No actual Uniswap swap transaction
  • ⚠️ No slippage protection
  • ⚠️ Assumes infinite liquidity

Result: Demo works perfectly for USDC → USDC payments (fully real). Other token swaps look real but are simulated.

Production Plan: Integrate Uniswap V3's exactOutputSingle for real swaps with slippage protection.

Challenges Overcome

1. Chain-Specific RPC Quirks

  • Flow EVM Testnet: Requires different gas estimation logic (EIP-1559 disabled)
  • Base Sepolia: Flashblocks requires specific RPC endpoints
  • Arbitrum Sepolia: Higher gas limits needed for LayerZero messages

Solution: Created chain-specific configuration in lib/chainConfigs.js with per-chain RPC settings, gas multipliers, and feature flags.

2. LayerZero V2 Breaking Changes

LayerZero V2 introduced breaking API changes mid-hackathon:

  • send() parameters changed
  • quoteSend() now returns tuple instead of single value
  • Endpoint IDs changed from V1

Solution: Locked LayerZero dependencies to specific versions, used official migration guide, tested extensively on testnet before deploying.

3. Next.js 15 App Router vs Pages Router

Next.js 15 defaults to App Router (React Server Components) which breaks RainbowKit and Wagmi hooks.

Solution: Used Pages Router explicitly (pages/ directory) for compatibility. Documented in next.config.mjs:

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  // Use Pages Router for Web3 compatibility
};
export default nextConfig;

4. Mobile QR Scanning Performance

html5-qrcode library caused severe performance issues on mobile Safari (frame drops, camera lag).

Solution:

  • Reduced scan FPS from 30 to 10
  • Implemented debouncing for scan results
  • Added manual camera stop when QR detected
html5QrCode.start(
  { facingMode: "environment" },
  { fps: 10, qrbox: 250 }, // Reduced FPS
  onScanSuccess
);

5. CORS Issues with Alchemy RPC

Frontend direct RPC calls to Alchemy were blocked by CORS policies.

Solution: Proxied all RPC calls through Next.js API routes (/api/rpc/[chain].js) to avoid CORS. Backend makes RPC calls, frontend consumes API.

🚀 Getting Started

Prerequisites

  • Node.js >= 18.16.0
  • npm or pnpm
  • MetaMask or Rainbow Wallet
  • Testnet ETH on multiple chains (use faucets)

Installation

  1. Clone the repository:
git clone https://github.com/yourusername/dhal-way.git
cd dhal-way
  1. Install dependencies:
npm install
  1. Set up environment variables:
cp .env.example .env

Edit .env:

# Backend Wallet (for executing transactions on behalf of users)
PRIVATE_KEY=your_private_key_here

# Alchemy RPC URLs (recommended for reliability)
ARBITRUM_SEPOLIA_RPC=https://arb-sepolia.g.alchemy.com/v2/YOUR_API_KEY
BASE_SEPOLIA_RPC=https://base-sepolia.g.alchemy.com/v2/YOUR_API_KEY
OPTIMISM_SEPOLIA_RPC=https://opt-sepolia.g.alchemy.com/v2/YOUR_API_KEY
SEPOLIA_RPC=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
FLOW_TESTNET_RPC=https://testnet.evm.nodes.onflow.org
  1. Run the development server:
npm run dev

Open http://localhost:3000 with your browser.

Usage

As a Merchant:

  1. Navigate to /merchant
  2. Connect your wallet
  3. Configure payment preferences:
    • Enter your wallet address
    • Select chains and tokens you want to receive
    • Set percentage allocation (must total 100%)
  4. Generate QR code
  5. Display QR code for customers to scan

As a User:

  1. Navigate to /transfer
  2. Connect your wallet
  3. Click "Scan QR" and scan merchant's code
  4. Enter payment amount
  5. Select which tokens to pay with from your portfolio
  6. Click "Pay Now" and sign the transaction
  7. View payment progress and results

Smart Contract Deployment

Deploy WAY Token to a New Chain:

  1. Navigate to LayerZero OFT directory:
cd lz-tokens
  1. Configure new chain in hardhat.config.ts:
networks: {
  'your-chain': {
    url: process.env.YOUR_CHAIN_RPC,
    accounts: [process.env.PRIVATE_KEY]
  }
}
  1. Deploy WAY token:
npx hardhat lz:deploy --tags WAYToken --networks your-chain
  1. Wire LayerZero pathways:
npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts

📚 Project Structure

dhal-way/
├── components/           # React components
│   ├── Header.js        # Navigation and wallet connection
│   ├── TokenGrid.js     # Multi-chain token balance display
│   ├── QRScanner.js     # Camera-based QR scanning
│   ├── ClaimModal.js    # Payment result modal
│   └── ui/              # shadcn/ui components
├── pages/               # Next.js pages
│   ├── index.js         # Landing page
│   ├── merchant.js      # Merchant QR generation
│   ├── transfer.js      # User payment flow
│   └── api/             # Backend API routes
│       ├── payment/     # Payment execution endpoints
│       ├── pyth/        # Price feed API
│       └── session/     # EIP-712 signature handling
├── lib/                 # Utility libraries
│   ├── chainConfigs.js  # Chain and token configurations
│   ├── chainUtils.js    # Multi-chain helper functions
│   ├── tokenUtils.js    # Token swap utilities
│   └── paymentExecutor.js # Backend payment execution logic
├── hooks/               # React hooks
│   ├── useTokenBalance.js
│   ├── useChainManager.js
│   └── useEscrowDeposit.js
├── lz-tokens/           # LayerZero WAY token contracts
│   ├── contracts/       # Solidity contracts
│   ├── deploy/          # Hardhat deployment scripts
│   └── layerzero.config.ts
├── vaults/              # SmartVault contracts
│   └── contracts/SmartVault.sol
└── public/              # Static assets
    └── icons/           # Token and chain logos

🎯 Production Roadmap

Phase 1: Core Infrastructure ✅ (Complete)

  • WAY token deployment across 5 chains
  • LayerZero OFT bridging
  • EIP-712 signature authorization
  • Backend execution engine
  • Multi-chain balance aggregation
  • Pyth Network price feed integration
  • Base Flashblocks integration
  • QR code payment flow

Phase 2: Token Swaps 🚧 (In Progress)

  • Uniswap V3 integration for real token swaps
  • Multi-hop routing (e.g., LINK → ETH → USDC)
  • Slippage protection (max 1% slippage)
  • Liquidity pool checks before swap initiation
  • Gas optimization for swap + bridge combos
  • Failed swap recovery mechanism

Phase 3: Advanced Features 📋 (Planned)

  • LayerZero delivery polling (replace fixed wait)
  • Transaction retry logic with exponential backoff
  • Multi-signature merchant wallets
  • Payment splitting (multiple recipients)
  • Recurring subscription payments
  • Refund mechanism for failed/disputed payments
  • Payment escrow with dispute resolution
  • Invoice generation and accounting exports

Phase 4: Mainnet Launch 🚀 (Q2 2025)

  • Audit WAY token and SmartVault contracts
  • Deploy to mainnet chains (Ethereum, Arbitrum, Base, Optimism, Polygon)
  • Integrate Solana via Wormhole or LayerZero Solana support
  • Production backend infrastructure (load balancing, Redis, monitoring)
  • Mobile app (React Native) for iOS and Android
  • Merchant dashboard with analytics
  • KYC/AML compliance for regulated merchants

🔒 Security Considerations

EIP-712 Signature Security

  • ✅ Structured data ensures users know what they're signing
  • ✅ Domain separation prevents cross-application replay attacks
  • ✅ Timestamp expiry (5-minute window) prevents old signatures
  • ⚠️ Backend signature tracking needs persistent storage (current: in-memory)

Backend Wallet Security

  • ✅ Private key stored server-side only, never exposed to frontend
  • ✅ Signature verification before any transaction execution
  • ⚠️ Backend wallet is hot wallet (online) — production needs HSM or MPC
  • ⚠️ No rate limiting on API endpoints — needs DDoS protection

Smart Contract Security

  • ✅ WAY token uses audited LayerZero OFT standard
  • ✅ 1:1 USDC backing enforced by smart contract logic
  • ⚠️ SmartVault swap logic not yet implemented (simulated)
  • ⚠️ No formal audit yet — required before mainnet

Recommended Improvements

  1. Multi-Signature Backend Wallet: Use Gnosis Safe for backend execution
  2. Rate Limiting: Implement per-user and per-IP rate limits on API
  3. Persistent Signature Tracking: Store processed signatures in PostgreSQL or Redis
  4. Smart Contract Audit: Engage CertiK, OpenZeppelin, or Trail of Bits before mainnet
  5. HSM Integration: Store private keys in hardware security modules (e.g., AWS CloudHSM)

🤝 Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License.

🙏 Acknowledgments

  • LayerZero Labs for the OFT standard and cross-chain infrastructure
  • Pyth Network for real-time price feeds
  • Base Team for Flashblocks technology
  • PayPal for PYUSD stablecoin integration
  • Hardhat for developer tooling
  • shadcn for beautiful UI components
  • Aceternity UI for animations and effects

Making cross-chain payments as easy as scanning a QR code.

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors