Decentralized Escrow for Social Commerce
Marketplace-level trust β without the marketplace.
- Overview
- Features
- Architecture
- Smart Contracts
- Payment Flow
- API Reference
- Frontend Components
- Backend Services
- Environment Variables
- Project Structure
- Quick Start
- Development
- Deployment
- Testing
- Troubleshooting
- Contributing
- Tech Stack
- Security
- Roadmap
- License
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.
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.
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
| 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 |
- 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)
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
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Contract | Address | Explorer |
|---|---|---|
| VouchEscrow | 0xb015d8Eb15B5E82E10aCF1606c60cFD64C4c7cB2 |
View on Explorer |
| MockUSDC | 0xB7c78ceCB25a1c40b3fa3382bAf3F34c9b5bdD66 |
View on Explorer |
| MockIDRX | 0xDfef62cf7516508B865440E5819e5435e69adceb |
View on Explorer |
| 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 |
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 --> [*]
| 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 |
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
}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);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
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
Purpose: Protects sellers from buyers who receive items but refuse to release funds.
How It Works:
- Seller sets timeout when creating escrow (e.g.,
releaseTime = now + 7 days) - Buyer receives item and should call
releaseFunds()to release payment - If buyer doesn't release:
- After
releaseTimepasses, anyone can callreleaseFunds(escrowId) - Smart contract checks:
block.timestamp >= releaseTime - Funds automatically released to seller wallet
- After
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);
}- Development:
http://localhost:3002 - Production:
https://vouch.pramadani.site
No authentication required for read operations. Write operations (release funds) require a buyerToken obtained after payment.
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 /api/escrow/:idResponse:
{
"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 /api/escrow/seller/:addressResponse:
[
{
"id": "abc123",
"itemName": "Nike Air Jordan",
"status": "FUNDED",
"amountIdr": "250000",
"createdAt": "2026-01-10T12:00:00Z"
},
...
]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..."
}POST /api/escrow/:id/release
Content-Type: application/json
{
"buyerToken": "a1b2c3d4e5f6..."
}Response:
{
"success": true,
"txHash": "0x...",
"status": "RELEASED"
}GET /api/escrow/:id/on-chain-statusResponse:
{
"escrowId": 0,
"seller": "0x123...",
"buyer": "0x456...",
"amount": "250000000",
"funded": true,
"released": false,
"status": "FUNDED"
}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"
}POST /api/payment/check-status
Content-Type: application/json
{
"escrowId": "abc123"
}Response:
{
"success": true,
"status": "FUNDED",
"buyerToken": "a1b2c3d4e5f6..."
}POST /api/payment/simulate/:idResponse:
{
"success": true,
"message": "Payment simulated successfully",
"buyerToken": "a1b2c3d4e5f6..."
}POST /api/faucet
Content-Type: application/json
{
"address": "0x...",
"type": "usdc"
}Types: eth, usdc, idrx
Response:
{
"success": true,
"type": "usdc",
"amount": "1000",
"txHash": "0x..."
}| 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 |
| 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 |
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>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>
}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
}| Variable | Description | Example |
|---|---|---|
NEXT_PUBLIC_API_URL |
Backend API URL | https://vouch.pramadani.site |
NEXT_PUBLIC_LISK_CHAIN_ID |
Lisk chain ID | 4202 |
| 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 |
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
- Node.js 18+ (Download)
- PostgreSQL 14+ (for backend)
- MetaMask browser extension
- Git
git clone https://github.com/pramadanif/vouch.git
cd vouch# 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 devcd server
# Install dependencies
npm install
# Create environment file
cp .env.example .env
# Edit with your credentials
nano .envserver/.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- Frontend: http://localhost:3000
- Backend: http://localhost:3002/health
- Prisma Studio: http://localhost:5555 (run
npx prisma studio)
# Frontend tests (if implemented)
npm run test
# Backend tests (if implemented)
cd server && npm run test# Frontend
npm run build
npm run start
# Backend
cd server
npm run build
npm run start# TypeScript type checking
npm run type-check
# Linting
npm run lint
# Format code
npm run format-
Push to GitHub:
git add . git commit -m "feat: ready for deployment" git push origin main
-
Import to Vercel:
- Visit vercel.com/new
- Import your repository
- Framework: Next.js
- Root directory:
./
-
Environment Variables:
NEXT_PUBLIC_API_URL=https://vouch.pramadani.site NEXT_PUBLIC_LISK_CHAIN_ID=4202 -
Deploy: Click "Deploy" β Your site is live!
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- 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
cd contracts
# Run tests
forge test
# Run with gas report
forge test --gas-report
# Run with coverage
forge coverageSolution: 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: ETHSolution: Get testnet ETH from Lisk Sepolia Faucet or use /faucet page.
Causes:
- Webhook URL not configured in Xendit dashboard
- Callback token mismatch
- Firewall blocking Xendit IPs
Solution:
- Add webhook URL in Xendit:
https://vouch.pramadani.site/api/payment/xendit/callback - Verify
XENDIT_CALLBACK_TOKENmatches dashboard - Check server logs:
pm2 logs vouch-server
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 postgresqlCommon Causes:
- Insufficient token approval
- Escrow already funded/released
- Incorrect escrow ID
- Timeout not reached (for auto-release)
Debug: Check transaction on Lisk Sepolia Explorer
We welcome contributions! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
Use Conventional Commits:
feat: add new payment method
fix: resolve wallet connection issue
docs: update README with deployment guide
chore: update dependencies
| 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 |
| 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 |
| Technology | Version | Purpose |
|---|---|---|
| Solidity | 0.8.20 | Contract language |
| Foundry | Latest | Development framework |
| OpenZeppelin | 5.x | Security libraries |
| Lisk | Sepolia | EVM-compatible L2 |
| Technology | Purpose |
|---|---|
| Vercel | Frontend hosting |
| PM2 | Process management |
| Nginx | Reverse proxy |
| Certbot | SSL certificates |
| PostgreSQL | Production database |
- Audited Patterns: Uses OpenZeppelin's battle-tested libraries
- Reentrancy Protection:
ReentrancyGuardon all state-changing functions - Access Control:
onlyProtocolmodifier for sensitive operations - Seller Verification:
msg.sender = sellerproves ownership - SafeERC20: Prevents token transfer failures
- 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
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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.
- 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
- Audit smart contracts
- Deploy to Lisk mainnet
- Real Xendit integration (production keys)
- Multi-currency support (PHP, THB, VND)
- Email notifications
- WhatsApp integration
- Seller analytics dashboard
- Dispute resolution system
- Escrow templates
- API for third-party integrations
- Mobile app (React Native)
- Multi-chain support (Polygon, Base)
- Vouch SDK for e-commerce platforms
- Shopify plugin
- Browser extension
- Reputation system
- Vouch DAO governance
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
