Skip to content

pramadanif/vouch

Repository files navigation

Vouch Logo

Vouch

Decentralized Escrow for Social Commerce
Marketplace-level trust β€” without the marketplace.

Lisk Next.js Solidity Xendit MIT


πŸ“– Table of Contents


🎯 Overview

Vouch is a hybrid decentralized escrow platform that brings marketplace-level trust to social commerce transactions happening on Instagram, WhatsApp, and TikTok β€” without the marketplace fees or lock-in.

The Problem

Social commerce in Southeast Asia is booming, but trust remains the biggest obstacle:

  • Buyers are afraid to pay first β†’ Scammers take money and disappear
  • Sellers are afraid to ship first β†’ Risk of non-payment
  • Marketplaces charge 10-20% fees β†’ Erodes profit margins
  • Platform lock-in β†’ Sellers can't own their customer relationships
  • Manual "trust me bro" β†’ Screenshots, reviews don't prevent fraud

Result: Billions in potential transactions lost due to lack of trust infrastructure in social commerce.

The Solution

Vouch creates a shareable payment link that holds funds in a smart contract escrow on Lisk:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Seller    β”‚      β”‚    Buyer    β”‚      β”‚    Escrow    β”‚      β”‚   Seller    β”‚      β”‚    Buyer     β”‚
β”‚ creates linkβ”‚ ───▢ β”‚ pays (QRIS) β”‚ ───▢ β”‚ funds locked β”‚ ───▢ β”‚ ships item  β”‚ ───▢ β”‚   confirms   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                                              β”‚
                                                                                              β–Ό
                                                                                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                                                     β”‚ Funds released   β”‚
                                                                                     β”‚ to seller wallet β”‚
                                                                                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Innovation:

  • Buyers pay with familiar local payment methods (QRIS, bank transfer)
  • Sellers receive funds in their own Lisk wallet
  • Smart contract ensures neither party can cheat
  • No registration, no account creation, no marketplace fees

Payment Options:

  • Fiat (QRIS/VA/Bank Transfer): Buyer pays with local payment methods via Xendit β€” no wallet needed
  • Crypto (USDC/IDRX): Buyer pays directly on-chain with stablecoins β€” wallet required

Protection Mechanisms:

  • Buyer Protection: Funds locked in smart contract escrow until delivery confirmed
  • Seller Protection: Auto-release after timeout (e.g., 7 days) if buyer doesn't confirm delivery

✨ Features

Core Features

Feature Description Status
Decentralized Escrow Smart contract on Lisk holds funds trustlessly βœ… Live
Fiat Payments QRIS, Bank Transfer, E-wallets via Xendit βœ… Live
Crypto Payments Direct USDC/IDRX funding on-chain βœ… Live
Seller Wallet Seller signs with their own Lisk wallet βœ… Live
Buyer Anonymity No wallet needed for buyers (fiat flow) βœ… Live
Auto-Release Timeout protection for sellers βœ… Live
Dashboard Sellers can track all escrows βœ… Live
Mobile-First Optimized for DM sharing βœ… Live
Token Faucet Get testnet tokens for demo βœ… Live
Demo Pages Interactive chat simulations βœ… Live

Advanced Features

  • Multi-Currency Support: PHP, IDR, THB, SGD, MYR, VND, USD via currency API
  • Buyer Token: Unique token for secure release authorization
  • Webhook Integration: Real-time payment notifications from Xendit
  • On-Chain Verification: All escrows verifiable on block explorer
  • Responsive Design: Works seamlessly on mobile and desktop
  • Dark Mode Ready: Design system supports theme switching (future)

πŸ—οΈ Architecture

System Overview

