@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-authQuick 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.0github- GitHub OAuth 2.0discord- Discord OAuth 2.0twitter- 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
