npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@armadura/connector

v0.1.6

Published

Headless wallet connector client and React provider built on Wallet Standard

Readme


title: @solana/connector description: Production-ready wallet connector for Solana applications

ConnectorKit is your Solana wallet infrastructure. A headless, framework-agnostic wallet connector built on Wallet Standard that just work.

npm Bundle Size TypeScript

Why ConnectorKit?

  • Wallet Standard First: Built on the official Wallet Standard protocol for universal wallet compatibility
  • Modern & Legacy Support: Works with both @solana/kit (web3.js 2.0) and @solana/web3.js (legacy)
  • Framework Agnostic: React hooks + headless core for Vue, Svelte, or vanilla JavaScript
  • Production Ready: Event system for analytics, health checks for diagnostics, error boundaries for React apps
  • Enhanced Storage: Automatic validation, SSR fallback, and error handling out of the box
  • Mobile Support: Built-in Solana Mobile Wallet Adapter integration

Quick Start

1. Install

npm install @solana/connector
# or
pnpm add @solana/connector
# or
yarn add @solana/connector
# or 
bun add @solana/connector

2. Setup Provider (once in your app root)

'use client';

import { useMemo } from 'react';
import { AppProvider } from '@solana/connector/react';
import { getDefaultConfig, getDefaultMobileConfig } from '@solana/connector/headless';

export function Providers({ children }: { children: React.ReactNode }) {
    const connectorConfig = useMemo(() => {
        // Optional: Get custom RPC URL from environment variable
        const customRpcUrl = process.env.SOLANA_RPC_URL;

        // Optional: Create custom cluster configuration
        const clusters = customRpcUrl
            ? [
                  {
                      id: 'solana:mainnet' as const,
                      label: 'Mainnet (Custom RPC)',
                      name: 'mainnet-beta' as const,
                      url: customRpcUrl,
                  },
                  {
                      id: 'solana:devnet' as const,
                      label: 'Devnet',
                      name: 'devnet' as const,
                      url: 'https://api.devnet.solana.com',
                  },
              ]
            : undefined;

        return getDefaultConfig({
            appName: 'My App',
            appUrl: 'https://myapp.com',
            autoConnect: true,
            enableMobile: true,
            clusters,
        });
    }, []);

    const mobile = useMemo(
        () =>
            getDefaultMobileConfig({
                appName: 'My App',
                appUrl: 'https://myapp.com',
            }),
        [],
    );

    return (
        <AppProvider connectorConfig={connectorConfig} mobile={mobile}>
            {children}
        </AppProvider>
    );
}

3. Use Hooks (in any component)

'use client';

import { useConnector, useAccount } from '@solana/connector';

export function ConnectButton() {
    const { wallets, select, disconnect, connected, connecting, selectedWallet, selectedAccount } = useConnector();
    const { address, formatted, copy } = useAccount();

    if (connecting) {
        return <button disabled>Connecting...</button>;
    }

    if (!connected) {
        return (
            <div>
                {wallets.map(w => (
                    <button key={w.wallet.name} onClick={() => select(w.wallet.name)}>
                        Connect {w.wallet.name}
                    </button>
                ))}
            </div>
        );
    }

    return (
        <div>
            <button onClick={copy}>{formatted}</button>
            <button onClick={disconnect}>Disconnect</button>
        </div>
    );
}

That's it! You're ready to go. Everything else below is optional.


Core Hooks

These are the main hooks you'll use in your components.

useConnector()

Main hook for wallet connection and state.

import { useConnector } from '@solana/connector';

function Component() {
    const {
        // State
        wallets, // WalletInfo[] - All available wallets
        selectedWallet, // Wallet | null - Currently connected wallet
        selectedAccount, // string | null - Currently selected account address
        accounts, // AccountInfo[] - Connected accounts
        connected, // boolean - Connection status
        connecting, // boolean - Connecting in progress

        // Actions
        select, // (walletName: string) => Promise<void>
        disconnect, // () => Promise<void>
    } = useConnector();
}

Real Example - Connect Button with wallet selection:

'use client';

