0

After spending some days battling to implement Phantom + Anchor I found out about Solana's Wallet adapter. I sighed in relief thinking the end was nigh but:

  • The the wallet adapter for Solana Apps is broken out-of-the-box due to some webpack dependency
  • I haven't been able to adapt the example to Anchor

My current insights are that the Wallet Adapter at its cleanest allows a simple:

    const { connection } = useConnection();
    const { publicKey, sendTransaction } = useWallet();

With Anchor, the client side can then become:

  const { connection } = useConnection()
  const wallet = useAnchorWallet()

This guide seems to point in the right direction, yet it doesn't obviously bridge these two parts, leaving the WalletContextProvider component found in the Wallet Adapter unused.

Questions and some answers

Q: Where does Anchor document its client-side usage?

A: Nor their old documentation nor their future documentation covers this process. By googling "project-serum/anchor" and following the links I found a scary looking reference on their git's readme. Also, by diving into the repos of coral-xyz I also found a Anchor by example repo. None of these seem helpful.

I plan to over haul my strategy so that in the future I give up sooner on the documentation and instead inspire myself through open source implementations.

Q: Why do I even need Anchor in the front-end?

A: I am not sure! I believe it helps with the invocation of Solana Program's an the translation of data. But I'd prefer being more sure in order to better trust this code dependency.

Q: Do I just use useAnchorWallet forgetting this WalletContextProvider component?

A: I also don't know, the wallet adapter FAQ seems to imply this with:

Use the useAnchorWallet() hook in the React package to easily
get an Anchor-compatible Wallet interface.

Q: Do I need to useState to keep track of something like my wallet's public key or should I drop it completely and instead access it through these functions?

A: ...

Q: What advice do you have to more proficiently learn the ecosystem? Am I using the wrong tool-set?

Elaboration: This might be a issue in my workflow or maybe I underestimated the process of learning Web3, anyhow I'd greatly appreciate feedback on how to become more proficient. At the same time, this and other experiences have making me doubt developing in Solana. If I am to spend 2 weeks with each tiny thing, not being able to tell if the bug is my fault or not, my progress will be very slow.

The question in the title:

Q: How do I adapt Solana's Wallet Adapter example to use Anchor?

A: Because I have managed to adapt the example, I will answer in a post, even though I am dissatisfied with my lack of understanding.

3 Answers 3

1

Here's an easy to follow guide for integrating solana wallet adapter in your dapp. It contains examples using ReactJS.

https://www.solanawalletadapter.xyz/

Hope this helps!

0

Here is a minimal example of a frontend built using nextjs integrated with wallet-adapter and an Anchor counter program.

The setup for the wallet-adapter can be found in the provider.tsx file. (also included is setup for NextUI component library that can be removed)

"use client";

import { NextUIProvider } from "@nextui-org/react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
import {
  ConnectionProvider,
  WalletProvider,
} from "@solana/wallet-adapter-react";
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
import { SolflareWalletAdapter } from "@solana/wallet-adapter-solflare";
import { clusterApiUrl } from "@solana/web3.js";
import "@solana/wallet-adapter-react-ui/styles.css";
import { useMemo } from "react";

export function Providers({ children }: { children: React.ReactNode }) {
  const network = WalletAdapterNetwork.Devnet;
  const endpoint = useMemo(() => clusterApiUrl(network), [network]);
  const wallets = useMemo(() => [new SolflareWalletAdapter()], [network]);

  return (
    <NextUIProvider>
      <NextThemesProvider attribute="class" defaultTheme="dark">
        <ConnectionProvider endpoint={endpoint}>
          <WalletProvider wallets={wallets} autoConnect>
            <WalletModalProvider>{children}</WalletModalProvider>
          </WalletProvider>
        </ConnectionProvider>
      </NextThemesProvider>
    </NextUIProvider>
  );
}

The Anchor setup can be found in ./anchor that includes the program Idl file and a setup.ts

This is the minimal setup that doesn't use the useAnchorWallet hook. The program is set up with just the Idl and a connection (without a default signer wallet).

