Back to projects

C++ collaboration ยท Transaction platform

MonteCarlo Wallet

A multi-tenant iGaming wallet designed around atomic money movement, provable history, replayable events, and resilient operation.

  • C++20
  • PostgreSQL
  • Kafka
  • Redis
  • High availability
Diagram showing reserve, play, settle, and cancel operations in the wallet system.
RoleContributing Programmer
Team2-person collaboration
LanguageC++20
FocusReliable money movement

Overview

Project concept

MonteCarlo Wallet is a multi-tenant, game-agnostic transaction service designed to support casino, betting, and poker products across multiple operators. Games remain stateless and never own a player's balance. Bets, wins, bonuses, deposits, withdrawals, and transfers pass through one authoritative ledger, with atomic and idempotent operations preventing partial money movement or duplicate processing.

Operators can connect their own payment, KYC, AML, and responsible-gaming providers through defined service boundaries. The platform concentrates on the guarantees a game supplier must provide: correct balances, traceable transaction history, recoverable events, and evidence that can be checked after a failure or audit.

Role

What I worked on

I developed the wallet as part of a two-person collaboration. My work included the game-facing transaction flow, reserve, settle, and cancel behaviour, wallet integration, testing, and debugging. I also worked with the wider architecture and learned how individual operations fit into a reliable service rather than treating the wallet as a simple balance counter.

The repository is shared work, so this page describes the system and my contribution without claiming sole authorship of every subsystem. Building and testing it gave me practical experience with idempotency, persistence, event delivery, reconciliation, recovery, and the stricter engineering expectations that apply when software moves money.

Design focus

Core pillars

Integrity

An append-only ledger is the source of truth. Cash, bonus, and locked buckets are accounted for separately, and atomic operations prevent half-completed transfers or double spending.

Provability

A SHA-256 audit chain makes historical changes detectable. Stored events can be replayed by identifier range or player, while reconciliation checks ledger, transaction, outbox, and liability consistency.

Compliance

Pre-commit policy checks support self-exclusion, player limits, AML holds, deposit limits, maker-checker withdrawal approval, and segregated balance reporting.

Resilience

A transactional outbox protects event delivery to Kafka. The platform also includes failover, point-in-time recovery, replay tools, health checks, metrics, and distributed tracing.

Implementation

Technical contributions

Transaction lifecycle

Worked with the path from a game request to wager reservation, result settlement or cancellation, and the player's refreshed balance.

Shared contracts

Helped connect the roulette and slot clients to consistent wallet operations and error states, keeping game logic separate from balance ownership.

Testing and debugging

Tested accepted, rejected, repeated, settled, and cancelled requests and investigated cases where game state and transaction state did not agree.

System understanding

Worked across a larger service architecture and learned how the ledger, cache, outbox, replay, reconciliation, and policy layers support the player-facing flow.

Process

Implementation notes

Atomic money model

C++ REST endpoints execute money movement in PostgreSQL using stable transaction identifiers. Reserve, settle, cancel, transfer, deposit, withdrawal, bonus, locked-funds, bet, and jackpot flows share the same ledger-based model.

Security and policy

Requests can be protected with HMAC-SHA256 signatures, timestamp freshness, nonce replay detection, TLS or mTLS, and rate limiting. A policy-provider boundary runs responsible-gaming and AML checks before money is committed.

Events and recovery

The database transaction writes both wallet state and an outbox event. Publishers deliver those events to Kafka, and operators can replay a range, a player's history, or failed deliveries without changing the original ledger entries.

Operations and scaling

The stateless service can run behind a load balancer. PostgreSQL connection pooling, Redis, Prometheus, Grafana, tracing, Patroni and etcd failover, HAProxy routing, and point-in-time recovery provide a production-oriented operating model.

Capacity

Measured and projected throughput

The repository includes a configurable chaos and load harness that reports operations per second and latency percentiles. The figures below deliberately separate a measured development result from infrastructure projections; they are not presented as certified production capacity.

ScenarioThroughputEvidence
Development machine, virtualized disk219-235 durable ops/sMeasured with 32 threads and synchronous commits
Single node with dedicated NVMe WAL2,000-10,000+ ops/sProjected from 0.1-0.5 ms durable commit latency; re-test required
Operator or player shardingScales with shard countArchitecture direction; not implemented or benchmarked

A typical bet uses two durable wallet operations: reserve and settle. On that basis, the storage projection corresponds to roughly 1,000-5,000 completed bet cycles per second before application and workload overhead. Production readiness would still require representative load and soak tests, security review, regulatory validation, and disaster-recovery exercises.

Code samples

Selected C++ and SQL systems

These trimmed excerpts come from the shared two-person codebase and illustrate the architecture I worked with. They are included to explain the system, not to claim sole authorship of every subsystem.

ReplayService.cpp

Replayable event delivery

Re-publishes stored outbox events and records each success or failure, supporting targeted recovery without rewriting the ledger.

ReplayResult ReplayService::replayByPlayer(
    const std::string& operatorId,
    int64_t playerId,
    std::size_t limit)
{
    return replayBatch(
        repository.fetchByPlayer(
            operatorId, playerId, limit),
        false);
}

auto published = producer.publish(
    topic, event.aggregate_key, event.payload);

repository.recordReplaySuccess(
    event.id, published, repairStatus);
WalletTypes.h

Explicit transaction contract

Names the supported money movements and failure states so clients and services share one transaction vocabulary.

enum class TxStatus : int32_t {
    PENDING = 0,
    COMPLETED = 1,
    FAILED = 2,
    CANCELLED = 3
};

enum class TxType : int16_t {
    RESERVE = 1,
    SETTLE = 2,
    CANCEL = 3,
    ADMIN_ADJUST = 4,
    DEPOSIT = 5,
    WITHDRAW = 6,
    BONUS_CREDIT = 7,
    TRANSFER = 12
};

enum class ErrorCode : int32_t {
    INSUFFICIENT_FUNDS = 2,
    DUPLICATE_TRANSACTION = 4,
    POLICY_DENIED = 11,
    REPLAY_DETECTED = 25
};
V015__audit_chain.sql

Tamper-evident audit chain

Folds each immutable ledger row into a SHA-256 chain so a later verification can detect altered history.

h := audit_genesis();

FOR r IN
    SELECT * FROM wallet_ledger
    WHERE id > v_last_to
    ORDER BY id
    LIMIT p_max_rows
LOOP
    h := sha256(h || audit_canon(r));
END LOOP;

INSERT INTO audit_chain(
    batch_no, from_id, to_id,
    row_count, prev_hash, chain_hash);

Reflection

Challenges

Money-related state needs stricter guarantees than ordinary game state. Retries, concurrent requests, partial failures, and interrupted rounds must never duplicate a payout, lose a wager, or leave an unexplained balance.

The architecture also has to preserve those guarantees while events, caches, policy checks, replicas, and external services fail independently. That makes observability, replay, reconciliation, and operational testing part of correctness rather than optional extras.

Learning

What I learned

I learned how atomic operations, idempotency keys, append-only accounting, outbox delivery, replay, and reconciliation work together when a game action changes persistent money state.

I also learned to separate responsibilities clearly: games own rules and presentation, while the wallet owns balances, transaction state, policy gates, and evidence that the result is correct.

Next steps

Future iterations

The next step is to benchmark representative production hardware and workloads, then add full operator or player sharding for horizontal database scale. Before real-money deployment, the system would also need independent security review, regulatory validation, long-running soak tests, and repeated recovery drills.