Skip to content

Next.js

Live projectOpen in StackBlitz

Create a client component for the widget:

tsx
// components/PaymentWidget.tsx
'use client';

interface PaymentWidgetProps {
  wagmiConfig: Config;
  theme?: 'light' | 'dark' | 'auto';
}

export function PaymentWidget({ wagmiConfig, theme = 'dark' }: PaymentWidgetProps) {
  const containerRef = useRef<HTMLDivElement>(null);

  const walletAdapter = useMemo(
    () => new WagmiWalletAdapter(wagmiConfig),
    [wagmiConfig]
  );

  useEffect(() => {
    if (!containerRef.current) return;

    const widget = new TokenFlightWidget({
      container: containerRef.current,
      config: {
        toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
        amount: '100',
        tradeType: 'EXACT_OUTPUT',
        theme,
      },
      walletAdapter,
      callbacks: {
        onSwapSuccess: (data) => {
          console.log('Payment completed!', data.orderId, data.txHash);
        },
        onSwapError: (error) => {
          console.error(`[${error.code}] ${error.message}`);
        },
      },
    });

    widget.initialize();
    return () => widget.destroy();
  }, [walletAdapter, theme]);

  return <div ref={containerRef} style={{ minHeight: 560 }} />;
}

Use it in a server or client page:

tsx
// app/payment/page.tsx

export default function PaymentPage() {
  return (
    <main>
      <h1>Payment</h1>
      <PaymentWidget wagmiConfig={wagmiConfig} />
    </main>
  );
}

wagmi Configuration

ts
// lib/wagmi.ts

export const wagmiConfig = createConfig({
  chains: [mainnet, base, arbitrum],
  connectors: [injected()],
  transports: {
    [mainnet.id]: http(),
    [base.id]: http(),
    [arbitrum.id]: http(),
  },
});

Pages Router

Use next/dynamic with ssr: false:

tsx
// pages/payment.tsx

const PaymentWidget = dynamic(
  () => import('../components/PaymentWidget').then((mod) => mod.PaymentWidget),
  { ssr: false, loading: () => <div style={{ minHeight: 560 }}>Loading...</div> }
);

export default function PaymentPage() {
  return (
    <main>
      <h1>Payment</h1>
      <PaymentWidget wagmiConfig={wagmiConfig} />
    </main>
  );
}

Without a Wallet Adapter

If your Next.js app already manages wallet connections (via RainbowKit, ConnectKit, etc.), use the widget without an adapter:

tsx
'use client';

export function PaymentWidget() {
  const containerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!containerRef.current) return;

    const widget = new TokenFlightWidget({
      container: containerRef.current,
      config: {
        toToken: { chainId: 8453, address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' },
        amount: '100',
        tradeType: 'EXACT_OUTPUT',
        theme: 'dark',
      },
      callbacks: {
        onConnectWallet: () => {
          // Trigger your existing wallet connection UI
          document.querySelector('[data-connect-wallet]')?.click();
        },
        onSwapSuccess: (data) => {
          console.log('Payment completed:', data);
        },
      },
    });

    widget.initialize();
    return () => widget.destroy();
  }, []);

  return <div ref={containerRef} style={{ minHeight: 560 }} />;
}

Key Points

  • Always use 'use client' for components that reference @tokenflight/swap
  • Use useRef for the container — avoids React re-render conflicts with Shadow DOM
  • Cache walletAdapter with useMemo — prevents reconnection on re-renders
  • Call destroy() in the useEffect cleanup — cleans up the Shadow DOM on unmount
  • See React Integration for more patterns and TypeScript setup