Prerequisites

  • A React or Next.js app set up with Dynamic’s React SDK
  • Installed dependencies:
    • @coral-xyz/anchor
    • @solana/web3.js
    • @dynamic-labs/sdk-react-core
    • @dynamic-labs/solana
  • Your Anchor program’s IDL and TypeScript types

Step 1: Create the Anchor Provider Context

Create a file (e.g., AnchorProviderComponent.tsx) and add the following code. This context will provide your Anchor program, connection, provider, and public key throughout your app.

"use client";

import { AnchorProvider, Program } from "@coral-xyz/anchor";
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
import { isSolanaWallet } from "@dynamic-labs/solana";
import { Connection, PublicKey } from "@solana/web3.js";
import {
  createContext,
  ReactNode,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
} from "react";

// Import your program's types and IDL
import { YourProgram } from './path-to-your-program-types';
import yourProgramIdl from './path-to-your-idl.json';

interface AnchorProviderContextValue {
  program: Program<YourProgram> | null;
  connection: Connection | null;
  provider: AnchorProvider | null;
  publicKey: PublicKey | null;
}

const AnchorProviderContext = createContext<AnchorProviderContextValue>({
  program: null,
  connection: null,
  provider: null,
  publicKey: null,
});

export function useAnchorProvider() {
  return useContext(AnchorProviderContext);
}

interface AnchorProviderProps {
  children: ReactNode;
}

export default function AnchorProviderComponent({ children }: AnchorProviderProps) {
  const { primaryWallet, sdkHasLoaded } = useDynamicContext();

  const [connection, setConnection] = useState<Connection | null>(null);
  const [provider, setProvider] = useState<AnchorProvider | null>(null);
  const [program, setProgram] = useState<Program<YourProgram> | null>(null);

  // Derive the public key from the connected wallet
  const publicKey = useMemo(() => {
    if (!primaryWallet?.address) return null;
    try {
      return new PublicKey(primaryWallet.address);
    } catch (error) {
      console.error("Invalid public key:", error);
      return null;
    }
  }, [primaryWallet?.address]);

  // Initialize Anchor when wallet or SDK state changes
  const initAnchor = useCallback(async () => {
    if (!primaryWallet || !sdkHasLoaded || !publicKey) {
      setConnection(null);
      setProvider(null);
      setProgram(null);
      return;
    }

    try {
      if (!isSolanaWallet(primaryWallet)) {
        console.error("Primary wallet is not a Solana wallet");
        return;
      }

      const newConnection = await primaryWallet.getConnection();
      const signer = await primaryWallet.getSigner();

      const newProvider = new AnchorProvider(
        newConnection,
        {
          publicKey,
          signTransaction: async (tx) => signer.signTransaction(tx),
          signAllTransactions: async (txs) => signer.signAllTransactions(txs),
        },
        {
          commitment: "confirmed",
          skipPreflight: true,
        }
      );

      const newProgram = new Program<YourProgram>(yourProgramIdl, newProvider);

      setConnection(newConnection);
      setProvider(newProvider);
      setProgram(newProgram);
    } catch (error) {
      console.error("Error initializing Anchor provider:", error);
      setConnection(null);
      setProvider(null);
      setProgram(null);
    }
  }, [primaryWallet, sdkHasLoaded, publicKey]);

  useEffect(() => {
    initAnchor();
  }, [initAnchor]);

  const contextValue = useMemo(
    () => ({
      program,
      connection,
      provider,
      publicKey,
    }),
    [program, connection, provider, publicKey]
  );

  return (
    <AnchorProviderContext.Provider value={contextValue}>
      {children}
    </AnchorProviderContext.Provider>
  );
}

Step 2: Wrap Your App with the Provider

Wrap your app with the AnchorProviderComponent so all child components can access the Anchor context.

import AnchorProviderComponent from './AnchorProviderComponent';

function App({ children }) {
  return (
    <AnchorProviderComponent>
      {children}
    </AnchorProviderComponent>
  );
}

Step 3: Use the Hook in Your Components

You can now access the Anchor context anywhere in your component tree. Here’s an example of calling a program method and sending a transaction using the Dynamic wallet:

import { useAnchorProvider } from './AnchorProviderComponent';
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";

function MyComponent() {
  const { program, connection, provider, publicKey } = useAnchorProvider();
  const { primaryWallet } = useDynamicContext();

  // Example: Call a program method and send a transaction
  async function callYourMethod() {
    if (!program || !publicKey || !primaryWallet) return;

    // Build the transaction using Anchor
    const tx = await program.methods
      .yourMethodName()
      .accounts({
        // Add your required accounts here
        user: publicKey,
        // ... other accounts
      })
      .transaction();

    // Get the signer from Dynamic
    const signer = await primaryWallet.getSigner();
    // Sign and send the transaction (type mismatch warning is safe to ignore)
    const result = await signer.signAndSendTransaction(tx);
    // Handle result as needed
  }

  return <div>Connected as: {publicKey?.toBase58() ?? 'Not connected'}</div>;
}

Summary

By following this guide, you can connect your Solana Anchor programs to your React or Next.js app using Dynamic for wallet management. This setup enables seamless smart contract interactions and transaction signing, leveraging the strengths of both Anchor and Dynamic.

References: