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:
| Guide | Why |
|---|---|
| Getting Started | Platform overview and setup |
| ABI Reference | Smart 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:
- The Smart Wallet must be deployed
- Required policies and executors must be installed
- 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
| Option | Description |
|---|---|
| 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
| Module | Method | Purpose |
|---|---|---|
sdk.crypto.accountAbstraction | isPolicyInstalled() | Check if execution delay policy is installed |
sdk.crypto.accountAbstraction | isExecutorInstalled() | Check if funds management executor is installed |
sdk.crypto.accountAbstraction | signInPolicyAndExecutor() | Install both in one transaction |
sdk.crypto.accountAbstraction | signInPolicy() | Install policy only |
sdk.crypto.accountAbstraction | signInExecutor() | Install executor only |
sdk.crypto.accountContract | isWalletInAccounts() | Check if registered |
sdk.crypto.accountContract | registerInAccounts() | Register user |
sdk.crypto.wallet | getSmartWalletClient() | 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 viemConfiguration
Wirex provides the following during onboarding (see Platform Overview):
| Configuration | Description |
|---|---|
bundlerRpc | ZeroDev bundler endpoint |
paymasterRpc | ZeroDev paymaster endpoint for gas sponsorship |
rpcUrl | Blockchain RPC endpoint |
contractRegistryAddress | Address to resolve all other contract addresses |
partnerId | Your 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
-
Contract Resolution: All contract addresses are resolved from the ContractRegistry using
contractByName(). You only need the registry address from Wirex. -
Single Transaction: Executor installation, policy installation, and account registration are batched into one UserOperation, saving gas and reducing latency.
-
Signer Sources: The example shows how to obtain a signer from:
- Private key (embedded wallets, custodial solutions)
- Privy SDK (production)
- WalletConnect/wagmi (production)
-
Two Client Types:
- Initial deployment uses
simpleValidator(ECDSA) - Subsequent operations use
policyValidator(with ExecutionDelayPolicy)
- Initial deployment uses
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:
- Initialize Crossmint wallet via
@crossmint/client-sdk-react-ui - Install the execution delay plugin on the smart wallet
- Register in the Accounts contract with your
partner_id
Configuration Required from Wirex
| Configuration | Description |
|---|---|
| Crossmint API Key | For wallet initialization |
| Plugin Contract ID | Execution delay policy address |
| Accounts Contract ID | For user registration |
| Partner ID | 16-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:
| Contract | Purpose |
|---|---|
| Accounts | User registration |
| FundsManagement | Executor for oracle transactions |
| ExecutionDelayPolicy | Time-lock policy for security |
| TokensRegistry | Supported 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
| Field | Type | Description |
|---|---|---|
wallet_address | string | The Smart Wallet address (0x-prefixed hex) |
wallet_name | string | Display name for the wallet |
wallet_status | string | Confirmed, Rejected, or Unknown |
wallet_type | string | Primary, Secondary, Global, or Card |
balances | array | Token balances (empty in wallet creation webhook) |
Wallet Status Values
| Status | Description |
|---|---|
Confirmed | Wallet is properly configured as an Smart Wallet with required modules |
Rejected | Wallet configuration is invalid or incomplete |
Unknown | Status 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
- Create signer - From Privy, private key, or WalletConnect
- Deploy Smart Wallet - Single UserOperation with batched calls
- Install executor - FundsManagement module for oracle operations
- Install policy - ExecutionDelayPolicy as root validator
- Register on-chain -
createUserAccountWithWallet(partnerId)in Accounts contract - Event emitted - Wirex listener detects
UserAccountCreatedevent - Wirex verifies - Backend checks modules are installed, creates internal record
- Webhook sent - You receive wallet confirmation at
/v2/webhooks/wallets
Next Steps
After completing on-chain registration:
- Obtain an access token via Authentication
- Register the user in the Wirex backend via User Onboarding
Updated 8 days ago
