Overview
Overview
Wallet adapter is operator-owned server-to-server API used by OpenPoly to read balances and execute wallet mutations.
Ownership split
OpenPoly owns:
- Opinion quote, order, and redemption pipeline
- Polyflight native-IDR ticket and payment state
- signed webhook dispatch Operator owns:
- wallet balances
- debit and credit execution
- wallet auth secret
- wallet operation observability
Required contract paths
GET /balancePOST /debitPOST /creditPOST /debit-reversal
REST v1 has path-based mutation direction. OpenPoly selects the endpoint from the operation type; the JSON request body does not include a direction field.
The shared adapter config also has a credit_reversal_path field for compatibility with the internal direction enum, but the current Mini App trade/redemption processor does not call POST /credit-reversal in production flows.
Mutation request body
All mutation endpoints use the same JSON body shape:
{
"external_user_id": "operator-user-123",
"amount_minor": 10000,
"currency_code": "IDR",
"idempotency_key": "mini_app_order:1:debit",
"reason": "mini_app_buy_order",
"metadata": {}
}
OpenPoly sends every field below on every mutation request.
| Field | Type | Requirement | Notes |
|---|---|---|---|
external_user_id | string | Required | Stable operator-side user ID. |
amount_minor | integer | Required | Positive amount in the currency's minor unit; exactly 0 only for a valid Polyflight loss credit. Current REST v1 operators use IDR. |
currency_code | string | Required | Current REST v1 value: IDR; Polyflight requires IDR. |
idempotency_key | string | Required | Durable deduplication key. |
reason | string | Required | Business reason from the Opinion or Polyflight reason catalogue below. |
metadata | object | Required | Always sent; may be empty. Fields vary by flow and must be treated as additive. |
external_user_id is required for debit, credit, and reversal paths. OpenPoly resolves it from the Mini App launch/session user and sends it again on each wallet mutation.
Required metadata by flow
Metadata is part of the production contract. Operators must accept additional fields without rejecting the request.
| Flow | Reason | Required metadata | Optional additive metadata |
|---|---|---|---|
| Opinion buy debit | mini_app_buy_order | orderId, marketId | outcome, resolveDeadlineTs |
| Opinion buy compensation | mini_app_buy_compensation | orderId, marketId | balanceOperationId, resolveDeadlineTs |
| Opinion sell credit | mini_app_sell_order | orderId, marketId | outcome, resolveDeadlineTs |
| Opinion redemption credit | mini_app_redemption | redemptionItemId, marketId | resolveDeadlineTs |
| Polyflight purchase debit | mini_app_polyflight_purchase | product, operation, purchaseRequestId, flight/choice/native quote/timing/economy fields | Additive correlation fields |
| Polyflight purchase compensation | mini_app_polyflight_purchase_compensation | Purchase fields plus original debit reference | Additive correlation fields |
| Polyflight win credit | mini_app_polyflight_win | Common fields plus ticketId, original ticket quote, operation: "win", and result: "won" | Additive correlation fields |
| Polyflight loss credit | mini_app_polyflight_loss | Common fields plus ticketId, original ticket quote, operation: "loss", and result: "lost"; amount exactly zero | Additive correlation fields |
| Polyflight cancelled-ticket refund | mini_app_polyflight_refund | Common fields plus ticketId and original debit reference | Additive correlation fields |
The Opinion full-contract adapter test uses mini_app_wallet_adapter_test with test-only metadata. Polyflight full-contract checks use its native production reasons and metadata plus test context; their cleanup debit uses mini_app_wallet_adapter_test_cleanup. Neither creates a real product order or ticket.
See Native wallet contract for exact Polyflight keys, amounts, and metadata names. Polyflight purchase and payout amounts are already native IDR and do not use an Opinion FX snapshot.
The zero-amount loss credit durably acknowledges a losing result without changing the balance or creating monetary accounting entries. It uses the existing credit/idempotency/reconciliation lifecycle, not a separate endpoint or webhook event. Only this narrowly defined Polyflight loss contract accepts zero; generic wallet mutations and user-entered adapter-test amounts remain positive. Historical losses are not backfilled, and existing planned/dispatched requests keep their original evidence.
Core rules
- Every mutation must be idempotent.
- Direct mutation response
statusis required and must besucceeded,failed, orpending. Other response fields are optional unless an endpoint page says otherwise. - Optional receipt echoes are still checked: non-null
external_user_id,idempotency_key,currency_code, andamount_minormust identify the dispatched operation. A mismatch producesREST_V1_MUTATION_IDENTITY_MISMATCHand an internalunknown, not a successful or safely rejected payment. - Timeout, network failure, or an invalid HTTP
2xxresponse makes the primary mutation ambiguous. Without a valid REST v1 response body, HTTP408,429, or5xxis also ambiguous; another HTTP4xxis a failed mutation. - A valid response body with
status = succeeded,failed, orpendingis honored even on non-2xx.pendingremains ambiguous. - Do not retry an ambiguous primary debit or credit automatically. An operator may already have applied it. Reconciliation may authorize a retry only with the original key.
- Unknown wallet state must not be reported as success.
- OpenPoly order state and wallet state are not one atomic transaction.
- Manual review may be required when remote wallet result is unknown.
Recommended headers
Authorization: Bearer {opera...ken}
X-Polynion-Request-Id: req_...
X-Polynion-Timestamp: 2026-06-18T00:00:00.000Z
Optional request signing may also be enabled with X-Polynion-Signature.
Dashboard setup
Operator admins configure the adapter in Operator Dashboard → Integrations → Wallet Adapter.
The dashboard stores:
- base URL;
- bearer token;
- optional signing secret;
- timeout and path overrides.
Bearer and signing secrets are encrypted before storage. After saving, OpenPoly only shows whether a secret is configured; plaintext and encrypted payloads are never returned by the API.
Validation tests
The API exposes two modes through POST /api/operator/wallet-adapter/test, both requiring wallet_adapter:write:
- Connection — calls only
GET /balancefor a supplied external user ID. Integration UAT (/integrations/uat) exposes this as Run safe UAT checks, using saved settings. - Full contract — performs real wallet mutations through the API or API Playground with explicit confirmation. Opinion checks balance, debit, debit reversal, credit, second debit, then final balance. Polyflight has separate normal and failure/idempotency scenarios, including a zero-amount loss acknowledgement and duplicate in its normal sequence.
Wallet Settings saves configuration; it is not a full-contract test form. See Wallet adapter API for inputs and scenario outcomes. Stop and reconcile a test user whenever requires_manual_review is true; an expected failure scenario can meet its contract expectation while still needing manual review.
