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

@moon-key/react-auth

v1.1.0

Published

React authentication hooks and components for MoonKey

Readme

@streambird/react-auth

React hooks for interacting with Solana wallets through the Streambird authentication system. All hooks are designed to work seamlessly with both UI and headless modes.

Installation

npm install @streambird/react-auth

Quick Start

import { StreambirdProvider } from '@streambird/react-auth';
import { useWallets, useSignMessage } from '@streambird/react-auth/solana';

function App() {
  return (
    <StreambirdProvider
      config={{
        publishableKey: 'your-publishable-key',
        config: {
          solana: {
            rpcs: {
              'solana:mainnet': {
                rpc: 'https://api.mainnet-beta.solana.com'
              }
            }
          }
        }
      }}
    >
      <MyComponent />
    </StreambirdProvider>
  );
}

function MyComponent() {
  const { wallets, loading } = useWallets();
  const { signMessage } = useSignMessage();

  const handleSign = async () => {
    const message = new TextEncoder().encode('Hello, Solana!');
    const result = await signMessage({
      message,
      wallet: wallets[0],
      options: { uiOptions: { showWalletUI: true } }
    });
    console.log('Signature:', result.signature);
  };

  if (loading) return <div>Loading...</div>;
  
  return (
    <div>
      <h1>My Wallets</h1>
      {wallets.map(wallet => (
        <div key={wallet.id}>{wallet.public_address}</div>
      ))}
      <button onClick={handleSign}>Sign Message</button>
    </div>
  );
}

OAuth Authentication

useLoginWithOAuth()

A dedicated hook for OAuth authentication that supports both callback-based and state-based approaches.

Returns:

{
  state: {
    status: 'initial' | 'loading' | 'done' | 'error';
    error: Error | null;
    user: OAuthUser | null;
    isNewUser: boolean | null;
  };
  loading: boolean;
  initOAuth: (options: { provider: OAuthProvider }) => Promise<void>;
}

State-based Usage:

import { useLoginWithOAuth } from '@streambird/react-auth';

function LoginWithOAuth() {
  const { state, loading, initOAuth } = useLoginWithOAuth();

  const handleLogin = async () => {
    try {
      // The user will be redirected to OAuth provider's login page
      await initOAuth({ provider: 'google' });
    } catch (err) {
      // Handle errors (network issues, validation errors, etc.)
      console.error(err);
    }
  };

  return (
    <div>
      <button onClick={handleLogin} disabled={loading}>
        {loading ? 'Logging in...' : 'Log in with Google'}
      </button>
      {state.status === 'done' && (
        <p>Welcome, {state.user?.name}!</p>
      )}
      {state.status === 'error' && (
        <p>Error: {state.error?.message}</p>
      )}
    </div>
  );
}

Callback-based Usage:

import { useLoginWithOAuth } from '@streambird/react-auth';

function LoginWithOAuth() {
  const { initOAuth } = useLoginWithOAuth({
    onSuccess: ({ user, isNewUser }) => {
      console.log('User logged in successfully', user);
      if (isNewUser) {
        // Perform actions for new users
        console.log('Welcome new user!');
      }
    },
    onError: (error) => {
      console.error('Login failed', error);
    }
  });

  return (
    <button onClick={() => initOAuth({ provider: 'google' })}>
      Log in with Google
    </button>
  );
}

useStreambird()

The main hook for authentication and flow control.

Returns:

{
  // Authentication
  isAuthenticated: () => Promise<boolean>;
  getCurrentSession: () => Promise<any>;
  logout: () => Promise<LogoutResponse>;
  
  // Flow control
  loading: boolean;
  flowState: EmailOtpFlowState;
  start: () => Promise<any>;
  onError: (callback: (error: Error) => void) => void;
  
  // Metadata
  publishableKey: string;
}

Supported OAuth Providers:

  • google - Google OAuth 2.0
  • github - GitHub OAuth 2.0
  • discord - Discord OAuth 2.0
  • twitter - Twitter OAuth 2.0

Solana Wallet Hooks

useWallets()

Fetches and manages Solana wallets for the authenticated user.

Returns:

{
  wallets: PublicWallet[];
  loading: boolean;
  error?: Error;
  refetch: () => void;
}

Usage:

import { useWallets } from '@streambird/react-auth/solana';

function MyComponent() {
  const { wallets, loading, error, refetch } = useWallets();
  
  if (loading) return <div>Loading wallets...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      {wallets.map(wallet => (
        <div key={wallet.id}>{wallet.public_address}</div>
      ))}
      <button onClick={refetch}>Refresh</button>
    </div>
  );
}

