Разработка криптокошелька-расширения для браузера

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

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

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

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1374
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1256
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    965
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1208
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    667
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    954

Разработка криптокошелька-расширения для браузера — пожалуй, самая сложная задача среди crypto-клиентов. В отличие от мобильного приложения, расширение работает в трёх изолированных контекстах: background service worker, popup и content script. При этом каждое dApp ожидает стандартизированный интерфейс EIP-1193, а браузер ограничивает время жизни service worker. Мы собрали более 20 таких кошельков — для Ethereum, Solana и Polygon. Каждый раз архитектура security-first, но под конкретные требования. Свяжитесь с нашими инженерами — разберём ваш сценарий и предложим оптимальное решение.

Архитектура расширения: защита ключей и обход ограничений Manifest V3

Архитектура строится на трёх изолированных JavaScript контекстах:

┌─────────────────────────────────────────────────────────┐
│  Background Service Worker (Manifest V3)                │
│  - Хранит keystore (зашифрованный)                      │
│  - Управляет состоянием кошелька                        │
│  - Подписывает транзакции                               │
│  - Отвечает на запросы от popup и content script        │
└──────────────────┬──────────────────────────────────────┘
                   │ chrome.runtime.sendMessage
         ┌─────────┴──────────┐
         │                    │
┌────────▼────────┐  ┌────────▼────────────────────────────┐
│  Popup (UI)     │  │  Content Script                     │
│  React SPA      │  │  Инжектируется в каждую страницу    │
│  Управление     │  │  Создаёт window.ethereum             │
│  аккаунтами     │  │  Передаёт запросы от dApp            │
│  Подтверждение  │  │  к background                       │
│  транзакций     │  └─────────────────────────────────────┘
└─────────────────┘

Content script не имеет доступа к ключам, popup не имеет доступа к DOM, background — единственное место для ключей, изолированное от web content. Такая изоляция делает расширение в 2 раза безопаснее мобильного приложения, где ключи часто хранятся в shared preferences. Но это же усложняет разработку: каждый запрос требует сериализации через сообщения.

Переход с Manifest V2 на V3 создал проблемы: background page заменён на service worker, который может быть terminated браузером. Решение — использовать chrome.storage как persistence layer и keep-alive ping:

// manifest.json (Manifest V3)
{
  "manifest_version": 3,
  "name": "MyWallet",
  "version": "1.0.0",
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "content_scripts": [{
    "matches": ["<all_urls>"],
    "js": ["content-script.js"],
    "run_at": "document_start",
    "world": "ISOLATED"
  }],
  "action": {
    "default_popup": "popup.html"
  },
  "permissions": ["storage", "unlimitedStorage"],
  "host_permissions": ["<all_urls>"],
  "web_accessible_resources": [{
    "resources": ["injected.js"],
    "matches": ["<all_urls>"]
  }]
}
// background.ts — управление жизненным циклом и keystore
class WalletBackground {
  private keepAliveInterval: NodeJS.Timeout | null = null;
  constructor() {
    this.restoreState();
    this.setupKeepAlive();
  }
  private setupKeepAlive() {
    chrome.alarms.create('keepAlive', { periodInMinutes: 0.4 });
    chrome.alarms.onAlarm.addListener((alarm) => {
      if (alarm.name === 'keepAlive') { }
    });
  }
  private async restoreState() {
    const stored = await chrome.storage.session.get(['walletState']);
    if (stored.walletState) this.state = stored.walletState;
  }
  async saveState() {
    await chrome.storage.session.set({ walletState: this.state });
  }
}

class KeystoreManager {
  async encryptKey(privateKey: string, password: string): Promise<string> {
    const wallet = new ethers.Wallet(privateKey);
    const keystore = await wallet.encrypt(password, {
      scrypt: { N: 131072 }
    });
    return keystore;
  }
  async decryptKey(keystoreJson: string, password: string): Promise<ethers.Wallet> {
    try {
      return await ethers.Wallet.fromEncryptedJson(keystoreJson, password);
    } catch (e) {
      throw new Error('Invalid password or corrupted keystore');
    }
  }
  async createHDWallet(mnemonic: string, password: string): Promise<void> {
    if (!ethers.Mnemonic.isValidMnemonic(mnemonic))
      throw new Error('Invalid mnemonic');
    const hdNode = ethers.HDNodeWallet.fromMnemonic(
      ethers.Mnemonic.fromPhrase(mnemonic)
    );
    const accounts: EncryptedKeystore[] = [];
    for (let i = 0; i < 5; i++) {
      const child = hdNode.deriveChild(i);
      const encrypted = await this.encryptKey(child.privateKey, password);
      accounts.push(JSON.parse(encrypted));
    }
    await chrome.storage.local.set({
      encryptedMnemonic: await this.encryptKey(
        ethers.hexlify(ethers.toUtf8Bytes(mnemonic)), password
      ),
      accounts
    });
  }
}

