Интеграция кошелька с Phantom (Solana)
Phantom — де-факто стандартный кошелёк в экосистеме Solana. Его API инжектируется в window.solana и следует интерфейсу SolanaProvider из спецификации wallet-standard. Интеграция несложная, но есть несколько мест, где легко выстрелить себе в ногу.
Подключение и обнаружение провайдера
Первая ошибка — проверять window.solana сразу при загрузке страницы. Расширение инжектируется асинхронно, и на быстрых машинах оно успевает раньше, чем выполнится ваш JS, а на медленных — нет. Надёжный паттерн:
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, который абстрагирует все провайдеры через единый интерфейс.
Подключение, подпись и транзакции
// Подключение
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 вручную.
Обработка состояния и событий
Phantom эмитит события connect, disconnect и accountChanged. Обязательно подписываться на accountChanged — пользователь может переключить аккаунт внутри кошелька без переподключения, и ваше приложение об этом не узнает без явного listener'а.
provider.on('accountChanged', (publicKey: PublicKey | null) => {
if (publicKey) {
// Обновить состояние приложения
} else {
// Кошелёк заблокирован — разлогинить пользователя
provider.connect().catch(() => {});
}
});
Для React-приложений весь этот слой лучше вынести в @solana/wallet-adapter-react — он управляет жизненным циклом, мемоизацией и реконнектом автоматически.







