Openora Wallet: a modular crypto payments layer for modern iGaming platforms

Volodymyr Zakhovaiko
Volodymyr Zakhovaiko ·

As Openora continues to evolve into a complete infrastructure framework for iGaming platforms, we are expanding it with another building block: a crypto wallet layer designed specifically for operators and players.

Payments are one of the most complex parts of any iGaming platform. Crypto makes this challenge even more demanding - not because transferring funds is difficult, but because every transaction needs to be correctly attributed, reconciled, secured, and connected with the player account behind it.

That is why we started with crypto deposits and withdrawals. Building this layer first forces us to solve some of the hardest infrastructure problems: ownership, transaction attribution, custody, accounting, and operational control.

The goal is not to create another wallet provider. Openora is designed as a framework that gives operators the infrastructure they need while keeping them in control of their payment flows, data, and business logic. Here is how we approached building the wallet layer inside Openora. Learn what decisions shaped the architecture, and what remains ahead.

A balance is a row in a table

A player's balance does not live on-chain. It lives in our internal ledger, stored in wallet_balance per currency.

The chain is where the assets physically exist. The ledger is what the player sees and what the operator reconciles against. Once funds are pooled, the ledger becomes the only source of truth that knows who owns what.

Everything else follows from this principle. It also means the address-to-player mapping is the only way to attribute an incoming deposit. If that attribution is wrong, the wrong player receives the funds - and that is not something you solve with a simple retry.

One address per player? Depends on the chain

Our initial assumption was simple: every player should have their own deposit address for every currency. The reality is more complicated. Different blockchain networks solve address ownership and transaction attribution in different ways.

graphic assets

That is why wallet_deposit_address is keyed by player, currency, and network. The network part matters more than it may seem. The same address format can be valid across multiple chains, and using the address alone as an identifier creates a risk of miscrediting deposits as soon as an operator supports multiple networks.

Account-based chains provide one important advantage: multiple assets can share the same address. ETH, USDT ERC20, and USDC ERC20 can all use the same Ethereum address, meaning one vault per player can support multiple currencies on the same network. What we are avoiding is matching deposits based only on amount and timing on shared addresses. At any meaningful scale, that approach eventually creates attribution problems.

The deposit flow

So what actually happens when a player deposits?

The player requests a deposit address, and Openora returns the address already assigned to that player, currency, and network.

The player can send funds from any external wallet. On our side, nothing happens immediately - we don’t need to monitor every blockchain directly. The custody provider handles transaction monitoring and confirmation. Once the transaction reaches a terminal status, the vendor sends a webhook. That request is received through the operator's adapter, which transforms the provider-specific payload into a format Openora understands.

From that point, the process becomes part of Openora's responsibility: validate the transaction, match it using the address and network, identify the player, and credit the corresponding wallet_balance. Two parts of this flow are especially important.

deposit flow - openora wallet

Crediting and sweeping are two separate operations - and that separation is intentional. A player can play the moment the deposit is credited, while the coins sit where they landed until moving them is worth the gas.

Crediting and sweeping are two separate jobs, and that separation is the whole point. The player experience does not depend on when the underlying assets are moved. The only box that knows the custody provider's name is the adapter. Everything below the webhook looks identical, no matter who is holding the funds.

Where the money sits

architecure - where the money sits

Several principles shape this architecture:

  • Per-player vaults are created lazily, only when you request an account-based address. Creating them at registration buys nothing: an empty vault is never swept, and pre-creating thousands of them only burns API rate limits and clutters the workspace. 
  • Vault names contain internal player identifiers only - never personal data.
  • Player funds and company funds never share the same vault. This is a security requirement, not just an architectural preference.
  • The withdrawal pool consists of multiple vaults rather than one central pool, allowing payouts to run in parallel and preventing one blocked vault from affecting the entire queue.

It is worth being precise about where the cost actually sits, because it is easy to misunderstand. Additional UTXO addresses are not the expensive part - one shared vault can generate as many addresses as needed. The real cost on UTXO chains comes from fragmentation: a payout that gathers funds from hundreds of small addresses pays for every input it consumes. Sweeping is what gradually reduces this overhead by consolidating those inputs.