import { IdlAccounts, Program } from "@coral-xyz/anchor";
import { IDL, Counter } from "./idl";
import { clusterApiUrl, Connection, PublicKey } from "@solana/web3.js";

const programId = new PublicKey("B7TnWKNXQzDWXDCaA5nyrLJZrJKHPSoVZXEWzqBcykKf");
const connection = new Connection(clusterApiUrl("devnet"), "confirmed");

export const program = new Program<Counter>(IDL, programId, {
  connection,
});

With the program setup, you can use it in the same way as you would in the anchor test files. For example, in increment-button.tsx, you can built a transaction and send it using the wallet adapter.

export default function IncrementButton() {
  const { publicKey, sendTransaction } = useWallet();
  const { connection } = useConnection();
  const [isLoading, setIsLoading] = useState(false);

  const onClick = async () => {
      ...
      const transaction = await program.methods
        .increment()
        .accounts({
          user: publicKey,
          tokenAccount: associatedTokenAccount,
        })
        .transaction();

      const transactionSignature = await sendTransaction(
        transaction,
        connection
      );
      ...
  };

  ...
}
2
  • Thank you for your effort. I did not find this particularly helpful as my questions remain unanswered. I have already read too many examples without reaching a conclusion, specially so if they also require me to take up Next.js as part of the solution. I asked for the procedure about Phantom Wallet. In my code I found it adheres to the Wallet Standard so I don't even need an adapter. I'll edit the original post with one more question to make my goal more clear. Nov 16 at 15:59
  • In truth, I have my solution working, albeit with zero of the answers I wannted. Nov 16 at 16:25
0

Following this example code:

Setup phantom wallet in file WalletSetup.tsx:

// ... imports omitted

// This is needed to avoid: "Buffer is not defined"
import * as buffer from "buffer";
window.Buffer = buffer.Buffer;

export const WalletContextProvider: FC<{ children: ReactNode }> = () => {
    const network = WalletAdapterNetwork.Devnet;
    const endpoint = useMemo(() => clusterApiUrl(network), [network]);
    const wallets = useMemo(
        () => [], // PhantomWallet does not need an adapter
        // eslint-disable-next-line react-hooks/exhaustive-deps
        [network]
    );
    
    // TO-DO:
    // how to memoize the Idl-program so that getProgram doesn't need to always set it up?

    return (
        <ConnectionProvider endpoint={endpoint}>
            <WalletProvider wallets={wallets} autoConnect>
                <WalletModalProvider>
                    <WalletMultiButton />
                    <WalletDisconnectButton />
                    { children }
                </WalletModalProvider>
            </WalletProvider>
        </ConnectionProvider>
    );
};


In AnchorCall.tsx, adapted from the example:

// I find the procedure in getProgram redundant
function getProgram({ connection, wallet }: any) {
  const provider = new AnchorProvider(connection, wallet, {});
  return new Program(IDL, IDL.metadata.address, provider);
}

export const AnchorCall: FC = () => {
  const { connection } = useConnection();
  // I do not understand WHY this works:
  const wallet = useAnchorWallet();

  const onClick = async () => {
    if (!wallet) throw new WalletNotConnectedError();
    const program = getProgram({ connection, wallet });

    const dataAccount: Keypair = Keypair.generate();

    try {
      const initTx = await program.methods
        .new()
        .accounts({
          dataAccount: dataAccount.publicKey,
        })
        .signers([dataAccount])
        .rpc();
      console.log(initTx);

      const ret = await program.methods
        .AnchorCall()
        .accounts({
          dataAccount: dataAccount.publicKey,
        })
        .view();
      console.log(ret);
    } catch (error: any) {
      console.log(error);
    }
  };

  return (
    <button onClick={onClick} disabled={!wallet}>
      Call anchor!
    </button>
  );

In App.tsx I simply use:

function App() {
  return (
    <WalletContextProvider>
      <AnchorCall></AnchorCall>
    </WalletContextProvider>
  );
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.