useCreateWallet(callbacks?)

Creates a new Solana wallet for the authenticated user.

Parameters:

callbacks?: {
  onSuccess?: (result: { wallet: PublicWallet }) => void;
  onError?: (error: Error) => void;
}

Returns:

{
  createWallet: (options?: {
    createAdditional?: boolean;
    walletIndex?: number;
  }) => Promise<{
    wallet: PublicWallet;
  }>;
}

Usage:

import { useCreateWallet } from '@streambird/react-auth/solana';

function CreateWalletButton() {
  const { createWallet } = useCreateWallet({
    onSuccess: ({ wallet }) => {
      console.log('Wallet created:', wallet.public_address);
    },
    onError: (error) => {
      console.error('Failed to create wallet:', error);
    }
  });

  const handleCreate = async () => {
    try {
      const result = await createWallet();
      console.log('New wallet:', result.wallet);
    } catch (error) {
      console.error('Error:', error);
    }
  };

  return <button onClick={handleCreate}>Create Wallet</button>;
}

useSignMessage()

Signs a message with the specified wallet.

Returns:

{
  signMessage: (params: {
    message: Uint8Array;
    wallet: PublicWallet;
    options?: {
      uiOptions?: SignMessageUIOptions;
    };
  }) => Promise<{
    signature: Uint8Array;
  }>;
}

Usage:

import { useSignMessage } from '@streambird/react-auth/solana';

function SignMessageButton() {
  const { signMessage } = useSignMessage();

  const handleSign = async () => {
    const message = new TextEncoder().encode('Hello, Solana!');
    const wallet = wallets[0]; // Your wallet
    
    try {
      const result = await signMessage({
        message,
        wallet,
        options: {
          uiOptions: {
            showWalletUI: true // Show UI modal
          }
        }
      });
      
      console.log('Signature:', result.signature);
    } catch (error) {
      console.error('Sign failed:', error);
    }
  };

  return <button onClick={handleSign}>Sign Message</button>;
}

useSignTransaction()

Signs a transaction with the specified wallet.

Returns:

{
  signTransaction: (params: {
    transaction: Uint8Array;
    wallet: PublicWallet;
    options?: SignTransactionOptions & {
      uiOptions?: SignTransactionUIOptions;
    };
  }) => Promise<{
    signedTransaction: Uint8Array;
  }>;
}

Usage:

import { useSignTransaction } from '@streambird/react-auth/solana';

function SignTransactionButton() {
  const { signTransaction } = useSignTransaction();

  const handleSign = async () => {
    const transaction = new Uint8Array([...]); // Your transaction bytes
    const wallet = wallets[0]; // Your wallet
    
    try {
      const result = await signTransaction({
        transaction,
        wallet,
        options: {
          uiOptions: {
            showWalletUI: false // Headless mode
          }
        }
      });
      
      console.log('Signed transaction:', result.signedTransaction);
    } catch (error) {
      console.error('Sign failed:', error);
    }
  };

  return <button onClick={handleSign}>Sign Transaction</button>;
}

useSendTransaction()

Sends a signed transaction to the Solana network.

Returns:

{
  sendTransaction: (params: {
    transaction: Uint8Array;
    wallet: PublicWallet;
    chain?: SolanaChain;
    options?: SendTransactionOptions & {
      uiOptions?: SendTransactionUIOptions;
    };
  }) => Promise<{
    signature: Uint8Array;
  }>;
}

Usage:

import { useSendTransaction } from '@streambird/react-auth/solana';

function SendTransactionButton() {
  const { sendTransaction } = useSendTransaction();

  const handleSend = async () => {
    const transaction = new Uint8Array([...]); // Your signed transaction
    const wallet = wallets[0]; // Your wallet
    
    try {
      const result = await sendTransaction({
        transaction,
        wallet,
        chain: 'solana:mainnet',
        options: {
          commitment: 'confirmed',
          uiOptions: {
            showWalletUI: true
          }
        }
      });
      
      console.log('Transaction signature:', result.signature);
    } catch (error) {
      console.error('Send failed:', error);
    }
  };

  return <button onClick={handleSend}>Send Transaction</button>;
}

useGetBalance()

Fetches the balance of a Solana wallet.

Returns:

{
  getBalance: (params: SolanaGetBalanceParams) => Promise<BaseGetBalanceResult>;
  loading: boolean;
  error: undefined;
}

Parameters:

interface SolanaGetBalanceParams {
  wallet: PublicWallet;
  chain?: SolanaChain; // 'solana:mainnet' | 'solana:devnet' | 'solana:testnet'
  options?: {
    commitment?: TransactionCommitment;
    minContextSlot?: number;
  };
}