graph TB
    subgraph Users["πŸ‘₯ Users"]
        Seller["Seller<br/>(Requires Wallet)"]
        BuyerFiat["Buyer - Fiat<br/>(No Wallet Needed)"]
        BuyerCrypto["Buyer - Crypto<br/>(Requires Wallet)"]
    end

    subgraph Frontend["πŸ–₯️ Frontend (Next.js 15)"]
        Landing["/ Landing Page"]
        Create["/create - Seller"]
        Pay["/pay/[id] - Buyer"]
        Dashboard["/dashboard - Seller"]
        Demo["/demo/* - Demo Pages"]
        Deck["/deck - Pitch Deck"]
        Faucet["/faucet - Token Faucet"]
    end

    subgraph Backend["βš™οΈ Backend API (Express.js)"]
        EscrowRoutes["/api/escrow/*<br/>Metadata Storage"]
        PaymentRoutes["/api/payment/*<br/>Xendit Integration"]
        FaucetRoutes["/api/faucet/*<br/>Token Distribution"]
        Prisma[(PostgreSQL<br/>Metadata Storage)]
        HotWallet["πŸ” Protocol Wallet<br/>Funds Fiat Escrows"]
    end

    subgraph Blockchain["⛓️ Lisk Sepolia Testnet"]
        VouchEscrow["VouchEscrow.sol<br/>Main Logic"]
        MockUSDC["MockUSDC.sol<br/>Test Stablecoin"]
        MockIDRX["MockIDRX.sol<br/>IDR Stablecoin"]
    end

    subgraph External["🌐 External Services"]
        Xendit["Xendit API<br/>Payment Gateway"]
        Currency["Currency API<br/>Exchange Rates"]
    end

    Seller --> Create
    BuyerFiat --> Pay
    BuyerCrypto --> Pay
    
    %% Seller creates escrow directly on-chain
    Create -->|"createEscrow()<br/>(Seller signs)"| VouchEscrow
    Create -->|"Save metadata"| EscrowRoutes
    EscrowRoutes --> Prisma
    
    %% Fiat payment flow (QRIS/VA - no wallet)
    Pay -->|"Fiat: QRIS/VA"| PaymentRoutes
    PaymentRoutes --> Xendit
    Xendit -."Webhook<br/>(Payment confirmed)".-> PaymentRoutes
    PaymentRoutes -->|"Fund escrow<br/>+ markFunded()"| HotWallet
    HotWallet -->|"Transfer USDC/IDRX<br/>+ markFunded()"| VouchEscrow
    
    %% Crypto payment flow (direct - requires wallet)
    Pay -."Crypto: Direct<br/>(Buyer signs)"..-> VouchEscrow
    
    Dashboard --> EscrowRoutes
    Faucet --> FaucetRoutes
    FaucetRoutes --> MockUSDC
    FaucetRoutes --> MockIDRX
    
    EscrowRoutes -."Read state".-> VouchEscrow
Loading

Technology Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    PRESENTATION LAYER                         β”‚
β”‚  Next.js 15 | React 19 | TailwindCSS | Framer Motion       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β–² β”‚
                            β”‚ β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    APPLICATION LAYER                          β”‚
β”‚  wagmi v2 | React Query | Zustand (State) | API Client      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β–² β”‚
                            β”‚ β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    BUSINESS LOGIC LAYER                       β”‚
β”‚  Express.js | Prisma ORM | Ethers.js | Xendit SDK           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                            β–² β”‚
                            β”‚ β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    DATA PERSISTENCE LAYER                     β”‚
β”‚  PostgreSQL (Metadata) | Lisk Blockchain (Escrow State)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“œ Smart Contracts

Deployed Addresses (Lisk Sepolia)

Contract Address Explorer
VouchEscrow 0xb015d8Eb15B5E82E10aCF1606c60cFD64C4c7cB2 View on Explorer
MockUSDC 0xB7c78ceCB25a1c40b3fa3382bAf3F34c9b5bdD66 View on Explorer
MockIDRX 0xDfef62cf7516508B865440E5819e5435e69adceb View on Explorer

Network Configuration

Property Value
Network Name Lisk Sepolia Testnet
Chain ID 4202
RPC URL https://rpc.sepolia-api.lisk.com
Currency Symbol ETH
Block Explorer https://sepolia-blockscout.lisk.com
Faucet https://sepolia-faucet.lisk.com

VouchEscrow.sol - State Machine

stateDiagram-v2
    [*] --> CREATED: createEscrow()<br/>(Seller signs)
    
    CREATED --> FUNDED: markFunded()<br/>(Fiat payment)
    CREATED --> FUNDED: fundEscrow()<br/>(Crypto payment)
    
    FUNDED --> RELEASED: releaseFunds()<br/>(Buyer confirms)
    FUNDED --> RELEASED: Auto-release<br/>(After timeout)
    
    CREATED --> CANCELLED: cancelEscrow()<br/>(Before funding)
    FUNDED --> REFUNDED: refundEscrow()<br/>(Dispute resolution)
    
    RELEASED --> [*]
    CANCELLED --> [*]
    REFUNDED --> [*]
Loading

Contract Functions Reference

Function Signature Access Control Description
createEscrow (address token, uint256 amount, uint256 releaseTime) returns (uint256) Public Create new escrow (seller = msg.sender)
fundEscrow (uint256 escrowId) Public Fund escrow directly with crypto (buyer must approve token first)
markFunded (uint256 escrowId, address buyer) Protocol Only Mark escrow as funded after fiat payment verification. Protocol wallet transfers IDRX to contract before calling this.
releaseFunds (uint256 escrowId) Buyer/Protocol Release funds to seller (immediate if buyer, after timeout if protocol)
cancelEscrow (uint256 escrowId) Protocol Only Cancel unfunded escrow
refundEscrow (uint256 escrowId) Protocol Only Refund funded escrow to buyer (dispute resolution)
getEscrow (uint256 escrowId) View Get full escrow details
getEscrowStatus (uint256 escrowId) View Get human-readable status string
getSellerEscrows (address seller) View Get all escrow IDs for a seller address
updateProtocolWallet (address newWallet) Protocol Only Update protocol wallet address

Escrow Data Structure

struct Escrow {
    address seller;        // Receives funds on release
    address buyer;         // Confirms delivery (can be zero for anonymous)
    address token;         // ERC20 token (USDC/IDRX)
    uint256 amount;        // Escrow amount in token's smallest unit
    uint256 releaseTime;   // Unix timestamp for auto-release eligibility
    bool funded;           // Payment received
    bool released;         // Funds sent to seller
    bool cancelled;        // Escrow cancelled/refunded
}