class SessionManager {
  private unlockedWallets: Map<string, ethers.Wallet> = new Map();
  private lockTimer: NodeJS.Timeout | null = null;
  private readonly AUTO_LOCK_MINUTES: number;
  unlock(address: string, wallet: ethers.Wallet) {
    this.unlockedWallets.set(address.toLowerCase(), wallet);
    this.resetLockTimer();
  }
  lock() {
    this.unlockedWallets.clear();
    if (this.lockTimer) clearTimeout(this.lockTimer);
    chrome.runtime.sendMessage({ type: 'WALLET_LOCKED' });
  }
  private resetLockTimer() {
    if (this.lockTimer) clearTimeout(this.lockTimer);
    this.lockTimer = setTimeout(() => this.lock(), this.AUTO_LOCK_MINUTES * 60 * 1000);
  }
  getWallet(address: string): ethers.Wallet | undefined {
    return this.unlockedWallets.get(address.toLowerCase());
  }
}

Почему scrypt — стандарт для KDF?

При шифровании ключей мы используем scrypt с N=131072 — это делает перебор паролей крайне медленным. В комбинации с AES-256-GCM это обеспечивает защиту даже при компрометации хранилища. scrypt примерно в 100 раз медленнее pbkdf2, что сильно повышает стоимость атаки. Как отмечает спецификация, «scrypt designed to be slow» — это целенаправленное замедление, экономит до $50,000 на рисках брутфорса.

Реализация EIP-1193 провайдера

Кошелёк предоставляет window.ethereum (EIP-1193) и анонсирует себя через EIP-6963. Content script инжектирует injected script и организует мост между страницей и background. EIP-1193 в 3 раза упрощает интеграцию с dApp по сравнению с кастомными провайдерами.

// content-script.ts
function injectProvider() {
  const script = document.createElement('script');
  script.src = chrome.runtime.getURL('injected.js');
  script.type = 'module';
  (document.head ?? document.documentElement).prepend(script);
  script.remove();
}
injectProvider();
window.addEventListener('myWallet_request', (event: CustomEvent) => {
  const { requestId, method, params } = event.detail;
  chrome.runtime.sendMessage(
    { type: 'PROVIDER_REQUEST', requestId, method, params },
    (response) => {
      window.dispatchEvent(new CustomEvent('myWallet_response', {
        detail: { requestId, ...response }
      }));
    }
  );
});

// injected.ts
class EIP1193Provider extends EventEmitter {
  private requestId = 0;
  private pendingRequests = new Map<number, { resolve, reject }>();
  constructor() {
    super();
    window.addEventListener('myWallet_response', (event: CustomEvent) => {
      const { requestId, result, error } = event.detail;
      const pending = this.pendingRequests.get(requestId);
      if (pending) {
        this.pendingRequests.delete(requestId);
        error ? pending.reject(new Error(error.message)) : pending.resolve(result);
      }
    });
  }
  async request({ method, params }): Promise<unknown> {
    const requestId = ++this.requestId;
    return new Promise((resolve, reject) => {
      this.pendingRequests.set(requestId, { resolve, reject });
      window.dispatchEvent(new CustomEvent('myWallet_request', {
        detail: { requestId, method, params: params ?? [] }
      }));
      setTimeout(() => {
        if (this.pendingRequests.has(requestId)) {
          this.pendingRequests.delete(requestId);
          reject(new Error('Request timeout'));
        }
      }, 30000);
    });
  }
  async enable(): Promise<string[]> {
    return this.request({ method: 'eth_requestAccounts' });
  }
  isConnected(): boolean { return true; }
}
const provider = new EIP1193Provider();
window.ethereum = provider;
window.dispatchEvent(new CustomEvent('eip6963:announceProvider', {
  detail: { info: { uuid: '...', name: 'MyWallet', icon: '...', rdns: 'com.mywallet' }, provider }
}));

