Part 2: Wallet Integration

This is Part 2 of our guide to building a Cardano transaction application with Next.js and Weld. In this section, you'll implement wallet connectivity with proper SSR support.

Weld Wallet Integration Overview

Weld is a universal Cardano wallet connector library that simplifies wallet integration by providing:

  • A unified interface for multiple Cardano wallets

  • TypeScript support with full type safety

  • React hooks for state management

  • Server-side rendering compatibility

See the Weld documentation for more information.

Implementation Steps

1. Create the Weld Provider Component

First, we need to create a provider component that will make Weld available throughout your application. This component will handle wallet connectivity state and server/client hydration:

// src/components/WeldProvider.tsx
"use client";

import { WeldProvider, type WeldProviderProps } from "@ada-anvil/weld/react";

export function ClientWeldProvider({
  children,
  lastConnectedWallet,
}: {
  children: React.ReactNode;
  lastConnectedWallet?: NonNullable<
    WeldProviderProps["wallet"]
  >["tryToReconnectTo"];
}) {
  return (
    <WeldProvider
      updateInterval={30_000} // 30 seconds
      wallet={{ tryToReconnectTo: lastConnectedWallet }} // Restore wallet connection state
    >
      {children}
    </WeldProvider>
  );
}

The updateInterval option (set to 30 seconds) helps maintain active wallet connections during longer user sessions.

2. Integrate the Provider in Your Layout

Next, we'll update the app layout to use our ClientWeldProvider. Notice that we include the lastConnectedWallet prop to restore wallet connection state between server and client. This is important for maintaining wallet connectivity during page navigation, and preventing React hydration errors due to server-side rendering being inconsistent with client-side rendering.

See the Server-Side Rendering (SSR) section in the Weld documentation for more information.

// src/app/layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

// Add the three imports.
import { ClientWeldProvider } from "@/components/WeldProvider";
import { cookies } from "next/headers";
import { STORAGE_KEYS } from "@ada-anvil/weld/server";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Cardano Transaction App",
  description: "Send Cardano transactions using the Anvil API",
};

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {

  // Add the cookie retrieval logic.
  const cookieStore = await cookies();
  const wallet = cookieStore.get(STORAGE_KEYS.connectedWallet)?.value;
  const changeAddress = cookieStore.get(STORAGE_KEYS.connectedChange)?.value;
  const stakeAddress = cookieStore.get(STORAGE_KEYS.connectedStake)?.value;
  const lastConnectedWallet = wallet ? { wallet, changeAddress, stakeAddress } : undefined;

  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {/* Wrap the children with the ClientWeldProvider component. */}
        <ClientWeldProvider lastConnectedWallet={lastConnectedWallet}>
          {children}
        </ClientWeldProvider>
      </body>
    </html>
  );
}

3. Understanding SSR with Weld

Our implementation addresses three critical aspects of wallet integration in Next.js with SSR:

  1. Hydration Consistency: By retrieving wallet connection data from cookies during server-side rendering, we ensure the initial client-side state matches what was rendered on the server, preventing React hydration errors.

  2. Connection Persistence: The updateInterval configuration keeps an active wallet connection stable through periodic state checks.

  3. Seamless Reconnection: The tryToReconnectTo property provides a smooth user experience by maintaining wallet connections across page refreshes.

4. Create a Wallet Connector Component

Now let's create a UI component that users will interact with to connect their wallets:

// src/components/WalletConnector.tsx
"use client";
import { useState } from "react";

// Import Weld hooks to get wallet and user extensions. 
import { useWallet, useExtensions } from "@ada-anvil/weld/react";

// Import supported wallets from Weld.
import { SUPPORTED_WALLETS } from "@ada-anvil/weld";

// Helper function to truncate address for display
const truncateAddress = (address: string) => {
  if (!address) return "";
  return `${address.slice(0, 8)}...${address.slice(-8)}`;
};

// Component to display wallet info
const WalletInfo = ({ label, value }: { label: string; value: string }) => (
  <div>
    <span>{label} </span>
    <span>
      <b>{value}</b>
    </span>
  </div>
);

export default function WalletConnector() {
  const wallet = useWallet();
  const { supportedMap: installedWallets, isLoading } = useExtensions(
    "supportedMap",
    "isLoading",
  );
  const availableWallets = SUPPORTED_WALLETS.filter((w) =>
    installedWallets.has(w.key),
  );
  const [selectedWallet, setSelectedWallet] = useState<string>("");

  // Reset connection state if wallet selection changes
  const handleWalletSelection = (value: string) => {
    if (wallet.isConnectingTo && value !== wallet.isConnectingTo) {
      // Cancel any pending connection
      wallet
        .disconnect()
        .catch((err) => console.error("Failed to disconnect wallet:", err));
    }
    setSelectedWallet(value);
  };

  const handleConnect = async (walletKey?: string) => {
    if (!walletKey) return;
    try {
      await wallet.connectAsync(walletKey);
    } catch (error) {
      console.error("Failed to connect wallet:", error);
    }
  };

  return (
    <section className="paper">
      <h2>Wallet</h2>

      {wallet.isConnected ? (
        // Connected state - show wallet info and disconnect button
        <>
          <WalletInfo label="Connected to:" value={wallet.displayName || ""} />
          <WalletInfo
            label="Address:"
            value={truncateAddress(wallet.changeAddressBech32 || "")}
          />
          <WalletInfo
            label="Balance:"
            value={`${wallet.balanceAda?.toFixed(2) || "0.00"} ADA`}
          />

          <button
            onClick={() =>
              wallet
                .disconnect()
                .catch((err) =>
                  console.error("Failed to disconnect wallet:", err),
                )
            }
            className="btn mt-4"
          >
            Disconnect
          </button>
        </>
      ) : // Disconnected state - show wallet selector and connect button
      isLoading ? (
        <div>Detecting wallet extensions...</div>
      ) : (
        <div>
          <select
            className="custom-rounded mb-4"
            name="wallet-key"
            value={selectedWallet}
            onChange={(e) => handleWalletSelection(e.target.value)}
          >
            {availableWallets.length === 0 ? (
              <option value="">No wallets</option>
            ) : (
              <>
                <option value="">Select a wallet</option>
                {availableWallets.map((w) => (
                  <option key={w.key} value={w.key}>
                    {w.displayName}
                  </option>
                ))}
              </>
            )}
          </select>
          <button
            onClick={() => selectedWallet && handleConnect(selectedWallet)}
            className="btn text-center"
            disabled={wallet.isConnecting || availableWallets.length === 0}
          >
            {wallet.isConnecting
              ? `Connecting to ${wallet.isConnectingTo}...`
              : selectedWallet
                ? "Connect Wallet"
                : "Select a Wallet"}
          </button>
        </div>
      )}
    </section>
  );
}

5. Update the Home Page

Now that we have created the component. Lets update your home page to include the wallet connector component:

// src/app/page.tsx
import WalletConnector from "@/components/WalletConnector";

export default function Home() {
  return (
    <main className="container mx-auto p-4">
      <h1 className="text-2xl font-bold mb-6">Cardano Transaction App</h1>
      <div className="max-w-md mx-auto">
        <WalletConnector />
      </div>
    </main>
  );
}

Testing Your Wallet Integration

Now let's test the wallet integration to ensure it's working correctly:

  1. Start your development server:

npm run dev
  1. Navigate to your application (usually at http://localhost:3000)

  2. Test the wallet connection flow:

    • Verify that available wallets are correctly detected in the dropdown

    • Select a wallet and click "Connect Wallet"

    • Confirm that the wallet popup appears requesting connection

    • After approving, verify that wallet information is displayed:

      • Connected wallet name

      • Truncated wallet address

      • ADA balance

  3. Test disconnection:

    • Click the "Disconnect" button

    • Verify that the UI returns to the wallet selection state

Troubleshooting

No Wallets Detected

If no wallets are appearing in the dropdown list:

  1. Make sure you have wallet extensions installed (Eternl, Lace, etc.)

  2. Refresh the page after installing a new wallet extension

  3. Check your browser console for any errors

What's Next?

Now that you have a working wallet integration, you're ready to implement transaction functionality. In Part 3: Building Transactions, we'll create the components and API routes needed to build and submit Cardano transactions.

Last updated

Was this helpful?