The invisible accounting engine behind your product.
Build wallets, credits, rewards, transfers, refunds, and balance management — without ever writing a ledger.
Quick Start · Architecture · API Examples · Data Model · Deployment
Every startup eventually builds the same financial primitives: wallets, credits, loyalty points, refunds, transfers, merchant balances. And every team discovers the same bugs: incorrect balances, race conditions, missing audit trails, duplicated transactions, and reconciliation nightmares.
Argent eliminates this. It provides double-entry bookkeeping, immutable ledger entries, and balance management behind a simple REST API — so developers can focus on their product, not their books.
Your App ──── REST API ──── Argent ──── Double-Entry Ledger
──── Immutable Audit Trail
──── Balance Management
──── Environment Isolation
graph TB
subgraph Client["Client Applications"]
A1[Food Delivery App]
A2[Marketplace]
A3[SaaS Platform]
A4[Gaming Backend]
end
subgraph Gateway["API Gateway"]
B1[Nginx Reverse Proxy]
end
subgraph Backend["Spring Boot Application"]
C1[Auth Module<br/>JWT + API Keys]
C2[Wallet Module<br/>CRUD + Status]
C3[Transaction Module<br/>Strategy Pattern]
C4[Ledger Module<br/>Double-Entry]
C5[Balance Module<br/>Cache + Compute]
C6[Audit Module<br/>Full Trail]
C7[Reporting Module<br/>CSV Export]
end
subgraph Data["Data Layer"]
D1[(PostgreSQL 16<br/>ACID + Triggers)]
D2[(Redis 7<br/>Balance Cache)]
end
subgraph Infra["Infrastructure"]
E1[Docker Compose]
E2[Render Blueprint]
end
Client --> Gateway
Gateway --> Backend
Backend --> Data
Backend --> Infra
Every transaction follows this atomic path through the system:
sequenceDiagram
participant C as Client
participant A as Auth
participant T as TransactionService
participant E as Engine (Deposit/Transfer/...)
participant L as LedgerEntryService
participant B as BalanceService
participant D as PostgreSQL
C->>A: POST /api/v1/deposits<br/>+ Idempotency-Key
A->>T: Validate + Route
T->>E: Execute transaction type
E->>L: createBalancedEntries()
Note over L,D: BEGIN TRANSACTION
L->>D: INSERT debit entry
L->>D: INSERT credit entry
L->>D: UPDATE account A balance
L->>D: UPDATE account B balance
Note over L,D: COMMIT (atomic)
L-->>E: Entries created
E-->>T: Transaction complete
T-->>C: 201 Created + idempotency key
flowchart LR
A[HTTP Request] --> B{Auth Type?}
B -->|JWT| C[Validate Token<br/>Extract User]
B -->|API Key| D[Hash + Lookup<br/>Resolve Org]
C --> E[RBAC Check<br/>Role Permissions]
D --> F[Env Scope Check<br/>Sandbox/Production]
E --> G[Service Layer<br/>Business Logic]
F --> G
G --> H{Write Operation?}
H -->|Yes| I[Idempotency Check]
H -->|No| J[Cache Lookup]
I --> K[Execute +<br/>Audit Log]
J --> L[Return Response]
K --> L
Every transaction creates two ledger entries (debit + credit) that always balance. If the sum of debits != sum of credits, the transaction is rejected. Period.
graph LR
subgraph Deposit["Deposit $100"]
D1[Debit: Platform Account<br/>-$100] --> C1[Credit: Customer Wallet<br/>+$100]
end
subgraph Transfer["Transfer $50"]
D2[Debit: Sender Wallet<br/>-$50] --> C2[Credit: Receiver Wallet<br/>+$50]
end
subgraph Withdrawal["Withdrawal $30"]
D3[Debit: Customer Wallet<br/>-$30] --> C3[Credit: Platform Account<br/>+$30]
end
style D1 fill:#ff6b6b,color:#fff
style D2 fill:#ff6b6b,color:#fff
style D3 fill:#ff6b6b,color:#fff
style C1 fill:#51cf66,color:#fff
style C2 fill:#51cf66,color:#fff
style C3 fill:#51cf66,color:#fff
Ledger entries are physically impossible to modify at the database level. PostgreSQL BEFORE UPDATE and BEFORE DELETE triggers raise exceptions if any mutation is attempted — even direct SQL cannot alter them.
-- This will FAIL at the database level:
UPDATE ledger_entries SET amount = 999 WHERE id = '...';
-- Error: ledger_entries are immutable
DELETE FROM ledger_entries WHERE id = '...';
-- Error: ledger_entries are immutableEach transaction type is an independent engine class with its own business logic:
TransactionService
├── DepositEngine (funds in)
├── WithdrawalEngine (funds out)
├── TransferEngine (wallet-to-wallet)
├── RefundEngine (reverse transaction)
└── AdjustmentEngine (manual correction)
Adding a new transaction type = adding one new engine class. Zero changes to existing code.
API keys are scoped to SANDBOX or PRODUCTION. A sandbox key cannot touch production data. The environment chain flows:
Wallet(environment) → Account(inherits) → LedgerEntry(inherits)
Dashboard users (via JWT) see all environments — operators need full visibility, while client applications stay isolated.
erDiagram
Organization ||--o{ User : has
Organization ||--o{ ApiKey : issues
Organization ||--o{ Wallet : owns
Organization ||--o{ AuditLog : generates
Wallet ||--|| Account : linked_to
Wallet ||--o{ Wallet : "transfers between"
Account ||--o{ Transaction : processes
Account ||--o{ LedgerEntry : records
Account ||--|| Balance : tracks
Transaction ||--o{ LedgerEntry : "creates (2 entries)"
Transaction ||--o{ AuditLog : "audited by"
Organization {
uuid id PK
string name
string slug UK
enum status
}
User {
uuid id PK
uuid organizationId FK
string email
enum role "OWNER|ADMIN|DEVELOPER"
string passwordHash
}
Wallet {
uuid id PK
uuid organizationId FK
string label
enum type "CUSTOMER|MERCHANT|ESCROW|PLATFORM"
enum status "ACTIVE|FROZEN|CLOSED"
jsonb metadata
}
Account {
uuid id PK
uuid organizationId FK
enum type "ASSET|LIABILITY|EQUITY"
enum environment "SANDBOX|PRODUCTION"
}
Transaction {
uuid id PK
uuid organizationId FK
enum type "DEPOSIT|WITHDRAWAL|TRANSFER|REFUND"
enum status "PENDING|COMPLETED|FAILED"
decimal amount
string idempotencyKey UK
}
LedgerEntry {
uuid id PK
uuid transactionId FK
uuid accountId FK
enum type "DEBIT|CREDIT"
decimal amount
decimal balanceAfter
}
Balance {
uuid id PK
uuid accountId FK "unique"
decimal current
decimal available
decimal pending
decimal reserved
}
AuditLog {
uuid id PK
uuid organizationId FK
string entityType
uuid entityId
string action
jsonb previousState
jsonb newState
}
| Layer | Technology | Why This |
|---|---|---|
| Language | Java 21 | Virtual threads, LTS, bulletproof BigDecimal handling |
| Framework | Spring Boot 3.4.1 | Industry standard for financial backends |
| Database | PostgreSQL 16 | ACID compliance, JSONB metadata, trigger-based immutability |
| Cache | Redis 7 | Sub-millisecond balance lookups with smart invalidation |
| Frontend | React 18 + TypeScript | Type-safe dashboard, Vite for speed |
| Styling | Tailwind CSS | Consistent design system, zero runtime overhead |
| State | Zustand + TanStack Query | Client state + server state separated cleanly |
| Build | Gradle (Kotlin DSL) | Multi-module support, faster builds than Maven |
| Containerization | Docker Compose | Reproducible local dev, one-command setup |
| Deployment | Render (Blueprint) | Free tier, auto-deploy from render.yaml |
- Docker & Docker Compose
- Git
git clone https://github.com/Abdul-Rafy2005/Argent.git && cd Argent && docker compose up --buildThat's it. The entire stack boots in under 2 minutes:
| Service | URL | Purpose |
|---|---|---|
| Frontend | localhost:3000 | React Dashboard |
| Backend API | localhost:8080 | REST API |
| Swagger UI | localhost:8080/swagger-ui.html | API Explorer |
| Health Check | localhost:8080/actuator/health | Service Status |
| Field | Value |
|---|---|
admin@argent.com |
|
| Password | admin123 |
curl -X POST http://localhost:8080/api/v1/wallets \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"label": "Main Wallet",
"type": "CUSTOMER",
"metadata": {
"customerId": "cust_123",
"email": "user@example.com"
}
}'Response:
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"label": "Main Wallet",
"type": "CUSTOMER",
"status": "ACTIVE",
"environment": "SANDBOX",
"balance": 0,
"metadata": {
"customerId": "cust_123",
"email": "user@example.com"
},
"createdAt": "2026-07-21T22:00:00Z"
}
}curl -X POST http://localhost:8080/api/v1/deposits \
-H "X-Api-Key: <sandbox-key>" \
-H "Idempotency-Key: dep_001" \
-H "Content-Type: application/json" \
-d '{
"walletId": "550e8400-e29b-41d4-a716-446655440000",
"amount": "100.00",
"description": "Initial deposit",
"reference": "order_789"
}'Response:
{
"success": true,
"data": {
"id": "txn_a1b2c3d4",
"type": "DEPOSIT",
"status": "COMPLETED",
"amount": "100.00",
"idempotencyKey": "dep_001",
"ledgerEntries": [
{
"type": "DEBIT",
"amount": "100.00",
"accountType": "PLATFORM"
},
{
"type": "CREDIT",
"amount": "100.00",
"accountType": "CUSTOMER"
}
]
}
}curl -X POST http://localhost:8080/api/v1/transfers \
-H "X-Api-Key: <sandbox-key>" \
-H "Idempotency-Key: trf_001" \
-H "Content-Type: application/json" \
-d '{
"sourceWalletId": "wallet_sender",
"destinationWalletId": "wallet_receiver",
"amount": "25.50",
"description": "Payment for services"
}'curl "http://localhost:8080/api/v1/ledger/entries?walletId=wallet_123&page=0&size=20" \
-H "X-Api-Key: <sandbox-key>"Response:
{
"success": true,
"data": [
{
"id": "entry_001",
"type": "DEBIT",
"amount": "25.50",
"balanceAfter": "74.50",
"description": "Transfer to wallet_receiver",
"createdAt": "2026-07-21T22:05:00Z"
},
{
"id": "entry_002",
"type": "CREDIT",
"amount": "25.50",
"balanceAfter": "125.50",
"description": "Transfer from wallet_sender",
"createdAt": "2026-07-21T22:05:00Z"
}
],
"meta": {
"page": 0,
"size": 20,
"total": 2,
"totalPages": 1
}
}curl http://localhost:8080/api/v1/balances/wallet_123 \
-H "X-Api-Key: <sandbox-key>"{
"success": true,
"data": {
"walletId": "wallet_123",
"current": "125.50",
"available": "125.50",
"pending": "0.00",
"reserved": "0.00",
"currency": "USD"
}
}curl "http://localhost:8080/api/v1/statements?walletId=wallet_123&from=2026-07-01&to=2026-07-31" \
-H "X-Api-Key: <sandbox-key>" \
-o statement.csvArgent/
├── backend/ # Spring Boot application
│ ├── src/main/java/com/argent/
│ │ ├── common/ # Shared utilities & config
│ │ │ ├── config/ # Security, Redis, CORS, DataSource
│ │ │ ├── exception/ # Global exception hierarchy
│ │ │ └── response/ # ApiResponse, PagedResponse
│ │ └── module/ # Feature modules
│ │ ├── auth/ # JWT + API Key authentication
│ │ ├── wallet/ # Wallet CRUD & status management
│ │ ├── transaction/ # Transaction processing engine
│ │ │ └── engine/ # Strategy pattern engines
│ │ ├── ledger/ # Double-entry ledger system
│ │ ├── balance/ # Balance management & cache
│ │ ├── audit/ # Full audit trail
│ │ └── reporting/ # Reports & CSV export
│ ├── src/main/resources/
│ │ └── db/migration/ # 18 Flyway migrations (V1–V18)
│ └── src/test/ # 190 tests (unit + integration)
│
├── frontend/ # React dashboard
│ ├── src/
│ │ ├── pages/ # Dashboard, Wallets, Transactions...
│ │ ├── components/ # Reusable UI components
│ │ ├── api/ # Axios client + interceptors
│ │ ├── store/ # Zustand auth store
│ │ └── types/ # TypeScript definitions
│ └── nginx.conf # Production reverse proxy
│
├── render.yaml # Render Blueprint (deploy config)
├── docker-compose.yml # Local development stack
└── Docs/ # Architecture, PRD, Rules, Memory
| Category | Count | Framework |
|---|---|---|
| Unit Tests | ~128 | JUnit 5 + Mockito |
| Integration Tests | ~62 | JUnit 5 + Testcontainers |
| Frontend Tests | ~18 | Vitest + React Testing Library |
| Total | 190+ |
- Ledger Immutability: DB-level trigger rejection (UPDATE + DELETE both blocked)
- Double-Entry Balance: Every transaction produces balanced debit/credit pairs
- Environment Scoping: Sandbox key cannot access production data
- Idempotency: Duplicate requests return cached response (no double-charge)
- Optimistic Locking: Concurrent balance updates return 409 Conflict
- Platform Wallet: Lazy-created counterparty for deposits/withdrawals
- Audit Trail: Every write operation logged with before/after state
# Backend (all 190 tests)
cd backend && ./gradlew test
# Frontend
cd frontend && npm test| Variable | Description | Default |
|---|---|---|
SPRING_PROFILES_ACTIVE |
Spring profile | dev |
DATABASE_URL |
PostgreSQL connection | jdbc:postgresql://localhost:5432/argent_dev |
SPRING_DATA_REDIS_HOST |
Redis host | localhost |
ARGENT_JWT_SECRET |
JWT signing secret | (required) |
ARGENT_CORS_ALLOWED_ORIGINS |
Allowed CORS origins | http://localhost:3000 |
BACKEND_URL |
Backend URL for nginx proxy | http://localhost:8080 |
# Dashboard users (JWT)
Authorization: Bearer <access-token>
# Client applications (API Key)
X-Api-Key: <api-key>
The project includes a render.yaml Blueprint that provisions:
- PostgreSQL 16 (free tier)
- Redis 7 (free tier)
- Backend (Docker, Spring Boot)
- Frontend (Docker, React + Nginx)
# Deploy via Render Dashboard
# 1. Push to GitHub
# 2. Create Blueprint from render.yaml
# 3. Set ARGENT_CORS_ALLOWED_ORIGINS to your frontend URLdocker compose up --build -d
docker compose ps # Verify all services running
docker compose logs -f # Watch logs
docker compose down # Stop everything| Principle | Implementation |
|---|---|
| BigDecimal everywhere | No float or double for financial data. Ever. |
| UUID primary keys | All entities use UUID. No auto-increment integers. |
| Immutable ledger | PostgreSQL triggers prevent any UPDATE or DELETE on ledger_entries. |
| Idempotency required | All write endpoints accept Idempotency-Key header. |
| Environment isolation | API keys scoped to SANDBOX or PRODUCTION. Cross-env = 403. |
| Audit everything | Every write operation logged with who, what, when, before, after. |
| Fail loudly | Optimistic locking failures → 409. Invalid states → 400. No silent errors. |
- Multi-tenant organization management
- JWT + API Key authentication with RBAC
- Wallet CRUD with status lifecycle
- Double-entry ledger with immutable entries
- Transaction engine (Deposit, Withdrawal, Transfer, Refund, Adjustment)
- Balance management with Redis caching
- Full audit trail
- Reporting & CSV export
- React dashboard with 7 pages
- Docker Compose local development
- Render Blueprint deployment
- Webhook subscriptions for event notifications
- Reserved balances for hold/freeze scenarios
- Scheduled & recurring transfers
- Multi-currency support
- Rate limiting per API key
- Advanced analytics dashboard
- Stripe / payment gateway integration
- SDKs (Java, Node.js, Python)
- AML/KYC compliance hooks
- Enterprise SSO & custom roles
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing) - Open a Pull Request
- No new dependencies without discussion
- Tests must pass before merge
- BigDecimal only for financial amounts
- UUID only for primary keys
- No secrets in code or config files
MIT License - see LICENSE for details.
Built with care by Abdul-Rafy
Financial infrastructure that developers can trust.