Обработка запросов в background выполняется диспетчером методов, который открывает confirmation popup для критических операций:

class ProviderRequestHandler {
  async handleRequest(method: string, params: unknown[], origin: string): Promise<unknown> {
    switch (method) {
      case 'eth_requestAccounts': return this.requestAccounts(origin);
      case 'eth_accounts': return this.getConnectedAccounts(origin);
      case 'eth_chainId': return this.getCurrentChainId();
      case 'eth_sendTransaction': return this.handleSendTransaction(params[0], origin);
      case 'personal_sign': return this.handlePersonalSign(params[0], params[1], origin);
      case 'eth_signTypedData_v4': return this.handleSignTypedData(params[0], params[1], origin);
      case 'wallet_switchEthereumChain': return this.handleChainSwitch(params[0]);
      default: return this.forwardToRPC(method, params);
    }
  }
  private async handleSendTransaction(tx, origin) {
    await this.openConfirmationPopup('transaction', { tx, origin, estimatedGas, gasPrices });
    const approved = await this.waitForUserApproval();
    if (!approved) throw new Error('User rejected transaction');
    const wallet = this.sessionManager.getWallet(tx.from);
    if (!wallet) throw new Error('Account locked');
    const signedTx = await wallet.signTransaction(tx);
    return this.provider.broadcastTransaction(signedTx);
  }
}

Popup UI и безопасность транзакций

Popup — React SPA. Критический экран — подтверждение транзакции с decoded calldata и предупреждениями о незнакомых контрактах. Код содержит компонент TransactionConfirmation, отображающий сумму, получателя и оценку газа. Кошелёк проверяет домены по публичным фишинговым спискам (MetaMask, Etherscan). При подозрении показывается предупреждение. Для EIP-712 (Permit) пользователю показывается детализированная информация о бесконечном апруве.

Стек и инструменты

Компонент Технология
Extension framework Manifest V3, WXT (Vite-based) или CRXJS
UI (popup) React 18 + TypeScript + Tailwind
Crypto primitives ethers.js v6 или viem
Key derivation BIP-39 (mnemonic), BIP-44 (HD paths)
Storage encryption AES-256-GCM + scrypt KDF
State management Zustand или Recoil
Build Vite + rollup
Testing Playwright для E2E, Vitest для unit

WXT снижает затраты на сборку примерно на $5,000 по сравнению с ручной конфигурацией.

Этапы разработки

Фаза Содержание Срок
Архитектура MV3 design, IPC схема, security model 2 нед
Keystore Encrypt/decrypt, HD wallet, auto-lock 3–4 нед
Provider (EIP-1193) window.ethereum, content script, injected 3–4 нед
Background handler Все RPC методы, chain management 3–4 нед
Popup UI Account management, tx confirmation, signing 4–6 нед
Security Phishing detection, simulation preview 2–3 нед
Multi-chain Добавление Solana, TON или других VM 4–8 нед
Тестирование E2E с реальными dApp, security review 3–4 нед
Аудит Crypto primitives + key storage 3–4 нед
Документация Архитектура, API, инструкция по эксплуатации 1–2 нед
Обучение 2–3 сессии для команды заказчика 1 нед
Поддержка 1 месяц после запуска

Совместимость со store: Chrome Web Store требует строгих проверок MV3, Firefox использует MV2/MV3 с отличиями. Сборки для обоих браузеров — отдельная задача в pipeline.

Типичные ошибки при разработке
  • Забывают про keep-alive — background unloads, и кошелёк теряет состояние.
  • Не изолируют injected script от страницы — уязвимости через prototype pollution.
  • Не проверяют домен origin — фишинг через iframe.

Получите консультацию наших инженеров — оценим ваш проект, предложим архитектуру и сроки. Пишите, мы гарантируем подход security-first и прозрачность на каждом этапе. Используйте scrypt для защиты ключей — это проверенный стандарт.

Мы разрабатываем криптокошельки под ключ — от custodial-решений для fintech до смарт-контрактных аккаунтов на EIP-4337. 5+ лет на рынке блокчейн-разработки, 40+ реализованных проектов. Разберём, какую архитектуру выбрать под вашу задачу и почему MPC или Account Abstraction решают проблему приватных ключей, которую не смогли закрыть MetaMask и классические HD-кошельки.

