Requirement Notes Node.js ≥ 18 Required runtime viem ^2.0.0Peer dependency — install separately CLIENT_ID / CLIENT_SECRETOAuth2 credentials from Wirex partner onboarding COMPANY_IDYour company identifier in hex format
@wirexapp/wpay-baas-sdk is the official TypeScript/JavaScript SDK for the Wirex BaaS platform. It wraps the full OAuth2 token flow, all BaaS REST endpoints, and on-chain Account Abstraction wallet operations (ZeroDev Kernel V3.1) into a single typed package.
The SDK is published on npm: @wirexapp/wpay-baas-sdk .
npm install @wirexapp/wpay-baas-sdk
npm install viem
TypeScript
import { createSDK, type WirexPaySDK } from '@wirexapp/wpay-baas-sdk';
const sdk: WirexPaySDK = await createSDK({
env: 'dev', // 'dev' → sandbox | 'prod' → production
companyId: process.env.COMPANY_ID, // hex company ID from Wirex onboarding
getMainWalletClient: () => mainWalletClient,
enableLogging: false, // optional
logger: customLogger, // optional, requires enableLogging: true
});
Parameter Type Required Description env'dev' | 'prod'Yes 'dev' → api-baas.wirexapp.tech + Base Sepolia (84532); 'prod' → api-baas.wirexapp.com + Base Mainnet (8453)companyIdstringYes Company identifier in hex format getMainWalletClient() => MainWalletClientYes Factory returning the user's embedded wallet client enableLoggingbooleanNo Enable SDK-level logging (default false) loggerLoggerNo Custom logger instance
getMainWalletClient must return an object implementing this interface:
TypeScript
interface MainWalletClient {
address: string;
getEthereumProvider(): Promise<{
request(args: { method: string; params?: unknown[] }): Promise<unknown>;
}>;
}
The EIP-1193 provider must handle at minimum:
RPC Method Used for eth_accountsResolve the signer address personal_signSign messages (SIWE, auth challenges) eth_signTypedData_v4Sign typed data (UserOperations, EIP-712)
TypeScript
import { createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { baseSepolia } from 'viem/chains';
import type { MainWalletClient } from '@wirexapp/wpay-baas-sdk';
export function createWalletClientFromKey(
privateKey: `0x${string}`,
rpcUrl: string,
): MainWalletClient {
const account = privateKeyToAccount(privateKey);
const client = createWalletClient({ account, chain: baseSepolia, transport: http(rpcUrl) });
return {
address: account.address,
getEthereumProvider: async () => ({
request: async ({ method, params = [] }) => {
if (method === 'eth_accounts') return [account.address];
if (method === 'personal_sign') {
const [data] = params as [`0x${string}`, string];
return client.signMessage({ account, message: { raw: data } });
}
if (method === 'eth_signTypedData_v4') {
const [, json] = params as [string, string];
const { domain, types, primaryType, message } = JSON.parse(json);
const { EIP712Domain: _, ...filteredTypes } = types;
return client.signTypedData({ account, domain, types: filteredTypes, primaryType, message });
}
return (client.transport as any).request({ method, params });
},
}),
};
}
TypeScript
import type { MainWalletClient } from '@wirexapp/wpay-baas-sdk';
// Inside a React component / hook where usePrivy is available:
function getPrivyWalletClient(wallet: ConnectedWallet): MainWalletClient {
return {
address: wallet.address,
getEthereumProvider: () => wallet.getEthereumProvider(),
};
}
The SDK supports three authentication paths depending on your integration type.
Used when your backend calls the Wirex API on behalf of users. This is the standard B2B partner flow.
TypeScript
// Step 1 — Exchange company credentials for an Auth0 token
const tokenRes = await sdk.api.baas.token.getToken({
grant_type: 'client_credentials',
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
audience: 'https://api-business.wirexpaychain.tech', // same for dev and prod
});
// Step 2 — Prime SDK auth state with the company token + user EOA wallet
sdk.auth.setAuthState(
{ access_token: tokenRes.access_token, expires_at: tokenRes.expires_at },
userEoaWallet,
);
// Step 3 — (After user registration) Login to get a per-user PSI token.
// All sdk.api.baas.* calls automatically use the PSI token after this.
await sdk.auth.login({
authorizationToken: tokenRes.access_token,
userWallet: userEoaWallet,
});
console.log('Authenticated:', sdk.auth.isAuthenticated()); // true
console.log('Token:', sdk.auth.getAccessToken());
Used in consumer apps where users authenticate through Privy.
TypeScript
import { getAccessToken, useIdentityToken } from '@privy-io/react-auth';
const accessToken = await getAccessToken();
const { identityToken } = useIdentityToken();
// Login (existing user)
const authResponse = await sdk.auth.login({
privy: { accessToken, identityToken },
});
// Or register (new user)
const userResponse = await sdk.auth.registerUser({
privy: { accessToken, identityToken },
country: 'FR',
});
Used when you issue your own tokens or use an auth provider other than Privy.
TypeScript
// Login
const authResponse = await sdk.auth.login({
authorizationToken: 'your-jwt-token',
userWallet: userEoaWallet,
});
// Register
const userResponse = await sdk.auth.registerUser({
authorizationToken: 'your-jwt-token',
country: 'FR',
});
After login the SDK holds the token internally. Use these methods to inspect or manage auth state:
Method Returns Description sdk.auth.isAuthenticated()booleanWhether a valid token is held sdk.auth.getAccessToken()string | nullCurrent token, or null if expired sdk.auth.ensureAuthenticated()voidThrows AuthenticationError if not authenticated sdk.auth.getAuthHeaders()object{ Authorization, X-User-Wallet } headerssdk.auth.getUserWallet()stringCurrent user wallet address sdk.auth.setUserWallet(wallet)voidUpdate user wallet address sdk.auth.setAuthState(response, wallet?)voidRestore auth state from a stored response sdk.auth.clearAuth()voidClear all auth state (logout)
Complete flow from fresh account to authenticated user. Steps 1–5 are on-chain; steps 6–7 are API calls.
sequenceDiagram
participant App as Your App
participant SDK as wpay-baas-sdk
participant Chain as Base Blockchain
participant BaaS as Wirex BaaS
Note over App,Chain: On-chain setup
App->>SDK: getSmartWalletAddress()
SDK-->>App: smartWalletAddress
App->>SDK: isPolicyInstalled() / isExecutorInstalled()
SDK-->>App: false / false
App->>SDK: signInPolicyAndExecutor()
SDK->>Chain: UserOperation (install executor + policy)
Chain-->>SDK: tx receipt
App->>SDK: registerInAccounts()
SDK->>Chain: createUserAccountWithWallet(companyId)
Chain-->>SDK: tx receipt
Note over App,BaaS: API registration
App->>SDK: createUser({ email, country, wallet_address })
SDK->>BaaS: POST /api/v1/user
BaaS-->>SDK: user_id
App->>SDK: auth.login({ authorizationToken, userWallet })
SDK->>BaaS: POST /api/v1/user/authorize
BaaS-->>SDK: PSI token (stored internally)
SDK-->>App: AuthResponse
TypeScript
const smartWalletAddress = await sdk.crypto.wallet.getSmartWalletAddress();
console.log('Smart Wallet:', smartWalletAddress);
TypeScript
const isPolicyInstalled = await sdk.crypto.accountAbstraction.isPolicyInstalled();
const isExecutorInstalled = await sdk.crypto.accountAbstraction.isExecutorInstalled();
TypeScript
if (!isPolicyInstalled || !isExecutorInstalled) {
const result = await sdk.crypto.accountAbstraction.signInPolicyAndExecutor();
console.log('TX hash:', result?.receipt?.transactionHash);
}
To install separately:
TypeScript
await sdk.crypto.accountAbstraction.signInPolicy(); // ExecutionDelayPolicy only
await sdk.crypto.accountAbstraction.signInExecutor(); // FundsManagementExecutor only
Required only when using Privy as the auth provider. Skip for JWT / OAuth2 flows.
TypeScript
import { useLinkWithSiwe } from '@privy-io/react-auth';
const { generateSiweMessage, linkWithSiwe } = useLinkWithSiwe();
const smartWalletClient = await sdk.crypto.wallet.getSmartWalletClient();
const message = await generateSiweMessage({
address: smartWalletClient.account.address,
chainId: 'eip155:' + smartWalletClient.chain?.id,
});
const signature = await smartWalletClient.signMessage({
account: smartWalletClient.account,
message,
});
await linkWithSiwe({
message,
chainId: 'eip155:' + smartWalletClient.chain?.id,
signature,
walletClientType: 'smart_wallet',
connectorType: 'kernel',
});
TypeScript
const isRegistered = await sdk.crypto.accountContract.isWalletInAccounts();
if (!isRegistered) {
await sdk.crypto.accountContract.registerInAccounts();
}
For B2B (corporate) accounts:
TypeScript
await sdk.crypto.accountContract.registerInCorporateAccounts();
TypeScript
// Option A — Privy
const user = await sdk.auth.registerUser({
privy: { accessToken, identityToken },
country: 'FR',
});
// Option B — JWT / OAuth2
const user = await sdk.auth.registerUser({
authorizationToken: companyToken,
country: 'FR',
});
console.log('User ID:', user.id);
For company/corporation registration:
TypeScript
await sdk.auth.registerCompany({
authorizationToken: companyToken,
companyAddress: '0x...',
companyName: 'Acme Corp',
registrationCountry: 'GB',
registrationNumber: '12345678',
});
TypeScript
// Option A — Privy
const auth = await sdk.auth.login({
privy: { accessToken, identityToken },
});
// Option B — JWT / OAuth2
const auth = await sdk.auth.login({
authorizationToken: companyToken,
userWallet: userEoaAddress,
});
console.log('Access token:', auth.access_token);
// All subsequent sdk.api.baas.* calls now use this token automatically.
Method Description login(params)Login retail user (Privy or JWT) loginCompany(params)Login company/corporation registerUser(params)Register retail user registerCompany(params)Register company/corporation isAuthenticated()Check if a valid token is held getAccessToken()Returns current token or null if expired ensureAuthenticated()Throws AuthenticationError if not authenticated getAuthHeaders()Returns { Authorization, X-User-Wallet } headers getUserWallet()Get current user wallet address setUserWallet(wallet)Set user wallet address setAuthState(response, wallet?)Manually restore auth state clearAuth()Clear all auth state (logout)
Method Description getToken(request)Exchange client_credentials for an access token
TypeScript
const tokenRes = await sdk.api.baas.token.getToken({
grant_type: 'client_credentials',
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
audience: 'https://api-business.wirexpaychain.tech',
});
// → { access_token, token_type: 'Bearer', expires_at }
Method Description getUser()Get current user (v1) getUserV2()Get current user with capabilities array createUser(request)Create a new user ({ email, country, wallet_address }) createRetailUser(request)Create a retail user validateRetailUser()Validate retail user authorizeUser(params)Authorize user — returns PSI token updatePhoneNumber(request)Update phone number confirmPhoneNumber(request)Confirm phone number via OTP uploadDocument(formData)Upload KYC document getVerificationToken()Get verification token (Wirex-hosted KYC) getVerificationLink()Get verification link (Wirex-hosted KYC) getSharingToken(request)Get SumSub sharing token setSharingToken(request)Set SumSub sharing token updateFreshdeskId(request)Update Freshdesk ID
TypeScript
// Check user capabilities (e.g. card issuance eligibility)
const user = await sdk.api.baas.user.getUserV2();
const caps = user.capabilities;
const canIssueVirtualCard = caps.find(c => c.type === 'VisaVirtualCard')?.status === 'Active';
Method Description getWallet(request?)Get wallet by ID or default wallet getWallets()Get all user wallets createGlobalWallet(request)Create a global (multi-chain deposit) wallet
TypeScript
const wallet = await sdk.api.baas.wallet.getWallet();
// Response: { balances: [{ token_symbol, token_address, balance }] }
const balances = wallet.balances;
Method Description issueVirtualCard(request)Issue a virtual card ({ card_name }) issuePlasticCard(request)Issue a physical card getCards(request?)List cards — returns { data: Card[] } getCard(cardId)Get card details — { id, status, card_data: { card_name, format } } getCardTransactions(request?)Get card transactions activateCard(cardId, request)Activate card blockCard(cardId)Block card unblockCard(cardId)Unblock card closeCard(cardId)Close card (terminal state) getCardDetails(cardId, request)Get PAN and expiry — requires action token getCardCvv(cardId, request)Get CVV — requires action token getCardPin(cardId, request)Get PIN — requires action token (physical cards) changeCardLimit(cardId, request)Change spending limit changeCardName(cardId, request)Rename card estimateCardTransfer(request)Estimate card top-up executeCardTransfer(request)Execute card top-up estimateCardWithdrawal(cardId, request)Estimate withdrawal from card executeCardWithdrawal(cardId, request)Execute withdrawal from card getActive3dsRequests()Get pending 3DS challenges — returns Array approve3dsRequest(transactionId)Approve a 3DS challenge decline3dsRequest(transactionId)Decline a 3DS challenge getDeliveryCountries()List countries available for physical card delivery getDeliveryMethods(country)Get delivery methods for a country getOrderFees(type, country)Get card order fees createOrderFeesInvoice(type, country, request)Create invoice for card order fees
Sensitive card data (PAN, CVV, PIN) requires an action token obtained via SMS OTP:
TypeScript
// 1. Request SMS OTP
const session = await sdk.api.baas.confirmation.requestSms({
action_type: 'GetCardDetails',
});
// 2. Verify OTP → receive action token (UAT code is '000000')
const tokenRes = await sdk.api.baas.confirmation.verifySms({
session_id: session.session_id,
code: userInputCode,
});
const actionToken = tokenRes.token;
// 3. Use action token to retrieve sensitive data
const details = await sdk.api.baas.cards.getCardDetails(cardId, { action_token: actionToken });
// → { card_number: '4111111111111111', expiry_date: '12/2028' }
const cvv = await sdk.api.baas.cards.getCardCvv(cardId, { action_token: actionToken });
// → { cvv: '123' }
Method Description requestSms(request)Request SMS OTP ({ action_type }) — returns { session_id } verifySms(request)Verify OTP ({ session_id, code }) — returns { token } verifySignature(request)Verify signature for action confirmation
Method Description getAccounts(request?)Get bank accounts — returns { data: BankAccount[] } activateAccount(request)Activate bank account estimateTransfer(request)Estimate bank transfer executeTransfer(request)Execute bank transfer estimateCorridorTransferV2(corridor, request)Estimate SEPA / corridor transfer executeCorridorTransferV2(corridor, request)Execute SEPA / corridor transfer
TypeScript
// Estimate a SEPA transfer
const estimate = await sdk.api.baas.bank.estimateCorridorTransferV2('SEPA', {
destination_amount: 100,
recipient_id: recipientId,
recipient_payment_details_id: paymentDetailsId,
account_id: eurAccountId,
});
// → { estimation_id, fee, ... }
Method Description getRecipients(request?)List recipients — returns { data: Recipient[] } getRecipientById(recipientId)Get recipient — { id, personal_info: { first_name, last_name } } getRecipientsByCatalog(request)Filter by catalog getRecipientsByCurrency(request)Filter by currency getRecipientsByName(request)Search by name getRecipientsByType(request)Filter by type createRecipient(request)Create individual recipient createRecipientV2(request)Create recipient (v2) updateRecipient(recipientId, request)Update recipient deleteRecipient(recipientId)Delete recipient createRecipientPaymentDetails(recipientId, request)Add payment details (SEPA/SWIFT/etc.) updateRecipientPaymentDetails(recipientId, detailsId, request)Update payment details deleteRecipientPaymentDetails(recipientId, detailsId)Delete payment details incrementRecipientUsage(recipientId)Increment usage counter
TypeScript
// Create recipient with SEPA payment details
const recipient = await sdk.api.baas.recipients.createRecipient({
first_name: 'John',
last_name: 'Doe',
is_business: false,
});
await sdk.api.baas.recipients.createRecipientPaymentDetails(recipient.id, {
type: 'Sepa',
currencies: ['EUR'],
sepa: {
iban: 'DE89370400440532013000',
bic: 'COBADEFFXXX',
},
});
Method Description getFeed(request?)Get activity feed — returns { data: Activity[] } getById(request)Get activity item by ID getFullStatement(request)Get statement for a date range ({ start, end } — Unix timestamps in seconds) getRequestedErcWithdrawals()Get pending ERC-20 withdrawal requests
TypeScript
// Paginated feed
const feed = await sdk.api.baas.activity.getFeed({ page_number: 0, page_size: 20 });
// Statement for the last 30 days
const statement = await sdk.api.baas.activity.getFullStatement({
start: Math.floor((Date.now() - 30 * 24 * 60 * 60 * 1000) / 1000),
end: Math.floor(Date.now() / 1000),
});
Activity item type values: CardTransaction, BankTransfer, Crypto.
Activity item direction values: Inbound, Outbound.
Method Description getWithdrawalRequests()List pending ERC-20 withdrawal requests
Used in the two-phase transfer flow to retrieve the call_data needed for phase 2.
Method Description getRates(request)Get exchange rates
Method Description getConfig()Get client app config
Method Description getSmartWalletAddress()Get the Smart (AA) wallet address getSmartWalletClient()Get Kernel client with ExecutionDelayPolicy getSmartWalletClientWithoutPolicy()Get Kernel client without policy getSmartAccountSigner()Get signer for Smart Account getMainWalletClient()Get the main (EOA) wallet client sendUserOperationsAndWaitReceipt(callData, client?)Send UserOperation and wait for receipt sendUserOperationsWithNonceAndWaitReceipt(callData, nonce)Send UserOperation with a specific nonce invalidateLocalCache()Clear cached wallet clients (force re-init)
Method Description isPolicyInstalled()Check if ExecutionDelayPolicy is installed as root validator isExecutorInstalled()Check if FundsManagementExecutor is installed signInPolicyAndExecutor()Install both in one UserOperation signInPolicy()Install ExecutionDelayPolicy only signInExecutor()Install FundsManagementExecutor only
Method Description isWalletInAccounts()Check registration in retail Accounts contract registerInAccounts()Register wallet (retail) isWalletInCorporateAccounts()Check registration in corporate Accounts contract registerInCorporateAccounts()Register wallet (B2B)
Method Description makeFullErc20Transfer(params)Both phases in one call makeErc20TransferFirstPart(params)Phase 1: submit withdrawal request makeErc20TransferFinal(callData)Phase 2: execute after delay expires makeErc20TransferSynthetic(params)Transfer synthetic (wrapped) tokens batchTransfer(params)Multiple transfers in one UserOperation
ERC-20 withdrawals use a two-phase flow enforced by ExecutionDelayPolicy. A minimum delay (~3 seconds in sandbox, longer in production) separates the request and execution.
TypeScript
interface Erc20TransferParams {
tokenAddress: `0x${string}`;
recipientAddress:`0x${string}`;
amount: number;
}
interface BatchTransferItem {
to: `0x${string}`;
amount: string;
token: string; // e.g. 'USDC'
guid: string; // unique transfer identifier for deduplication
}
Method Description fetchYieldData()Get APY, current, weekly, and lifetime yield getYieldDataForGraphic()Get time-series data points for chart display getWeeklyYield()Get yield earned in the current week withdraw()Withdraw accumulated yield to wallet balance switchYieldToken(token)Change the token used for yield accumulation
TypeScript
interface YieldData {
tokenSymbol: string; // yield token symbol
apy: string; // annual percentage yield
currentYield: string; // accumulated yield pending withdrawal
weeklyYield: string; // yield earned this week
lifetimeYield: string; // total yield since account creation
}
Method Description initializeSmartDepositAddresses(forceRefresh?)Create or fetch all deposit addresses getAddresses()Return cached addresses (no API call) clearAddresses()Clear the local address cache
Supported deposit networks: Base, Arbitrum, BNB Chain, Ethereum, Kaia, Optimism, Polygon, Solana, TRON.
TypeScript
const result = await sdk.crypto.transfer.makeFullErc20Transfer({
tokenAddress: '0x920D17f7A226Ce306f2B51F74a0f0dA89C3b8dF3',
recipientAddress: '0xRecipientAddress',
amount: 100,
});
console.log('Phase 1 tx:', result.firstPartTxHash);
console.log('Phase 2 tx:', result.finalTxHash);
console.log('Success: ', result.success);
Use when you need to inspect or cancel the request between phases.
TypeScript
// Phase 1 — submit request
await sdk.crypto.transfer.makeErc20TransferFirstPart({
tokenAddress: '0x920D17f7A226Ce306f2B51F74a0f0dA89C3b8dF3',
recipientAddress: '0xRecipientAddress',
amount: 100,
});
// Fetch pending requests to get call_data
const withdrawals = await sdk.api.withdrawal.getWithdrawalRequests();
const pending = withdrawals.find(w => w.status === 'Requested');
// Phase 2 — execute after valid_after timestamp
await sdk.crypto.transfer.makeErc20TransferFinal(pending.call_data);
TypeScript
await sdk.crypto.transfer.batchTransfer({
items: [
{ to: '0xAddress1', amount: '100', token: 'USDC', guid: 'transfer-001' },
{ to: '0xAddress2', amount: '50', token: 'USDC', guid: 'transfer-002' },
],
});
TypeScript
const data = await sdk.crypto.yield.fetchYieldData();
console.log('APY: ', data.apy);
console.log('Earned: ', data.currentYield);
await sdk.crypto.yield.withdraw();
await sdk.crypto.yield.switchYieldToken('WEUR');
TypeScript
const addresses = await sdk.crypto.smartDepositAddress.initializeSmartDepositAddresses();
for (const addr of addresses) {
console.log(`Chain: ${addr.depositChain}`);
console.log(`Address: ${addr.depositAddress}`);
console.log(`Tokens: ${addr.supportedTokens.join(', ')}`);
}
TypeScript
// Issue
const card = await sdk.api.baas.cards.issueVirtualCard({ card_name: 'My Card' });
const cardId = card.id;
// Block / Unblock
await sdk.api.baas.cards.blockCard(cardId);
await sdk.api.baas.cards.unblockCard(cardId);
// Get PAN (requires SMS OTP)
const session = await sdk.api.baas.confirmation.requestSms({ action_type: 'GetCardDetails' });
const tokenRes = await sdk.api.baas.confirmation.verifySms({ session_id: session.session_id, code: '000000' });
const details = await sdk.api.baas.cards.getCardDetails(cardId, { action_token: tokenRes.token });
console.log('PAN:', details.card_number);
// Close
await sdk.api.baas.cards.closeCard(cardId);
TypeScript
import type {
WirexPaySDK,
SDKConfig,
SDKEnvironment,
MainWalletClient,
Logger,
// Auth
PrivyAuth,
LoginUserParams,
LoginCompanyParams,
RegisterUserParams,
RegisterCompanyParams,
AuthResponse,
RegisterUserResponse,
// Crypto
Hex,
TransactionHash,
TransferRequest,
TransferResult,
BatchTransferItem,
BatchTransferResult,
YieldData,
YieldGraphicDataPoint,
// Wallet / contract
TokenInfo,
ContractAddresses,
AccountStatus,
PolicyStatus,
} from '@wirexapp/wpay-baas-sdk';
All SDK errors extend a typed base class, allowing instanceof checks:
Error class Cause SDKErrorBase class for all SDK errors ConfigurationErrorInvalid or missing SDK configuration NetworkErrorHTTP failure — exposes statusCode ContractErrorSmart contract interaction failure WalletErrorWallet client unavailable or misconfigured TransactionErrorUserOperation reverted or timed out AuthenticationErrorToken missing, expired, or invalid ValidationErrorInput validation failure
TypeScript
import {
AuthenticationError,
NetworkError,
TransactionError,
ContractError,
} from '@wirexapp/wpay-baas-sdk';
try {
await sdk.crypto.transfer.makeFullErc20Transfer(params);
} catch (error) {
if (error instanceof AuthenticationError) {
// re-login
} else if (error instanceof NetworkError) {
console.error('HTTP', error.statusCode);
} else if (error instanceof ContractError) {
// execution delay not expired, or invalid calldata
} else if (error instanceof TransactionError) {
// UserOperation reverted on-chain
}
}