One contract, two front ends
The integration surface is deliberately small: a handful of JSON endpoints for authentication, balance, debit, credit and rollback. A player lobby and an agent back office both sit on top of it, and neither one requires the contract to change.
Keeping it that small is what makes a second front end cheap. Every field added for one consumer becomes a field the other has to ignore, so the contract stays at the level both genuinely share: an account, a balance, a round, a transaction.
The callback model in brief
Games do not hold balances. When a round starts, the game server calls the platform's wallet to debit the stake; when it resolves, it calls again to credit the win. The platform remains the single source of truth for money, and the game remains the single source of truth for outcomes.
That split is what lets the same game run against a direct wallet, an aggregator or an agent ledger without knowing which it is talking to.
Idempotency is the whole game
Every mutating call carries a transaction id that the wallet must treat as unique. A retried debit with an id already seen returns the original result rather than taking the stake twice. This is not an optimisation; it is the property that makes the network safe to be unreliable.
The failure it prevents is the expensive one. Without it, a timeout on a debit is genuinely ambiguous: the platform cannot tell a lost request from a lost response, and the reconciliation that follows is manual.
Where the agent tree enters the request
It mostly doesn't, and that is the point. The tree is resolved on the platform's side from the account in the request. The game asks to debit an account; whether that account sits under three agents or none is a question for the ledger, not for the game.
Commission accrual hangs off the settled transaction rather than the API call, which keeps the hot path short and the hierarchy logic in one place.
Errors, timeouts and the rollback path
Three outcomes need distinct handling: a refusal (insufficient funds, blocked account), a fault (the wallet is down), and a timeout. Only the first is a normal part of play. The other two put the round in an unresolved state that has to be closed deliberately.
That is what rollback is for: a call that voids a specific transaction id and returns the stake, safe to retry until it is acknowledged. A round that cannot resolve should end with the player's money back, not with a support ticket.
Versioning without breaking live integrations
Additive changes only, on a versioned path, with old fields kept until nothing reads them. A live integration is production infrastructure with its own release cycle; a breaking change is downtime scheduled by somebody else.
Where behaviour genuinely has to change, it goes behind a flag on the account record so it can be rolled out one integration at a time.
