Orby is a mobile-first digital wallet application for festival attendees built on Ethereum Sepolia. It implements a Vault-based Escrow Architecture where USDT deposits are locked in a smart contract vault while festival-specific ERC20 tokens are minted 1:1.
Orby leverages Tether Wallet Development Kit (WDK) for secure, non-custodial wallet management on EVM chains. WDK provides:
- BIP-39 Mnemonic Generation: Secure seed phrase creation with
WDK.getRandomSeedPhrase() - HD Wallet Derivation: Multiple accounts from a single seed using
wdk.getAccount('ethereum', index) - Transaction Signing: Native transaction signing and broadcasting via
account.sendTransaction() - Multi-Chain Support: Built-in support for Ethereum and EVM-compatible networks
// Client-side: User wallet management
const seedPhrase = WDK.getRandomSeedPhrase();
const wdk = new WDK(seedPhrase).registerWallet('ethereum', WalletManagerEvm, { provider: rpcUrl });
const account = await wdk.getAccount('ethereum', 0);
const address = account.getAddress();
// Server-side: Transaction signing
const { hash } = await account.sendTransaction({ to, value, data });WDK enables Orby to provide a seamless Web2-like experience while maintaining full Web3 security guarantees.
┌─────────────────────────────────────────────────────────────────┐
│ Frontend (Next.js 15) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Login │ │ Wallet │ │ Admin │ │ QR │ │
│ │ Page │ │ Page │ │ Page │ │ Page │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────────┘ │
│ │ │ │ │
│ └─────────────┼─────────────┘ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ 🔐 WDK Client │ │
│ │ • Seed generation │ │
│ │ • Address derivation │ │
│ │ • Balance queries │ │
│ └───────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ API Layer (Next.js) │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ /topup │ │ /cashout │ │ /balances │ │
│ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │ │
│ └───────────────┼───────────────┘ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ 🔐 WDK Server │ │
│ │ • Transaction signing │ │
│ │ • Contract interactions │ │
│ │ • Multi-account support │ │
│ └───────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Ethereum Sepolia (Chain ID: 11155111) │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ FestivalFactory │──│ FestivalToken │──│ FestivalVault │ │
│ │ (Deployer) │ │ (ERC20) │ │ (Escrow) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │ │
│ ┌───────┴───────┐ │
│ │ USDT (6 dec) │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Traditional festival token systems require organizers to:
- Create a separate Liquidity Pool (expensive, complex)
- Provide initial liquidity (capital intensive)
- Manage price volatility and impermanent loss
- Deal with AMM complexity and slippage
Orby eliminates all of this with a revolutionary Vault-based Escrow design:
┌─────────────────────────────────────────────────────────────────────────┐
│ VAULT-BASED ESCROW ARCHITECTURE │
│ │
│ ┌─────────────┐ DEPOSIT ┌─────────────────────┐ │
│ │ │ ───────────────────────▶ │ │ │
│ │ USER │ 100 USDT │ FESTIVAL VAULT │ │
│ │ │ ◀─────────────────────── │ │ │
│ └─────────────┘ 100 Festival Tokens │ ┌───────────────┐ │ │
│ │ │ escrowedUSDT │ │ │
│ │ │ mapping │ │ │
│ ┌─────────────┐ WITHDRAW │ └───────────────┘ │ │
│ │ │ ───────────────────────▶ │ │ │ │
│ │ USER │ 100 Festival Tokens │ ▼ │ │
│ │ │ ◀─────────────────────── │ ┌───────────────┐ │ │
│ └─────────────┘ 100 USDT │ │ USDT Balance │ │ │
│ │ │ (Locked) │ │ │
│ │ └───────────────┘ │ │
│ └─────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ CORE INVARIANT │ │
│ │ │ │
│ │ Vault.usdtBalance × 10¹² == FestivalToken.totalSupply │ │
│ │ │ │
│ │ • Every token is ALWAYS backed 1:1 by locked USDT │ │
│ │ • No liquidity pool needed │ │
│ │ • No price volatility │ │
│ │ • No impermanent loss │ │
│ │ • Instant, guaranteed redemption │ │
│ └───────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
| Traditional LP Approach | Orby Vault Approach |
|---|---|
| ❌ Requires initial liquidity | ✅ Zero initial capital needed |
| ❌ Price fluctuates with trades | ✅ Fixed 1:1 exchange rate |
| ❌ Impermanent loss risk | ✅ No IL - tokens are burned, not swapped |
| ❌ Complex AMM mechanics | ✅ Simple deposit/withdraw |
| ❌ Slippage on large trades | ✅ No slippage ever |
| ❌ Arbitrage opportunities | ✅ No arbitrage possible |
| ❌ Requires DEX integration | ✅ Self-contained system |
The magic: When users deposit USDT, it's locked in the Vault and tokens are minted. When they withdraw, tokens are burned and USDT is released. The Vault IS the liquidity - no external pool required!
// Deposit: Lock USDT, Mint Tokens
function deposit(uint256 usdtAmount) external {
usdt.transferFrom(msg.sender, address(this), usdtAmount);
token.mint(msg.sender, usdtAmount * 10**12); // 6 → 18 decimals
escrowedUSDT[msg.sender] += usdtAmount;
}
// Withdraw: Burn Tokens, Release USDT
function withdraw(uint256 tokenAmount) external {
uint256 usdtAmount = tokenAmount / 10**12; // 18 → 6 decimals
token.burn(msg.sender, tokenAmount);
usdt.transfer(msg.sender, usdtAmount);
escrowedUSDT[msg.sender] -= usdtAmount;
}This design means any festival organizer can launch their own token economy in minutes - just deploy through the Factory and they're ready to go. No DeFi expertise required, no liquidity bootstrapping, no ongoing pool management.
| Contract | Address | Etherscan |
|---|---|---|
| TestUSDT | 0xD630eb858fA2b08bc816aF83a480eeF4Cc23f843 |
View ↗ |
| FestivalFactory | 0xA0129c4cFE46e53f532ee7F7B9fF4FdeaB832a62 |
View ↗ |
| FestivalToken | 0x506bb9654757044a224E642598C5c30B43b30568 |
View ↗ |
| FestivalVault | 0x9965867dC3EEfb856634d4B7aB160f8654402A5b |
View ↗ |
Note: TestUSDT is a custom ERC20 token (6 decimals) deployed for testing. It includes a
faucet()function for minting test tokens. All contracts are verified on Etherscan.
To verify contracts on Etherscan:
cd packages/contracts
# Set your Etherscan API key in .env
# ETHERSCAN_API_KEY=your_api_key
# Verify a contract
npx hardhat verify --network sepolia <CONTRACT_ADDRESS>| Layer | Technology |
|---|---|
| Frontend | Next.js 15, React 19, Tailwind CSS |
| Wallet | Tether WDK (EVM) |
| Smart Contracts | Solidity 0.8.20, OpenZeppelin, Hardhat |
| Blockchain | Ethereum Sepolia (testnet) |
| Package Manager | pnpm 9.0.0 |
orby/
├── apps/
│ └── web/ # Next.js frontend
│ ├── app/
│ │ ├── api/ # API routes
│ │ │ ├── admin/ # Admin endpoints
│ │ │ └── festival/ # User endpoints
│ │ ├── wallet/ # Wallet view
│ │ ├── admin/ # Admin dashboard
│ │ └── login/ # Authentication
│ └── lib/
│ └── wdk/ # WDK client & server modules
│
└── packages/
├── contracts/ # Solidity smart contracts
│ ├── contracts/ # Source .sol files
│ ├── scripts/ # Deployment scripts
│ └── test/ # Contract tests
│
└── types/ # Shared TypeScript types
- Node.js >= 18.0.0
- pnpm 9.0.0+
- A Sepolia testnet wallet with ETH for gas
pnpm installCreate .env.local in apps/web/:
# Sepolia Network
NEXT_PUBLIC_CHAIN_ID=11155111
NEXT_PUBLIC_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
# Contract Addresses (Sepolia)
NEXT_PUBLIC_TESTUSDT_ADDRESS=0xD630eb858fA2b08bc816aF83a480eeF4Cc23f843
NEXT_PUBLIC_FACTORY_ADDRESS=0xA0129c4cFE46e53f532ee7F7B9fF4FdeaB832a62
NEXT_PUBLIC_FESTIVAL_TOKEN_ADDRESS=0x506bb9654757044a224E642598C5c30B43b30568
NEXT_PUBLIC_FESTIVAL_VAULT_ADDRESS=0x9965867dC3EEfb856634d4B7aB160f8654402A5b
# Server-side only (for signing transactions)
PRIVATE_KEY=your_private_key_hereCreate .env in packages/contracts/:
# Private key for contract deployment
PRIVATE_KEY=your_private_key_here
# Sepolia RPC URL
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
# TestUSDT (custom test token)
TESTUSDT_ADDRESS=0xD630eb858fA2b08bc816aF83a480eeF4Cc23f843
# Factory
FESTIVAL_FACTORY_ADDRESS=0xA0129c4cFE46e53f532ee7F7B9fF4FdeaB832a62
# Festival Token & Vault
FESTIVAL_TOKEN_ADDRESS=0x506bb9654757044a224E642598C5c30B43b30568
FESTIVAL_VAULT_ADDRESS=0x9965867dC3EEfb856634d4B7aB160f8654402A5bpnpm devOpen http://localhost:3000 in your browser.
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_CHAIN_ID |
Yes | Sepolia chain ID (11155111) |
NEXT_PUBLIC_RPC_URL |
Yes | Sepolia RPC endpoint |
NEXT_PUBLIC_TESTUSDT_ADDRESS |
Yes | TestUSDT contract address |
NEXT_PUBLIC_FACTORY_ADDRESS |
Yes | FestivalFactory contract address |
NEXT_PUBLIC_FESTIVAL_TOKEN_ADDRESS |
Yes | FestivalToken contract address |
NEXT_PUBLIC_FESTIVAL_VAULT_ADDRESS |
Yes | FestivalVault contract address |
PRIVATE_KEY |
Yes | Private key for server-side transactions |
| Variable | Required | Description |
|---|---|---|
PRIVATE_KEY |
Yes | Deployer wallet private key |
SEPOLIA_RPC_URL |
Yes | Sepolia RPC endpoint |
TESTUSDT_ADDRESS |
Yes | TestUSDT contract address |
FESTIVAL_FACTORY_ADDRESS |
Yes | Deployed factory address |
FESTIVAL_TOKEN_ADDRESS |
Yes | FestivalToken contract address |
FESTIVAL_VAULT_ADDRESS |
Yes | FestivalVault contract address |
-
Compile contracts:
cd packages/contracts pnpm compile -
Deploy FestivalFactory:
pnpm deploy:factory
This deploys the factory contract and logs the address. Update
FESTIVAL_FACTORY_ADDRESSin.env. -
Create a Festival:
pnpm create:festival
This creates a new FestivalToken + FestivalVault pair and saves the config to
festival-config.json. -
Test the Flow:
pnpm test:flow
Executes a complete deposit/withdrawal cycle to verify the system.
cd packages/contracts
npx hardhat run scripts/verifySystem.ts --network sepoliaThis script checks:
- Factory deployment status
- Active festival configuration
- Vault USDT balance
- Token total supply
- Escrow invariant (1:1 backing)
Join a festival and receive a wallet address.
Request:
{
"nickname": "string"
}Response:
{
"userId": "uuid",
"sessionToken": "uuid",
"address": "0x..."
}Deposit USDT and receive festival tokens.
Request:
{
"userId": "string",
"sessionToken": "string",
"amountUsdt": 100
}Response:
{
"success": true,
"approvalHash": "0x...",
"depositHash": "0x..."
}Withdraw all festival tokens and receive USDT.
Request:
{
"userId": "string"
}Response:
{
"success": true,
"withdrawHash": "0x...",
"usdtReturned": "100000000"
}Query on-chain balances.
Response:
{
"usdt": "1000000",
"festivalTokens": "1000000000000000000",
"escrowedUSDT": "1000000",
"userAddress": "0x...",
"treasuryAddress": "0x..."
}Deploy a new festival token and vault.
Request:
{
"festivalName": "Summer Fest 2025",
"festivalSymbol": "SF25"
}Response:
{
"name": "Summer Fest 2025",
"symbol": "SF25",
"tokenAddress": "0x...",
"vaultAddress": "0x...",
"ownerAddress": "0x...",
"transactionHash": "0x..."
}function mint(address to, uint256 amount) external; // onlyOwner
function burn(address from, uint256 amount) external; // onlyOwnerfunction deposit(uint256 usdtAmount) external;
function withdraw(uint256 tokenAmount) external;
function setRedemptionOpen(bool open) external; // onlyOwner
function escrowedUSDT(address user) external view returns (uint256);
function redemptionOpen() external view returns (bool);function createFestival(
string calldata name,
string calldata symbol,
uint256 startTime,
uint256 endTime
) external returns (address token, address vault);
event FestivalCreated(
address indexed token,
address indexed vault,
string name,
string symbol
);# Frontend tests
cd apps/web
pnpm test
# Contract tests
cd packages/contracts
pnpm test# Frontend coverage
cd apps/web
pnpm test:coverage
# Contract coverage
cd packages/contracts
npx hardhat coverage# Install dependencies
pnpm install
# Development
pnpm dev # Start Next.js dev server
# Build
pnpm build # Build the web app
# Linting
pnpm lint # Run linting
# Contract commands
cd packages/contracts
pnpm compile # Compile contracts
pnpm test # Run contract tests
pnpm deploy:factory # Deploy factory to Sepolia
pnpm create:festival # Create new festival
pnpm test:flow # Test deposit/withdraw flowMIT