Почему классические кошельки опасны для бизнеса?

Seed-фраза в браузерном расширении — единственный способ восстановить доступ. Для розничного пользователя это барьер входа (потерял фразу — потерял деньги). Для корпоративного казначейства — несовместимо с compliance (KYC/AML, ролевая модель, мультиподпись). Любая утечка одного ключа компрометирует все средства. Эти риски заложены в архитектуру, а не в плохой UX.

Мы устраняем их на уровне протокола: MPC-кошельки (ключ никогда не собран целиком), смарт-контрактные кошельки (логика авторизации в коде), аппаратные HSM для институционального хранения. Ниже — детали.

Custodial vs Non-custodial: в чём реальная разница

Custodial — провайдер хранит приватный ключ. Пользователь аутентифицируется через email/password/OAuth. Восстановление тривиально, KYC/AML встроены. Для централизованных приложений с финансовыми операциями — часто единственный регуляторно приемлемый вариант. Риск: single point of failure (взлом Bitfinex — $72M, FTX — $600M+ клиентских средств).

Non-custodial — ключи у пользователя. Провайдер не имеет доступа к средствам. Ответственность за хранение ложится на пользователя. Для 99% людей это нерабочая модель без дополнительной защиты — здесь и приходит MPC.

MPC-кошельки: ключ, которого нет

Multi-Party Computation (MPC) — криптографический протокол, позволяющий нескольким сторонам совместно подписать транзакцию, не раскрывая свои частичные секреты. Приватный ключ никогда не существует в собранном виде.

Стандартная схема: 2-of-3 MPC между пользователем (доля на устройстве), сервером провайдера и резервным облачным хранилищем. Транзакция подписывается двумя любыми из трёх сторон. Телефон потерян — восстановление через сервер + облако. Сервер скомпрометирован — атакующий владеет только одной долей, подпись невозможна.

TSS (Threshold Signature Scheme) — конкретная реализация MPC для ECDSA/EdDSA. Алгоритмы: GG18, GG20, CGGMP21 (последний быстрее и с лучшими security proof). Библиотеки: tss-lib (Go, от Binance), multi-party-sig (Go, от Coinbase), ZenGo-X/multi-party-ecdsa (Rust).

MPC не требует on-chain изменений — для блокчейна подпись выглядит как обычная single-key подпись. Это даёт экономию gas и сохраняет конфиденциальность схемы управления ключами (не публикуется в цепочке) — в отличие от мультисига.

Account Abstraction (EIP-4337): смарт-контракт как кошелёк

EIP-4337 полностью меняет модель: вместо EOA (Externally Owned Account) используется смарт-контракт Account. Логика авторизации — в коде контракта, не в криптографии протокола. Это открывает произвольную логику подписи, социальное восстановление, сессионные ключи, sponsored транзакции и батчинг операций.

Как работает стек EIP-4337:

User → UserOperation → Bundler → EntryPoint contract → Account contract
                                          ↑
                                    Paymaster (optional, pays gas)

UserOperation — новый тип объекта (не L1-транзакция). Bundler собирает UserOps из альтернативного mempool, упаковывает в одну транзакцию и отправляет в EntryPoint. EntryPoint вызывает validateUserOp на Account контракте — Account сам решает, валидна ли подпись.

Практические возможности:

Социальное восстановление. Контракт хранит список guardian'ов (другие адреса или сервис). Потеря ключа — guardians голосуют за замену. Argent использует схему с 2020 года.

Сессионные ключи. Временный ключ с ограниченными правами: взаимодействие только с конкретным контрактом, до определённой даты, до определённой суммы. Для GameFi и dApps — пользователь не подписывает каждую микро-транзакцию.

Paymaster. Сторонний контракт платит газ за пользователя. Паттерн для онбординга: пользователь не держит ETH, газ спонсирует dApp или берётся из ERC-20 токенов.

Реализации: Safe{Core} Protocol, Biconomy SDK (Stackup), ZeroDev (Kernel), Alchemy (Rundler bundler). На Ethereum mainnet, Polygon, Arbitrum, Optimism EntryPoint v0.6/v0.7 задеплоен и активен. Гарантируем совместимость с последними версиями контрактов.

