EazPay Bot
P2P stablecoin payments on Telegram, powered by the Tempo blockchain.
What is EazPay?
A Telegram bot that lets users send USDC to each other instantly via smart contract wallets on the Tempo L1 blockchain. Sub-$0.001 fees, 0.6s finality, fully on-chain.
Key Features
- P2P Payments —
/send @user 10sends 10 USDC instantly - Smart Contract Wallets — each user gets an on-chain wallet with daily limits
- Atomic Fee Collection — 0.1% fee handled automatically by TreasuryRouter
- Group Payments —
/splitand/requestfor group expenses - Cross-Chain Bridge —
/bridgeto bring USDC from 8+ chains - Security — PIN lock, 2FA for large transactions, anti-phishing codes
- Mini App — Telegram Web App with full wallet UI
Quick Start
Prerequisites
- Node.js 18+
- PostgreSQL database
- Telegram Bot Token (from @BotFather)
- Operator wallet with USDC on Tempo for gas
Environment Variables
# Telegram
BOT_TOKEN=$BOT_TOKEN
# Tempo Network
TEMPO_RPC_URL=https://rpc.tempo.xyz
CHAIN_ID=4217
# Database
DATABASE_URL=$DATABASE_URL
# Security
WALLET_ENCRYPTION_KEY=$WALLET_ENCRYPTION_KEY
# Smart Contract System (Phase 6)
OPERATOR_PRIVATE_KEY=$OPERATOR_PRIVATE_KEY
OPERATOR_ADDRESS=$OPERATOR_ADDRESS
TREASURY_ROUTER_ADDRESS=0xf6b1285907F206848e1D51857eA4554B53E45ebc
USER_WALLET_FACTORY_ADDRESS=0x7F076a59501b2d61c64e196d697a95415cCf3C40
EAZPAY_CONTRACT_ADDRESS=0x8741ac2b1e4715084618B2540a4079EdA5D45b99
# Admin
ADMIN_TELEGRAM_ID=$ADMIN_TELEGRAM_ID
# Webhook (optional — polling mode if not set)
# WEBHOOK_URL=https://your-domain.com/webhook
# WEBHOOK_SECRET=$WEBHOOK_SECRET
Install & Run
git clone https://github.com/tomajackmac/eazpay-bot.git
cd eazpay-bot
npm install
cp .env.example .env # fill in your values
npx prisma db push
npm run build
npm start
Development
npm run dev # runs with ts-node in watch mode
Bot Commands
| Command | Description | Auth Required |
|---|---|---|
/start | Create wallet + welcome | No |
/wallet | View address + balance | Session |
/deposit | Show deposit address | Session |
/send @user amount | Send USDC | Session + Rate Limit |
/split total @u1 @u2 | Split payment equally | Session + Rate Limit |
/request @user amount [memo] | Request payment (24h expiry) | Session |
/bridge amount from chain | Bridge USDC from other chains | Session + Rate Limit |
/chains | List supported bridge chains | No |
/history | Recent transactions | Session |
/leaderboard | Top payers in group | No |
/export | Export private key (DM only) | Session |
/setpin PIN | Set security PIN | No |
/unlock PIN | Unlock session | No |
/setcode word | Set anti-phishing code | No |
/revenue | Revenue dashboard (admin) | Admin |
/help | List commands | No |
Smart Contracts (Tempo Mainnet)
| Contract | Address | Function |
|---|---|---|
| TreasuryRouter | 0xf6b1285907F206848e1D51857eA4554B53E45ebc | Fee collection (0.1%) |
| UserWalletFactory | 0x7F076a59501b2d61c64e196d697a95415cCf3C40 | Deploys per-user wallets |
| EazPay V3 | 0x8741ac2b1e4715084618B2540a4079EdA5D45b99 | Payment router — sends, batch payments, emergency pause |
- Chain: Tempo Mainnet (Chain ID: 4217)
- USDC:
0x20c000000000000000000000b9537d11c60e8b50(TIP-20) - Explorer: https://explore.tempo.xyz
Architecture
See ARCHITECTURE for detailed system design.
License
MIT
Commands Reference
All commands available in @EazPay_bot.
Getting Started
/start
Creates your account and deploys a smart contract wallet on Tempo L1.
/start
Your wallet is created in the background. You'll receive a confirmation once it's ready.
Wallet
/wallet
Shows your wallet address and current USDC balance (on-chain).
/wallet
/deposit
Shows your wallet address for receiving USDC on Tempo L1.
/deposit
Send USDC to this address from any wallet on Tempo (Chain ID: 4217).
/export
Shows your contract wallet address and self-custody instructions. Works only in DMs with the bot (not in groups).
/export
Your funds are always on-chain in your smart contract wallet. Even if EazPay goes offline, you can interact with the contract directly.
Payments
/send
Send USDC to another user. A 0.1% fee is automatically deducted.
/send @username 10
/send @username 25.50
- Recipient must have an EazPay wallet
- Transaction settles in ~0.6 seconds
- Fee is collected on-chain by TreasuryRouter
/split
Split a bill equally among multiple users. Executed as a single atomic batch transaction.
/split 30 @alice @bob @carol
Each person pays 10 USDC (30 ÷ 3). All transfers happen in one on-chain transaction — all succeed or all fail.
/request
Request a payment from another user. Expires after 24 hours.
/request @username 15
/request @username 50 dinner
The recipient gets a notification and can accept or decline.
History
/history
Shows your recent transactions.
/history
Each entry includes: direction (sent/received), amount, counterparty, tx hash, and timestamp.
Cross-Chain Bridge
/bridge
Bridge USDC from another chain to your Tempo wallet via Relay Protocol.
/bridge 100 from ethereum
/bridge 50 from base
/chains
Shows all supported networks for bridging.
/chains
Supported networks: Ethereum, Base, Arbitrum, Polygon, Optimism, Avalanche, BSC, Solana
Bridge times vary by source chain (typically 1–10 minutes). See Bridge docs for details.
Security
/setpin
Set a PIN to protect your wallet. Required before high-value transactions.
/setpin
/unlock
Unlock your session with your PIN.
/unlock
/setcode
Set an anti-phishing code. This code appears in every bot message so you can verify it's really EazPay.
/setcode
Groups
/leaderboard
Shows the payment leaderboard for the current group.
/leaderboard
Rankings based on volume sent within the group.
Admin (restricted)
/revenue
Admin-only dashboard showing revenue stats, volume, and fee collection.
/revenue
Only available to the admin account.
Architecture — EazPay Bot
System Overview
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ Telegram User │────▶│ Bot (Railway) │────▶│ Tempo Blockchain │
│ (@EazPay_bot) │◀────│ Node.js/grammY │◀────│ Chain ID: 4217 │
└─────────────────┘ └──────────────────┘ └─────────────────────┘
│ │
▼ │
┌──────────┐ ┌─────┴──────────┐
│ Postgres │ │ Smart Contracts │
│ (Railway)│ │ (4 contracts) │
└──────────┘ └────────────────┘
On-Chain Architecture
[Telegram User]
|
v
[Bot Server] -- uses OPERATOR_KEY (limited permissions)
|
v
[EazPay Orchestrator] -- orchestrator
|
|-- registerUser(telegramId, userEOA)
| |
| v
| [UserWalletFactory.sol]
| |
| v
| [UserWallet.sol] -- per-user smart contract wallet
| - owner = user's EOA
| - botOperator = bot's key
| - dailyLimit = 1000 USDC
|
+-- sendPayment(fromTgId, toTgId, amount)
|
v
[UserWallet.send()]
|
|-- fee --> [TreasuryRouter.sol] --> Treasury wallet
|
+-- net --> [Recipient wallet]
Smart Contracts
UserWallet.sol
- Purpose: Per-user smart contract wallet
- Owner: User's EOA (full control, no daily limit)
- Bot Operator: Can call
send()within daily limits - Daily Limit: 1000 USDC default (configurable by owner)
- Key functions:
send(token, to, amount)— bot-operated transfer with daily limit + feewithdraw(to, amount)— owner-only, no limitsetDailyLimit(newLimit)— owner-onlysetBotOperator(newOperator)— owner-only
UserWalletFactory.sol
- Purpose: Deploys one UserWallet per Telegram user
- Access: Only botOperator can create wallets
- Key functions:
createWallet(telegramUserId, ownerAddress)— deploys new walletgetWallet(telegramUserId)— lookup by Telegram IDhasWallet(telegramUserId)— check existence
EazPay Orchestrator
- Purpose: Central orchestrator
- Access: Only botOperator
- Key functions:
registerUser(telegramUserId, userOwner)— creates wallet via factorysendPayment(fromTelegramId, toTelegramId, amount)— atomic P2P payment
TreasuryRouter.sol
- Purpose: Automatic fee collection
- Fee: 0.1% (10 basis points), max 2%
- Key functions:
collectFee(token, amount)— called by UserWallet during sendsweepToTreasury(token)— forward fees to treasury walletsetFeePercent(newFee)— owner-only
Security Model
Access Control
Deployer (0x3e55...e915)
+-- owner of: TreasuryRouter, UserWalletFactory, EazPay
+-- can: change fees, set treasury, update bot operator
Operator (0x99aD...f8BB)
+-- botOperator of: UserWalletFactory, EazPay, each UserWallet
+-- can: create wallets, execute payments (within daily limits)
+-- cannot: withdraw, change limits, change operator
User EOA
+-- owner of: their UserWallet
+-- can: withdraw any amount, change daily limit, change bot operator
Compromise Scenarios
| Compromised | Impact | Mitigation |
|---|---|---|
| Operator key | Max 1000 USDC/day per wallet | Daily limit on-chain |
| Railway server | Operator key exposed | Daily limit caps damage |
| Database | Legacy encrypted keys (being phased out) | Contract wallets don't need DB keys |
| TreasuryRouter owner | Fee change (max 2%) | MAX_FEE cap in contract |
Tech Stack
| Layer | Technology |
|---|---|
| Runtime | Node.js 18+ / TypeScript |
| Bot Framework | grammY |
| Blockchain | ethers.js v6 |
| Database | PostgreSQL (Prisma ORM) |
| Smart Contracts | Solidity 0.8.20 (Hardhat) |
| Hosting | Railway (bot), Vercel (website) |
| CI/CD | GitHub Actions |
Database Schema
| Model | Purpose |
|---|---|
| User | Telegram user → wallet mapping, PIN, anti-phishing |
| Transaction | Payment records with fee tracking |
| PendingTransfer | Confirmation flow (5min TTL) |
| PaymentRequest | /request with 24h expiry |
| BridgeTransaction | Cross-chain bridge status tracking |
Key Fields on User
walletAddress— user's EOAcontractAddress— smart contract wallet (Phase 6+)encryptedKey— legacy, being phased outpinHash— bcrypt hashed PIN
Payment Flow
1. User sends: /send @bob 10
2. Bot validates: amount, balance, recipient exists
3. Small tx (<100 USDC): inline confirm/cancel buttons
Large tx (>=100 USDC): 2FA code required
4. User confirms
5. Bot calls: EazPay.sendPayment(fromTgId, toTgId, 10_000000)
6. On-chain:
a. UserWallet.send() checks daily limit
b. TreasuryRouter.collectFee() -> 0.05 USDC fee
c. 9.95 USDC -> recipient wallet
d. 0.05 USDC -> treasury
7. Bot updates DB, notifies sender + receiver
Middleware Stack
Request -> updateUsername -> rateLimitGeneral -> [command handler]
|
requireSession (if sensitive)
|
rateLimitSend (if /send, /split, /bridge)
- rateLimitGeneral: 5 commands / 30 seconds
- rateLimitSend: 3 sends / 60 seconds
- requireSession: checks PIN unlock status
Bridge (Cross-Chain)
Uses Relay Protocol API for cross-chain USDC bridging:
- Supported chains: Ethereum, Arbitrum, Optimism, Base, Polygon, Avalanche, BSC, Zora
- Flow: get quote → generate deposit address → user deposits → Relay bridges to Tempo
- Status tracking via BridgeTransaction model
Security
Overview
EazPay uses a layered security model: smart contract constraints on-chain + user-facing protections in the bot.
On-Chain Security
Limited Operator
The bot's operator key can only call send() on your UserWallet. It cannot:
- Withdraw funds
- Execute arbitrary contract calls
- Change the wallet owner
- Exceed the daily limit
Daily Spending Limit
- 1,000 USDC per day per wallet, enforced on-chain
- Resets every 24 hours
- Only applies to operator-initiated transfers
- Owner can withdraw directly without limits
Emergency Pause
The EazPay contract (V3) includes an emergency pause function. If a vulnerability is detected, the contract owner can pause all operations until resolved.
No Generic Execute
Unlike many account abstraction wallets, EazPay's UserWallet does not have a generic execute() function. The operator can only call the specific send() function — no arbitrary calldata.
User-Facing Security
PIN Protection
- Set a PIN with
/setpin - Required to unlock sessions after inactivity
- Unlock with
/unlock
Anti-Phishing Code
- Set with
/setcode - Your personal code appears in every bot message
- If a message doesn't include your code, it's not from EazPay
Session Auto-Lock
Sessions automatically lock after a period of inactivity. Use /unlock with your PIN to resume.
DM-Only Sensitive Operations
/exportonly works in direct messages with the bot- Private key and wallet details are never shown in group chats
Audit Status
- Community-reviewed — contract code is open for inspection
- Not formally audited — no third-party audit firm has reviewed the contracts
- Bug bounty active — responsible disclosure encouraged at contact@eazpay.xyz
Best Practices
- Set a PIN — protects against unauthorized access if someone has your Telegram
- Set an anti-phishing code — verify every message is from the real bot
- Don't share your wallet details in groups — use
/exportin DMs only - Verify transaction details — always confirm recipient and amount before sending
Fees
Transaction Fee
- 0.1% per
/sendtransaction - Collected automatically on-chain by the TreasuryRouter contract
- Hard cap: 2% — enforced on-chain, cannot be raised above this
Example
Sending 100 USDC:
- Fee: 0.10 USDC (0.1%)
- Recipient receives: 99.90 USDC
- Treasury receives: 0.10 USDC
How It Works
- You send
/send @friend 100 - Bot calls
EazPay.sendPayment()on-chain - Contract reads fee rate from TreasuryRouter
- Fee is split automatically: 99.90 → recipient, 0.10 → treasury
- All in one atomic transaction
The fee rate is read from the TreasuryRouter contract — not hardcoded in the bot. You can verify it on-chain at any time.
Gas Fees
- You pay $0 in gas — the operator pays all gas costs
- Tempo L1 gas costs are sub-$0.001 per transaction
- Gas is paid in stablecoin (no native token needed)
Bridge Fees
Cross-chain bridge transfers via Relay Protocol have their own fee structure:
- Relay Protocol charges a small fee per bridge transaction
- Fees vary by source chain and amount
- See
/bridgeoutput for exact fee before confirming
No Hidden Fees
- No account creation fee
- No monthly fees
- No withdrawal fees (direct on-chain access is free)
- No fee for receiving payments
- Only the 0.1% on outgoing
/sendtransactions
Cross-Chain Bridge
Overview
EazPay supports cross-chain USDC transfers from 8 networks to your Tempo wallet via Relay Protocol.
Supported Networks
| Network | Token |
|---|---|
| Ethereum | USDC |
| Base | USDC |
| Arbitrum | USDC |
| Polygon | USDC |
| Optimism | USDC |
| Avalanche | USDC |
| BSC | USDC |
| Solana | USDC |
How to Bridge
/bridge 100 from ethereum
/bridge 50 from base
- Run
/bridge <amount> from <chain> - Bot generates a Relay Protocol bridge request
- You receive a deposit address on the source chain
- Send USDC to that address from your external wallet
- Relay bridges the funds to your Tempo wallet
Timing
Bridge times depend on the source chain:
| Source Chain | Estimated Time |
|---|---|
| Base, Arbitrum, Optimism | 1–3 minutes |
| Polygon, Avalanche, BSC | 2–5 minutes |
| Ethereum | 5–15 minutes |
| Solana | 2–5 minutes |
Times are estimates and may vary based on network congestion.
Fees
Relay Protocol charges a fee per bridge transaction. The exact fee depends on:
- Source chain
- Amount being bridged
- Current network conditions
The fee is shown before you confirm the bridge — no surprises.
Checking Status
Use /history to see the status of bridge transactions. Each bridge entry shows:
- Source chain
- Amount
- Status (pending / completed / failed)
- Relay request ID
Limitations
- Direction: Currently supports inbound bridges only (other chains → Tempo)
- Token: USDC only
- Minimum: Varies by chain (typically ~$1)
Smart Contracts
All contracts are deployed on Tempo L1 Mainnet (Chain ID: 4217).
Explorer: explore.tempo.xyz
Deployed Contracts
| Contract | Address | Purpose |
|---|---|---|
| EazPay V3 | 0x8741ac2b1e4715084618B2540a4079EdA5D45b99 |
Payment router — handles sends, batch payments, fee splitting |
| UserWalletFactory | 0x7F076a59501b2d61c64e196d697a95415cCf3C40 |
Deploys individual UserWallet contracts for each user |
| TreasuryRouter | 0xf6b1285907F206848e1D51857eA4554B53E45ebc |
Collects fees (0.1%, capped at 2%) and routes to treasury |
Key Addresses
| Role | Address |
|---|---|
| Treasury | 0x7A33c090E6cbb65ed909cD174713857be171169a |
| USDC (TIP-20) | 0x20c000000000000000000000b9537d11c60e8b50 |
Deprecated Contracts
| Contract | Address | Note |
|---|---|---|
| TempoPay V1 | 0xc8aF7810751Fa7e7542372eADc44A3aB1cDCaaA9 | Replaced by V2 |
| EazPay V2 | 0x1CF26AAbb80Ba614672C37fde6A27D9de2566dc5 | Replaced by V3 (added emergency pause) |
Contract Details
EazPay V3 (Payment Router)
The core payment contract. Handles:
sendPayment()— single P2P transfer with automatic fee deductionbatchSend()— atomic batch payments (used by/split)- Emergency pause — owner can pause all operations if needed
Key properties:
- Operator can only call send functions (no arbitrary execution)
- Fee rate read from TreasuryRouter (not hardcoded)
- All-or-nothing batch execution
UserWalletFactory
Deploys one UserWallet contract per user:
- Called on
/startfor new users - Deterministic addresses
- Sets user as owner, bot as limited operator
UserWallet
Each user's individual smart contract wallet:
- Owner: the user (full control, can withdraw anytime)
- Operator: the bot (can only call
send(), limited to daily cap) - Daily limit: 1,000 USDC/day (on-chain enforced)
- No generic
execute()— operator cannot make arbitrary calls
TreasuryRouter
Fee collection contract:
- Current fee: 0.1%
- Hard cap: 2% (on-chain, cannot exceed)
- Routes fees to treasury address
- Fee rate readable on-chain:
feePercent()
Source Code
- GitHub: github.com/tomajackmac/eazpay-bot
- Contracts located in
contracts/directory - Written in Solidity, compiled with Hardhat
Verification
All contract interactions can be verified on the Tempo Explorer. Every /send and /split creates an on-chain transaction with a visible tx hash.
FAQ
General
Is EazPay custodial?
No. Every user gets a smart contract wallet on Tempo L1. You are the owner. Your funds live on-chain, not on our servers. The bot's operator key has limited permissions (can only execute send() within a daily limit). See Wallet Architecture.
What tokens are supported?
USDC (TIP-20) on Tempo L1. You can bridge USDC from 8 other chains — see Bridge.
What blockchain does EazPay run on?
Tempo L1 — an EVM-compatible blockchain with sub-$0.001 fees and 0.6-second finality. Chain ID: 4217.
Fees
What are the fees?
0.1% per outgoing /send transaction. Capped at 2% on-chain. No other fees — no account fee, no monthly fee, no withdrawal fee. See Fees.
Do I pay gas?
No. The operator pays all gas costs. Tempo gas fees are sub-$0.001 per transaction.
Security
Can the operator steal my funds?
No. The operator (bot) can only call send() on your wallet, limited to 1,000 USDC/day. It cannot withdraw funds, make arbitrary calls, or change the wallet owner. See Security.
What happens if EazPay goes down?
Your funds remain on-chain in your smart contract wallet. You can interact with the contract directly on the Tempo Explorer using the owner key. No dependency on the bot.
Is there a daily limit?
Yes — 1,000 USDC/day per wallet, enforced on-chain. This limits what the operator can do. As the wallet owner, you can withdraw directly without this limit.
Are the contracts audited?
Community-reviewed, not formally audited by a third-party firm. Bug bounty is active — report vulnerabilities to contact@eazpay.xyz.
Wallet
What if I lose access to my Telegram account?
Your funds are on-chain in your smart contract wallet. If you have your owner key, you can call withdraw() on the contract directly. Use /export (in DMs) to see your contract address and self-custody instructions while you still have access.
How do I withdraw my funds?
Use /export in DMs with the bot for instructions. You can also call withdraw() directly on your UserWallet contract via the Tempo Explorer or any web3 wallet.
Can I use my wallet outside of EazPay?
Yes. Your UserWallet is a standard smart contract on Tempo L1. You can interact with it from any EVM-compatible wallet or directly on the explorer.
Bridge
How long does a bridge take?
Depends on the source chain — typically 1–15 minutes. See Bridge for estimated times per network.
Can I bridge out of Tempo?
Currently, only inbound bridges are supported (other chains → Tempo). Outbound bridges are planned.