SDK Setup
Requirements, installation, environments and the credential provider for the iOS SDK.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| iOS 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 |
|---|---|
| iOS | 15 or later — the framework's deployment target |
| Xcode | 26.5 or later. The binary ships a .swiftinterface that only the compiler that built it, or a newer one, can read |
| Package manager | Swift Package Manager or CocoaPods |
Install
Swift Package Manager
In Xcode, File › Add Package Dependencies… with https://github.com/wirexpay/baas-mobile-ios-sdk, or in
Package.swift:
.package(url: "https://github.com/wirexpay/baas-mobile-ios-sdk.git", from: "0.3.15")Add the WirexMobileSDK product to your app target.
CocoaPods
pod 'WirexMobileSDK', '~> 0.3.15'Both install the same WirexMobileSDK.xcframework attached to the release of each version. Swift Package
Manager verifies it against the checksum in Package.swift; CocoaPods against the sha256 in the podspec.
Integration Values
| Value | What it is | Where it goes |
|---|---|---|
testMode | false — production, true — the Wirex test environment. Used by the card surfaces | WirexReveal.create, WirexCollect.create, WirexThreeDS(testMode:) |
| Environment name | The same choice by name: "production" or "test", resolved with WirexWalletEnvironment.resolve(_:). 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: on create(...) |
clientId | Optional, wallet only. Selects your branding on the Wirex-hosted checkout pages. Leave nil unless Wirex assigned one. It is not a credential | WirexWalletConfig(clientId:) |
The SDK holds the environment hosts. No public API takes a URL. An unknown environment name is an error — the
SDK never falls back to another environment.
Trap:
WirexReveal.create(...)andWirexCollect.create(...)returnnil— they do not throw — when
the build has no endpoints for the environment orchainIdis empty. Call
WirexEnvironment.resolve(_:chainId:)to get the reason.
Credential Provider
The SDK never issues credentials. You implement WirexCredentialProvider; the SDK calls it when it needs a
token, so tokens are never pushed in advance.
import WirexMobileSDK
final class AppCredentials: WirexCredentialProvider {
var userEmail: String? { nil }
var userWallet: String? { "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18" }
func accessToken() async throws -> String {
try await session.freshAccessToken()
}
func actionToken(scope: WirexActionScope) async throws -> String {
guard try await promptStepUp(scope) else { throw WirexDeclined() }
return try await backend.mintActionToken(scope)
}
}| Member | Returns | Used by |
|---|---|---|
accessToken() | The user token from User Token Issuance (Login as User), sent as Authorization: Bearer | Reveal, Collect |
actionToken(scope:) | An action_token for scope: .cardData (PAN, expiry, CVV) or .pin | Reveal |
userEmail | Sent as X-User-Email | Reveal, Collect |
userWallet | Sent as X-User-Wallet | Reveal, Collect |
- 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 asWirexErrorwith code
unauthorized. - 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
WirexDeclined()fromactionToken(scope:)when the user backs out. The SDK stops the operation and
reports it as declined, not as an error. - Values are read per call. A refreshed token, a sign-out and a sign-in take effect on the next SDK
request. Keep the provider reading live state rather than a captured copy — no reconfiguration is needed
when the user changes.
Configure Once Per Process
- Create
WirexReveal,WirexCollectandWirexThreeDSonce and keep them; clear the cached instance when
create(...)returnsnil, so a later attempt retries. - Derive
testModefrom your runtime environment when your app switches backends at runtime. - Log at
debugin development builds andwarnin release — see
Logging.
Screen Protection
Revealed values and the card form are drawn in a capture-proof layer. The iOS Simulator renders that layer
blank. The 3-D Secure challenge is covered while the screen is recorded or mirrored.
Important:
dangerouslyDisableScreenProtection: trueoncreate(...)— and on
WirexThreeDS(dangerouslyDisableScreenProtection:)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 created once, a top-up card collected and an issued card
revealed. Each surface is covered in detail on its own page.
import UIKit
import WirexMobileSDK
protocol Auth: AnyObject {
func freshAccessToken() async throws -> String
func confirmStepUp() async -> Bool
func mintActionToken() async throws -> String // GetCardDetails, via POST /api/v1/confirmation/signature/verify
var walletAddress: String { get }
}
final class AppCredentials: WirexCredentialProvider {
private let auth: Auth
init(auth: Auth) { self.auth = auth }
var userEmail: String? { nil }
var userWallet: String? { auth.walletAddress }
func accessToken() async throws -> String { try await auth.freshAccessToken() }
func actionToken(scope: WirexActionScope) async throws -> String {
guard await auth.confirmStepUp() else { throw WirexDeclined() }
return try await auth.mintActionToken()
}
}
final class CardsViewController: UIViewController {
var auth: Auth!
var topUp: ((String) async throws -> Void)! // your backend: estimate + execute with external_card_id
private var form: WirexCollectForm?
private var session: WirexRevealSession?
override func viewDidLoad() {
super.viewDidLoad()
let credentials = AppCredentials(auth: auth)
guard let collect = WirexCollect.create(credentials: credentials, testMode: true, chainId: "<chain id>"),
let reveal = WirexReveal.create(credentials: credentials, testMode: true, chainId: "<chain id>") else {
return // no endpoints for this environment, or empty chainId
}
let form = collect.form(profile: .topup)
view.addSubview(form)
form.frame = CGRect(x: 16, y: 100, width: view.bounds.width - 32, height: form.naturalHeight)
self.form = form
if let session = try? reveal.session(cardId: "<card id>", fields: [.pan], cardholderName: "ALEX GREY") {
let panRow = WirexRevealFieldView()
panRow.bind(session: session, field: .pan)
view.addSubview(panRow)
panRow.frame = CGRect(x: 16, y: 100 + form.naturalHeight + 16, width: view.bounds.width - 32, height: panRow.naturalHeight)
self.session = session
Task { try await session.load() }
}
}
func addCard() {
guard let form else { return }
Task { @MainActor in
let token = try await form.tokenizeTopUp(WirexTopUpTokenizeParams(
billingDetails: WirexBillingDetails(line1: "1 High Street", zipCode: "N1 9GU", city: "London", country: "GB")))
try await topUp(token.cardId)
}
}
}Updated about 3 hours ago

