Cryptocurrency Wallet Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Cryptocurrency Wallet Development
Complex
from 2 weeks to 3 months
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Crypto Wallet Development

A crypto wallet is not a coin storage. Coins live on the blockchain. The wallet stores private keys and signs transactions. This fundamental difference defines the entire architecture: the main task is secure key management, not balance storage. Improper key handling makes any beautiful UI meaningless — if the seed phrase is stored in localStorage or sent to a server, the wallet is insecure regardless of everything else.

Wallet development divides into two fundamentally different classes: custodial (service holds keys) and non-custodial (user holds keys). This is not just a technical choice — it's a legal and business decision with different compliance requirements.

Wallet Types and Architectural Solutions

HD Wallet (Hierarchical Deterministic, BIP-32/BIP-44)

The standard for most non-custodial wallets. From one seed phrase (12/24 BIP-39 words), a tree of keys is derived. Each chain, each account, each address — separate child key.

import { ethers } from 'ethers';
import * as bip39 from 'bip39';

// Seed phrase generation
const mnemonic = bip39.generateMnemonic(256); // 24 words
// "word1 word2 ... word24"

// HD wallet derivation
const hdNode = ethers.HDNodeWallet.fromPhrase(mnemonic);

// Account by standard derivation path (BIP-44)
// m/44'/60'/0'/0/0 — first Ethereum account
const wallet = hdNode.derivePath("m/44'/60'/0'/0/0");
console.log(wallet.address);      // 0x...
console.log(wallet.privateKey);   // 0x... (NEVER show)

// Next accounts
const wallet1 = hdNode.derivePath("m/44'/60'/0'/0/1");
const wallet2 = hdNode.derivePath("m/44'/60'/0'/0/2");

// Different chains (SLIP-44 coin types):
// ETH: m/44'/60'/...
// BTC: m/44'/0'/...
// Solana: m/44'/501'/...
// Cosmos: m/44'/118'/...

One seed phrase → all wallets for all chains. User only needs to remember 12/24 words.

Secure Key Storage on Device

The most critical place. Multiple layers of protection:

iOS/Android: Secure Enclave / Keystore

// React Native: expo-secure-store or @react-native-async-storage
// + OS-level encryption

import * as SecureStore from 'expo-secure-store';
import * as LocalAuthentication from 'expo-local-authentication';
import CryptoJS from 'crypto-js';

async function storeEncryptedMnemonic(mnemonic: string, pin: string): Promise<void> {
  // Derive encryption key from PIN via PBKDF2
  const salt = CryptoJS.lib.WordArray.random(128 / 8).toString();
  const key = CryptoJS.PBKDF2(pin, salt, {
    keySize: 256 / 32,
    iterations: 100000,  // 100K iterations — slow for attacker, acceptable for user
  });

  const encrypted = CryptoJS.AES.encrypt(mnemonic, key.toString()).toString();

  // Save to Secure Enclave (iOS) or Android Keystore
  await SecureStore.setItemAsync('encrypted_mnemonic', encrypted);
  await SecureStore.setItemAsync('pbkdf2_salt', salt);
}

async function loadMnemonic(pin: string): Promise<string | null> {
  // Optional: biometrics before decryption
  const biometricResult = await LocalAuthentication.authenticateAsync({
    promptMessage: 'Confirm identity',
  });

  if (!biometricResult.success) return null;

  const encrypted = await SecureStore.getItemAsync('encrypted_mnemonic');
  const salt = await SecureStore.getItemAsync('pbkdf2_salt');

  if (!encrypted || !salt) return null;

  const key = CryptoJS.PBKDF2(pin, salt, { keySize: 256/32, iterations: 100000 });
  const bytes = CryptoJS.AES.decrypt(encrypted, key.toString());
  return bytes.toString(CryptoJS.enc.Utf8);
}

Web/Extension: don't store private key in localStorage

Private key in browser extension is stored in encrypted Chrome storage (chrome.storage.local) with user password. MetaMask uses this approach with PBKDF2 + AES-256-GCM.

Important: key should be in memory only while wallet is unlocked. When locked — key is removed from memory, only encrypted blob remains.

Hardware Wallet Integration: HID/WebUSB

import TransportWebUSB from '@ledgerhq/hw-transport-webusb';
import Eth from '@ledgerhq/hw-app-eth';

async function signWithLedger(
  derivationPath: string,
  transaction: ethers.TransactionRequest
): Promise<string> {
  const transport = await TransportWebUSB.create();
  const eth = new Eth(transport);

  // Get address from Ledger (verification)
  const { address } = await eth.getAddress(derivationPath);
  console.log('Ledger address:', address);

  // Serialize transaction for signing
  const unsignedTx = ethers.Transaction.from(transaction);
  const serialized = ethers.getBytes(unsignedTx.unsignedSerialized);

  // Sign on device — private key never leaves Ledger
  const signature = await eth.signTransaction(derivationPath, Buffer.from(serialized).toString('hex'), null);

  const signedTx = ethers.Transaction.from({
    ...transaction,
    signature: {
      r: '0x' + signature.r,
      s: '0x' + signature.s,
      v: parseInt(signature.v, 16),
    },
  });

  return signedTx.serialized;
}

Multi-chain Support

Modern wallet supports EVM-compatible networks (Ethereum, Polygon, Arbitrum, BSC, Avalanche — one key, different RPCs) and non-EVM (Solana, Bitcoin, Cosmos — different signing algorithms).

EVM chains: single key

class EVMWalletProvider {
  private wallet: ethers.Wallet;

  constructor(privateKey: string) {
    this.wallet = new ethers.Wallet(privateKey);
  }