import { useConnector } from '@solana/connector';
import { useState } from 'react';

export function ConnectButton() {
    const [isModalOpen, setIsModalOpen] = useState(false);
    const { connected, connecting, selectedWallet, selectedAccount, wallets, select } = useConnector();

    if (connecting) {
        return <button disabled>Connecting...</button>;
    }

    if (connected && selectedAccount && selectedWallet) {
        const shortAddress = `${selectedAccount.slice(0, 4)}...${selectedAccount.slice(-4)}`;
        return (
            <div>
                <span>{shortAddress}</span>
                <button onClick={() => select(selectedWallet.name)}>Switch Wallet</button>
            </div>
        );
    }

    return (
        <>
            <button onClick={() => setIsModalOpen(true)}>Connect Wallet</button>
            {/* Wallet selection modal */}
        </>
    );
}

useAccount()

Hook for working with the connected account.

import { useAccount } from '@solana/connector';

function Component() {
    const {
        address, // string | null - Full wallet address
        formatted, // string - Shortened address (e.g., "5Gv8...x3kF")
        copy, // () => Promise<boolean> - Copy address to clipboard
        copied, // boolean - True for 2s after copying
        connected, // boolean - Connection status
        accounts, // AccountInfo[] - All accounts
        selectAccount, // (address: string) => Promise<void>
    } = useAccount();
}

Real Example - Account Switcher for multi-account wallets:

'use client';

import { useAccount } from '@solana/connector';

export function AccountSwitcher() {
    const { accounts, address, selectAccount, connected } = useAccount();

    if (!connected || accounts.length <= 1) {
        return null;
    }

    return (
        <select
            value={address || ''}
            onChange={e => selectAccount(e.target.value)}
        >
            {accounts.map(account => (
                <option key={account.address} value={account.address}>
                    {account.address.slice(0, 6)}...{account.address.slice(-6)}
                </option>
            ))}
        </select>
    );
}

useCluster()

Hook for managing Solana network/cluster.

import { useCluster } from '@solana/connector';

function Component() {
    const {
        cluster, // SolanaCluster | null - Active cluster
        clusters, // SolanaCluster[] - Available clusters
        setCluster, // (id: SolanaClusterId) => Promise<void>
        isMainnet, // boolean - Convenience flags
        isDevnet, // boolean
        rpcUrl, // string - RPC endpoint URL
        explorerUrl, // string - Solana Explorer base URL
    } = useCluster();
}

Real Example - Network Selector:

'use client';

import { useCluster } from '@solana/connector';

export function ClusterSelector() {
    const { cluster, clusters, setCluster } = useCluster();

    return (
        <select
            value={cluster?.id || ''}
            onChange={e => setCluster(e.target.value as SolanaClusterId)}
        >
            {clusters.map(c => (
                <option key={c.id} value={c.id}>
                    {c.label}
                </option>
            ))}
        </select>
    );
}

useWalletInfo()

Hook for accessing current wallet metadata.

import { useWalletInfo } from '@solana/connector';

function Component() {
    const {
        name, // string | null - Wallet name
        icon, // string | undefined - Wallet icon URL
        wallet, // WalletInfo | null - Full wallet info
        connecting, // boolean - Connection in progress
    } = useWalletInfo();
}

Transaction Signing

ConnectorKit provides powerful transaction signing capabilities with support for both legacy @solana/web3.js and modern @solana/kit APIs.

Modern API (@solana/kit)

Use useKitTransactionSigner() for modern, type-safe transaction building:

'use client';

import { useState } from 'react';
import {
    address,
    createSolanaRpc,
    pipe,
    createTransactionMessage,
    setTransactionMessageFeePayerSigner,
    setTransactionMessageLifetimeUsingBlockhash,
    appendTransactionMessageInstructions,
    sendAndConfirmTransactionFactory,
    getSignatureFromTransaction,
    signTransactionMessageWithSigners,
    createSolanaRpcSubscriptions,
    lamports,
    assertIsTransactionWithBlockhashLifetime,
} from '@solana/kit';
import { getTransferSolInstruction } from '@solana-program/system';
import { useKitTransactionSigner, useCluster, useConnectorClient, LAMPORTS_PER_SOL } from '@solana/connector';

