SDK Setup
Requirements, installation, environments and the credential provider for the Flutter SDK.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| Flutter SDK | Surfaces, isolation model and flows |
| Authentication | The user token accessToken() returns |
| Api Basics | X-Chain-Id and the identity headers the SDK sends |
Requirements
| Requirement | Value |
|---|---|
| Flutter / Dart | Flutter 3.19 or later, Dart 3.3 or later |
| iOS | 15 or later, Xcode 26.5 or later |
| Android | 6.0 (API 23) or later, mavenCentral() among the repositories |
Install
flutter pub add wirex_mobile_sdk# pubspec.yaml
dependencies:
wirex_mobile_sdk: ^0.3.15The package installs the native SDKs at its own version: the WirexMobileSDK pod from CocoaPods trunk and
com.wirexapp.baas:mobile-sdk from Maven Central. Flutter templates already list mavenCentral(). The
Android artifact merges the <queries> block Google Pay needs on Android 11 and later into your manifest —
no manual manifest change is required.
One import covers every surface:
import 'package:wirex_mobile_sdk/wirex_mobile_sdk.dart';Integration Values
| Value | What it is | Where it goes |
|---|---|---|
testMode | false — production, true — the Wirex test environment. Used by the card surfaces | WirexReveal.instance.configure, WirexCollect.configure, WirexThreeDS.instance.configure |
| Environment name | The same choice by name: 'production' or 'test', resolved with WirexWalletEnvironment.resolve(name). Used by the wallet | WirexWalletConfig(environment: …) |
chainId | The chain your integration operates on, sent as X-Chain-Id on every authenticated call. Required for Reveal and Collect — an empty value fails configure with invalid_environment | chainId: in configure(...) |
clientId | Optional, wallet only. Selects your branding on the Wirex-hosted checkout pages. Omit it unless Wirex assigned one. It is not a credential | WirexWalletConfig(clientId: …) |
The native SDK holds the environment hosts. No API takes a URL. An unknown environment name is an error —
the SDK never falls back to another environment.
Credential Provider
The SDK never issues credentials. You extend WirexCredentialProvider; the SDK calls it when it needs a
token, so tokens are never pushed in advance.
import 'package:wirex_mobile_sdk/wirex_mobile_sdk.dart';
class AppCredentials extends WirexCredentialProvider {
@override
Future<String> accessToken() async => fetchAccessToken();
@override
Future<String> actionToken(String scope) async {
final bool approved = await promptStepUp(scope);
if (!approved) throw const WirexDeclined();
return fetchActionToken(scope);
}
@override
String? get userEmail => null;
@override
String? get userWallet => '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18';
}| Member | Returns | Used by |
|---|---|---|
accessToken() | The user token from User Token Issuance (Login as User), sent as Authorization: Bearer | Reveal |
actionToken(scope) | An action_token for scope: 'cardData' (PAN, expiry, CVV) or 'pin' | Reveal |
userEmail | Sent as X-User-Email | Reveal |
userWallet | Sent as X-User-Wallet | Reveal |
- Exactly one identity. Return
userEmailoruserWallet, never both. Both set is a400from the API. - The SDK sends the bearer as-is and never retries a
401. Check the token's expiry inside
accessToken()and refresh before returning. A stale token surfaces asunauthorized. - Both action scopes are answered with a
GetCardDetailsaction token. Mint it through
POST /api/v1/confirmation/signature/verifyor the SMS OTP flow in
Card Details. The token is valid for 5 minutes. - Throw
const WirexDeclined()fromactionTokenwhen the user backs out. The SDK stops the operation and
reports it as declined, not as an error.
Collect Credentials
Collect needs no action token and takes no provider object. WirexCollect.configure takes an accessToken
callback plus the identity:
Future<void> configureCollect() => WirexCollect.configure(
chainId: '<chain id>',
accessToken: () async => fetchAccessToken(),
userWallet: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18',
testMode: true,
);Configure Once Per Process
- Cache the configure future and clear the cache on failure, so a later attempt retries.
- Derive
testModefrom your runtime environment when your app switches backends at runtime. - Identity is read at
configuretime. Re-runconfigurewhen the signed-in user changes. A stale or
absent identity header is a401, not a soft failure.
Screen Protection
Revealed values and the card form are drawn in a capture-proof layer: FLAG_SECURE on Android, a secure
layer on iOS. The iOS Simulator renders that layer blank.
Important:
dangerouslyDisableScreenProtection: trueonconfigure(...)— and on
WirexThreeDS.instance.configure(...)for the challenge — turns this off for simulator development and
automated UI tests. It defeats PCI screen-capture protection. Never ship it enabled.
Quickstart
One credential provider, both card surfaces configured once, a top-up card collected and an issued card
revealed. Each surface is covered in detail on its own page.
import 'package:flutter/widgets.dart';
import 'package:wirex_mobile_sdk/wirex_mobile_sdk.dart';
abstract class Auth {
Future<String> freshAccessToken();
Future<bool> confirmStepUp();
Future<String> mintActionToken(); // GetCardDetails, via POST /api/v1/confirmation/signature/verify
String get walletAddress;
}
abstract class Backend {
Future<void> topUp(String externalCardId); // estimate + execute with external_card_id
}
class AppCredentials extends WirexCredentialProvider {
AppCredentials(this._auth);
final Auth _auth;
@override
Future<String> accessToken() => _auth.freshAccessToken();
@override
Future<String> actionToken(String scope) async {
if (!await _auth.confirmStepUp()) throw const WirexDeclined();
return _auth.mintActionToken();
}
@override
String? get userEmail => null;
@override
String? get userWallet => _auth.walletAddress;
}
Future<void> configureSdk(Auth auth) async {
final credentials = AppCredentials(auth);
await WirexReveal.instance.configure(chainId: '<chain id>', credentials: credentials, testMode: true);
await WirexCollect.configure(
chainId: '<chain id>',
accessToken: auth.freshAccessToken,
userWallet: auth.walletAddress,
testMode: true,
);
}
class AddCard extends StatefulWidget {
const AddCard({required this.backend, super.key});
final Backend backend;
@override
State<AddCard> createState() => _AddCardState();
}
class _AddCardState extends State<AddCard> {
WirexCollectFormController? _controller;
WirexCollectState _state = const WirexCollectState.empty();
Future<void> submit() async {
final controller = _controller;
if (controller == null || !_state.isComplete) return;
final token = await controller.tokenizeTopUp(const WirexTopUpTokenizeParams(
billingDetails: WirexBillingDetails(line1: '1 High Street', zipCode: 'N1 9GU', city: 'London', country: 'GB'),
));
await widget.backend.topUp(token.cardId);
}
@override
Widget build(BuildContext context) => WirexCollectFormView(
profile: WirexCollectProfile.topup,
onCreated: (c) => _controller = c,
onChange: (s) => setState(() => _state = s),
);
}
Future<WirexRevealSession> revealPan(String cardId) async {
final session = await WirexReveal.instance.createSession(
cardId: cardId,
fields: const <WirexRevealField>[WirexRevealField.pan],
cardholderName: 'ALEX GREY',
);
await session.load();
return session; // render WirexRevealFieldView(session: session, field: WirexRevealField.pan)
}Updated about 3 hours ago