  getProvider(chainId: number): ethers.Provider {
    const rpcUrls: Record<number, string> = {
      1: 'https://eth-mainnet.alchemyapi.io/v2/...',
      137: 'https://polygon-mainnet.alchemyapi.io/v2/...',
      42161: 'https://arb-mainnet.g.alchemy.com/v2/...',
      8453: 'https://base-mainnet.g.alchemy.com/v2/...',
    };
    return new ethers.JsonRpcProvider(rpcUrls[chainId]);
  }

  async sendTransaction(
    tx: ethers.TransactionRequest,
    chainId: number
  ): Promise<ethers.TransactionResponse> {
    const provider = this.getProvider(chainId);
    const connectedWallet = this.wallet.connect(provider);
    return connectedWallet.sendTransaction({ ...tx, chainId });
  }
}

Token Balances: Request Batching

Separate RPC call per token per chain = huge delay. Multicall3 allows getting all balances in one call:

import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';
import { erc20Abi } from 'viem';

async function getAllTokenBalances(
  address: `0x${string}`,
  tokenAddresses: `0x${string}`[]
) {
  const client = createPublicClient({ chain: mainnet, transport: http(RPC_URL) });

  // Multicall: one request for N tokens
  const results = await client.multicall({
    contracts: tokenAddresses.map(token => ({
      address: token,
      abi: erc20Abi,
      functionName: 'balanceOf',
      args: [address],
    })),
  });

  return tokenAddresses.map((addr, i) => ({
    token: addr,
    balance: results[i].status === 'success' ? results[i].result : 0n,
  }));
}

Transaction Simulation

Before sending, show user what will happen. Tenderly Simulation API or Alchemy's alchemy_simulateAssetChanges:

async function simulateTransaction(tx: ethers.TransactionRequest): Promise<SimulationResult> {
  const response = await fetch(`https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      id: 1,
      jsonrpc: '2.0',
      method: 'alchemy_simulateAssetChanges',
      params: [{
        from: tx.from,
        to: tx.to,
        data: tx.data,
        value: tx.value ? `0x${BigInt(tx.value).toString(16)}` : '0x0',
      }],
    }),
  });

  const result = await response.json();
  // result.result.changes: list of balance changes
  // result.result.error: if transaction reverts

  return {
    willSucceed: !result.result.error,
    balanceChanges: result.result.changes,
    gasEstimate: result.result.gasUsed,
  };
}

If simulation shows the transaction reverts or unexpectedly drains the balance — warn the user before real sending.

WalletConnect v2 Integration

Standard wallet-to-dApp connection protocol:

import { WalletKit } from '@reown/walletkit';
import { Core } from '@walletconnect/core';

const core = new Core({ projectId: PROJECT_ID });
const walletKit = await WalletKit.init({ core, metadata: { name: 'My Wallet', ... } });

// Handle connection request from dApp
walletKit.on('session_proposal', async ({ id, params }) => {
  // Show user: dApp requests connection
  const userApproved = await showConnectionModal(params);

  if (userApproved) {
    await walletKit.approveSession({
      id,
      namespaces: {
        eip155: {
          chains: ['eip155:1', 'eip155:137'],
          methods: ['eth_sendTransaction', 'personal_sign', 'eth_signTypedData_v4'],
          events: ['accountsChanged', 'chainChanged'],
          accounts: ['eip155:1:' + walletAddress, 'eip155:137:' + walletAddress],
        },
      },
    });
  }
});

// Handle signature request from dApp
walletKit.on('session_request', async ({ id, params }) => {
  const { method, params: reqParams } = params.request;

  if (method === 'eth_sendTransaction') {
    const tx = reqParams[0];
    const simulation = await simulateTransaction(tx);

    // Show user: what does the transaction do
    const userApproved = await showTransactionModal(tx, simulation);

    if (userApproved) {
      const signedTx = await wallet.sendTransaction(tx);
      await walletKit.respondSessionRequest({ id, response: { result: signedTx.hash } });
    }
  }
});

Security: Checklist

Item Description
Seed storage PBKDF2 + AES-256-GCM, storage in Secure Enclave/Keystore
Memory security Private key in memory only when unlocked
Screen capture Block screenshots when displaying seed phrase
Clipboard Clear buffer after 60 seconds of copying
Transaction simulation Warn on revert or drain
Phishing protection Verify dApp URL, warn about unknown contracts
Biometrics Optional biometric protection for opening
Transport Only HTTPS/WSS, certificate pinning for mobile
Dependency audit npm audit / Snyk on all dependencies

Technology Stack

Mobile (React Native): React Native + expo-secure-store + ethers.js v6 + viem + WalletConnect SDK + Reown AppKit.

Browser Extension (Chrome/Firefox): React + WebExtension API + chrome.storage + MetaMask Snap API (for extending existing wallets).

Web-based (PWA): Next.js + wagmi + viem + WalletConnect — for custodial or MPC-based wallets where keys are not stored in browser.

Work Process

Architectural Decision (1 week). Custodial or non-custodial. Mobile, web or extension. List of supported chains and tokens. Hardware wallet integration.

Core Development (3-4 weeks). Key management, HD wallet, transaction signing, multi-chain support.

UI Development (2-3 weeks). Onboarding (seed generation/import), portfolio view, send/receive, transaction history, dApp browser.

Security Review (1-2 weeks). Penetration testing of key functions, seed storage audit.

Testing and Launch (1-2 weeks). TestFlight/Play Store beta, mainnet tests with small amounts.

Full cycle for mobile wallet: 3-4 months. Cost depends on feature set and platforms.