Disclaimer: This repository contains only a partial excerpt of a larger application, shared for review purposes with the permission of the copyright holder. It is not intended to be a fully functional standalone project.
A backend REST API for managing Non-Custodial Wallets (NCW) via Fireblocks. Built with Symfony 6.4 and PHP 8.1+.
The service allows users to manage cryptocurrency wallets through the Fireblocks NCW infrastructure:
- Register and authenticate via JWT
- Manage wallet devices and accounts
- Add and track crypto assets (Bitcoin, Ethereum, ERC20/BEP20 tokens, etc.)
- View balances and deposit addresses (served from local cache, synced asynchronously)
- Create and cancel transactions
- Browse transaction history
Balances and addresses are kept fresh via background workers that periodically sync with the Fireblocks API, allowing the REST API to respond without blocking on third-party calls.
| Layer | Technology |
|---|---|
| Framework | Symfony 6.4 |
| Language | PHP 8.1+ |
| Database | MySQL 8 + Doctrine ORM 3 |
| Cache / Queue | Redis + Symfony Messenger |
| Authentication | JWT (lexik/jwt-authentication-bundle) |
| HTTP Client | Guzzle |
| Background Jobs | Symfony Scheduler + Supervisord |
| Real-time | Mercure (WebSocket over Caddy) |
| API Docs | OpenAPI / Swagger (nelmio/api-doc-bundle) |
| Infrastructure | Docker Compose |
src/
├── Client/Fireblocks/ # Fireblocks API client (HTTP, serialization, DTOs, exceptions)
├── Command/ # CLI commands (transaction import, client test)
├── Controller/ # HTTP controllers (API v1, auth, webhooks, health)
├── Entity/ # Doctrine ORM entities (User, Wallet, Device, Asset, Balance, ...)
├── EventListener/ # Global exception handler → JSON error responses
├── Infrastructure/ # Redis factory, WebSocket publisher, custom Doctrine types
├── Model/ # Business logic layer
│ ├── Object/ # Domain objects and enums
│ ├── RequestProcessor/ # Request → Response handlers (per domain area)
│ ├── BalanceUpdater/ # Async balance sync via Messenger
│ ├── AddressFetcher/ # Async address sync via Messenger
│ ├── AccountAssetsUpdater/# Async asset list sync via Messenger
│ └── TransactionImporter/ # Async transaction import via Messenger + Scheduler
└── Repository/ # Doctrine repositories
All endpoints are under /api/v1 and require JWT authentication.
| Method | Path | Description |
|---|---|---|
| POST | /auth/register |
Register a new user |
| POST | /auth/login |
Obtain JWT token |
| GET | /devices |
List user's wallet devices |
| GET | /devices/{device} |
Get device details |
| GET | /devices/{device}/setup-status |
Get device setup status |
| POST | /devices/{device}/rpc |
Invoke Fireblocks RPC method |
| GET | /devices/{device}/accounts |
List accounts |
| GET | /devices/{device}/accounts/{id} |
Get account details |
| GET | /assets |
List supported crypto assets |
| GET | /devices/{device}/accounts/{id}/assets |
List account assets |
| POST | /devices/{device}/accounts/{id}/assets/{assetId} |
Add asset to account |
| GET | /devices/{device}/accounts/{id}/assets/{assetId} |
Get asset details |
| GET | /devices/{device}/accounts/{id}/assets/{assetId}/balance |
Get cached balance |
| GET | /devices/{device}/accounts/{id}/assets/{assetId}/address |
Get deposit address |
| GET | /devices/{device}/transactions |
List transactions |
| GET | /devices/{device}/transactions/{txId} |
Get transaction details |
| POST | /devices/{device}/accounts/{id}/assets/{assetId}/transactions |
Create transaction |
| POST | /devices/{device}/transactions/{txId}/cancel |
Cancel transaction |
| POST | /callback |
Fireblocks webhook receiver |
| GET | /health |
Health check |
Interactive API documentation is available at /api/doc.
flowchart TD
A[HTTP Request] --> B[Controller]
B --> C[RequestProcessor]
C --> D[Service]
D --> E[Fireblocks API Client]
D --> F[(MySQL)]
F --> G[Background Workers\nMessenger + Scheduler]
G --> C
G --> H[(Redis\nqueue / cache)]
G --> I[Mercure Hub\nWebSocket]
Background workers run independently via Symfony Messenger:
- BalanceUpdater — fetches asset balances from Fireblocks and stores them in MySQL
- AddressFetcher — fetches deposit addresses from Fireblocks and stores them in MySQL
- AccountAssetsUpdater — keeps account asset lists in sync
- TransactionImporter — imports transaction history on schedule and via webhooks
API endpoints for balance and address serve the cached data immediately without waiting for Fireblocks.
After async workers update balances, addresses or transaction statuses, they publish events via WebsocketPublisher to a Mercure hub. Clients subscribe to topics and receive live updates without polling.
Caddy serves as both the HTTPS reverse proxy and the Mercure hub (via the built-in FrankenPHP + Caddy Docker image), exposing the hub at /.well-known/mercure.