Sweephy is a hardware-assisted Hedera swap experience: a physical device (ESP32) triggers a swap workflow, while a web dashboard provides pairing, rules, allowance setup, monitoring, and audit trails. Transaction signing is delegated to an AWS KMS-backed platform key, while users retain control through Hedera allowances.
- Web app (Next.js) for onboarding, rules, admin, monitoring, audit logs.
- Device firmware (ESP32 Arduino) for pairing and “press-to-swap”.
- Supabase (Postgres + Edge Functions) as the backend API and state store.
- Hedera Mainnet integration (Transfers, ContractExecute for SaucerSwap router).
- AWS KMS signing flow for the platform operator key.
- Device pairing via pairing code (device ↔ dashboard).
- One-click swap trigger from device (e.g. HBAR → USDC).
- Allowance-driven custody model: user grants allowance to platform signer; user can revoke any time.
- Rule engine (per user): amount per click, max per swap, daily limit, cooldown, slippage tolerance.
- Real-time dashboard stats + chart + recent activity.
- Audit logs and transaction details (transfer / swap / refund ids).
- Admin panel for device provisioning and management.
- Automatic refund on failure / timeout paths (backend side).
- Frontend: Next.js (App Router), React, TypeScript, TailwindCSS, Recharts, Framer Motion
- Wallet integration: Reown AppKit (+ Hedera adapters)
- Backend: Supabase (Postgres + Edge Functions)
- Blockchain: Hedera Mainnet,
@hashgraph/sdk - Signing: AWS KMS (secp256k1),
@aws-sdk/client-kms - Swap: SaucerSwap Router contract (Hedera EVM)
- Device: ESP32 Arduino (WiFi, HTTPS, ArduinoJson, ST7789 display)
.
├─ device/ # ESP32 firmware
│ ├─ sweephy.ino
│ └─ qrcode.*
├─ scripts/ # Node scripts for ops/dev tooling
├─ src/ # Next.js app
│ ├─ app/ # App Router pages
│ ├─ components/
│ ├─ hooks/
│ └─ lib/
├─ supabase/
│ ├─ functions/ # Edge Functions (Deno)
│ └─ migrations/ # DB schema migrations
└─ env.example # Environment variables template
- Node.js (LTS recommended)
- Supabase project (cloud) or Supabase CLI (local)
- An AWS KMS key (ECDSA secp256k1) + AWS credentials (for KMS-backed signing)
- Optional: Arduino IDE / PlatformIO (to flash ESP32)
npm installCopy template:
cp env.example .env.localThen fill values in .env.local.
npm run devnpm run lint
npx tsc --noEmitThe repository ships with env.example as a template. Variables fall into three buckets:
These are exposed to the browser (must be safe to expose).
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYNEXT_PUBLIC_REOWN_PROJECT_IDNEXT_PUBLIC_KMS_ACCOUNT_ID(spender account id used in allowance UI)- Optional custom node:
NEXT_PUBLIC_HEDERA_NODE_IPNEXT_PUBLIC_HEDERA_NODE_ACCOUNT_ID
- Admin page display fields (informational):
NEXT_PUBLIC_KMS_KEY_IDNEXT_PUBLIC_OPERATOR_ID
Set these as Supabase function secrets (not in client env).
SUPABASE_URLSUPABASE_SERVICE_ROLE_KEYAWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_REGIONAWS_KMS_KEY_IDKMS_ACCOUNT_ID- SaucerSwap / token constants:
SAUCERSWAP_ROUTER_IDWHBAR_TOKEN_IDUSDC_TOKEN_ID
The scripts/ folder uses Node environment variables. You can export them in your shell, or reuse .env.local by sourcing it:
set -a
source .env.local
set +aCommon script env vars:
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_REGION,AWS_KMS_KEY_IDKMS_ACCOUNT_ID- WHBAR unwrap helpers:
WHBAR_CONTRACT_IDWHBAR_TOKEN_IDWHBAR_SPENDER_ID(defaults to contract id)MIRROR_NODE_API
- Deploy tooling:
HEDERA_OPERATOR_ID,HEDERA_OPERATOR_PRIVATE_KEY,HEDERA_OPERATOR_KEY_TYPESWEEPHY_OPERATOR_ACCOUNT_ID,SWEEPHY_FEE_RECIPIENT_ADDRESS
Schema is managed via SQL migrations in supabase/migrations.
Core tables (simplified):
profiles: user profile keyed by wallet address.devices: device inventory, pairing status, last seen, and ownership.pairing_codes: short-lived codes to pair a device to a profile.rules: per-profile swap rules (limits + slippage).intents: swap requests and their lifecycle.
Intent status values include:
pending(created / waiting)processing(transfer/swap in progress)completed(swap confirmed)failed(failed and optionally refunded)
Swap detail fields on intents include:
tx_id_transfer,tx_id_swap,tx_id_refund,tx_id_receiptamount_receivednote(human-readable status / error reason)
The web app uses Reown AppKit to connect wallets and identify users by wallet_address.
Main flows:
- Grant allowance: The UI constructs an
AccountAllowanceApproveTransactionto approve HBAR allowance from the user to the platform KMS spender (NEXT_PUBLIC_KMS_ACCOUNT_ID). - Revoke allowance: The UI sets allowance to 0.
- Rules editing: The UI writes to
rulesin Supabase. - Monitoring: dashboard pages query
intentsanddevicesthrough Supabase client queries.
Key files:
- Dashboard rules UI: rules/page.tsx
- Supabase client: supabase.ts
- AppKit wiring: web3-provider.tsx
Firmware lives in device/sweephy.ino.
High-level behavior:
- Connects to WiFi (provisioning via local web server + preferences).
- Calls Supabase Edge Function endpoints:
process-intentto request a swapget-statusto poll latest intent status and show progressget-pricefor price display
- Uses HMAC signatures based on a per-device secret.
Important: do not hardcode production secrets in firmware for real deployments. Use a provisioning flow and store secrets securely (at minimum in NVS/Preferences).
Functions are in supabase/functions.
Key endpoints:
process-intent: creates a new intent and runs the swap workflow asynchronously (transfer → swap → verify → update DB; refund on failure).get-status: device polling endpoint for pairing + latest intent.get-price: price endpoint used by device UI.update-intents: (internal/maintenance) intent updates.
sequenceDiagram
autonumber
actor User
participant Device as ESP32 Device
participant App as Web Dashboard (Next.js)
participant SB as Supabase (Edge + DB)
participant KMS as AWS KMS (secp256k1)
participant Hedera as Hedera Mainnet
participant Saucer as SaucerSwap Router (EVM)
participant Mirror as Hedera Mirror Node
User->>App: Connect wallet
App->>Hedera: User grants HBAR allowance to KMS spender
App->>SB: Save rules (limits, slippage)
User->>Device: Hold / press button
Device->>SB: POST /process-intent (device_id + payload + HMAC)
SB->>SB: Create intent row (status=pending/processing)
SB-->>Device: 200 { intent_id } (fast response)
SB->>SB: Validate pairing + rules + allowance + limits
SB->>KMS: Sign Hedera transactions (transfer + contract execute)
KMS-->>SB: Signature (DER)
SB->>Hedera: Execute transfer (pull HBAR via allowance)
SB->>Hedera: ContractExecuteTransaction(to=Saucer router, fn=swapExactETHForTokens, payable=HBAR)
Hedera->>Saucer: Execute router call (HBAR/WHBAR -> USDC)
Saucer-->>Hedera: Emit logs + HTS token transfers (USDC to user)
Hedera-->>SB: Receipts / status (CONTRACT_EXECUTED)
SB->>Mirror: Verify tx result + token transfers
Mirror-->>SB: SUCCESS + token_transfers (amount_received)
SB->>SB: Update intent (tx ids, amount_received, status=completed)
loop Poll status
Device->>SB: POST /get-status (device_id + HMAC)
SB-->>Device: { intent.status, intent.note, intent.amount_received }
end
Device-->>User: Show result on screen
sequenceDiagram
autonumber
actor User
participant App as Web Dashboard
participant SB as Supabase (Edge + DB)
participant Device as ESP32 Device
User->>App: Open Dashboard / Devices
App->>SB: Request pairing code (get-status can also generate)
SB->>SB: Insert pairing_codes (expires_at)
SB-->>App: pairing_code
User->>Device: Enter / scan pairing code
Device->>SB: POST /process-intent action=pair (device_id + pairing_code + HMAC)
SB->>SB: Validate pairing_code and mark used
SB->>SB: Update devices.is_paired=true
SB-->>Device: Paired
Device-->>User: Shows paired state
The platform KMS key does not custody user funds. Instead, it relies on Hedera allowance:
- User approves a limited HBAR allowance to the KMS spender account.
- KMS signs transactions to spend up to that allowance.
- User can revoke allowance at any time.
If transfer succeeded but swap fails or times out, backend attempts to refund HBAR back to the user and marks intent as failed with a note.
Scripts live in scripts/.
Common examples:
- Deploy contract (see script usage inside file):
- deploy-sweephy-contract.ts
- Unwrap WHBAR from KMS account:
- unwrap-kms-whbar.ts
- Run:
npx tsx scripts/unwrap-kms-whbar.ts -- --all
- Withdraw HBAR from KMS:
- withdraw-hbar.ts
-
Dashboard shows “Console Error {}”
- Usually caused by logging a non-Error object. The codebase now formats unknown errors in dashboard logs via format-error.ts.
-
Device shows stuck “Swapping..”
- Check latest intent in
intentstable (status,note,tx_id_*fields). - Verify Mirror Node shows SUCCESS for
tx_id_swap.
- Check latest intent in
-
SPENDER_DOES_NOT_HAVE_ALLOWANCE- For swaps: user has not granted enough HBAR allowance to the KMS spender.
- For WHBAR unwrap: token allowance for the WHBAR spender is required.
- Do not expose
SUPABASE_SERVICE_ROLE_KEYto the client. - Review Supabase RLS policies; current policies may be permissive for iteration speed.
- Rotate device secrets and avoid hardcoding secrets in firmware images.
- Add rate limiting and replay protection for device requests.