export function ModernSolTransfer() {
    const { signer, ready } = useKitTransactionSigner();
    const { cluster } = useCluster();
    const client = useConnectorClient();
    const [signature, setSignature] = useState<string | null>(null);

    async function handleTransfer(recipientAddress: string, amount: number) {
        if (!signer || !client) {
            throw new Error('Wallet not connected');
        }

        // Get RPC URL from connector client
        const rpcUrl = client.getRpcUrl();
        if (!rpcUrl) {
            throw new Error('No RPC endpoint configured');
        }

        // Create RPC client using web3.js 2.0
        const rpc = createSolanaRpc(rpcUrl);
        const rpcSubscriptions = createSolanaRpcSubscriptions(rpcUrl.replace('http', 'ws'));

        // Get recent blockhash
        const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

        // Convert SOL to lamports
        const amountInLamports = lamports(BigInt(Math.floor(amount * Number(LAMPORTS_PER_SOL))));

        // Create transfer instruction
        const transferInstruction = getTransferSolInstruction({
            source: signer,
            destination: address(recipientAddress),
            amount: amountInLamports,
        });

        // Build transaction message
        const transactionMessage = pipe(
            createTransactionMessage({ version: 0 }),
            tx => setTransactionMessageFeePayerSigner(signer, tx),
            tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
            tx => appendTransactionMessageInstructions([transferInstruction], tx),
        );

        // Sign transaction
        const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);

        // Send and confirm
        assertIsTransactionWithBlockhashLifetime(signedTransaction);
        await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction, {
            commitment: 'confirmed',
        });

        const transactionSignature = getSignatureFromTransaction(signedTransaction);
        setSignature(transactionSignature);
    }

    return (
        <div>
            {/* Your form UI */}
            <button onClick={() => handleTransfer('...', 0.1)} disabled={!ready}>
                Send SOL
            </button>
            {signature && <div>Transaction: {signature}</div>}
        </div>
    );
}

Legacy API (@solana/web3.js)

Use useTransactionSigner() for legacy web3.js compatibility:

import { useTransactionSigner } from '@solana/connector';
import { Transaction, SystemProgram } from '@solana/web3.js';

function SendTransaction() {
    const { signer, ready } = useTransactionSigner();

    const handleSend = async () => {
        if (!signer) return;

        const transaction = new Transaction().add(
            SystemProgram.transfer({
                fromPubkey: signer.address,
                toPubkey: recipientPubkey,
                lamports: 1000000,
            })
        );

        const signature = await signer.signAndSendTransaction(transaction);
        console.log('Transaction sent:', signature);
    };

    return <button onClick={handleSend} disabled={!ready}>Send</button>;
}

UI Elements

ConnectorKit provides composable UI elements that handle data fetching and state management for you. Use the render prop pattern to customize the UI.

Available Elements

  • BalanceElement - Display SOL balance with refresh
  • ClusterElement - Network/cluster selector
  • TokenListElement - List of SPL tokens
  • TransactionHistoryElement - Recent transaction history
  • DisconnectElement - Disconnect button
  • AccountElement - Account display and switcher
  • WalletListElement - List of available wallets

Example: Wallet Dropdown

'use client';

import {
    BalanceElement,
    ClusterElement,
    TokenListElement,
    TransactionHistoryElement,
    DisconnectElement,
} from '@solana/connector/react';

