> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.wuzzlo.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.wuzzlo.com/_mcp/server.

# Build the wallet API

# Build the wallet API

All wallet endpoints accept and return JSON. Except where the authentication contract explicitly uses HTTP `404`, wallet business results are returned with HTTP `200` and an exact status in the JSON body.

## Required routes

| Route                   | Called when                                         | Mutation                       |
| ----------------------- | --------------------------------------------------- | ------------------------------ |
| `POST /auth/login`      | Wuzzlo resolves a launch token to an operator user. | No                             |
| `POST /balance`         | Wuzzlo needs the current available balance.         | No                             |
| `POST /betrequest`      | A player places a stake.                            | Debit                          |
| `POST /resultrequest`   | A bet is settled or corrected.                      | Credit or approved correction  |
| `POST /rollbackrequest` | An eligible unpaid debit must be restored.          | Credit back the original debit |

The interactive API reference is authoritative for request fields, response schemas, and examples.

## Request processing order

For every signed wallet callback:

1. Read and retain the raw request bytes.
2. Read the Base64 value from the `Signature` header.
3. Verify RSA-SHA256 over the raw bytes using the Wuzzlo public key.
4. Parse JSON only after verification succeeds.
5. Validate `operatorId`, required fields, session ownership, amount, and precision.
6. Check durable `reqId` idempotency.
7. Check the durable `transactionId` lifecycle.
8. Lock the wallet and transaction records in one database transaction.
9. Apply at most one balance mutation and save the idempotency result atomically.
10. Return the calculated balance and exact status.

## Authentication

`POST /auth/login` validates the token created during launch and identifies the wallet user. Do not infer a user from an untrusted `userId` hint. Validate the authoritative token, its operator, expiry, and revocation state.

Success returns the canonical operator user:

```json
{
  "success": true,
  "status": 200,
  "userId": "1001",
  "username": "Player 1001",
  "currency": "INR",
  "expiresAtUtc": "2026-07-16T16:00:00Z"
}
```

## Balance

`POST /balance` is read-only. Validate the signed request, operator, session token, and user relationship before returning a JSON number:

```json
{
  "balance": 10000.00,
  "status": "OP_SUCCESS"
}
```

Do not return currency-formatted strings or binary floating-point approximations.

## Debit a bet

`POST /betrequest` subtracts `amount` exactly once. The critical identifiers have different roles:

* `reqId` identifies one delivery attempt and detects an exact replay.
* `transactionId` identifies the logical wallet transaction and its lifecycle.
* `roundId` groups activity in a game round.
* `userId` and `operatorId` establish ownership.

Within one database transaction, verify sufficient funds, insert the transaction record, subtract the balance, and store the response. A repeated `reqId` returns `OP_DUPLICATE_REQUEST` and the unchanged current balance. A new `reqId` reusing an existing debit `transactionId` returns `OP_DUPLICATE_TRANSACTION` without another debit.

## Settle a result

`POST /resultrequest` settles the referenced transaction exactly once. A win credits the authoritative `amount`; a loss may settle with zero. Validate that the original debit exists, belongs to the same user, and has not been rolled back or already settled.

Never calculate the payout again from client-side game information. The signed Wuzzlo settlement amount is authoritative after all validation succeeds.

## Roll back a debit

`POST /rollbackrequest` restores only an eligible, unsettled debit. Require the original `transactionId`, verify ownership and amount, then mark it rolled back and return the restored balance atomically. Do not roll back a settled transaction, an unknown transaction, or the same transaction twice.

## Minimum data model

Persist at least:

```text
WalletAccount(operatorId, userId, currency, balance, version)
WalletRequest(reqId, endpoint, requestHash, responseStatus, responseBalance, createdAtUtc)
WalletTransaction(transactionId, operatorId, userId, roundId, gameId,
                  debitAmount, creditAmount, state, createdAtUtc, updatedAtUtc)
```

Use a unique constraint for `reqId` in its agreed scope and another for the logical `transactionId`. Use row locking, serializable transactions, or optimistic concurrency with retry so simultaneous callbacks cannot overspend or double-credit.

## Response rules

* Always return `balance` as a JSON number.
* Successful mutation responses contain the post-transaction balance.
* Duplicate and rejected responses contain the unchanged balance unless the contract explicitly requires zero.
* Invalid signatures return HTTP `200`, `balance: 0`, and `status: OP_INVALID_SIGNATURE` without touching wallet or idempotency storage.
* Return only the status assigned to the matched scenario; a plausible alternative fails certification.