Разработка криптовалютного кошелька

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка криптовалютного кошелька
Сложная
от 2 недель до 3 месяцев
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка криптокошелька

Криптокошелёк — это не хранилище монет. Монеты живут на блокчейне. Кошелёк хранит приватные ключи и подписывает транзакции. Это принципиальное различие определяет всю архитектуру: главная задача — безопасное управление ключами, а не хранение балансов. Неправильная работа с ключами делает любой красивый UI бессмысленным — если seed phrase хранится в localStorage или передаётся на сервер, кошелёк небезопасен независимо от остального.

Разработка кошелька делится на два принципиально разных класса: кастодиальный (ключи у сервиса) и некастодиальный (ключи у пользователя). Это не просто технический выбор — это юридическое и бизнес-решение с разными compliance требованиями.

Типы кошельков и архитектурные решения

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

Стандарт для большинства некастодиальных кошельков. Из одного seed phrase (12/24 слова BIP-39) деривируется дерево ключей. Каждая цепь, каждый аккаунт, каждый адрес — отдельный child key.

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

// Генерация seed phrase
const mnemonic = bip39.generateMnemonic(256); // 24 слова
// "word1 word2 ... word24"

// Деривация HD кошелька
const hdNode = ethers.HDNodeWallet.fromPhrase(mnemonic);

// Аккаунт по стандартному derivation path (BIP-44)
// m/44'/60'/0'/0/0 — первый Ethereum аккаунт
const wallet = hdNode.derivePath("m/44'/60'/0'/0/0");
console.log(wallet.address);      // 0x...
console.log(wallet.privateKey);   // 0x... (НИКОГДА не показывать)

// Следующие аккаунты
const wallet1 = hdNode.derivePath("m/44'/60'/0'/0/1");
const wallet2 = hdNode.derivePath("m/44'/60'/0'/0/2");

// Разные цепи (SLIP-44 coin types):
// ETH: m/44'/60'/...
// BTC: m/44'/0'/...
// Solana: m/44'/501'/...
// Cosmos: m/44'/118'/...

Один seed phrase → все кошельки для всех цепей. Пользователю нужно запомнить только 12/24 слова.

Безопасное хранение ключей на устройстве

Самое критичное место. Несколько уровней защиты:

iOS/Android: Secure Enclave / Keystore

// React Native: expo-secure-store или @react-native-async-storage
// + шифрование на уровне ОС

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> {
  // Деривируем encryption key из PIN через PBKDF2
  const salt = CryptoJS.lib.WordArray.random(128 / 8).toString();
  const key = CryptoJS.PBKDF2(pin, salt, {
    keySize: 256 / 32,
    iterations: 100000,  // 100K итераций — медленно для атакующего, приемлемо для пользователя
  });

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

  // Сохраняем в Secure Enclave (iOS) или Android Keystore
  await SecureStore.setItemAsync('encrypted_mnemonic', encrypted);
  await SecureStore.setItemAsync('pbkdf2_salt', salt);
}

async function loadMnemonic(pin: string): Promise<string | null> {
  // Опционально: биометрия перед расшифровкой
  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: не храним приватный ключ в localStorage

Приватный ключ в расширении браузера хранится в зашифрованном хранилище Chrome (chrome.storage.local) с паролем пользователя. MetaMask использует именно этот подход с PBKDF2 + AES-256-GCM.

Важно: ключ должен быть в памяти только пока кошелёк разблокирован. При блокировке — ключ из памяти удаляется, остаётся только зашифрованный blob.

Аппаратные кошельки: интеграция через 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);

  // Получаем адрес с Ledger (верификация)
  const { address } = await eth.getAddress(derivationPath);
  console.log('Ledger address:', address);

  // Сериализуем транзакцию для подписи
  const unsignedTx = ethers.Transaction.from(transaction);
  const serialized = ethers.getBytes(unsignedTx.unsignedSerialized);

  // Подписываем на устройстве — приватный ключ никогда не покидает 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;
}

Мультицепочечность

Современный кошелёк поддерживает EVM-совместимые сети (Ethereum, Polygon, Arbitrum, BSC, Avalanche — один ключ, разные RPC) и non-EVM (Solana, Bitcoin, Cosmos — разные алгоритмы подписи).

EVM chains: единый ключ

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: батчинг запросов

Отдельный RPC вызов per token per chain = огромная задержка. Multicall3 позволяет получить все балансы в одном вызове:

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: один запрос для N токенов
  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

Перед отправкой транзакции показываем пользователю что произойдёт. Tenderly Simulation API или 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: список изменений балансов
  // result.result.error: если транзакция reverts

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

Если симуляция показывает что транзакция reverts или неожиданно drain-ит баланс — предупреждаем пользователя до реальной отправки.

WalletConnect v2 интеграция

Стандарт подключения кошелька к dApp:

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', ... } });

// Обработка запроса подключения от dApp
walletKit.on('session_proposal', async ({ id, params }) => {
  // Показываем пользователю: dApp запрашивает подключение
  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],
        },
      },
    });
  }
});

// Обработка запроса подписи от 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);

    // Показываем пользователю: что делает транзакция
    const userApproved = await showTransactionModal(tx, simulation);

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

Безопасность: чеклист

Пункт Описание
Seed storage PBKDF2 + AES-256-GCM, хранение в Secure Enclave/Keystore
Memory security Приватный ключ в памяти только при разблокированном состоянии
Screen capture Блокировка скриншотов при отображении seed phrase
Clipboard Очистка буфера через 60 секунд после копирования
Transaction simulation Предупреждение при revert или drain
Phishing protection Верификация URL dApp, предупреждение о неизвестных контрактах
Biometrics Опциональная биометрическая защита открытия
Transport Только HTTPS/WSS, certificate pinning для мобильных
Dependency audit npm audit / Snyk на все зависимости

Технический стек

Мобильное (React Native): React Native + expo-secure-store + ethers.js v6 + viem + WalletConnect SDK + Reown AppKit.

Браузерное расширение (Chrome/Firefox): React + WebExtension API + chrome.storage + MetaMask Snap API (для расширения существующих кошельков).

Web-based (PWA): Next.js + wagmi + viem + WalletConnect — для кастодиальных или MPC-based кошельков, где ключи не хранятся в браузере.

Процесс работы

Архитектурное решение (1 неделя). Кастодиальный или некастодиальный. Мобильный, web или расширение. Список поддерживаемых цепей и токенов. Hardware wallet интеграция.

Разработка core (3-4 недели). Key management, HD wallet, transaction signing, multi-chain support.

Разработка UI (2-3 недели). Onboarding (seed generation/import), portfolio view, send/receive, transaction history, dApp browser.

Security review (1-2 недели). Penetration testing ключевых функций, seed storage аудит.

Тестирование и launch (1-2 недели). TestFlight/Play Store бета, mainnet тесты с малыми суммами.

Полный цикл мобильного кошелька: 3-4 месяца. Стоимость зависит от набора функций и платформ.