Phoenix Prediction Docs

Testing

Wallet integration cases to verify before production

Run these cases before enabling production traffic.

The contract lets you hold a separate reserved bucket, but it does not require one. Where a case below mentions reserved, it is observable only if your wallet keeps that bucket. The reference wallet in the demo operator does not: it collapses reserve_cash into an immediate debit, so available drops by the full amount and reserved always reports 0. Both shapes are valid; assert against available to stay portable.

Reservation Cases

CaseExpected result
Valid reserve_cashavailable decreases by the reserved amount and Phoenix can open the order. reserved increases only if your wallet holds a reservation bucket (the reference wallet debits immediately and keeps reserved at 0)
Duplicate reserve_cash idempotency keyStored result is returned and the cash is not reserved again
Insufficient fundsHTTP 422 with code: "insufficient_funds"
Invalid signatureHTTP 401, no balance change
Malformed bodyHTTP 400 or safe validation error, no balance change

Capture and Release Cases

CaseExpected result
Partial capture_cashFinalizes the captured cash. available does not change again (the player already lost it at reservation). If you hold a reservation bucket, reserved decreases by the captured amount; in the immediate-debit reference wallet, capture is a ledger no-op that returns the current balance
Duplicate capture_cash idempotency keyStored result is returned and cash is not captured again
release_cash after cancellationUnused cash returns to available. If you hold a reservation bucket, reserved decreases by the released amount; the reference wallet applies it as a plain credit to available
Duplicate release_cash idempotency keyStored result is returned and cash is not released again
Capture plus release exceeds reservationHTTP 422, no balance change
Invalid signatureHTTP 401, no balance change

Credit Cases

CaseExpected result
Sell proceeds credit_cashavailable increases by the proceeds
Settlement payout credit_cashavailable increases by the payout
Refund or void credit_cashavailable increases by the refund
Duplicate credit_cash idempotency keyStored result is returned and cash is not credited again

Retry Cases

Phoenix retries 408, 429, 503, transport errors, and timeouts with the same idempotency key, and also retries a 2xx body it cannot classify (no recognised status verb) because commit state is unknown. Idempotency keys make any already-applied move a no-op on the retry.

CaseExpected result
Temporary 503Phoenix retries with the same idempotency key and later succeeds
Temporary 429 or 408Same: Phoenix retries with the same idempotency key and later succeeds
Settlement move (capture_cash / release_cash / credit_cash) errors transientlyPhoenix retries the whole settlement batch until it succeeds; settlement moves are never declined, only retried
reserve_cash business rejection (422)Terminal: Phoenix declines the order and does not retry

Status Cases

A status probe is the same signed envelope as the original move (full body, same idempotency-key header) sent to /wallet/transactions/status. Report the recorded outcome for that key without moving money.

CaseExpected result
Completed operation statusStatus returns the recorded accepted/rejected outcome and balance for that idempotency key
Unknown idempotency keyReturn a safe response. Operators may report accepted, rejected, or an explicit unknown; the reference wallet returns accepted for both known and unknown keys, and Phoenix reconciles unknowns from its own event log on the next retry

Launch Cases

Test wallet behavior together with iframe launch:

  • Visitor can browse but cannot place an order.
  • Player can place an order after token upgrade.
  • Token refresh keeps the session alive.
  • Deposit flow returns focus to the iframe.
  • Phoenix signature verification passes in the sandbox environment.

Automated self-service tester

Phoenix runs the reservation, capture/release, credit, status, and security cases above against your wallet for you, so you can confirm your integration before launch without hand-driving each call. It signs every probe with the platform key (exactly as production does), drives the cases against your wallet base URL, and returns a per-check verdict.

Authenticate with the same Ed25519 request signature as the rest of the operator API (X-Operator-Code, X-Operator-Environment, X-Signature-Timestamp, X-Signature). Target your sandbox environment: the run moves real balances and then restores them, so it is safe to repeat.

POST /operator/api/wallet-test
Content-Type: application/json

{
  "player_id": "<a sandbox player external id with funds>",
  "player_id_b": "<optional second player, for the cross-player isolation check>",
  "currency": "USD",
  "amount": "1.00"
}

The response carries the verdict and every check (passed, failed, skipped, not_applicable), with the request/response transcript per check so a failure points at the exact bytes. The launch gate cleareds only when every check that ran passed against the current suite version.

GET /operator/api/wallet-test/runs

returns your recent runs and whether your latest run cleared the gate.

The test player needs at least 10x the test amount available. A check marked not_applicable (for example the over-reservation case on an immediate-debit wallet) does not count against the gate.