Wallet Deployment

Deploy Account Abstraction wallets, install required modules, and register in the Wirex Accounts contract.

Before You Start

Read the following guides before proceeding:

GuideWhy
Getting StartedPlatform overview and setup
ABI ReferenceSmart contract ABIs

Overview

Wirex uses Smart Wallets (Account Abstraction). Users hold their own private keys via smart contract wallets. Before calling the Wirex API to register a user:

  1. The Smart Wallet must be deployed
  2. Required policies and executors must be installed
  3. The wallet must be registered in the Accounts smart contract

Components

Kernel v3.1 Smart Account

The Smart Wallet itself. A smart contract wallet that:

  • Is controlled by the user's EOA (externally owned account) as the signer
  • Supports modular plugins for validators, executors, and policies
  • Enables gas sponsorship via paymasters (users don't need native tokens for gas)
  • Allows batched transactions (multiple operations in a single transaction)

FundsManagement Executor

An executor module installed on the Kernel wallet that:

  • Grants the Wirex oracle permission to execute specific operations on behalf of the wallet
  • Enables automated processing of card payments, withdrawals, and other financial operations
  • Only allows predefined operations - the oracle cannot arbitrarily move funds

Without this executor, Wirex cannot process transactions that require wallet signatures (e.g., card top-ups, fiat withdrawals).

ExecutionDelayPolicy

A policy module installed as the root validator that:

  • Enforces a time delay on sensitive operations for security
  • Allows users to cancel pending transactions within the delay window
  • Protects against unauthorized access even if the EOA is compromised

This policy is mandatory for all Wirex wallets to ensure transaction security.

Accounts Contract Registration

On-chain registration that:

  • Links the Smart Wallet to your parentEntity (partner_id)
  • Enables Wirex to associate the wallet with a user account
  • Records the wallet's verification and account status on-chain

The Wirex API validates that wallets are registered before accepting API registration requests.


Deployment Options

OptionDescription
Wirex SDK (Recommended)Abstracts deployment and configuration details
Direct Deployment (EVM)Use ZeroDev SDK for Kernel v3.1 wallets on Base
Direct Deployment (Stellar)Use Crossmint SDK for Stellar smart wallets

Option A: Wirex SDK (Recommended)

When using the Wirex SDK (@wirexapp/wirexpay-sdk), the SDK abstracts deployment and configuration details.

SDK Initialization

import { createSDK } from '@wirexapp/wirexpay-sdk';

const sdk = await createSDK({
  env: 'production', // or 'sandbox'
  companyId: '0x00000000000000000000000000000007', // Your partner_id
  enableLogging: true,
  getMainWalletClient: async () => {
    // Return the user's EOA wallet client (from Privy, WalletConnect, etc.)
    return mainWalletClient;
  },
});

Check and Install Policy/Executor

// Check if already installed
const policyInstalled = await sdk.crypto.accountAbstraction.isPolicyInstalled();
const executorInstalled = await sdk.crypto.accountAbstraction.isExecutorInstalled();

// Install both policy and executor in one transaction
if (!policyInstalled || !executorInstalled) {
  await sdk.crypto.accountAbstraction.signInPolicyAndExecutor();
}

Register in Accounts Contract

// Check if already registered
const isRegistered = await sdk.crypto.accountContract.isWalletInAccounts();

// Register
if (!isRegistered) {
  await sdk.crypto.accountContract.registerInAccounts();
}

Available SDK Methods

ModuleMethodPurpose
sdk.crypto.accountAbstractionisPolicyInstalled()Check if execution delay policy is installed
sdk.crypto.accountAbstractionisExecutorInstalled()Check if funds management executor is installed
sdk.crypto.accountAbstractionsignInPolicyAndExecutor()Install both in one transaction
sdk.crypto.accountAbstractionsignInPolicy()Install policy only
sdk.crypto.accountAbstractionsignInExecutor()Install executor only
sdk.crypto.accountContractisWalletInAccounts()Check if registered
sdk.crypto.accountContractregisterInAccounts()Register user
sdk.crypto.walletgetSmartWalletClient()Get Kernel account client

Option B: Direct Deployment (EVM with ZeroDev SDK)

For EVM chains, use the ZeroDev SDK to deploy Kernel v3.1 smart contract accounts.

Prerequisites

Install required dependencies:

npm install @zerodev/sdk @zerodev/ecdsa-validator @zerodev/permissions viem

Configuration

Wirex provides the following during onboarding (see Platform Overview):

ConfigurationDescription
bundlerRpcZeroDev bundler endpoint
paymasterRpcZeroDev paymaster endpoint for gas sponsorship
rpcUrlBlockchain RPC endpoint
contractRegistryAddressAddress to resolve all other contract addresses
partnerIdYour 16-byte on-chain identifier

Complete Example

For ABI definitions used in this example, see ABI Reference.

// ============================================================
// Imports
// ============================================================
import { createZeroDevPaymasterClient, KernelV3_1AccountAbi } from '@zerodev/sdk';
import { createKernelAccount, createKernelAccountClient } from '@zerodev/sdk';
import { signerToEcdsaValidator } from '@zerodev/ecdsa-validator';
import { getEntryPoint, KERNEL_V3_1, VALIDATOR_TYPE } from '@zerodev/sdk/constants';
import { toPermissionValidator } from '@zerodev/permissions';
import { toSudoPolicy } from '@zerodev/permissions/policies';
import { toECDSASigner } from '@zerodev/permissions/signers';
import {
  createPublicClient,
  http,
  encodeFunctionData,
  concatHex,
  pad,
  zeroAddress,
  zeroHash,
  type PrivateKeyAccount,
} from 'viem';
import { base } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

// ============================================================
// ABIs - see ABI Reference guide for complete definitions
// ============================================================
import { ContractRegistryAbi, AccountsAbi } from './abi';
// Or define inline - see: ABI Reference guide

// ============================================================
// Configuration (provided by Wirex during onboarding)
// ============================================================
interface WirexConfig {
  chainId: number;
  rpcUrl: string;
  bundlerRpc: string;
  paymasterRpc: string;
  contractRegistryAddress: `0x${string}`;
  partnerId: `0x${string}`;
}

// Example configuration for Base mainnet
const config: WirexConfig = {
  chainId: 8453,
  rpcUrl: 'https://...', // Provided by Wirex
  bundlerRpc: 'https://...', // Provided by Wirex
  paymasterRpc: 'https://...', // Provided by Wirex
  contractRegistryAddress: '0x...', // Provided by Wirex
  partnerId: '0x00000000000000000000000000000007', // Your partner_id
};

// ============================================================
// Step 1: Initialize Clients
// ============================================================
async function initializeClients(config: WirexConfig) {
  const chain = base;

  const publicClient = createPublicClient({
    chain,
    transport: http(config.rpcUrl, { timeout: 30_000 }),
  });

  // Kernel public client for account operations
  const kernelPublicClient = createPublicClient({
    chain,
    transport: http(config.rpcUrl, { timeout: 60_000 }),
  });

  const paymasterClient = createZeroDevPaymasterClient({
    chain,
    transport: http(config.paymasterRpc),
  });

  return { chain, publicClient, kernelPublicClient, paymasterClient };
}

// ============================================================
// Step 2: Resolve Contract Addresses from Registry
// ============================================================
interface ContractAddresses {
  accounts: `0x${string}`;
  fundsManagement: `0x${string}`;
  executionDelayPolicy: `0x${string}`;
}

async function resolveContractAddresses(
  publicClient: any,
  registryAddress: `0x${string}`
): Promise<ContractAddresses> {
  const getAddress = async (name: string): Promise<`0x${string}`> => {
    return publicClient.readContract({
      address: registryAddress,
      abi: ContractRegistryAbi,
      functionName: 'contractByName',
      args: [name],
    });
  };

  return {
    accounts: await getAddress('Accounts'),
    fundsManagement: await getAddress('FundsManagement'),
    executionDelayPolicy: await getAddress('ExecutionDelayPolicy'),
  };
}

// ============================================================
// Step 3: Obtain User Signer
// ============================================================

// Option A: From Privy
// import { usePrivy, useWallets } from '@privy-io/react-auth';
// const { wallets } = useWallets();
// const wallet = wallets[0];
// await wallet.switchChain(config.chainId);
// const provider = await wallet.getEthereumProvider();
// const signer = ... // Create viem wallet client from provider

// Option B: From private key (embedded wallets, custodial solutions)
function getSignerFromPrivateKey(privateKey: `0x${string}`): PrivateKeyAccount {
  return privateKeyToAccount(privateKey);
}

// Option C: From WalletConnect / wagmi
// import { useWalletClient } from 'wagmi';
// const { data: walletClient } = useWalletClient();

// ============================================================
// Step 4: Create and Deploy Smart Wallet (Single Transaction)
// ============================================================
async function createAndDeployAAWallet(
  signer: PrivateKeyAccount,
  config: WirexConfig
) {
  const { chain, publicClient, kernelPublicClient, paymasterClient } =
    await initializeClients(config);

  const contracts = await resolveContractAddresses(
    publicClient,
    config.contractRegistryAddress
  );

  console.log('Resolved contract addresses:', contracts);

  // Create simple ECDSA validator for initial deployment
  const simpleValidator = await signerToEcdsaValidator(kernelPublicClient, {
    signer,
    entryPoint: getEntryPoint('0.7'),
    kernelVersion: KERNEL_V3_1,
  });

  // Create kernel account (not yet deployed on-chain)
  const kernelAccount = await createKernelAccount(kernelPublicClient, {
    entryPoint: getEntryPoint('0.7'),
    kernelVersion: KERNEL_V3_1,
    plugins: {
      sudo: simpleValidator,
    },
  });

  // Create kernel client with paymaster for gas sponsorship
  const kernelClient = createKernelAccountClient({
    account: kernelAccount,
    chain,
    bundlerTransport: http(config.bundlerRpc),
    paymaster: {
      getPaymasterData: (userOperation) =>
        paymasterClient.sponsorUserOperation({ userOperation }),
    },
  });

  console.log('Smart Wallet address (counterfactual):', kernelAccount.address);

  // ---- Build batched calls ----

  // 1. Executor installation
  const executorCallData = encodeFunctionData({
    abi: KernelV3_1AccountAbi,
    functionName: 'installModule',
    args: [
      BigInt(2), // Module type: Executor
      contracts.fundsManagement,
      concatHex([zeroAddress, zeroHash]),
    ],
  });

  // 2. Policy installation
  const rootPolicy = toSudoPolicy({
    policyAddress: contracts.executionDelayPolicy,
  });

  const permissionValidator = await toPermissionValidator(kernelPublicClient, {
    entryPoint: getEntryPoint('0.7'),
    signer: await toECDSASigner({ signer }),
    kernelVersion: KERNEL_V3_1,
    policies: [rootPolicy],
  });
  permissionValidator.address = contracts.executionDelayPolicy;

  const rootValidatorId = concatHex([
    VALIDATOR_TYPE.PERMISSION,
    pad(permissionValidator.getIdentifier(), { size: 20, dir: 'right' }),
  ]);

  const policyCallData = encodeFunctionData({
    abi: KernelV3_1AccountAbi,
    functionName: 'changeRootValidator',
    args: [
      rootValidatorId,
      zeroAddress,
      await permissionValidator.getEnableData(kernelAccount.address),
      '0x',
    ],
  });

  // 3. Account registration
  const registerCallData = encodeFunctionData({
    abi: AccountsAbi,
    functionName: 'createUserAccountWithWallet',
    args: [config.partnerId],
  });

  // 4. Batch all calls
  const batchedCalls = await kernelAccount.encodeCalls([
    {
      to: kernelAccount.address,
      value: BigInt(0),
      data: policyCallData,
    },
    {
      to: kernelAccount.address,
      value: BigInt(0),
      data: executorCallData,
    },
    {
      to: contracts.accounts,
      value: BigInt(0),
      data: registerCallData,
    },
  ]);

  // 5. Send single UserOperation
  console.log('Sending UserOperation...');
  const txHash = await kernelClient.sendUserOperation({
    callData: batchedCalls,
  });

  console.log('UserOperation hash:', txHash);

  const receipt = await kernelClient.waitForUserOperationReceipt({
    hash: txHash,
    timeout: 60_000,
  });

  console.log('Transaction hash:', receipt.receipt.transactionHash);
  console.log('Smart Wallet deployed:', kernelAccount.address);

  return {
    smartWalletAddress: kernelAccount.address,
    eoaAddress: signer.address,
    transactionHash: receipt.receipt.transactionHash,
  };
}

// ============================================================
// Step 5: Create Client with Policy (for subsequent operations)
// ============================================================
async function createPolicyClient(
  signer: PrivateKeyAccount,
  smartWalletAddress: `0x${string}`,
  config: WirexConfig
) {
  const { chain, publicClient, kernelPublicClient, paymasterClient } =
    await initializeClients(config);

  const contracts = await resolveContractAddresses(
    publicClient,
    config.contractRegistryAddress
  );

  // Create permission validator with policy
  const policyValidator = await toPermissionValidator(kernelPublicClient, {
    entryPoint: getEntryPoint('0.7'),
    signer: await toECDSASigner({ signer }),
    kernelVersion: KERNEL_V3_1,
    policies: [toSudoPolicy({ policyAddress: contracts.executionDelayPolicy })],
  });
  policyValidator.address = contracts.executionDelayPolicy;

  // Create kernel account pointing to existing address
  const kernelAccount = await createKernelAccount(kernelPublicClient, {
    entryPoint: getEntryPoint('0.7'),
    kernelVersion: KERNEL_V3_1,
    address: smartWalletAddress,
    plugins: {
      sudo: policyValidator,
    },
  });

  return createKernelAccountClient({
    account: kernelAccount,
    chain,
    bundlerTransport: http(config.bundlerRpc),
    paymaster: {
      getPaymasterData: (userOperation) =>
        paymasterClient.sponsorUserOperation({ userOperation }),
    },
  });
}

// ============================================================
// Usage Example
// ============================================================
async function main() {
  // From private key (embedded wallets, custodial solutions)
  // Or from Privy, WalletConnect, or other wallet provider
  const signer = getSignerFromPrivateKey('0x...');

  // Deploy Smart Wallet with policy, executor, and registration
  const result = await createAndDeployAAWallet(signer, config);

  console.log('Deployment complete:');
  console.log('  EOA:', result.eoaAddress);
  console.log('  Smart Wallet:', result.smartWalletAddress);
  console.log('  TX Hash:', result.transactionHash);

  // For subsequent operations, create client with policy
  const policyClient = await createPolicyClient(
    signer,
    result.smartWalletAddress,
    config
  );

  // Now use policyClient for all subsequent operations
}

Key Points

  1. Contract Resolution: All contract addresses are resolved from the ContractRegistry using contractByName(). You only need the registry address from Wirex.

  2. Single Transaction: Executor installation, policy installation, and account registration are batched into one UserOperation, saving gas and reducing latency.

  3. Signer Sources: The example shows how to obtain a signer from:

    • Private key (embedded wallets, custodial solutions)
    • Privy SDK (production)
    • WalletConnect/wagmi (production)
  4. Two Client Types:

    • Initial deployment uses simpleValidator (ECDSA)
    • Subsequent operations use policyValidator (with ExecutionDelayPolicy)

Verification

If your application does not persist Smart Wallet setup state, use these functions to determine whether the wallet was created, modules were installed, and registration completed.

import { KernelV3_1AccountAbi } from '@zerodev/sdk';
import { AccountsAbi } from './abi'; // See ABI Reference

async function verifyWalletSetup(
  publicClient: any,
  smartWalletAddress: `0x${string}`,
  contracts: ContractAddresses,
  partnerId: `0x${string}`
) {
  // 1. Check if executor is installed
  const isExecutorInstalled = await publicClient.readContract({
    address: smartWalletAddress,
    abi: KernelV3_1AccountAbi,
    functionName: 'isModuleInstalled',
    args: [BigInt(2), contracts.fundsManagement, '0x'],
  });
  console.log('Executor installed:', isExecutorInstalled);

  // 2. Check if policy is installed (root validator set)
  const rootValidator = await publicClient.readContract({
    address: smartWalletAddress,
    abi: KernelV3_1AccountAbi,
    functionName: 'rootValidator',
  });
  const isPolicyInstalled = rootValidator && rootValidator !== '0x';
  console.log('Policy installed:', isPolicyInstalled);

  // 3. Check if registered in Accounts contract
  try {
    const account = await publicClient.readContract({
      address: contracts.accounts,
      abi: AccountsAbi,
      functionName: 'getUserAccount',
      args: [smartWalletAddress, partnerId],
    });
    const isRegistered = account && account.status !== 0;
    console.log('Account registered:', isRegistered);
    return { isExecutorInstalled, isPolicyInstalled, isRegistered };
  } catch {
    console.log('Account not registered');
    return { isExecutorInstalled, isPolicyInstalled, isRegistered: false };
  }
}

Option C: Direct Deployment (Stellar with Crossmint SDK)

For Stellar chain, use the Crossmint SDK for smart wallet management. Contact Wirex for Stellar-specific integration documentation and contract addresses.

Overview

Stellar Smart Wallets use Crossmint's smart wallet infrastructure:

  1. Initialize Crossmint wallet via @crossmint/client-sdk-react-ui
  2. Install the execution delay plugin on the smart wallet
  3. Register in the Accounts contract with your partner_id

Configuration Required from Wirex

ConfigurationDescription
Crossmint API KeyFor wallet initialization
Plugin Contract IDExecution delay policy address
Accounts Contract IDFor user registration
Partner ID16-byte identifier (without 0x prefix for Stellar)

For detailed Stellar integration steps, refer to the Crossmint SDK documentation and contact Wirex for chain-specific contract addresses.


Chain-Specific Contract Addresses

Contact Wirex during onboarding to receive:

ContractPurpose
AccountsUser registration
FundsManagementExecutor for oracle transactions
ExecutionDelayPolicyTime-lock policy for security
TokensRegistrySupported token list

Contract addresses differ per chain and environment (Sandbox/Production). See Platform Overview for ContractRegistry addresses.


Webhooks

When a wallet is registered on-chain, Wirex sends a webhook notification to your configured endpoint.

Endpoint

POST {your_webhook_base_url}/v2/webhooks/wallets

Payload

{
  "wallet_address": "0xA7E41d5680dE394EaA2ed417169DFf56840Fb3EE",
  "wallet_name": "Main Wallet",
  "wallet_status": "Confirmed",
  "wallet_type": "Primary",
  "balances": []
}

Fields

FieldTypeDescription
wallet_addressstringThe Smart Wallet address (0x-prefixed hex)
wallet_namestringDisplay name for the wallet
wallet_statusstringConfirmed, Rejected, or Unknown
wallet_typestringPrimary, Secondary, Global, or Card
balancesarrayToken balances (empty in wallet creation webhook)

Wallet Status Values

StatusDescription
ConfirmedWallet is properly configured as an Smart Wallet with required modules
RejectedWallet configuration is invalid or incomplete
UnknownStatus could not be determined

Implementation Notes

  • Wirex expects a 2xx response within 10 seconds
  • Failed deliveries are logged but not automatically retried
  • Implement idempotent handlers to safely process duplicate events
  • Contact Wirex support if you need event replay

On-Chain Flow Summary

Sequence Diagram

Your Backend             User Wallet              Smart Contracts              Wirex BE
   |                         |                          |                          |
   |  1. Create signer       |                          |                          |
   |------------------------>|                          |                          |
   |                         |                          |                          |
   |  2. Deploy Smart Wallet    |                          |                          |
   |  (UserOperation with batched calls)                |                          |
   |------------------------>|                          |                          |
   |                         |                          |                          |
   |                         | 3. installModule (executor)                         |
   |                         |------------------------->|                          |
   |                         |                          |                          |
   |                         | 4. changeRootValidator (policy)                     |
   |                         |------------------------->|                          |
   |                         |                          |                          |
   |                         | 5. createUserAccountWithWallet(partnerId)           |
   |                         |------------------------->|                          |
   |                         |                          |                          |
   |                         |                          | 6. Emit UserAccountCreated
   |                         |                          |------------------------->|
   |                         |                          |                          |
   |                         |                          |      7. Verify wallet    |
   |                         |                          |      (check modules,     |
   |                         |                          |       create record)     |
   |                         |                          |                          |
   |                         |<-------------------------|                          |
   |                         |      (tx confirmed)      |                          |
   |                         |                          |                          |
   | 8. Webhook: /v2/webhooks/wallets                   |                          |
   |<------------------------------------------------------------------|           |
   |                         |                          |                          |

Flow Description

  1. Create signer - From Privy, private key, or WalletConnect
  2. Deploy Smart Wallet - Single UserOperation with batched calls
  3. Install executor - FundsManagement module for oracle operations
  4. Install policy - ExecutionDelayPolicy as root validator
  5. Register on-chain - createUserAccountWithWallet(partnerId) in Accounts contract
  6. Event emitted - Wirex listener detects UserAccountCreated event
  7. Wirex verifies - Backend checks modules are installed, creates internal record
  8. Webhook sent - You receive wallet confirmation at /v2/webhooks/wallets

Next Steps

After completing on-chain registration:

  1. Obtain an access token via Authentication
  2. Register the user in the Wirex backend via User Onboarding