Custom Wallet Adapter
TokenFlight widgets don't bundle a wallet — they talk to wallets through an adapter interface. Official adapters exist for AppKit, wagmi, and ethers, but you can build your own for any provider.
Using a Built-in Adapter
const widget = new TokenFlightWidget({
container: '#widget',
config: {
toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
tradeType: 'EXACT_OUTPUT',
amount: '100',
theme: 'dark',
},
walletAdapter: new AppKitWalletAdapter(appkitInstance),
});
widget.initialize();| Package | Provider |
|---|---|
@tokenflight/adapter-appkit | WalletConnect / AppKit |
@tokenflight/adapter-wagmi | wagmi |
@tokenflight/adapter-ethers | ethers |
One-Call Helpers with RPC Overrides
Both the AppKit and wagmi adapter packages ship an async factory that wires custom per-chain RPC URLs into the wallet stack for you. Pass a map keyed by decimal chain ID; any chain without an override falls back to its default public RPC.
import { createAppKitAdapter } from '@tokenflight/adapter-appkit';
import { mainnet, arbitrum, base } from '@reown/appkit/networks';
import { VersionedTransaction } from '@solana/web3.js';
const { adapter, appkit } = await createAppKitAdapter({
projectId: 'YOUR_REOWN_PROJECT_ID',
networks: [mainnet, arbitrum, base],
rpcOverrides: {
'1': 'https://ethereum-rpc.publicnode.com',
'42161': 'https://arb1.arbitrum.io/rpc',
},
solana: { VersionedTransaction },
});import { createWagmiAdapter } from '@tokenflight/adapter-wagmi';
import { mainnet, arbitrum } from 'viem/chains';
const adapter = await createWagmiAdapter({
chains: [mainnet, arbitrum],
rpcOverrides: {
'1': 'https://ethereum-rpc.publicnode.com',
},
});Both helpers return a ready-to-use adapter that you pass straight to the widget via the walletAdapter option. You can pair them with getDefaults() if you want to pull the override map from a shared config endpoint at runtime:
import { getDefaults } from '@tokenflight/swap';
import { createAppKitAdapter } from '@tokenflight/adapter-appkit';
const defaults = await getDefaults();
const { adapter } = await createAppKitAdapter({
projectId: 'YOUR_REOWN_PROJECT_ID',
networks: [mainnet, arbitrum, base],
rpcOverrides: defaults?.rpcOverrides,
});Example Projects
The IWalletAdapter Interface
Implement this interface to connect any wallet:
IWalletAdapter,
WalletAction,
WalletActionResult,
WalletActionType,
WalletEvent,
WalletEventType,
ChainType,
} from '@tokenflight/swap';
class MyAdapter implements IWalletAdapter {
readonly name = 'My Wallet';
readonly icon = 'https://example.com/icon.svg'; // optional
readonly supportedActionTypes: WalletActionType[] = [
'eip1193_request', // EVM JSON-RPC
'solana_signAndSendTransaction', // Solana
];
// Optional: restrict the widget to only show tokens on these chains
readonly supportedChainIds = [1, 8453, 42161]; // Ethereum, Base, Arbitrum
async connect(chainType?: ChainType): Promise<void> { /* ... */ }
async disconnect(): Promise<void> { /* ... */ }
isConnected(chainType?: ChainType): boolean { /* ... */ }
async getAddress(chainType?: ChainType): Promise<string | null> { /* ... */ }
async executeWalletAction(action: WalletAction): Promise<WalletActionResult> { /* ... */ }
// Optional
async signMessage?(message: string, chainType?: ChainType): Promise<string> { /* ... */ }
on(event: WalletEventType, handler: (e: WalletEvent) => void): void { /* ... */ }
off(event: WalletEventType, handler: (e: WalletEvent) => void): void { /* ... */ }
}Key Concepts
Chain Types
type ChainType = 'evm' | 'solana';The widget tells the adapter which chain family it needs. If your wallet only supports one chain type, ignore the parameter and throw on unsupported types.
Supported Chain IDs
Set supportedChainIds to restrict which chains the widget operates on. When set, the widget filters:
- Token list — only tokens on the specified chains are shown
- Token balances — balance queries are scoped to the specified chains
- Token search — search results are limited to the specified chains
- Chain selector — only the specified chains appear in the filter
When omitted, all chains from the API are shown.
// Only show tokens on Ethereum, Base, and Arbitrum
readonly supportedChainIds = [1, 8453, 42161];wagmi and AppKit adapters derive supportedChainIds automatically from their config — no extra setup needed:
// supportedChainIds is automatically [1, 8453] from wagmiConfig.chains
const adapter = new WagmiWalletAdapter(wagmiConfig);
// supportedChainIds is automatically derived from appkit.getCaipNetworks()
// Solana networks are mapped to the internal Solana chain ID
const adapter = new AppKitWalletAdapter(appkitInstance);ethers adapter requires explicit chain IDs since an EIP-1193 provider has no configured chain list:
const adapter = new EthersWalletAdapter(window.ethereum, {
supportedChainIds: [1, 8453, 42161],
});All adapters accept an explicit supportedChainIds option to override auto-detection.
Common pitfall: If the widget shows zero balance on a chain that the API supports, the chain is likely missing from your wallet library's network configuration. The auto-derived
supportedChainIdsonly includes chains your wallet is configured for. Add the missing chain to your AppKit/wagmi config, or passsupportedChainIdsexplicitly. See Troubleshooting → Wallet shows zero balance.
Wallet Actions
The widget sends transaction requests as typed actions:
// EVM — a standard JSON-RPC request
interface EvmWalletAction {
type: 'eip1193_request';
chainId: number;
method: string; // e.g. 'eth_sendTransaction'
params: unknown[];
}
// Solana — sign a base64-encoded transaction
interface SolanaSignAndSendAction {
type: 'solana_signAndSendTransaction';
transaction: string; // base64
}Action Results
Return a standardized result:
interface WalletActionResult {
success: boolean;
data?: unknown; // provider-specific payload
error?: string; // human-readable error
txHash?: string; // if a transaction was sent
}Events
Emit lifecycle events so the widget reacts to wallet state changes:
type WalletEventType = 'connect' | 'disconnect' | 'chainChanged' | 'accountsChanged';Full Example: EVM-Only Adapter
IWalletAdapter,
WalletAction,
WalletActionResult,
WalletActionType,
WalletEvent,
WalletEventType,
ChainType,
} from '@tokenflight/swap';
interface EIP1193Provider {
request(args: { method: string; params?: unknown[] }): Promise<unknown>;
}
export class EthersAdapter implements IWalletAdapter {
readonly name = 'Ethers.js';
readonly supportedActionTypes: WalletActionType[] = ['eip1193_request'];
private provider: import('ethers').BrowserProvider | null = null;
private signer: import('ethers').Signer | null = null;
private address: string | null = null;
private listeners = new Map<WalletEventType, Set<(e: WalletEvent) => void>>();
constructor(private ethereum: EIP1193Provider) {}
async connect(): Promise<void> {
const { BrowserProvider } = await import('ethers');
this.provider = new BrowserProvider(this.ethereum);
this.signer = await this.provider.getSigner();
this.address = await this.signer.getAddress();
this.emit('connect', { address: this.address });
}
async disconnect(): Promise<void> {
this.address = null;
this.signer = null;
this.emit('disconnect');
}
isConnected(): boolean {
return this.address !== null;
}
async getAddress(): Promise<string | null> {
return this.address;
}
async executeWalletAction(action: WalletAction): Promise<WalletActionResult> {
if (action.type !== 'eip1193_request') {
return { success: false, error: 'Unsupported action type' };
}
try {
const result = await this.ethereum.request({
method: action.method,
params: action.params,
});
return { success: true, data: result, txHash: typeof result === 'string' ? result : undefined };
} catch (err: unknown) {
return { success: false, error: (err as Error).message };
}
}
on(event: WalletEventType, handler: (e: WalletEvent) => void): void {
if (!this.listeners.has(event)) this.listeners.set(event, new Set());
this.listeners.get(event)!.add(handler);
}
off(event: WalletEventType, handler: (e: WalletEvent) => void): void {
this.listeners.get(event)?.delete(handler);
}
private emit(type: WalletEventType, data?: unknown): void {
for (const h of this.listeners.get(type) ?? []) h({ type, data });
}
}Usage:
const widget = new TokenFlightWidget({
container: '#widget',
config: {
toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
tradeType: 'EXACT_OUTPUT',
amount: '100',
theme: 'dark',
},
walletAdapter: new EthersAdapter(window.ethereum),
});
widget.initialize();