export function WalletDropdown() {
    return (
        <div className="wallet-dropdown">
            {/* Balance */}
            <BalanceElement
                render={({ solBalance, isLoading, refetch }) => (
                    <div>
                        <div>Balance: {isLoading ? '...' : `${solBalance?.toFixed(4)} SOL`}</div>
                        <button onClick={refetch}>Refresh</button>
                    </div>
                )}
            />

            {/* Network Selector */}
            <ClusterElement
                render={({ cluster, clusters, setCluster }) => (
                    <select value={cluster?.id} onChange={e => setCluster(e.target.value)}>
                        {clusters.map(c => (
                            <option key={c.id} value={c.id}>
                                {c.label}
                            </option>
                        ))}
                    </select>
                )}
            />

            {/* Tokens */}
            <TokenListElement
                limit={5}
                render={({ tokens, isLoading }) => (
                    <div>
                        {isLoading ? (
                            <div>Loading tokens...</div>
                        ) : (
                            tokens.map(token => (
                                <div key={token.mint}>
                                    {token.symbol}: {token.formatted}
                                </div>
                            ))
                        )}
                    </div>
                )}
            />

            {/* Transaction History */}
            <TransactionHistoryElement
                limit={5}
                render={({ transactions, isLoading }) => (
                    <div>
                        {isLoading ? (
                            <div>Loading transactions...</div>
                        ) : (
                            transactions.map(tx => (
                                <a key={tx.signature} href={tx.explorerUrl} target="_blank">
                                    {tx.type} - {tx.formattedTime}
                                </a>
                            ))
                        )}
                    </div>
                )}
            />

            {/* Disconnect */}
            <DisconnectElement
                render={({ disconnect, disconnecting }) => (
                    <button onClick={disconnect} disabled={disconnecting}>
                        {disconnecting ? 'Disconnecting...' : 'Disconnect'}
                    </button>
                )}
            />
        </div>
    );
}

Configuration

Basic Configuration

import { getDefaultConfig } from '@solana/connector';

const config = getDefaultConfig({
    appName: 'My App', // Required
    appUrl: 'https://myapp.com', // Optional: for mobile wallet adapter
    autoConnect: true, // Auto-reconnect (default: true)
    network: 'mainnet-beta', // Initial network (default: 'mainnet-beta')
    enableMobile: true, // Mobile Wallet Adapter (default: true)
    debug: false, // Debug logging
});

Network Selection

const config = getDefaultConfig({
    appName: 'My App',
    network: 'devnet', // 'mainnet-beta' | 'devnet' | 'testnet' | 'localnet'
});

Custom RPC Endpoints

import { getDefaultConfig } from '@solana/connector';

const config = getDefaultConfig({
    appName: 'My App',
    clusters: [
        {
            id: 'solana:mainnet' as const,
            label: 'Mainnet (Custom RPC)',
            name: 'mainnet-beta' as const,
            url: 'https://my-custom-rpc.com',
        },
        {
            id: 'solana:devnet' as const,
            label: 'Devnet',
            name: 'devnet' as const,
            url: 'https://api.devnet.solana.com',
        },
    ],
});

Mobile Wallet Adapter

import { getDefaultMobileConfig } from '@solana/connector/headless';

const mobile = getDefaultMobileConfig({
    appName: 'My App',
    appUrl: 'https://myapp.com',
});

<AppProvider connectorConfig={config} mobile={mobile}>
    {children}
</AppProvider>

Advanced Usage

Headless Client (Vue, Svelte, Vanilla JS)

Use ConnectorClient for non-React frameworks:

import { ConnectorClient, getDefaultConfig } from '@solana/connector/headless';

// Create client
const client = new ConnectorClient(getDefaultConfig({ appName: 'My App' }));

// Get state
const state = client.getSnapshot();
console.log('Wallets:', state.wallets);

// Connect
await client.select('Phantom');

// Subscribe to changes
const unsubscribe = client.subscribe(state => {
    console.log('State updated:', state);
});

// Disconnect
await client.disconnect();

// Cleanup
client.destroy();

Custom Storage (React Native, SSR)

Storage uses nanostores with built-in enhancements that are automatically applied:

  • Validation (Solana address format checking)
  • Error handling (catches localStorage quota errors, private browsing)
  • SSR fallback (uses memory storage when localStorage unavailable)

Most users don't need to configure storage. Only customize for:

  • React Native (custom storage backend)
  • Additional validation rules
  • Custom error tracking
import { getDefaultConfig, createEnhancedStorageWallet, EnhancedStorageAdapter } from '@solana/connector';

