The wallet contract
Five calls, amounts as digit strings, and one idempotency rule that the rest follows from.
For whoever writes or reviews the adapter.
Five calls
Contract version 1.0. Every call carries the version, so a mismatch is an error rather than a surprise.
- launch
- Exchange your token for a session. Once per game opening.
- balance
- What the player has now, in minor units. Read only.
- debit
- Take a stake for a round. Carries the stake source, cash or bonus.
- credit
- Pay a round out. Names the debit it pays against, and says so explicitly when the round returned nothing, so you can book a closed round rather than infer one.
- rollback
- Void a debit for a round that then did not happen. An adapter that cannot roll back is an adapter that can keep a stake for a round nobody played.
A stake
{
"contract_version": "1.0",
"transaction_id": "<ours, identical on every retry of this intent>",
"player_ref": "<from the launch>",
"game_id": "nordon.unicorn",
"round_id": "<ours>",
"amount_minor": "250",
"currency": "EUR",
"stake_source": "CASH"
}An answer
Two shapes, discriminated by one field, so there is no answer that is neither.
{ "ok": true, "balance_minor": "1000", "partner_reference": "<yours, optional>" }
{ "ok": false, "code": "INSUFFICIENT_FUNDS", "partner_reference": "<yours, optional>" }Idempotency is the rule everything else rests on
Every economic call carries a transaction identifier we choose, and it is the same on every retry of the same intent. A partner that answers slowly, or does not answer at all, has to be askable again without moving money twice.
So a repeated transaction identifier must not move the balance a second time. Answering it with the result of the first attempt is right; answering it with a duplicate error is acceptable and the kit accepts both. Moving the money again is the one thing that fails.
This is not a performance concern. It is the difference between a timeout costing a retry and a timeout costing a player their stake.
Amounts are digit strings
Every amount travels as a string of digits with no sign and no separators, in minor units at the scale the launch declared.
JSON numbers are doubles. A minor unit can exceed what a double holds exactly, and a balance that silently loses its last digit is a reconciliation meeting rather than a bug report. The conformance kit puts a value through your adapter that a double cannot hold and checks it comes back whole.
Error codes
Your platform has its own error vocabulary. The adapter maps it onto this one, because a code we do not recognise is a code we cannot act on.
| Code | What it means | What the game does |
|---|---|---|
| INSUFFICIENT_FUNDS | The player cannot cover the stake. | The round does not start. The player is told, and nothing is taken. |
| LIMIT_EXCEEDED | A limit on your side refuses this stake. | The round does not start, and the game does not retry at a lower amount on behalf of the player. |
| DUPLICATE_TRANSACTION | This transaction identifier was already settled. | Treated as success. It is the answer to a retry, not a failure. |
| WALLET_TIMEOUT | No answer in time. | We ask again with the same identifier. A stake already taken is rolled back if the round cannot start. |
| WALLET_UNAVAILABLE | Your wallet is refusing traffic. | The game stops accepting stakes rather than queueing them. |
| AUTH_INVALID | The launch token is not one you will honour. | The game does not open, and nothing is retried. |
| SESSION_EXPIRED | The session is no longer yours to honour. | The game closes the session and the lobby has to launch again. |
| RG_SUSPENDED | A responsible gambling control on your side stops this player. | Play stops immediately. Never softened into a generic failure. |
| MARKET_DISABLED | This jurisdiction may not be served. | The game does not open. |
| GAME_DISABLED | This title may not be served to this player. | The game does not open. |