Usage:

import { useGetBalance } from '@streambird/react-auth/solana';

function BalanceDisplay() {
  const { getBalance } = useGetBalance();
  const [balance, setBalance] = useState<string>('0');

  const fetchBalance = async () => {
    try {
      const result = await getBalance({
        wallet: wallets[0],
        chain: 'solana:mainnet',
        options: {
          commitment: 'finalized'
        }
      });
      
      setBalance(result.balanceNativeUnit);
    } catch (error) {
      console.error('Failed to get balance:', error);
    }
  };

  return (
    <div>
      <p>Balance: {balance} SOL</p>
      <button onClick={fetchBalance}>Refresh</button>
    </div>
  );
}

useGetTokenAccounts()

Fetches token accounts for a Solana wallet.

Returns:

{
  getTokenAccounts: (params: SolanaGetTokenAccountsParams) => Promise<SolanaGetTokenAccountsResult>;
  loading: boolean;
  error: undefined;
}

Parameters:

interface SolanaGetTokenAccountsParams {
  wallet: PublicWallet;
  chain?: SolanaChain;
  mint?: string; // Specific mint address
  programId?: string; // SPL Token Program or Token-2022 Program
  options?: {
    commitment?: TransactionCommitment;
    minContextSlot?: number;
    dataSlice?: {
      offset: number;
      length: number;
    };
    encoding?: TransactionEncoding;
  };
}

Usage:

import { useGetTokenAccounts } from '@streambird/react-auth/solana';

function TokenAccountsList() {
  const { getTokenAccounts } = useGetTokenAccounts();
  const [tokens, setTokens] = useState([]);

  const fetchTokens = async () => {
    try {
      const result = await getTokenAccounts({
        wallet: wallets[0],
        chain: 'solana:mainnet',
        programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', // SPL Token Program
        options: {
          commitment: 'finalized'
        }
      });
      
      setTokens(result.tokenAccounts);
    } catch (error) {
      console.error('Failed to get tokens:', error);
    }
  };

  return (
    <div>
      {tokens.map(token => (
        <div key={token.mint}>
          {token.symbol}: {token.uiAmountString}
        </div>
      ))}
      <button onClick={fetchTokens}>Load Tokens</button>
    </div>
  );
}

useExportKey()

Exports the private key for a wallet (use with caution).

Returns:

{
  exportKey: (params: { wallet: PublicWallet }) => Promise<any>;
}

Usage:

import { useExportKey } from '@streambird/react-auth/solana';

function ExportKeyButton() {
  const { exportKey } = useExportKey();

  const handleExport = async () => {
    try {
      const key = await exportKey({ wallet: wallets[0] });
      console.log('Private key exported:', key);
      // Handle the exported key securely
    } catch (error) {
      console.error('Export failed:', error);
    }
  };

  return <button onClick={handleExport}>Export Key</button>;
}

Configuration

The hooks require proper configuration in your Streambird provider:

import { StreambirdProvider } from '@streambird/react-auth';

function App() {
  return (
    <StreambirdProvider
      config={{
        publishableKey: 'your-publishable-key',
        config: {
          solana: {
            rpcs: {
              'solana:mainnet': {
                rpc: 'https://api.mainnet-beta.solana.com'
              },
              'solana:devnet': {
                rpc: 'https://api.devnet.solana.com'
              },
              'solana:testnet': {
                rpc: 'https://api.testnet.solana.com'
              }
            }
          }
        }
      }}
    >
      {/* Your app */}
    </StreambirdProvider>
  );
}

UI vs Headless Mode

Most hooks support both UI and headless modes through the uiOptions parameter:

  • UI Mode: Shows a modal with wallet interface
  • Headless Mode: Performs the action without UI
// UI Mode
await signMessage({
  message,
  wallet,
  options: {
    uiOptions: { showWalletUI: true }
  }
});

// Headless Mode
await signMessage({
  message,
  wallet,
  options: {
    uiOptions: { showWalletUI: false }
  }
});

Error Handling

All hooks throw errors that should be caught and handled appropriately:

try {
  const result = await signMessage({ message, wallet });
} catch (error) {
  if (error.message.includes('User rejected')) {
    // Handle user rejection
  } else if (error.message.includes('Network error')) {
    // Handle network issues
  } else {
    // Handle other errors
  }
}

TypeScript Support

This package is written in TypeScript and provides full type definitions. All parameters and return types are properly typed for better development experience.

License

MIT