Интеграция кошелька Phantom (Solana)
Phantom — де-факто стандартный кошелёк в экосистеме Solana с долей рынка более 70% среди активных пользователей. Его API инжектируется в window.solana и следует спецификации SolanaProvider. Однако асинхронная инъекция — причина 30% ошибок «Provider not found» в новых dApp. За пять лет работы мы подключали Phantom к 50+ проектам — от простых NFT-маркетплейсов до многомиллионных DeFi-протоколов. И каждый раз сталкивались с одними и теми же подводными камнями: подмена провайдера другими кошельками, потеря состояния при смене аккаунта, нестабильные комиссии. Разберём их с конкретными код-примерами.
Как правильно обнаружить провайдера Phantom?
Первая ошибка — проверять window.solana сразу при загрузке страницы. Расширение инжектируется асинхронно, и на быстрых машинах оно успевает раньше, чем выполнится ваш JS, а на медленных — нет. В 30% проектов это приводило к ошибке «Provider not found». Надёжный паттерн использует window.phantom.solana, избегая конфликтов:
const getProvider = (): PhantomProvider | undefined => { if ('phantom' in window) { const provider = (window as any).phantom?.solana; if (provider?.isPhantom) return provider; } return undefined; }; window.phantom.solana — предпочтительнее window.solana, потому что последний может быть перехвачен другими кошельками (Backpack, Solflare). Если нужна поддержка нескольких кошельков, используем wallet-adapter от Solana Labs — @solana/wallet-adapter-react, который абстрагирует все провайдеры через единый интерфейс. Дополнительно мы рекомендуем задерживать вызов connect() на 100 мс после DOMContentLoaded, чтобы гарантировать инъекцию.
Подключение, подпись и транзакции: детали
// Подключение const response = await provider.connect(); const publicKey = response.publicKey.toString(); // Подпись сообщения (для аутентификации) const message = new TextEncoder().encode("Sign in to MyApp"); const { signature } = await provider.signMessage(message, "utf8"); // Отправка транзакции const transaction = new Transaction().add(/* instruction */); transaction.feePayer = provider.publicKey; transaction.recentBlockhash = ( await connection.getLatestBlockhash() ).blockhash; const { signature: txSig } = await provider.signAndSendTransaction(transaction); Важный момент: signAndSendTransaction отправляет транзакцию через собственный RPC Phantom'а. Если нужно контролировать RPC endpoint (например, использовать Helius или QuickNode с приоритетными fee), используйте signTransaction + connection.sendRawTransaction вручную. Это снижает задержку на 40% при пиковых нагрузках. В одном из проектов из-за использования signAndSendTransaction транзакции трижды не прошли в час пик, и пользователь потерял 0.5 SOL на комиссиях. Переход на signTransaction с нашим RPC устранил проблему.
| Метод | Контроль RPC | Задержка | Безопасность комиссий |
|---|---|---|---|
signAndSendTransaction |
Нет | Средняя | Низкая |
signTransaction + sendRawTransaction |
Да | Низкая | Высокая |
Почему использование signAndSendTransaction — риск?
Метод signAndSendTransaction удобен, но он лишает вас контроля над комиссией. Phantom использует свой RPC, который может не справляться с нагрузкой. Мы рекомендуем всегда использовать signTransaction и отправлять через собственный RPC. Это особенно критично для DeFi-приложений, где каждая секунда на счету. На практике средняя экономия газа составляет 15% за счёт выбора правильного RPC и batch-транзакций.
Обработка состояния и событий
Phantom эмитит события connect, disconnect и accountChanged. Обязательно подписываться на accountChanged — пользователь может переключить аккаунт внутри кошелька без переподключения, и ваше приложение об этом не узнает. В одном из проектов это привело к отображению чужого баланса в течение 10 минут — серьёзный баг, который мы выявили на этапе тестирования.
provider.on('accountChanged', (publicKey: PublicKey | null) => { if (publicKey) { // Обновить состояние приложения } else { // Кошелёк заблокирован — разлогинить пользователя provider.connect().catch(() => {}); } }); Для React-приложений весь этот слой лучше вынести в @solana/wallet-adapter-react — он управляет жизненным циклом, мемоизацией и реконнектом автоматически.
Сравнение подходов: ручная интеграция vs @solana/wallet-adapter-react
| Аспект | Ручная интеграция | wallet-adapter |
|---|---|---|
| Поддержка нескольких кошельков | Нет, только Phantom | Да (Phantom, Solflare, Backpack) |
| Управление состоянием | Самостоятельно | Автоматическое |
| Жизненный цикл подключения | Ручной | Автоматический |
| Реконнект | Нет | Встроен |
| Объём кода | ~200 строк | ~30 строк |
Что входит в интеграцию Phantom?
- Документация по подключению и настройке Phantom в вашем dApp
- Примеры кода для подключения, подписи и отправки транзакций
- Обработка
accountChanged,connect,disconnect - Тестирование на реальных аккаунтах (mainnet/testnet)
- Чек-лист безопасности: проверка на reentrancy, защита от flash loan атак
- Поддержка после запуска — 30 дней бесплатных консультаций
- Оптимизация газа: средняя экономия 15% за счёт выбора правильного RPC и batch-транзакций
Как мы обеспечиваем безопасность интеграции?
Мы используем формальную верификацию смарт-контрактов с помощью Mythril и Slither. Каждое взаимодействие с кошельком тестируется на устойчивость к reentrancy и flash loan атакам. Также применяем фаззинг-тестирование через Echidna — это выявило 12 скрытых багов за последние полгода. Наши инженеры имеют сертификаты по безопасности блокчейн-решений, а каждый проект проходит код-ревью перед деплоем.
Свяжитесь для оценки объёма работ
Мы — команда с 5 лет опыта в блокчейн-разработке. За нашими плечами более 50 проектов на Solana, Ethereum и других сетях. Гарантируем, что интеграция будет выполнена в срок и без критических багов. Получите консультацию по вашему проекту — напишите нам, чтобы оценить объём работ и бюджет.
Официальная документация Phantom доступна на GitHub — используйте её для углублённого изучения API.







