title: '@trezoa/connector'
description: Production-ready wallet connector for Trezoa applications
ConnectorKit is your Trezoa wallet infrastructure. A headless, framework-agnostic wallet connector built on Wallet Standard that just work.

Why ConnectorKit?
- Wallet Standard First: Built on the official Wallet Standard protocol for universal wallet compatibility
- Modern & Legacy Support: Works with both
@trezoa/kit (web3.js 2.0) and @trezoa/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 Trezoa Mobile Wallet Adapter integration
Quick Start
1. Install
npm install @trezoa/connector
pnpm add @trezoa/connector
yarn add @trezoa/connector
bun add @trezoa/connector
2. Setup Provider (once in your app root)
'use client';
import { useMemo } from 'react';
import { AppProvider } from '@trezoa/connector/react';
import { getDefaultConfig, getDefaultMobileConfig } from '@trezoa/connector/headless';
export function Providers({ children }: { children: React.ReactNode }) {
const connectorConfig = useMemo(() => {
const customRpcUrl = process.env.TREZOA_RPC_URL;
const clusters = customRpcUrl
? [
{
id: 'trezoa:mainnet' as const,
label: 'Mainnet (Custom RPC)',
url: customRpcUrl,
},
{
id: 'trezoa:devnet' as const,
label: 'Devnet',
url: 'https://api.devnet.trezoa.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 } from '@trezoa/connector/react';
export function ConnectButton() {
const {
connectors,
connectWallet,
disconnectWallet,
isConnected,
isConnecting,
isError,
walletError,
account,
} = useConnector();
if (isError) {
return (
<div>
<p>Error: {walletError?.message ?? 'Unknown error'}</p>
</div>
);
}
if (!isConnected) {
return (
<div>
{connectors.map(connector => (
<button
key={connector.id}
onClick={() => connectWallet(connector.id)}
disabled={isConnecting || !connector.ready}
>
{isConnecting ? 'Connecting...' : `Connect ${connector.name}`}
</button>
))}
</div>
);
}
return (
<div>
<span>{account}</span>
<button onClick={disconnectWallet}>
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 '@trezoa/connector/react';
function Component() {
const {
connectors,
walletStatus,
connectorId,
connector,
account,
sessionAccounts,
isConnected,
isConnecting,
isError,
walletError,
connectWallet,
disconnectWallet,
wallets,
selectedWallet,
selectedAccount,
accounts,
connected,
connecting,
select,
disconnect,
} = useConnector();
}
Real Example - Connect Button with wallet selection:
'use client';
import { useConnector } from '@trezoa/connector/react';
export function ConnectButton() {
const { connectors, connectWallet, disconnectWallet, isConnected, isConnecting, account } = useConnector();
if (isConnecting) {
return <button disabled>Connecting...</button>;
}
if (isConnected && account) {
const shortAddress = `${account.slice(0, 4)}...${account.slice(-4)}`;
return (
<div>
<span>{shortAddress}</span>
<button onClick={disconnectWallet}>Disconnect</button>
</div>
);
}
return (
<div>
{connectors.map(connector => (
<button
key={connector.id}
onClick={() => connectWallet(connector.id)}
disabled={isConnecting || !connector.ready}
>
Connect {connector.name}
</button>
))}
</div>
);
}
useAccount()
Hook for working with the connected account.
import { useAccount } from '@trezoa/connector';
function Component() {
const {
address,
formatted,
copy,
copied,
connected,
accounts,
selectAccount,
} = useAccount();
}
Real Example - Account Switcher for multi-account wallets:
'use client';
import { useAccount } from '@trezoa/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 Trezoa network/cluster.
import { useCluster } from '@trezoa/connector';
function Component() {
const {
cluster,
clusters,
setCluster,
isMainnet,
isDevnet,
rpcUrl,
explorerUrl,
} = useCluster();
}
Real Example - Network Selector:
'use client';
import { useCluster } from '@trezoa/connector';
export function ClusterSelector() {
const { cluster, clusters, setCluster } = useCluster();
return (
<select
value={cluster?.id || ''}
onChange={e => setCluster(e.target.value as TrezoaClusterId)}
>
{clusters.map(c => (
<option key={c.id} value={c.id}>
{c.label}
</option>
))}
</select>
);
}
useWalletInfo()
Hook for accessing current wallet metadata.
import { useWalletInfo } from '@trezoa/connector';
function Component() {
const {
name,
icon,
wallet,
connecting,
} = useWalletInfo();
}
vNext API (Recommended)
The vNext API provides a cleaner, more type-safe approach to wallet connections using stable connector IDs and a wallet status state machine.
You can access the same vNext state + actions either through the focused hooks below, or via useConnector() (single hook) which also includes legacy compatibility fields.
useWallet()
Primary hook for wallet status in vNext. Uses a discriminated union for type-safe status checks.
import { useWallet } from '@trezoa/connector/react';
function Component() {
const {
status,
isConnected,
isConnecting,
account,
accounts,
connectorId,
error,
} = useWallet();
if (status === 'connected') {
return <p>Connected: {account}</p>;
}
}
useWalletConnectors()
Get available wallet connectors with stable IDs.
import { useWalletConnectors } from '@trezoa/connector/react';
function WalletList() {
const connectors = useWalletConnectors();
return (
<ul>
{connectors.map(connector => (
<li key={connector.id}>
<img src={connector.icon} alt={connector.name} />
{connector.name}
{connector.ready ? '✓' : 'Not Ready'}
</li>
))}
</ul>
);
}
useConnectWallet()
Connect to a wallet using its stable connector ID.
import { useConnectWallet, useWalletConnectors } from '@trezoa/connector/react';
function ConnectButton() {
const { connect, isConnecting, error, resetError } = useConnectWallet();
const connectors = useWalletConnectors();
return (
<div>
{connectors.map(connector => (
<button
key={connector.id}
onClick={() => connect(connector.id)}
disabled={isConnecting || !connector.ready}
>
Connect {connector.name}
</button>
))}
{error && (
<p>
Error: {error.message}
<button onClick={resetError}>Dismiss</button>
</p>
)}
</div>
);
}
useDisconnectWallet()
Disconnect the current wallet session.
import { useDisconnectWallet, useWallet } from '@trezoa/connector/react';
function DisconnectButton() {
const { isConnected } = useWallet();
const { disconnect, isDisconnecting } = useDisconnectWallet();
if (!isConnected) return null;
return (
<button onClick={disconnect} disabled={isDisconnecting}>
{isDisconnecting ? 'Disconnecting...' : 'Disconnect'}
</button>
);
}
Silent-First Auto-Connect
The vNext API supports silent-first auto-connect, which attempts to reconnect without prompting the user:
const { connect } = useConnectWallet();
await connect('wallet-standard:phantom', {
silent: true,
allowInteractiveFallback: false,
});
await connect('wallet-standard:phantom', {
silent: true,
allowInteractiveFallback: true,
});
Migration Guide (Legacy → vNext)
Connect by Wallet Name → Connect by Connector ID
Before (Legacy):
const { select, wallets } = useConnector();
await select('Phantom');
After (vNext):
const { connect } = useConnectWallet();
await connect('wallet-standard:phantom');
Check Connection Status
Before (Legacy):
const { connected, connecting } = useConnector();
if (connected) {
}
After (vNext):
const { status, isConnected, isConnecting } = useWallet();
if (status === 'connected') {
}
if (isConnected) {
}
Get Selected Account
Before (Legacy):
const { selectedAccount } = useConnector();
After (vNext):
const { account } = useWallet();
const { accounts, account } = useWallet();
Disconnect
Before (Legacy):
const { disconnect } = useConnector();
await disconnect();
After (vNext):
const { disconnect } = useDisconnectWallet();
await disconnect();
Transaction Signing
ConnectorKit provides powerful transaction signing capabilities with support for both legacy @trezoa/web3.js and modern @trezoa/kit APIs.
Modern API (@trezoa/kit)
Use useKitTransactionSigner() for modern, type-safe transaction building:
'use client';
import { useState } from 'react';
import {
address,
createTrezoaRpc,
pipe,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
sendAndConfirmTransactionFactory,
getSignatureFromTransaction,
signTransactionMessageWithSigners,
createTrezoaRpcSubscriptions,
lamports,
assertIsTransactionWithBlockhashLifetime,
} from '@trezoa/kit';
import { getTransferTrzInstruction } from '@trezoa-program/system';
import { useKitTransactionSigner, useCluster, useConnectorClient, LAMPORTS_PER_TRZ } from '@trezoa/connector';
export function ModernTrzTransfer() {
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');
}
const rpcUrl = client.getRpcUrl();
if (!rpcUrl) {
throw new Error('No RPC endpoint configured');
}
const rpc = createTrezoaRpc(rpcUrl);
const rpcSubscriptions = createTrezoaRpcSubscriptions(rpcUrl.replace('http', 'ws'));
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const amountInLamports = lamports(BigInt(Math.floor(amount * Number(LAMPORTS_PER_TRZ))));
const transferInstruction = getTransferTrzInstruction({
source: signer,
destination: address(recipientAddress),
amount: amountInLamports,
});
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(signer, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
tx => appendTransactionMessageInstructions([transferInstruction], tx),
);
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
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 TRZ
</button>
{signature && <div>Transaction: {signature}</div>}
</div>
);
}
Legacy API (@trezoa/web3.js)
Use useTransactionSigner() for legacy web3.js compatibility:
import { useTransactionSigner } from '@trezoa/connector';
import { Transaction, SystemProgram } from '@trezoa/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 - Ditplay TRZ balance with refresh
ClusterElement - Network/cluster selector
TokenListElement - List of TPL tokens
TransactionHistoryElement - Recent transaction history
DisconnectElement - Disconnect button
AccountElement - Account ditplay and switcher
WalletListElement - List of available wallets
Example: Wallet Dropdown
'use client';
import {
BalanceElement,
ClusterElement,
TokenListElement,
TransactionHistoryElement,
DisconnectElement,
} from '@trezoa/connector/react';
export function WalletDropdown() {
return (
<div className="wallet-dropdown">
{/* Balance */}
<BalanceElement
render={({ trzBalance, isLoading, refetch }) => (
<div>
<div>Balance: {isLoading ? '...' : `${trzBalance?.toFixed(4)} TRZ`}</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 '@trezoa/connector';
const config = getDefaultConfig({
appName: 'My App',
appUrl: 'https://myapp.com',
autoConnect: true,
network: 'mainnet-beta',
enableMobile: true,
debug: false,
});
Network Selection
const config = getDefaultConfig({
appName: 'My App',
network: 'devnet',
});
Custom RPC Endpoints
import { getDefaultConfig } from '@trezoa/connector';
const config = getDefaultConfig({
appName: 'My App',
clusters: [
{
id: 'trezoa:mainnet' as const,
label: 'Mainnet (Custom RPC)',
url: 'https://my-custom-rpc.com',
},
{
id: 'trezoa:devnet' as const,
label: 'Devnet',
url: 'https://api.devnet.trezoa.com',
},
],
});
Mobile Wallet Adapter
import { getDefaultMobileConfig } from '@trezoa/connector/headless';
const mobile = getDefaultMobileConfig({
appName: 'My App',
appUrl: 'https://myapp.com',
});
<AppProvider connectorConfig={config} mobile={mobile}>
{children}
</AppProvider>
WalletConnect Integration
Connect mobile wallets via QR code or deep link using WalletConnect. This enables users to connect wallets like Trust Wallet, Exodus, and other WalletConnect-compatible Trezoa wallets.
1. Install WalletConnect dependency:
npm install @walletconnect/universal-provider
2. Get a WalletConnect Cloud Project ID:
Visit cloud.walletconnect.com and create a project to get your projectId.
3. Enable WalletConnect in your config:
'use client';
import { getDefaultConfig } from '@trezoa/connector/headless';
import { useMemo } from 'react';
import { AppProvider } from '@trezoa/connector/react';
export function Providers({ children }: { children: React.ReactNode }) {
const connectorConfig = useMemo(() => {
return getDefaultConfig({
appName: 'My App',
appUrl: 'https://myapp.com',
walletConnect: true,
});
}, []);
return (
<AppProvider connectorConfig={connectorConfig}>
{children}
<WalletConnectQRModal />
</AppProvider>
);
}
4. Render a QR code when a pairing URI is available:
'use client';
import { useConnector } from '@trezoa/connector/react';
import { QRCodeSVG } from 'qrcode.react';
export function WalletConnectQRModal() {
const { walletConnectUri, clearWalletConnectUri } = useConnector();
if (!walletConnectUri) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="w-full max-w-sm rounded-xl bg-white p-6">
<h2 className="text-lg font-semibold">Scan with your wallet</h2>
<div className="mt-4 flex justify-center">
<QRCodeSVG value={walletConnectUri} size={256} />
</div>
<p className="mt-4 text-center text-sm text-gray-500">
Open your WalletConnect-compatible wallet and scan this QR code
</p>
<button
type="button"
onClick={clearWalletConnectUri}
className="mt-4 w-full rounded bg-gray-100 py-2"
>
Cancel
</button>
</div>
</div>
);
}
Once enabled, "WalletConnect" appears as a connector (id: walletconnect) in your wallet list. When selected, useConnector().walletConnectUri will be set to a wc: URI that you can ditplay as a QR code or use for deep linking.
Supported WalletConnect Trezoa methods:
trezoa_getAccounts / trezoa_requestAccounts - Get connected accounts
trezoa_signMessage - Sign arbitrary messages
trezoa_signTransaction - Sign transactions
trezoa_signAllTransactions - Sign multiple transactions
trezoa_signAndSendTransaction - Sign and broadcast transactions
See the WalletConnect Trezoa documentation for more details.
Security Considerations
RPC API Key Protection
If you're using a paid RPC provider (Helius, QuickNode, etc.), avoid exposing your API key client-side. Anyone can grab it from the browser's network tab.
Solution: RPC Proxy Route
Create an API route that proxies RPC requests, keeping the API key server-side:
import { NextRequest, NextResponse } from 'next/server';
const RPC_URL = process.env.TREZOA_RPC_URL || 'https://api.mainnet-beta.trezoa.com';
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const response = await fetch(RPC_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const data = await response.json();
return NextResponse.json(data);
} catch (error) {
return NextResponse.json({ error: 'RPC request failed' }, { status: 500 });
}
}
Then configure the connector to use the proxy:
'use client';
import { getDefaultConfig } from '@trezoa/connector/headless';
const getOrigin = () => {
if (typeof window !== 'undefined') return window.location.origin;
return 'http://localhost:3000';
};
const config = getDefaultConfig({
appName: 'My App',
clusters: [
{
id: 'trezoa:mainnet' as const,
label: 'Mainnet',
url: `${getOrigin()}/api/rpc`,
},
],
});
Your .env file (no NEXT_PUBLIC_ prefix):
TREZOA_RPC_URL=https://mainnet.helius-rpc.com/?api-key=your-key
Token Image Privacy
When using useTokens() or useTransactions(), token metadata (including logo URLs) is fetched from external APIs. By default, these image URLs are returned directly, which means when your users' browsers fetch these images, the image host can see:
- User IP addresses
- Request timing (when users viewed their tokens)
- User agent and browser information
This could potentially be exploited by malicious token creators who set tracking URLs in their token metadata.
Image Proxy Configuration
To protect user privacy, you can configure an image proxy that fetches images on behalf of your users:
const config = getDefaultConfig({
appName: 'My App',
imageProxy: '/_next/image?w=64&q=75&url=',
});
When imageProxy is set, all token image URLs returned by useTokens() and useTransactions() will be automatically transformed:
// Original URL from token metadata
https://raw.githubusercontent.com/.../token-logo.png
// Transformed URL (when imageProxy is set)
/_next/image?w=64&q=75&url=https%3A%2F%2Fraw.githubusercontent.com%2F...%2Ftoken-logo.png
Common Proxy Options
| Next.js Image | imageProxy: '/_next/image?w=64&q=75&url=' |
| Cloudflare | imageProxy: '/cdn-cgi/image/width=64,quality=75/' |
| imgproxy | imageProxy: 'https://imgproxy.example.com/insecure/fill/64/64/' |
| Custom API | imageProxy: '/api/image-proxy?url=' |
Custom Proxy API Route (Next.js Example)
import { NextRequest, NextResponse } from 'next/server';
import dns from 'dns/promises';
const ALLOWED_DOMAINS = [
'raw.githubusercontent.com',
'arweave.net',
'ipfs.io',
'cloudflare-ipfs.com',
'nftstorage.link',
];
function isPrivateOrReservedIP(ip: string): boolean {
const ipv4PrivateRanges = [
/^127\./,
/^10\./,
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
/^192\.168\./,
/^169\.254\./,
/^0\./,
/^100\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\./,
/^192\.0\.0\./,
/^192\.0\.2\./,
/^198\.51\.100\./,
/^203\.0\.113\./,
/^224\./,
/^240\./,
/^255\.255\.255\.255$/,
];
const ipv6PrivatePatterns = [
/^::1$/,
/^fe80:/i,
/^fc00:/i,
/^fd/i,
/^::ffff:(127\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.|169\.254\.)/i,
];
for (const range of ipv4PrivateRanges) {
if (range.test(ip)) return true;
}
for (const pattern of ipv6PrivatePatterns) {
if (pattern.test(ip)) return true;
}
return false;
}
function validateUrl(urlString: string): URL | null {
try {
const parsed = new URL(urlString);
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return null;
}
return parsed;
} catch {
return null;
}
}
function isAllowedDomain(hostname: string): boolean {
return ALLOWED_DOMAINS.some(domain => hostname === domain || hostname.endsWith(`.${domain}`));
}
export async function GET(request: NextRequest) {
const urlParam = request.nextUrl.searchParams.get('url');
if (!urlParam) {
return new NextResponse('Missing URL parameter', { status: 400 });
}
const parsedUrl = validateUrl(urlParam);
if (!parsedUrl) {
return new NextResponse('Invalid URL or protocol', { status: 400 });
}
if (!isAllowedDomain(parsedUrl.hostname)) {
return new NextResponse('Domain not allowed', { status: 403 });
}
try {
const addresses = await dns.resolve(parsedUrl.hostname);
for (const ip of addresses) {
if (isPrivateOrReservedIP(ip)) {
return new NextResponse('Resolved IP is not allowed', { status: 403 });
}
}
} catch {
return new NextResponse('Failed to resolve hostname', { status: 400 });
}
try {
const response = await fetch(parsedUrl.toString());
const buffer = await response.arrayBuffer();
return new NextResponse(buffer, {
headers: {
'Content-Type': response.headers.get('Content-Type') || 'image/png',
'Cache-Control': 'public, max-age=86400',
},
});
} catch {
return new NextResponse('Failed to fetch image', { status: 500 });
}
}
CoinGecko API & Rate Limits
The useTokens() hook fetches token prices from CoinGecko. CoinGecko has rate limits that may affect your application:
Rate Limits (as of 2024)
| Free (Public) | 10-30 requests/minute | No |
| Demo | 30 requests/minute | Yes (free) |
| Analyst | 500 requests/minute | Yes (paid) |
| Pro | 1000+ requests/minute | Yes (paid) |
Handling Rate Limits
ConnectorKit automatically handles rate limits with:
- Exponential backoff: Retries with increasing delays
- Jitter: Random delay added to prevent thundering herd
- Retry-After header: Honors server-specified wait times
- Bounded timeout: Won't block forever (default 30s max)
Adding a CoinGecko API Key
For higher rate limits, add a free Demo API key from CoinGecko:
const config = getDefaultConfig({
appName: 'My App',
coingecko: {
apiKey: process.env.COINGECKO_API_KEY,
isPro: false,
},
});
Advanced Configuration
const config = getDefaultConfig({
appName: 'My App',
coingecko: {
apiKey: process.env.COINGECKO_API_KEY,
isPro: false,
maxRetries: 3,
baseDelay: 1000,
maxTimeout: 30000,
},
});
Caching
Token prices are cached for 60 seconds to minimize API calls. The retry logic only applies to uncached token IDs, so frequently-viewed tokens won't trigger additional API calls.
Advanced Usage
Error Handling with tryCatch
ConnectorKit exports a tryCatch utility for consistent async error handling:
import { tryCatch } from '@trezoa/connector/headless';
async function sendTransaction() {
const { data: signature, error } = await tryCatch(signer.signAndSendTransaction(transaction));
if (error) {
console.error('Transaction failed:', error.message);
return;
}
console.log('Transaction sent:', signature);
}
The tryCatch utility returns a Result<T, E> type that's either a success with data or a failure with error:
interface Success<T> {
data: T;
error: null;
}
interface Failure<E> {
data: null;
error: E;
}
Also available: tryCatchSync for synchronous operations, and isSuccess/isFailure type guards.
Cache Invalidation with Query Keys
For advanced cache management, ConnectorKit exports query key generators:
import {
getBalanceQueryKey,
getTokensQueryKey,
getTransactionsQueryKey,
invalidateSharedQuery,
} from '@trezoa/connector/react';
async function sendAndRefresh() {
await sendTransaction();
const balanceKey = getBalanceQueryKey(rpcUrl, address);
if (balanceKey) invalidateSharedQuery(balanceKey);
const txKey = getTransactionsQueryKey({ rpcUrl, address, clusterId });
if (txKey) invalidateSharedQuery(txKey);
}
Configuration Validation
Configuration is validated at runtime using Zod schemas. For manual validation:
import { validateConfigOptions } from '@trezoa/connector/headless';
const result = validateConfigOptions({
appName: 'My App',
network: 'mainnet',
});
if (!result.success) {
console.error('Validation errors:', result.error.issues);
}
Headless Client (Vue, Svelte, Vanilla JS)
Use ConnectorClient for non-React frameworks:
import { ConnectorClient, getDefaultConfig, createConnectorId } from '@trezoa/connector/headless';
const client = new ConnectorClient(getDefaultConfig({ appName: 'My App' }));
const state = client.getSnapshot();
console.log('Connectors:', state.connectors);
await client.connectWallet(createConnectorId('Phantom'));
const unsubscribe = client.subscribe(state => {
console.log('State updated:', state);
});
await client.disconnectWallet();
unsubscribe();
client.destroy();
Custom Storage (React Native, SSR)
Storage uses nanostores with built-in enhancements that are automatically applied:
- Validation (Trezoa 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 '@trezoa/connector';
const config = getDefaultConfig({
appName: 'My App',
storage: {
wallet: new EnhancedStorageAdapter(
createEnhancedStorageWallet({
validator: walletName => {
return walletName !== null && walletName.length > 0;
},
onError: error => {
Sentry.captureException(error);
},
}),
),
},
});
Package Exports
Main Export
import { AppProvider, useConnector, getDefaultConfig } from '@trezoa/connector';
Headless Export (Framework Agnostic)
import { ConnectorClient, getDefaultConfig } from '@trezoa/connector/headless';
React Export
import { AppProvider, useConnector, useWallet, useConnectWallet } from '@trezoa/connector/react';
API Reference
Hooks
vNext Hooks (Recommended)
useWallet() | Wallet status state machine | { status, isConnected, isConnecting, account, accounts, error } |
useWalletConnectors() | Available wallet connectors | WalletConnectorMetadata[] |
useConnectWallet() | Connect by connector ID | { connect, isConnecting, error, resetError } |
useDisconnectWallet() | Disconnect current wallet | { disconnect, isDisconnecting } |
Legacy Hooks
useConnector() | Main wallet connection hook (vNext + legacy) | ConnectorSnapshot |
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 (@trezoa/kit) | { signer, ready, address } |
useBalance() | TRZ balance hook | { trzBalance, isLoading, refetch } |
useTokens() | TPL tokens hook | { tokens, isLoading, refetch } |
useTransactions() | Transaction history hook | { transactions, isLoading, refetch } |
Configuration Functions
getDefaultConfig(options) | Create default connector configuration |
getDefaultMobileConfig(options) | Create mobile wallet adapter configuration |
Utility Functions
formatAddress(address, options?) | Format Trezoa address |
formatTRZ(lamports, options?) | Format TRZ amount |
copyAddressToClipboard(address) | Copy address to clipboard |
getTransactionUrl(cluster, signature) | Get Trezoa Explorer transaction URL |
getAddressUrl(cluster, address) | Get Trezoa Explorer address URL |
Types
import type {
ConnectorConfig,
DefaultConfigOptions,
ExtendedConnectorConfig,
ConnectorState,
ConnectorSnapshot,
WalletInfo,
AccountInfo,
Wallet,
WalletAccount,
TrezoaCluster,
TrezoaClusterId,
WalletConnectorId,
WalletConnectorMetadata,
WalletSession,
WalletStatus,
SessionAccount,
ConnectOptions,
UseClusterReturn,
UseAccountReturn,
UseWalletInfoReturn,
UseTransactionSignerReturn,
UseKitTransactionSignerReturn,
} from '@trezoa/connector';
Development
Commands
pnpm install
pnpm build
pnpm dev
pnpm type-check
pnpm lint
pnpm test
pnpm test:watch
pnpm test:coverage
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 - Trezoa Mobile Wallet Adapter
Supported Wallets
Compatible with all Wallet Standard compliant wallets:
- Phantom - Browser extension and mobile
- Trzflare - Browser extension and mobile
- Backpack - xNFT and wallet
- Glow - Browser extension
- Brave Wallet - Built-in browser wallet
- Trezoa Mobile - All mobile wallet adapter compatible wallets
- WalletConnect - Connect any WalletConnect-compatible mobile wallet via QR code
- Any Wallet Standard wallet - Full compatibility
License
MIT