SDK Setup
Requirements, installation, Expo configuration, environments and the credential provider for the React Native SDK.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| React Native SDK | Surfaces, isolation model and flows |
| Authentication | The user token getAccessToken returns |
| Api Basics | X-Chain-Id and the identity headers the SDK sends |
Requirements
| Requirement | Value |
|---|---|
| React Native | 0.74 or later, New Architecture enabled. The modules are TurboModules; the old architecture is not supported |
| React / Node | React 18 or later, Node 18 or later |
| iOS | 15 or later, Xcode 26.5 or later |
| Android | 6.0 (API 23) or later, mavenCentral() among the repositories |
| Expo | A development build. Expo Go cannot load native modules |
Install
npm install @wirexapp/[email protected]
cd ios && pod installThe package installs the native SDKs at its own version: the WirexMobileSDK pod from CocoaPods trunk and
com.wirexapp.baas:mobile-sdk from Maven Central. React Native templates already list mavenCentral().
Expo
Add the config plugin to app.json or app.config.ts:
{ "expo": { "plugins": ["@wirexapp/mobile-sdk"] } }The plugin raises the iOS and Android minimum versions where they are lower than the SDK's, and adds the
Android <queries> intents Google Pay needs on Android 11 and later.
Bare React Native
Without Expo, add the Google Pay intents to AndroidManifest.xml yourself:
<queries>
<intent><action android:name="org.chromium.intent.action.PAY" /></intent>
<intent><action android:name="org.chromium.intent.action.IS_READY_TO_PAY" /></intent>
<intent><action android:name="org.chromium.intent.action.UPDATE_PAYMENT_DETAILS" /></intent>
</queries>Without them, the Google Pay button reports unavailable on Android 11 and later.
Integration Values
| Value | What it is | Where it goes |
|---|---|---|
testMode | false — production, true — the Wirex test environment. Used by the card surfaces | WirexReveal.configure, WirexCollect.configure, WirexThreeDS.configure |
environment | The same choice by name: 'production' or 'test'. 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 | 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 implement one WirexCredentialProvider; the SDK calls it when it
needs a token, so tokens are never pushed in advance.
import { WirexDeclined, type WirexActionScope, type WirexCredentialProvider } from '@wirexapp/mobile-sdk';
declare function fetchAccessToken(): Promise<string>;
declare function fetchActionToken(scope: WirexActionScope): Promise<string>;
declare function userConfirmedStepUp(): Promise<boolean>;
export const credentials: WirexCredentialProvider = {
getAccessToken: () => fetchAccessToken(),
async getActionToken(scope) {
if (!(await userConfirmedStepUp())) throw new WirexDeclined();
return fetchActionToken(scope);
},
userEmail: undefined,
userWallet: '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18',
};| Member | Returns | Used by |
|---|---|---|
getAccessToken() | The user token from User Token Issuance (Login as User), sent as Authorization: Bearer | Reveal, Collect |
getActionToken(scope) | An action_token for scope: 'cardData' or 'pin' | Reveal |
userEmail | Sent as X-User-Email | Reveal, Collect |
userWallet | Sent as X-User-Wallet | Reveal, Collect |
- Exactly one identity. Set
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
getAccessTokenand 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
new WirexDeclined()fromgetActionTokenwhen the user backs out. The SDK stops the operation and
reports it as declined, not as an error. - Collect needs no action token.
WirexCollectCredentialProviderhas onlygetAccessTokenand the identity;
a fullWirexCredentialProvidersatisfies it.
Configure Once Per Process
import { WirexCollect, WirexReveal, WirexThreeDS } from '@wirexapp/mobile-sdk';
let configured: Promise<void> | undefined;
export const configureSdk = (testMode: boolean, dev: boolean): Promise<void> => {
configured ??= (async () => {
const logging = dev ? ('debug' as const) : ('warn' as const);
const shared = { testMode, chainId: '<chain id>', credentials, logging };
await Promise.all([
WirexReveal.configure(shared),
WirexCollect.configure(shared),
WirexThreeDS.configure({ testMode, logging }),
]);
})().catch((error: unknown) => { configured = undefined; throw error; });
return configured;
};- Cache the configure promise and clear the cache on failure, so a later attempt retries.
- Derive
testModefrom your runtime environment when your app switches backends at runtime.
Identity Changes
The identity headers are read once at configure time and then held natively. Push the new identity
whenever the signed-in user or wallet changes:
WirexReveal.setIdentity(userEmail, userWallet);
WirexCollect.setIdentity(userEmail, userWallet);Call it only on a real change — a store that updates often otherwise turns into bridge traffic.
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.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 * as React from 'react';
import { useEffect, useRef, useState } from 'react';
import { Button, View } from 'react-native';
import {
WirexCollect, WirexCollectFormView, WirexDeclined, WirexReveal, WirexRevealFieldView,
type WirexCollectFormHandle, type WirexCollectState, type WirexCredentialProvider,
type WirexRevealSession,
} from '@wirexapp/mobile-sdk';
declare const auth: {
freshAccessToken(): Promise<string>;
confirmStepUp(): Promise<boolean>;
mintActionToken(): Promise<string>; // GetCardDetails, via POST /api/v1/confirmation/signature/verify
walletAddress(): string;
};
declare const backend: { topUp(externalCardId: string): Promise<void> };
const credentials: WirexCredentialProvider = {
getAccessToken: () => auth.freshAccessToken(),
async getActionToken() {
if (!(await auth.confirmStepUp())) throw new WirexDeclined();
return auth.mintActionToken();
},
get userEmail() { return undefined; },
get userWallet() { return auth.walletAddress(); },
};
let configured: Promise<void> | undefined;
const configureSdk = (): Promise<void> => {
configured ??= (async () => {
const shared = { testMode: true, chainId: '<chain id>', credentials };
await Promise.all([WirexReveal.configure(shared), WirexCollect.configure(shared)]);
})().catch((error: unknown) => { configured = undefined; throw error; });
return configured;
};
export function AddCard() {
const form = useRef<WirexCollectFormHandle | null>(null);
const [state, setState] = useState<WirexCollectState | null>(null);
useEffect(() => { void configureSdk(); }, []);
const submit = async () => {
if (!form.current || !state?.isComplete) return;
const token = await form.current.tokenizeTopUp({
billingDetails: { line1: '1 High Street', zipCode: 'N1 9GU', city: 'London', country: 'GB' },
});
await backend.topUp(token.cardId); // estimate + execute with external_card_id
};
return (
<View>
<WirexCollectFormView profile="topup" onReady={(h) => { form.current = h; }} onChange={setState} style={{ width: '100%' }} />
<Button title="Add card" disabled={!state?.isComplete} onPress={() => void submit()} />
</View>
);
}
export function CardNumber({ cardId }: { cardId: string }) {
const [session, setSession] = useState<WirexRevealSession | null>(null);
const [height, setHeight] = useState<number | undefined>();
useEffect(() => {
let current: WirexRevealSession | null = null;
void configureSdk()
.then(() => WirexReveal.createSession({ cardId, fields: ['pan'], cardholderName: 'ALEX GREY' }))
.then((s) => { current = s; setSession(s); void s.load(); });
return () => { void current?.destroy(); };
}, [cardId]);
if (!session) return null;
return <WirexRevealFieldView session={session} field="pan" onNaturalHeight={setHeight} style={{ height }} />;
}Updated about 3 hours ago