const config = getDefaultConfig({
    appName: 'My App',
    storage: {
        wallet: new EnhancedStorageAdapter(
            createEnhancedStorageWallet({
                validator: walletName => {
                    // Custom validation
                    return walletName !== null && walletName.length > 0;
                },
                onError: error => {
                    // Custom error tracking
                    Sentry.captureException(error);
                },
            }),
        ),
    },
});

Package Exports

Main Export

// Full library - includes React and headless
import { ConnectorProvider, useConnector, useAccount } from '@solana/connector';

Headless Export (Framework Agnostic)

// Headless core only - no React
import { ConnectorClient, getDefaultConfig } from '@solana/connector/headless';

React Export

// React-specific exports only
import { useConnector, useAccount } from '@solana/connector/react';

API Reference

Hooks

| Hook | Description | Returns | |------|-------------|---------| | useConnector() | Main wallet connection hook | { wallets, selectedWallet, accounts, connected, connecting, select, disconnect } | | useAccount() | Account management hook | { address, formatted, copy, copied, accounts, selectAccount } | | useCluster() | Network/cluster management hook | { cluster, clusters, setCluster, isMainnet, isDevnet, rpcUrl } | | useWalletInfo() | Wallet metadata hook | { name, icon, wallet, connecting } | | useTransactionSigner() | Legacy transaction signer (web3.js) | { signer, ready, address, capabilities } | | useKitTransactionSigner() | Modern transaction signer (@solana/kit) | { signer, ready, address } | | useBalance() | SOL balance hook | { solBalance, isLoading, refetch } | | useTokens() | SPL tokens hook | { tokens, isLoading, refetch } | | useTransactions() | Transaction history hook | { transactions, isLoading, refetch } |

Configuration Functions

| Function | Description | |----------|-------------| | getDefaultConfig(options) | Create default connector configuration | | getDefaultMobileConfig(options) | Create mobile wallet adapter configuration | | createConfig(options) | Create unified config for ConnectorKit + Armadura |

Utility Functions

| Function | Description | |----------|-------------| | formatAddress(address, options?) | Format Solana address | | formatSOL(lamports, options?) | Format SOL amount | | copyAddressToClipboard(address) | Copy address to clipboard | | getTransactionUrl(cluster, signature) | Get Solana Explorer transaction URL | | getAddressUrl(cluster, address) | Get Solana Explorer address URL |


Types

import type {
    // Configuration
    ConnectorConfig,
    DefaultConfigOptions,
    UnifiedConfig,

    // State & Info
    ConnectorState,
    ConnectorSnapshot,
    WalletInfo,
    AccountInfo,

    // Wallet Standard
    Wallet,
    WalletAccount,

    // Clusters
    SolanaCluster,
    SolanaClusterId,

    // Hook Returns
    UseClusterReturn,
    UseAccountReturn,
    UseWalletInfoReturn,
    UseTransactionSignerReturn,
    UseKitTransactionSignerReturn,
} from '@solana/connector';

Development

Commands

# Install dependencies
pnpm install

# Build package
pnpm build

# Development mode with watch
pnpm dev

# Type checking
pnpm type-check

# Linting
pnpm lint

# Run tests
pnpm test

# Test in watch mode
pnpm test:watch

# Coverage report
pnpm test:coverage

# Check bundle size
pnpm size

Examples

Check out the examples directory for complete working examples:

  • Next.js Example - Full-featured wallet connection UI with shadcn/ui
  • Transaction Signing - Modern and legacy transaction examples
  • Network Switching - Cluster/network management
  • Account Management - Multi-account support
  • Mobile Support - Solana Mobile Wallet Adapter

Supported Wallets

Compatible with all Wallet Standard compliant wallets:

  • Phantom - Browser extension and mobile
  • Solflare - Browser extension and mobile
  • Backpack - xNFT and wallet
  • Glow - Browser extension
  • Brave Wallet - Built-in browser wallet
  • Solana Mobile - All mobile wallet adapter compatible wallets
  • Any Wallet Standard wallet - Full compatibility

License

MIT