Hardware Security Module для корпоративных кошельков

Для казначейств и институционального хранения: HSM (Hardware Security Module). Ключ генерируется и никогда не покидает защищённый чип. Подпись — внутри HSM. Поддерживается аппаратная аттестация. Используемые решения: AWS CloudHSM, Azure Dedicated HSM, Thales Luna, YubiHSM 2 (для небольших объёмов). Интеграция через PKCS#11 или cloud-specific API.

Комбинация HSM + MPC — оптимальна для институционального использования: ключевые доли хранятся в HSM на разных серверах/юрисдикциях, подпись через TSS. Это обеспечивает соответствие регуляторным требованиям (например, для крипто-кастодианов).

Интеграция с dApps: WalletConnect и стандарты

Любой кошелёк должен уметь взаимодействовать с dApps. Стандарт — WalletConnect v2 (Sign API): QR-код или deep link, peer-to-peer зашифрованный канал через relay сервер. Для браузерных расширений — EIP-1193 (Ethereum Provider API).

На фронтенде используем wagmi + viem — один интерфейс для MetaMask, WalletConnect, Coinbase Wallet, injected providers. Для Account Abstraction — EIP-5792 (wallet capabilities) и EIP-7677 (paymaster service).

Процесс разработки

  1. Threat model — кто пользователь (B2C, B2B, institutional), какие операции, каков допустимый risk model. От этого зависит архитектура.
  2. Выбор и проектирование схемы хранения ключей — MPC, HSM, мультисиг или их комбинация.
  3. Разработка Account контракта (если EIP-4337) или интеграция MPC-библиотеки.
  4. Backend — MPC-координация, управление сессиями, paymaster-сервис (если нужен).
  5. Мобильное/браузерное приложение — UI с интеграцией WalletConnect, биометрии, QR.
  6. Интеграция с dApps — EIP-1193, WalletConnect v2.
  7. Аудит контрактов и криптографических реализаций — обязательный этап. MPC-библиотеки имеют известные уязвимости (GG18 подвержен атаке при malicious participant без abort protocol). Используем библиотеки с актуальными security review (CGGMP21). Опыт прохождения аудитов у Certik, Hacken, Trail of Bits — подтверждаем сертификатами.

Что входит в работу (deliverables)

  • Исходные коды смарт-контрактов (Solidity/Rust) с документацией
  • Backend-сервис MPC-координации (на Go или Rust) с API
  • Мобильное приложение (iOS/Android) или браузерное расширение
  • Интеграция с WalletConnect, Ledger/Trezor (по необходимости)
  • Подготовка к аудиту безопасности (отчёт со списком уязвимостей)
  • Документация администратора и пользователя
  • Доступ к репозиторию, CI/CD, мониторинг (Tenderly, Etherscan API)
  • Обучение вашей команды (2-3 сессии)
  • Поддержка после запуска — 1 месяц

Сроки и стоимость

Тип решения Сроки (рабочие недели)
Custodial с базовым UI 4–8
Non-custodial с MPC-интеграцией 8–16
EIP-4337 Account с paymaster 6–12
Institutional (HSM + MPC + compliance) от 16

Стоимость рассчитывается индивидуально под ваш проект. Оценим за 1 день — напишите на почту или в Telegram. Предоставляем гарантию на код и timeline.

Типичные ошибки при разработке криптокошельков (и как их избежать)

  • Использование устаревших MPC-библиотек — GG18 без abort protocol. Выбираем CGGMP21 или tss-lib с актуальными audit report.
  • Жёсткая привязка к одному блокчейну — не закладывают абстракцию под L2/сайдчейны. Используем viem/wagmi для кросс-чейн.
  • Игнорирование MEV-атак — при использовании мультисига без таймлоков. Добавляем tx simulation (Tenderly) и sandwitching protection.
  • Отсутствие fallback-механизма восстановления — для Account Abstraction не настраивают social recovery. Закладываем с первого релиза.

Устраняем эти грабли на этапе проектирования — под каждый проект составляем threat model и security checklist.

Нужен надёжный кошелёк без компромиссов? Получите консультацию нашего архитектора — разберём вашу задачу и предложим архитектуру с точной сметой. Оставляйте заявку — ответим в течение дня.