← Back to blog index · 2026-05-10
Building Yieldsforge — Architecture, Deploy Pitfalls, Design Decisions
Complete technical writeup of Yieldsforge. Tech stack, why Bitfinex's region restriction is a Railway-default-region trap, why we picked NOWPayments over Stripe, multi-user isolation design. Transparency = trust.
Previous post covered the structural reasons users churn from Coinlend. This post is Yieldsforge’s technical record — architecture decisions, pitfalls hit, tech stack choices.
For two audiences:
- Potential users who want to know “what’s inside Yieldsforge” — transparency = trust
- Devs considering similar SaaS — my mistakes can save you time
TL;DR Tech Stack
- Core strategy: Python + numpy/pandas (
lending_bot/) - Web: FastAPI + fastapi-users (
saas/web/) - Worker: ARQ + Redis (
saas/worker/) - DB: PostgreSQL (multi-tenant)
- Frontend: Astro + Tailwind (
marketing/) + plain HTML+JS (saas dashboard) - Deploy: Railway (3 services + Postgres + Redis)
- Billing: NOWPayments (USDT crypto payments)
- Auth: Google OAuth + email/password fallback
- Monitoring: Sentry
- Backtest: custom-written walk-forward + fill model + prepayment model
Architecture Decision 1: Strategy as Standalone Module
Design goal: lending_bot/ module usable independently (personal CLI), or wrapped into multi-user SaaS.
Modules:
- collector.py: pull funding book + funding stats from Bitfinex
- decision_tree.py: multi-bucket allocation + floor + ladder logic
- order_manager/: place + cancel + re-place
- strategy_engine.py: tie above together, run on 60-second tick
Benefit: strategy logic can be backtested independently without spinning up the full web stack.
Architecture Decision 2: Backtest Engine Determines Strategy Quality
Before writing the walk-forward backtest, our strategy (like most funding bot devs’) was based on intuition. Three blindspots only became visible after the backtest:
- Single-30d looks best, but loses to multi-bucket after prepayment (detail)
- Floor shouldn’t be uniform across symbols — fUSD has spikes, fUST doesn’t, need per-symbol tuning
- “candle.low fills” assumption is severely over-optimistic — real queue competition is harsher
Each backtest exposed a wrong assumption. Final strategy looked nothing like v1.
Full backtest results in this post.
Architecture Decision 3: ARQ + Redis for Multi-User Ticks
Initial idea: single worker process running all users’ ticks per minute. Simple but problematic:
- 100 users × 60s tick = 1.7 calls/sec to Bitfinex (rate limit risky)
- One user’s tick stuck blocks everyone
- Bitfinex API timeouts need retry logic
Switched to ARQ + Redis, every (user, symbol) tick is an independent job:
- Scheduler enqueues all jobs every minute
- Worker pool processes concurrently (max 20 today)
- ARQ job_id dedups to prevent double-enqueue per minute
Deploy Pitfall 1: Bitfinex Returns HTTP 451 to US IPs
Most painful technical trap.
Railway defaults to us-west2. First user tick from there got rejected by Bitfinex. Reason: Bitfinex doesn’t serve US jurisdictions, blocks by IP.
This is a “Yieldsforge on Railway’s default region” pitfall — Coinlend / Cryptolend etc. are German / EU operations with EU infrastructure, never hit this from day one.
Fix: workers must run in asia-southeast1. Multi-region Railway deploy + verify all outbound Bitfinex traffic exits SG. Web service can stay in us-west2 (no Bitfinex calls).
This decision became a hard rule in CLAUDE.md. Never move worker back to US region.
Deploy Pitfall 2: Multi-User Isolation
The “things that explode” portion of the build. Multi-tenant SaaS always carries cross-user data leak risk.
Design:
- API key encryption: Fernet symmetric encryption + global master key, decrypt only briefly during worker tick
- All DB queries forced to user_id: enforced at ORM layer (every SQLAlchemy query carries user_id where clause)
- Funding-only verification: when user submits a key, auto-test permissions; reject any key with withdraw
Every API endpoint has cross-user fuzz test: “if user A brings user B’s cookie, can they get B’s data?” Answer must always be no.
Design Decision: NOWPayments Over Stripe
Stripe has the best dev experience but is sensitive to “crypto-related services” and may ban. Crypto-adjacent businesses on Stripe either get rejected outright or have accounts frozen later.
NOWPayments:
- Accepts USDT / USDC / BTC / ETH crypto
- 0.5% transfer fee (vs Stripe 2.9% + $0.30)
- Users don’t need credit cards, matches our crypto-native audience
Cost: dev pain higher than Stripe (sparse docs, IPN webhook signature verification complex).
Design Decision: Astro Marketing + Plain HTML Dashboard
Marketing site (/, /blog, /privacy): Astro + Tailwind. Reasons: SSG, SEO-friendly, fast build, TypeScript-friendly.
Dashboard (/dashboard/*): plain HTML + vanilla JS, no React. Reasons:
- Data is server-rendered + 60s polling — no need for reactive framework
- No build step, fast deploy
- Small bundle
- All charts are inline SVG, no chart library imported
Might trigger React believers, but for dashboard-level complexity, vanilla JS is plenty.
i18n: Strict zh-en Parity
All strings go through translation table, no hardcoding. zh-en parity 100% — because:
- Bilingual SEO indexing
- Maintenance consistency (drift makes the brand look unprofessional)
- Easy to add languages (Spanish, Japanese coming next phase)
Implementation: i18n.py is a dict, t(key, lang) looks up, no i18n framework. Simple but effective.
Post-Ship Iteration (May 2026 onward)
What shipped after launch:
- Per-symbol floor upgrade — fUST APY 7.6% → 17.6%
- Cancel_misaligned floor-aware — auto-reprice when floors change
- Adaptive floor Phase 1 — MarketStats table accumulating data, Phase 2 will auto-tune
- Dashboard major redesign — depth charts, donuts, pressure bars
- 20 blog posts in this content batch (yes, this one)
Roadmap (H2 2026):
- USDC funding pair
- Adaptive floor opens for user opt-in
- Partial source code open
- Mobile-friendly dashboard
Advice for Devs Considering a Funding Bot SaaS
Real advice you won’t see in startup blog posts:
- Bitfinex region restriction is a deal-breaker — confirm you can deploy in asia region before starting
- Multi-tenant SaaS is 10x more complex than personal script — auth + billing + cross-user isolation + monitoring, each must be done right
- Don’t try Stripe for crypto-adjacent business — high ban risk, go directly to NOWPayments
- Without backtest, you don’t know your strategy is bad — intuition strategies typically underperform tested ones by 2-5pp
- Open-sourcing modules is marketing — transparency beats ads
If you only want to use it personally / share with friends, don’t build SaaS. Write a personal script and invite friends to ssh in. SaaS legal + ops cost isn’t worth it under 10 users.
Related Reading
- Why long-term Coinlend users churn
- 5.5-year walk-forward backtest results
- Yieldsforge vs Coinlend Detailed Comparison
- Funding Bot Full Comparison
- Why Bitfinex Funding beats DeFi yields
Yieldsforge 7-day free trial →
Disclosure: I’m the developer of Yieldsforge. Technical details based on actual repo code; roadmap is current plan. Not investment or development advice.