Розробка криптовалютного гаманця

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка криптовалютного гаманця
Складний
від 2 тижнів до 3 місяців
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1218
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    920
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1147
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    610
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    885

Розробка криптовалютного гаманця

Криптовалютний гаманець — це не сховище монет. Монети живуть у блокчейні. Гаманець зберігає приватні ключі та підписує транзакції. Це принципіальне розходження визначає всю архітектуру: головне завдання — безпечне управління ключами, а не збереження балансів. Неправильна робота з ключами робить будь-який красивий UI безглуздим — якщо seed phrase зберігається в localStorage або передається на сервер, гаманець небезпечний незалежно від усього іншого.

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

Типи гаманців та архітектурні рішення

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

Стандарт для більшості некастодіальних гаманців. З однієї seed phrase (12/24 слова BIP-39) виводиться дерево ключів. Кожен ланцюг, кожен аккаунт, кожна адреса — окремий дочірній ключ.

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> {
  // Дериворуємо ключ шифрування з 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 ланцюги: один ключ

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 });
  }
}

Баланси токенів: Пакетування запитів

Окремий RPC виклик на токен на ланцюг = величезна затримка. 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,
  }));
}

Симуляція транзакцій

Перед відправкою показуємо користувачу що буде. 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 або неочікувано зменшує баланс — попередити користувача перед реальною відправкою.

Інтеграція 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 PBKDF2 + AES-256-GCM, зберігання в Secure Enclave/Keystore
Безпека пам'яті Приватний ключ в пам'яті тільки при розблокованому стані
Захоплення екрану Блокування скриншотів при відображенні seed phrase
Clipboard Очистка буфера через 60 секунд після копіювання
Симуляція транзакцій Попередження при revert або drain
Захист від фішингу Верифікація URL dApp, попередження про невідомі контракти
Біометрія Опціональна біометрична захист відкриття
Transport Тільки HTTPS/WSS, certificate pinning для мобільних
Аудит залежностей 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 або розширення. Список підтримуваних ланцюгів та токенів. Інтеграція апаратного гаманця.

Розробка core (3-4 тижні). Управління ключами, HD wallet, підпис транзакцій, мультиланцюгова підтримка.

Розробка UI (2-3 тижні). Onboarding (генерація/імпорт seed), portfolio view, send/receive, історія транзакцій, dApp browser.

Security review (1-2 тижні). Penetration testing ключових функцій, аудит зберігання seed.

Тестування та запуск (1-2 тижні). TestFlight/Play Store beta, тести mainnet з малими сумами.

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