Events

event EscrowCreated(uint256 indexed escrowId, address indexed seller, uint256 amount, uint256 releaseTime);
event EscrowFunded(uint256 indexed escrowId, address indexed buyer, address token, uint256 amount);
event EscrowReleased(uint256 indexed escrowId, address indexed seller, uint256 amount);
event EscrowCancelled(uint256 indexed escrowId);
event EscrowRefunded(uint256 indexed escrowId, address indexed buyer, uint256 amount);
event ProtocolWalletUpdated(address indexed oldWallet, address indexed newWallet);

πŸ’³ Payment Flow

Flow 1: Fiat Payment (Xendit)

sequenceDiagram
    participant Seller
    participant Frontend
    participant Backend
    participant Xendit
    participant ProtocolWallet as Protocol Wallet
    participant Lisk
    participant Buyer

    Note over Seller: Seller creates escrow
    Seller->>Frontend: Open /create page
    Seller->>Lisk: createEscrow(token, amount, releaseTime)<br/>(Seller signs with wallet)
    Lisk-->>Seller: escrowId created
    Seller->>Backend: POST /api/escrow/create<br/>(Save metadata + txHash)
    Backend->>Backend: Store in PostgreSQL
    Backend-->>Seller: {id: "abc123", paymentUrl}
    
    Note over Buyer: Buyer pays with fiat
    Buyer->>Frontend: Open payment link (/pay/abc123)
    Frontend->>Backend: GET /api/escrow/abc123
    Backend-->>Frontend: Escrow details

    Buyer->>Frontend: Click "Pay Now"
    Frontend->>Backend: POST /api/escrow/abc123/create-invoice
    Backend->>Xendit: createInvoice(amount, currency)
    Xendit-->>Backend: {invoice_url, invoice_id}
    Backend->>Backend: Save invoice URL to DB
    Backend-->>Frontend: {invoice_url}
    
    Frontend->>Buyer: Redirect to Xendit checkout
    Buyer->>Xendit: Pay via QRIS/Bank Transfer
    Xendit->>Backend: POST /api/payment/xendit/callback<br/>{status: "PAID", external_id: "abc123"}
    
    Backend->>Backend: Verify signature
    Backend->>Backend: Generate buyerToken
    Backend->>Backend: Update DB: status=FUNDED
    
    Note over ProtocolWallet,Lisk: Protocol Wallet funds escrow
    Backend->>ProtocolWallet: Trigger funding
    ProtocolWallet->>Lisk: approve(VouchEscrow, amount)<br/>(ERC20 approval)
    ProtocolWallet->>Lisk: Transfer USDC/IDRX to escrow<br/>+ markFunded(escrowId, buyerAddress)
    Lisk-->>Backend: Transaction confirmed
    
    loop Status Polling
        Frontend->>Backend: POST /api/payment/check-status
        Backend-->>Frontend: {status: "FUNDED", buyerToken: "xxx"}
    end
    
    Frontend->>Buyer: Show "Payment Secured" + buyerToken
    
    Note over Seller: Ships item to buyer
    Seller->>Backend: POST /api/escrow/abc123/ship<br/>{proof: "tracking/photo"}
    Backend->>Backend: Update DB: status=SHIPPED
    Backend-->>Seller: {status: "SHIPPED"}
    Frontend->>Buyer: Show "Item Shipped" + Proof
    
    alt Buyer confirms delivery
        Buyer->>Frontend: Verify proof & Click "Release Funds"
        Frontend->>Backend: POST /api/escrow/abc123/confirm<br/>{buyerToken: "xxx"}
        Backend->>Backend: Verify buyerToken
        Backend->>ProtocolWallet: Trigger release
        ProtocolWallet->>Lisk: releaseFunds(escrowId)
        Lisk->>Seller: Transfer tokens to seller wallet
        Lisk-->>Backend: Transaction confirmed
        Backend-->>Frontend: {status: "RELEASED"}
        Frontend->>Buyer: Show "Payment Complete"
    else Timeout reached (buyer doesn't confirm)
        Note over Lisk: After releaseTime passes
        ProtocolWallet->>Lisk: releaseFunds(escrowId)<br/>(Auto-release)
        Lisk->>Seller: Transfer tokens to seller wallet
        Note over Seller: Seller receives funds automatically
    end
Loading

Flow 2: Crypto Payment

sequenceDiagram
    participant Buyer
    participant Frontend
    participant Lisk
    participant Seller

    Buyer->>Frontend: Open payment link
    Frontend->>Lisk: getEscrow(escrowId)
    Lisk-->>Frontend: Escrow details

    Buyer->>Frontend: Click "Pay with Crypto"
    Frontend->>Frontend: Connect wallet
    Buyer->>Lisk: approve(escrowContract, amount)<br/>(ERC20 approval)
    Lisk-->>Frontend: Approval confirmed
    
    Buyer->>Lisk: fundEscrow(escrowId)
    Lisk->>Lisk: transferFrom(buyer, escrow, amount)
    Lisk->>Lisk: Set escrow.funded = true
    Lisk-->>Frontend: Escrow FUNDED
    
    Frontend->>Buyer: Show "Payment Secured"
    
    Note over Seller: Ships item to buyer
    Seller->>Frontend: Upload shipment proof
    Frontend->>Backend: POST /api/escrow/abc123/ship<br/>{proof}
    Backend->>Backend: Status = SHIPPED
    Frontend-->>Buyer: Show "Item Shipped" + Proof
    
    alt Buyer confirms delivery
        Buyer->>Lisk: releaseFunds(escrowId)
        Lisk->>Seller: Transfer tokens to seller wallet
        Lisk-->>Frontend: Transaction confirmed
        Frontend->>Buyer: Show "Payment Complete"
    else Timeout reached (buyer doesn't confirm)
        Note over Lisk: After releaseTime passes
        Seller->>Lisk: releaseFunds(escrowId)<br/>(Auto-release)
        Lisk->>Seller: Transfer tokens to seller wallet
        Note over Seller: Seller receives funds automatically
    end
Loading

Auto-Release Mechanism

Purpose: Protects sellers from buyers who receive items but refuse to release funds.

How It Works:

  1. Seller sets timeout when creating escrow (e.g., releaseTime = now + 7 days)
  2. Buyer receives item and should call releaseFunds() to release payment
  3. If buyer doesn't release:
    • After releaseTime passes, anyone can call releaseFunds(escrowId)
    • Smart contract checks: block.timestamp >= releaseTime
    • Funds automatically released to seller wallet

Who Can Trigger Release:

Actor Before Timeout After Timeout
Buyer βœ… Yes (anytime) βœ… Yes
Protocol Wallet ❌ No βœ… Yes
Seller ❌ No βœ… Yes
Anyone ❌ No βœ… Yes (public function)

Example Timeline:

Day 0: Escrow created (releaseTime = Day 7)
Day 1: Buyer pays, escrow funded
Day 2: Seller ships item
Day 3: Buyer receives item
Day 4: Buyer can release funds βœ…
Day 7: Timeout reached
Day 8: Anyone can trigger auto-release βœ…

Smart Contract Logic:

function releaseFunds(uint256 escrowId) external {
    Escrow storage escrow = escrows[escrowId];
    require(escrow.funded && !escrow.released, "Invalid state");
    
    // Buyer can release anytime
    // Others can only release after timeout
    require(
        msg.sender == escrow.buyer || 
        block.timestamp >= escrow.releaseTime,
        "Not authorized or timeout not reached"
    );
    
    escrow.released = true;
    IERC20(escrow.token).transfer(escrow.seller, escrow.amount);
}

πŸ“‘ API Reference

Base URL

  • Development: http://localhost:3002
  • Production: https://vouch.pramadani.site

Authentication

No authentication required for read operations. Write operations (release funds) require a buyerToken obtained after payment.

Escrow Endpoints

Create Escrow

Note: This endpoint is called after the seller has already created the escrow on-chain by calling createEscrow() directly with their wallet. This endpoint only stores metadata and the transaction hash in the database.

POST /api/escrow/create
Content-Type: application/json

{
  "itemName": "Nike Air Jordan 1 Retro",
  "itemDescription": "Size 42, brand new with box",
  "itemImage": "https://...",
  "amount": "250",
  "fiatCurrency": "USD",
  "releaseDuration": 604800,
  "sellerAddress": "0x...",
  "txHash": "0x..."
}

Response:

{
  "id": "abc123",
  "escrowId": 0,
  "status": "CREATED",
  "paymentUrl": "https://vouch.vercel.app/pay/abc123"
}

Get Escrow

GET /api/escrow/:id

Response:

{
  "id": "abc123",
  "escrowId": 0,
  "itemName": "Nike Air Jordan 1 Retro",
  "amountUsdc": "250000000",
  "amountIdr": "250000",
  "fiatCurrency": "USD",
  "status": "FUNDED",
  "sellerAddress": "0x123...",
  "buyerAddress": "0x456...",
  "xenditInvoiceUrl": "https://checkout.xendit.co/...",
  "createdAt": "2026-01-10T12:00:00Z"
}

Get Seller Escrows

GET /api/escrow/seller/:address

Response:

[
  {
    "id": "abc123",
    "itemName": "Nike Air Jordan",
    "status": "FUNDED",
    "amountIdr": "250000",
    "createdAt": "2026-01-10T12:00:00Z"
  },
  ...
]

Create Payment Invoice

POST /api/escrow/:id/create-invoice
Content-Type: application/json

{
  "payerEmail": "buyer@example.com"
}

Response:

{
  "success": true,
  "invoiceUrl": "https://checkout.xendit.co/web/...",
  "invoiceId": "inv_123..."
}

Release Funds

POST /api/escrow/:id/release
Content-Type: application/json

{
  "buyerToken": "a1b2c3d4e5f6..."
}

Response:

{
  "success": true,
  "txHash": "0x...",
  "status": "RELEASED"
}

Get On-Chain Status

GET /api/escrow/:id/on-chain-status

Response:

{
  "escrowId": 0,
  "seller": "0x123...",
  "buyer": "0x456...",
  "amount": "250000000",
  "funded": true,
  "released": false,
  "status": "FUNDED"
}

Payment Endpoints

Xendit Webhook

POST /api/payment/xendit/callback
X-Callback-Token: <signature>
Content-Type: application/json

{
  "id": "inv_123...",
  "external_id": "abc123",
  "status": "PAID",
  "amount": 250000,
  "paid_at": "2026-01-10T12:05:00Z"
}

Check Payment Status

POST /api/payment/check-status
Content-Type: application/json

{
  "escrowId": "abc123"
}

Response:

{
  "success": true,
  "status": "FUNDED",
  "buyerToken": "a1b2c3d4e5f6..."
}

Simulate Payment (Dev Only)

POST /api/payment/simulate/:id

Response:

{
  "success": true,
  "message": "Payment simulated successfully",
  "buyerToken": "a1b2c3d4e5f6..."
}

Faucet Endpoints

Request Tokens

POST /api/faucet
Content-Type: application/json

{
  "address": "0x...",
  "type": "usdc"
}

Types: eth, usdc, idrx

Response:

{
  "success": true,
  "type": "usdc",
  "amount": "1000",
  "txHash": "0x..."
}

🧩 Frontend Components

Page Components

Component Path Description
Landing / Hero, benefits, how it works, FAQ
CreateEscrow /create Seller creates payment link (wallet required)
PaymentPage /pay/[id] Buyer payment interface
Dashboard /dashboard Seller views all escrows
Faucet /faucet Testnet token faucet
PitchDeck /deck 15-slide presentation
DemoSeller /demo/seller Seller POV chat simulation
DemoBuyer /demo/buyer Buyer POV chat simulation
Thumbnail /demo/thumbnail YouTube thumbnail generator

UI Components

Component Purpose
Hero Landing page hero with animated chat simulation
Header Site navigation with wallet connect
Footer Links, social media, copyright
Benefits 3-column benefits grid
HowItWorks 4-step process visualization
ProblemSolution Problem/solution comparison cards
SocialProof Trust badges and logos
Testimonials User testimonial slider
FAQ Collapsible Q&A accordion
CTASection Call-to-action with button
Button Reusable button with variants
Reveal Scroll animation wrapper (Intersection Observer)
DemoChatPhone Instagram-style phone mockup with chat animation

βš™οΈ Backend Services

Database Service (lib/db.ts)

Prisma-based database operations:

// Create escrow
async function createEscrow(data: CreateEscrowInput): Promise<Escrow>

// Get escrow by ID
async function getEscrowById(id: string): Promise<Escrow | null>

// Get seller's escrows
async function getEscrowsBySeller(address: string): Promise<Escrow[]>

// Mark escrow as funded
async function markEscrowFunded(id: string, invoiceId: string, buyerToken: string): Promise<void>

// Update escrow status
async function updateEscrowStatus(id: string, status: EscrowStatus): Promise<void>

Wallet Service (lib/wallet.ts)

Protocol hot wallet management:

class WalletManager {
  // Get escrow details from blockchain
  async getEscrowDetails(escrowId: number): Promise<EscrowDetails>
  
  // Mark escrow as funded on-chain
  async markFunded(escrowId: number, token: string, amount: string): Promise<string>
  
  // Release funds to seller
  async releaseFunds(escrowId: number): Promise<string>
  
  // Check on-chain status
  async getOnChainStatus(escrowId: number): Promise<OnChainStatus>
}

Xendit Service (lib/xendit.ts)

Payment gateway integration:

class XenditClient {
  // Create payment invoice
  async createInvoice(params: CreateInvoiceParams): Promise<XenditInvoice>
  
  // Get invoice status
  async getInvoice(invoiceId: string): Promise<XenditInvoice>
  
  // Verify webhook signature
  verifyCallback(payload: any, signature: string): boolean
  
  // Simulate payment (mock mode)
  simulatePaymentSuccess(invoiceId: string): XenditInvoice | null
}

πŸ”§ Environment Variables

Frontend (.env.local)

Variable Description Example
NEXT_PUBLIC_API_URL Backend API URL https://vouch.pramadani.site
NEXT_PUBLIC_LISK_CHAIN_ID Lisk chain ID 4202

Backend (server/.env)

Variable Required Description Example
DATABASE_URL βœ… PostgreSQL connection string postgresql://user:pass@localhost:5432/vouch
PORT ❌ Server port 3002
NODE_ENV ❌ Environment production
PRIVATE_KEY βœ… Protocol wallet private key 0x...
LISK_RPC_URL ❌ Lisk RPC endpoint https://rpc.sepolia-api.lisk.com
MOCK_USDC_ADDRESS βœ… MockUSDC contract address 0xB7c...
MOCK_IDRX_ADDRESS βœ… MockIDRX contract address 0xDfe...
VOUCH_ESCROW_ADDRESS βœ… VouchEscrow contract address 0xb01...
XENDIT_SECRET_KEY ❌ Xendit API key (for real payments) xnd_...
XENDIT_CALLBACK_TOKEN ❌ Xendit webhook verification token ...
FRONTEND_URL βœ… CORS allowed origin https://vouch.vercel.app

πŸ“ Project Structure

vouch/
β”œβ”€β”€ πŸ“± Frontend
β”‚   β”œβ”€β”€ app/                          # Next.js App Router
β”‚   β”‚   β”œβ”€β”€ page.tsx                  # Landing page
β”‚   β”‚   β”œβ”€β”€ layout.tsx                # Root layout + providers
β”‚   β”‚   β”œβ”€β”€ globals.css               # Global styles
β”‚   β”‚   β”œβ”€β”€ create/
β”‚   β”‚   β”‚   └── page.tsx              # Seller: Create escrow
β”‚   β”‚   β”œβ”€β”€ pay/[id]/
β”‚   β”‚   β”‚   └── page.tsx              # Buyer: Payment page
β”‚   β”‚   β”œβ”€β”€ dashboard/
β”‚   β”‚   β”‚   └── page.tsx              # Seller: Dashboard
β”‚   β”‚   β”œβ”€β”€ faucet/
β”‚   β”‚   β”‚   └── page.tsx              # Testnet faucet
β”‚   β”‚   β”œβ”€β”€ deck/
β”‚   β”‚   β”‚   └── page.tsx              # Pitch deck (15 slides)
β”‚   β”‚   └── demo/
β”‚   β”‚       β”œβ”€β”€ seller/page.tsx       # Seller chat demo
β”‚   β”‚       β”œβ”€β”€ buyer/page.tsx        # Buyer chat demo
β”‚   β”‚       └── thumbnail/page.tsx    # Thumbnail generator
β”‚   β”‚
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ Hero.tsx                  # Landing hero section
β”‚   β”‚   β”œβ”€β”€ Header.tsx                # Navigation bar
β”‚   β”‚   β”œβ”€β”€ Footer.tsx                # Site footer
β”‚   β”‚   β”œβ”€β”€ Benefits.tsx              # Benefits grid
β”‚   β”‚   β”œβ”€β”€ HowItWorks.tsx            # Process steps
β”‚   β”‚   β”œβ”€β”€ ProblemSolution.tsx       # Problem/solution cards
β”‚   β”‚   β”œβ”€β”€ SocialProof.tsx           # Trust badges
β”‚   β”‚   β”œβ”€β”€ Testimonials.tsx          # User testimonials
β”‚   β”‚   β”œβ”€β”€ FAQ.tsx                   # FAQ accordion
β”‚   β”‚   β”œβ”€β”€ CTASection.tsx            # Call-to-action
β”‚   β”‚   β”œβ”€β”€ Button.tsx                # Reusable button
β”‚   β”‚   β”œβ”€β”€ Reveal.tsx                # Scroll animation
β”‚   β”‚   β”œβ”€β”€ Providers.tsx             # wagmi providers
β”‚   β”‚   β”œβ”€β”€ demo/
β”‚   β”‚   β”‚   └── DemoChatPhone.tsx     # Phone mockup
β”‚   β”‚   └── ui/
β”‚   β”‚       └── FadeIn.tsx            # Fade animation
β”‚   β”‚
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ wagmi.ts                  # Wallet config
β”‚   β”‚   β”œβ”€β”€ contracts.ts              # Contract ABIs
β”‚   β”‚   β”œβ”€β”€ api.ts                    # API client
β”‚   β”‚   └── utils.ts                  # Utilities
β”‚   β”‚
β”‚   β”œβ”€β”€ public/
β”‚   β”‚   β”œβ”€β”€ logo.png                  # Vouch logo
β”‚   β”‚   β”œβ”€β”€ lisk.png                  # Lisk logo
β”‚   β”‚   └── grid.svg                  # Background
β”‚   β”‚
β”‚   β”œβ”€β”€ .env.local.example            # Frontend env template
β”‚   β”œβ”€β”€ next.config.ts                # Next.js config
β”‚   β”œβ”€β”€ tailwind.config.js            # Tailwind config
β”‚   β”œβ”€β”€ tsconfig.json                 # TypeScript config
β”‚   └── package.json                  # Dependencies
β”‚
β”œβ”€β”€ ⛓️ Smart Contracts
β”‚   └── contracts/
β”‚       β”œβ”€β”€ src/
β”‚       β”‚   β”œβ”€β”€ VouchEscrow.sol       # Main escrow logic
β”‚       β”‚   β”œβ”€β”€ MockUSDC.sol          # Test USDC
β”‚       β”‚   └── MockIDRX.sol          # Test IDRX
β”‚       β”œβ”€β”€ script/
β”‚       β”‚   β”œβ”€β”€ Deploy.s.sol          # Deployment script
β”‚       β”‚   β”œβ”€β”€ DeployAll.s.sol       # Deploy all
β”‚       β”‚   └── DeployIDRX.s.sol      # Deploy IDRX
β”‚       β”œβ”€β”€ foundry.toml              # Foundry config
β”‚       └── .gitignore                # Ignore build artifacts
β”‚
└── πŸ–₯️ Backend
    └── server/
        β”œβ”€β”€ src/
        β”‚   β”œβ”€β”€ index.ts              # Express app
        β”‚   β”œβ”€β”€ lib/
        β”‚   β”‚   β”œβ”€β”€ db.ts             # Prisma operations
        β”‚   β”‚   β”œβ”€β”€ wallet.ts         # Hot wallet manager
        β”‚   β”‚   └── xendit.ts         # Xendit client
        β”‚   └── routes/
        β”‚       β”œβ”€β”€ escrow.ts         # Escrow CRUD
        β”‚       β”œβ”€β”€ payment.ts        # Payment/webhooks
        β”‚       └── faucet.ts         # Token faucet
        β”‚
        β”œβ”€β”€ prisma/
        β”‚   β”œβ”€β”€ schema.prisma         # Database schema
        β”‚   └── migrations/           # Migration history
        β”‚
        β”œβ”€β”€ ecosystem.config.js       # PM2 config
        β”œβ”€β”€ nginx.conf                # Nginx template
        β”œβ”€β”€ DEPLOYMENT.md             # VPS deployment guide
        β”œβ”€β”€ .env.example              # Backend env template
        └── package.json              # Dependencies

πŸš€ Quick Start

Prerequisites

  • Node.js 18+ (Download)
  • PostgreSQL 14+ (for backend)
  • MetaMask browser extension
  • Git

1. Clone Repository

git clone https://github.com/pramadanif/vouch.git
cd vouch

2. Setup Frontend

# Install dependencies
npm install

# Create environment file
cp .env.local.example .env.local

# Edit with your backend URL
nano .env.local

.env.local:

NEXT_PUBLIC_API_URL=http://localhost:3002
NEXT_PUBLIC_LISK_CHAIN_ID=4202
# Start development server
npm run dev

3. Setup Backend

cd server

# Install dependencies
npm install

# Create environment file
cp .env.example .env

# Edit with your credentials
nano .env

server/.env:

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/vouch"
PORT=3002
PRIVATE_KEY=0x_your_wallet_private_key
FRONTEND_URL=http://localhost:3000
# Generate Prisma client
npx prisma generate

# Push database schema
npx prisma db push

# Start development server
npm run dev

4. Access Application


πŸ’» Development

Running Tests

# Frontend tests (if implemented)
npm run test

# Backend tests (if implemented)
cd server && npm run test

Building for Production

# Frontend
npm run build
npm run start

# Backend
cd server
npm run build
npm run start

Code Quality

# TypeScript type checking
npm run type-check

# Linting
npm run lint

# Format code
npm run format

☁️ Deployment

Frontend Deployment (Vercel)

  1. Push to GitHub:

    git add .
    git commit -m "feat: ready for deployment"
    git push origin main
  2. Import to Vercel:

    • Visit vercel.com/new
    • Import your repository
    • Framework: Next.js
    • Root directory: ./
  3. Environment Variables:

    NEXT_PUBLIC_API_URL=https://vouch.pramadani.site
    NEXT_PUBLIC_LISK_CHAIN_ID=4202
    
  4. Deploy: Click "Deploy" β†’ Your site is live!

Backend Deployment (VPS)

See server/DEPLOYMENT.md for the complete guide.

Quick Deploy:

# On VPS
cd ~/apps/vouch/server

# Install & build
npm install
npm run build

# Setup database
npx prisma generate
npx prisma db push

# Start with PM2
pm2 start ecosystem.config.js
pm2 save

# Setup Nginx
sudo cp nginx.conf /etc/nginx/sites-available/vouch-api
sudo ln -s /etc/nginx/sites-available/vouch-api /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

# SSL Certificate
sudo certbot --nginx -d vouch.pramadani.site

πŸ§ͺ Testing

Manual Testing Checklist

  • Seller can connect wallet
  • Seller can create escrow
  • Payment link is shareable
  • Buyer can pay via Xendit (mock mode)
  • Funds show as "secured" after payment
  • Buyer can release funds
  • Seller receives funds in wallet
  • Dashboard shows all escrows
  • Faucet distributes tokens

Smart Contract Testing

cd contracts

# Run tests
forge test

# Run with gas report
forge test --gas-report

# Run with coverage
forge coverage

πŸ” Troubleshooting

Common Issues

"Cannot connect to MetaMask"

Solution: Ensure MetaMask is installed and Lisk Sepolia network is added:

Network Name: Lisk Sepolia
RPC URL: https://rpc.sepolia-api.lisk.com
Chain ID: 4202
Currency Symbol: ETH

"Transaction failed: insufficient funds"

Solution: Get testnet ETH from Lisk Sepolia Faucet or use /faucet page.

"Xendit callback not received"

Causes:

  • Webhook URL not configured in Xendit dashboard
  • Callback token mismatch
  • Firewall blocking Xendit IPs

Solution:

  1. Add webhook URL in Xendit: https://vouch.pramadani.site/api/payment/xendit/callback
  2. Verify XENDIT_CALLBACK_TOKEN matches dashboard
  3. Check server logs: pm2 logs vouch-server

"Database connection failed"

Solution:

# Check PostgreSQL status
sudo systemctl status postgresql

# Test connection
psql -h localhost -U postgres -d vouch

# Fix pg_hba.conf if needed
sudo nano /etc/postgresql/*/main/pg_hba.conf
# Change "peer" to "md5"
sudo systemctl restart postgresql

"Contract call reverted"

Common Causes:

  • Insufficient token approval
  • Escrow already funded/released
  • Incorrect escrow ID
  • Timeout not reached (for auto-release)

Debug: Check transaction on Lisk Sepolia Explorer


🀝 Contributing

We welcome contributions! 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 'feat: add amazing feature')
  4. Push to branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Commit Convention

Use Conventional Commits:

feat: add new payment method
fix: resolve wallet connection issue
docs: update README with deployment guide
chore: update dependencies

πŸ“¦ Tech Stack

Frontend Stack

Technology Version Purpose
Next.js 15.x React framework with App Router
React 19.x UI library
TypeScript 5.x Type safety
TailwindCSS 3.x Utility-first CSS
Framer Motion 11.x Animations
wagmi 2.x Wallet connection
viem 2.x Ethereum interactions
React Query 5.x Data fetching

Backend Stack

Technology Version Purpose
Express.js 4.x Web framework
TypeScript 5.x Type safety
Prisma 5.x ORM
PostgreSQL 14+ Database
Ethers.js 6.x Blockchain interactions
Xendit SDK - Payment gateway

Smart Contract Stack

Technology Version Purpose
Solidity 0.8.20 Contract language
Foundry Latest Development framework
OpenZeppelin 5.x Security libraries
Lisk Sepolia EVM-compatible L2

DevOps Stack

Technology Purpose
Vercel Frontend hosting
PM2 Process management
Nginx Reverse proxy
Certbot SSL certificates
PostgreSQL Production database

πŸ›‘οΈ Security

Smart Contract Security

  • Audited Patterns: Uses OpenZeppelin's battle-tested libraries
  • Reentrancy Protection: ReentrancyGuard on all state-changing functions
  • Access Control: onlyProtocol modifier for sensitive operations
  • Seller Verification: msg.sender = seller proves ownership
  • SafeERC20: Prevents token transfer failures

Backend Security

  • Buyer Token: 64-character random token for release authorization
  • Webhook Verification: Xendit signature validation
  • CORS: Strict origin whitelist
  • Environment Variables: Secrets never committed to Git
  • SQL Injection: Prisma ORM prevents SQL injection
  • Input Validation: All user inputs sanitized

Trust Model

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              ON-CHAIN (Immutable & Trustless)            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  βœ“ Escrow creation (seller = msg.sender)                β”‚
β”‚  βœ“ Fund locking (smart contract holds tokens)           β”‚
β”‚  βœ“ Release logic (buyer confirms OR timeout)            β”‚
β”‚  βœ“ Refund logic (dispute resolution)                    β”‚
β”‚  βœ“ Timeout protection (auto-release after deadline)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β–²
                          β”‚ Verifiable on blockchain
                          β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            OFF-CHAIN (Replaceable & Convenient)          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  β€’ Fiat payment confirmation (Xendit webhook)           β”‚
β”‚  β€’ Metadata storage (PostgreSQL)                        β”‚
β”‚  β€’ UI/UX (Next.js frontend)                             β”‚
β”‚  β€’ Email notifications (future)                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Critical Security Property: If the backend disappears tomorrow, funds remain safe in the smart contract. Seller and buyer can still interact directly with the contract on Lisk.


πŸ—ΊοΈ Roadmap

Phase 1: MVP βœ… (Current)

  • Smart contract deployment on Lisk Sepolia
  • Fiat payment via Xendit (QRIS, Bank Transfer)
  • Crypto payment (USDC/IDRX)
  • Seller dashboard
  • Buyer payment page
  • Landing page with docs

Phase 2: Mainnet Launch πŸš€

  • Audit smart contracts
  • Deploy to Lisk mainnet
  • Real Xendit integration (production keys)
  • Multi-currency support (PHP, THB, VND)
  • Email notifications
  • WhatsApp integration

Phase 3: Scale πŸ“ˆ

  • Seller analytics dashboard
  • Dispute resolution system
  • Escrow templates
  • API for third-party integrations
  • Mobile app (React Native)
  • Multi-chain support (Polygon, Base)

Phase 4: Ecosystem 🌐

  • Vouch SDK for e-commerce platforms
  • Shopify plugin
  • Browser extension
  • Reputation system
  • Vouch DAO governance

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.


Built with ❀️ for the Lisk Hackathon
Vouch doesn't replace marketplaces.
It replaces the missing trust layer in social commerce.

Website β€’ GitHub β€’ API Status

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors