Unified Stablecoin Balance

1. Overview

The Unified Balance abstractly represents a user’s stable value across all operations in Wirex BaaS.
Instead of holding multiple versions of USDT, USDC, or EURC across different blockchains, Wirex normalizes all stable-value into:

  • WUSD — Wirex USD, for USD-equivalent value
  • WEUR — Wirex EUR, for EUR-equivalent value

These wrapped tokens exist inside Base Chain and are not directly transacted by developers on external blockchains. It is a chain-agnostic, and asset-agnostic model for all internal balances, transfers, and settlements.

Important:
Cross-chain deposits and Smart Deposit Addresses are described in the companion article.
This document focuses exclusively on the mechanics of wrapped stablecoins (WUSD/WEUR) and how they power the Unified Balance model.


2. Why WUSD / WEUR Exist

External stablecoins vary widely:

  • different token standards (ERC-20, TRC-20, SPL, KIP)
  • different decimal precision
  • chain-specific semantics and gas rules
  • inconsistent liquidity conditions
  • various settlement and finality models

This fragmentation makes unified accounting extremely complex.

WUSD and WEUR solve these issues by acting as the internal canonical stablecoins used for all accounting, settlement, and value transfers inside Wirex.

Problems solved by WUSD/WEUR

ProblemSolution
Different token standardsInternal canonical wrapped asset
Multi-chain fragmentationOne currency ledger
Complex reconciliationOne accounting source of truth
Conversion inconsistencyAlways 1:1
Bridge logicNever needed internally
Liquidity fragmentationUnified global pool


3. How WUSD/WEUR are Created (Minting)

Wrapped stablecoins are minted in two primary ways:

3.1 Deposit Minting (Wrap)

When users deposit external stablecoins (USDC, USDT, EURC) to their Wirex BaaS registered smart wallet, the system automatically:

  1. Receives the deposit on the source blockchain
  2. Verifies the transaction and amount
  3. Mints equivalent WUSD or WEUR at 1:1 ratio
  4. Credits the user's Unified Balance

The original tokens are held in reserve, while wrapped tokens represent the internal value.

3.2 Direct Credit Minting

Certain operations create WUSD/WEUR directly without external deposits:

  • Card refunds — When merchants refund card transactions
  • Bank transfers — When users receive fiat via bank transfer
  • Internal transfers — Peer-to-peer transfers between Wirex users
  • Cashback and rewards — Promotional credits

These operations mint wrapped tokens directly into the user's balance, maintaining the 1:1 backing through Wirex's reserve management.

Minting Flow Overview

flowchart LR
    subgraph Onchain["On-chain Deposits"]
        USDC[USDC]
        USDT[USDT]
        EURC[EURC]
    end

    subgraph OffChain["Off-chain Credits"]
        Refund[Card Refund]
        Bank[Bank Transfer]
    end

    Wrap[Wrap Process<br/>1:1 Conversion]
    DirectMint[Direct Mint<br/>Fiat-backed]

    AA[Smart Wallet AA<br/>Unified Balance]

    USDC -->|Deposit| Wrap
    USDT -->|Deposit| Wrap
    EURC -->|Deposit| Wrap
    
    Wrap -->|WUSD/WEUR| AA

    Refund -->|Credit| DirectMint
    Bank -->|Credit| DirectMint
    
    DirectMint -->|WUSD/WEUR| AA

    style Wrap fill:#4CAF50
    style DirectMint fill:#2196F3
    style AA fill:#FF9800

Key Properties

PropertyDescription
Minting ratioAlways 1:1 with source value
Decimal precision18 decimals (WUSD/WEUR)
BackingFully reserved (deposits + fiat reserves)
ReversibilityCan be unwrapped back to base tokens
TransferabilityInternal transfers within Wirex ecosystem

4. Unwrap Mechanics (Withdrawals)

When sending value to a blockchain address, wrapped assets are unwrapped into external stablecoins.

Unwrap Sequence


sequenceDiagram
    participant User
    participant App
    participant SmartWallet as Smart Wallet (AA)
    participant DelayPolicy as Execution Delay Policy
    participant SyntheticToken as Wirex Token (WUSD)
    participant BaseToken as Base Token (USDC)
    participant Recipient

    Note over User,Recipient: Phase 1: Initialize Request (First Transaction)
    
    User->>App: Initiate transfer (amount, recipient)
    App->>App: Generate unwrap call data
    App->>App: Generate ERC20 transfer call data
    App->>App: Encode both calls together
    App->>App: Calculate custom nonce<br/>from keccak256(encodedCalls)
    App->>SmartWallet: Get nonce with custom key
    SmartWallet-->>App: Return nonce value
    
    App->>App: Create request() call data<br/>(address, nonce, encodedCalls)
    App->>SmartWallet: sendUserOperation(requestCallData)
    SmartWallet->>DelayPolicy: Execute request()
    DelayPolicy->>DelayPolicy: Store request with delay<br/>(e.g., 24 hours)
    DelayPolicy-->>SmartWallet: Request stored
    SmartWallet-->>App: Transaction hash
    App-->>User: Request initiated ✓

    Note over User,Recipient: ⏰ Waiting Period (Delay Timer)
    
    Note over User,Recipient: Phase 2: Execute Request (Second Transaction)
    
    User->>App: Execute transfer<br/>(after delay period)
    App->>App: Calculate custom nonce<br/>from keccak256(encodedCalls)
    App->>SmartWallet: Get nonce with custom key
    SmartWallet-->>App: Return nonce value
    
    App->>SmartWallet: sendUserOperation(encodedCalls, nonce)
    SmartWallet->>DelayPolicy: Verify delay passed
    DelayPolicy-->>SmartWallet: Approved ✓
    
    SmartWallet->>SyntheticToken: withdraw(amount)
    SyntheticToken->>SyntheticToken: Burn wrapped tokens
    SyntheticToken->>BaseToken: Release base tokens
    BaseToken-->>SmartWallet: Base tokens received
    
    SmartWallet->>BaseToken: transfer(recipient, amount)
    BaseToken->>Recipient: Transfer base tokens
    BaseToken-->>SmartWallet: Transfer complete
    
    SmartWallet-->>App: Transaction hash
    App-->>User: Transfer completed ✓

Supported Unwrap Paths

WrappedResult TokenChains Supported
WUSDUSDCBase
WEUREURCBase

5. API: Retrieve Unified Balance

GET /api/v1/wallet

Response:

...
"balances": [
                {
                    "token_symbol": "WEUR",
                    "token_address": "0x5f0818Cfc6554aC3d018aeF81D310ABC1dcCcCD7",
                    "balance": 7.27,
                    "reference_balance": 8.403393,
                    "reference_currency": "USD"
                },
                {
                    "token_symbol": "WUSD",
                    "token_address": "0xD08C1401dd0E8aA012D9C7b4471d45FdC8f1C97E",
                    "balance": 1581.07,
                    "reference_balance": 1581.07,
                    "reference_currency": "USD"
                }
]
...

6. API: Unwrap & Send (Withdrawal)

import { encodeFunctionData, erc20Abi, keccak256, parseUnits } from 'viem';
import { getCustomNonceKeyFromString } from '@zerodev/sdk';

// ABIs (inline for self-contained example)
const syntheticAbi = [{ name: 'withdraw', type: 'function', inputs: [{ type: 'uint256' }] }];
const executionDelayPolicyAbi = [{ 
  name: 'request', 
  type: 'function', 
  inputs: [{ type: 'address' }, { type: 'uint256' }, { type: 'bytes' }] 
}];

interface TransferParams {
  tokenAddress: `0x${string}`;
  recipientAddress: `0x${string}`;
  amount: string;
  baseTokenAddress: `0x${string}`;
  decimals: number;
  baseDecimals: number;
}

/**
 * Step 1: Initialize unwrap and transfer request
 * Creates a delayed execution request for unwrapping synthetic token and transferring base token
 */
async function initializeUnwrapAndTransfer(
  smartWalletClient: any,
  executionDelayPolicyContract: `0x${string}`,
  params: TransferParams
) {
  // 1. Generate unwrap call data
  const unwrapCallData = encodeFunctionData({
    abi: syntheticAbi,
    functionName: 'withdraw',
    args: [parseUnits(params.amount, params.decimals)],
  });

  // 2. Generate ERC20 transfer call data
  const transferCallData = encodeFunctionData({
    abi: erc20Abi,
    functionName: 'transfer',
    args: [params.recipientAddress, parseUnits(params.amount, params.baseDecimals)],
  });

  // 3. Prepare call data array
  const callDataArray = [
    { to: params.tokenAddress, value: 0n, data: unwrapCallData },
    { to: params.baseTokenAddress, value: 0n, data: transferCallData }
  ];

  // 4. Encode execution calls
  const encodedCalls = await smartWalletClient.account.encodeCalls(callDataArray);

  // 5. Generate custom nonce for delayed execution
  const nonceKey = keccak256(encodedCalls);
  const customNonce = getCustomNonceKeyFromString(nonceKey, '0.7');
  const nonce = await smartWalletClient.account.getNonce({ key: customNonce });

  // 6. Create execution delay policy request
  const requestCallData = encodeFunctionData({
    abi: executionDelayPolicyAbi,
    functionName: 'request',
    args: [smartWalletClient.account.address, nonce, encodedCalls],
  });

  // 7. Encode and send request
  const requestCalls = await smartWalletClient.account.encodeCalls([{
    to: executionDelayPolicyContract,
    value: 0n,
    data: requestCallData,
  }]);

  const { hash } = await smartWalletClient.sendUserOperation({ callData: requestCalls });
  
  console.log('Request initiated:', hash);
  return { hash, encodedCalls };
}

/**
 * Step 2: Execute the delayed operation
 * After delay period, execute the unwrap and transfer
 */
async function executeUnwrapAndTransfer(
  smartWalletClient: any,
  withdrawalCallData: `0x${string}`
) {
  // 1. Calculate custom nonce from call data
  const nonceKey = keccak256(withdrawalCallData);
  const customNonce = getCustomNonceKeyFromString(nonceKey, '0.7');
  const nonce = await smartWalletClient.account.getNonce({ key: customNonce });

  // 2. Send user operation with custom nonce
  const { hash } = await smartWalletClient.sendUserOperation({
    callData: withdrawalCallData,
    nonce
  });

  console.log('Execution completed:', hash);
  return hash;
}

// Usage example
async function example(smartWalletClient: any, executionDelayPolicyContract: `0x${string}`) {
  const params: TransferParams = {
    tokenAddress: '0x...', // WUSD/WEUR
    baseTokenAddress: '0x...', // Base token (USDC/EURC)
    recipientAddress: '0x...',
    amount: '100',
    decimals: 18,
    baseDecimals: 6
  };

  // Step 1: Initialize request (creates delay)
  const { encodedCalls } = await initializeUnwrapAndTransfer(
    smartWalletClient,
    executionDelayPolicyContract,
    params
  );

  // Step 2: After delay period, execute
  await executeUnwrapAndTransfer(smartWalletClient, encodedCalls);
}


7. Summary

WUSD and WEUR are the foundational wrapped stablecoins powering all Unified Balance mechanics in Wirex BaaS.
They provide:

  • A single canonical asset per currency
  • Deterministic internal accounting
  • Unified global liquidity
  • Stable, predictable, chain-agnostic operations
  • Seamless unwrap → blockchain send workflows
  • Zero need for developers to manage token formats or bridging

By integrating with Unified Balance, developers gain a simple, reliable, and future-proof way to handle stablecoin value across any chain.