The component that genuinely introduces additional cost is creating a dedicated vault per player on account-based chains. That is why Openora creates vaults lazily, only when they are needed, and why gas management requires its own set of rules.

Sweeping is a gas problem

Moving player funds into an omnibus wallet costs gas, and gas is one of the least predictable variables in the entire system.

For example, sweeping USDT ERC20 requires ETH to pay transaction fees. On a normal day, the cost may be minimal. During network congestion, the same operation can become significantly more expensive. For smaller deposits, a sudden gas spike can make processing the transaction cost more than the deposited amount itself.

That is why sweeping rules should be configuration-driven rather than hardcoded.

The policy is simple:

  • Never sweep immediately after every deposit. Sweep only when maintaining withdrawal liquidity and operational efficiency.
  • Leave funds untouched until the balance justifies the transaction cost.
  • Skip the cycle above a gas ceiling, unless the omnibus wallet drops below its required liquidity level.
  • Never sweep dust. Minimum deposit values should be configured per network, not per currency.

On UTXO chains, sweeping works differently. It is relatively inexpensive and helps consolidate inputs, reducing future withdrawal costs.

Withdrawals

Payouts are processed from the withdrawal pool.

wallet_auto_withdrawal_config stores the relevant thresholds, and super-admins can adjust these settings at runtime.

If a withdrawal is below the configured threshold and the player has passed KYC verification, it can be processed automatically. Larger withdrawals - or withdrawals involving risk indicators such as high_risk or withdrawal_review - require manual approval before execution.

The exclusion rules are configurable as well,  so the automation stays fast without being blind. Payout addresses are stored on the platform and whitelisted with the custody provider. Limits are applied both per transaction and per day. A transaction-only limit is not enough - otherwise a player could simply split one large withdrawal into multiple smaller transfers.

Where Openora stops and operators stay in control

graph 3

Everything described above belongs to the Openora framework:

  • the internal ledger,
  • address management,
  • address-to-player attribution,
  • sweep policies,
  • thresholds,
  • risk rules,
  • audit trails.

That is the expensive part, and it is the same for every operator.

What operators build is the adapter layer. PaymentAdapter covers two different vendor shapes behind one interface: a synchronous PSP, and a custody rail where deposits are address-based and confirmed through webhooks. For the custody flow, the integration requires implementing issueDepositAddress and parseWebhook, binding it as PAYMENT_ADAPTER in a plugin, and connecting the provider's SDK.  In practice, the happy path is roughly a hundred lines of code. Idempotency keys, rate-limit backoff, and webhook signature verification add more. 

We built our first implementation against Fireblocks. Openora itself knows nothing about that provider - and it should not. Custody credentials are among the most sensitive assets an operator manages, so a framework has no reason to sit in that path.

Operators should be able to choose a different provider, run multiple providers at the same time, or migrate away in the future without rebuilding their wallet infrastructure. You change a binding, not the wallet.

Open source by design

Everything described in this article represents infrastructure logic - not a percentage-based payment business. The ledger, address management, address-to-player attribution, sweep policies, withdrawal rules, and audit trail are the parts that require significant engineering effort and that every crypto-enabled operator eventually needs to build.

Openora makes this infrastructure available as open source. There is no GGR share, no transaction fee, and no platform lock-in. You can self-host the framework, keep the revenue, and choose the custody provider that fits their business.

We intentionally stay away from the money path. A framework that takes a percentage of every transaction has a built-in incentive to make leaving difficult. Openora is designed around the opposite principle: giving operators more control, not less.

Still open

The wallet layer is still evolving.

Sweep scheduling and batched payouts are currently in progress. Batch transactions are mainly relevant for UTXO-based assets, while account-based chains require individual transactions per recipient. Fiat support will come later, once the crypto payment flow is fully mature and proven end to end.

The code is public while we build it - including the decisions, iterations, and mistakes along the way. That is exactly the point of building Openora in the open.