3

Solang currently uses Anchor's .view() to fetch account state from the client. The issue is that when using .view() in a frontend, it also prompts the wallet to sign a transaction. Is there a way to prevent the connected wallet from being prompted?

import { Button } from "@chakra-ui/react"
import { PublicKey } from "@solana/web3.js"
import { useWallet } from "@solana/wallet-adapter-react"
import { useProgram } from "@/contexts/ProgramContextProvider"

export default function ViewButton() {
  const { publicKey } = useWallet()
  const { program } = useProgram()

  const onClick = async () => {
    if (!publicKey || !program) return

    const [pda] = PublicKey.findProgramAddressSync(
      [Buffer.from("seed"), publicKey.toBuffer()],
      program.programId
    )

    const val = await program.methods
      .get()
      .accounts({ dataAccount: pda })
      .view()

    alert(val)
  }

  return <Button onClick={onClick}>View</Button>
}

The standard way of fetching account data in Anchor using .fetch currently isn't supported in Solang due to differences in how the account data is encoded (Solang doesn't use borsh).

// Cannot use this approach
const accountData = await program.account.accountType.fetch(address)

For additional context, here is the Solang program:


@program_id("GwnWG8hjzBBnXRtQAmr3Cbo6ShYjv5s6NHTAdsmURXKa")
contract pda_account {
    bool private value = true;

    @payer(payer) // payer address
    @seed("seed") // hardcoded seed
    @seed(abi.encode(payer)) // seed from payer address
    @bump(bump) // bump seed for pda address
    constructor(address payer, bytes bump) {
        print("Hello, World!");
    }

    /// A message that can be called on instantiated contracts.
    /// This one flips the value of the stored `bool` from `true`
    /// to `false` and vice versa.
    function flip() public {
        value = !value;
    }

    /// Simply returns the current value of our `bool`.
    function get() public view returns (bool) {
        return value;
    }
}

The program instance was set up using useAnchorWallet()

const wallet = useAnchorWallet()
   
...

// Set up AnchorProvider using the connected wallet
const provider = new AnchorProvider(connection, wallet, {})
setProvider(provider)

// Program Id from IDL
const programId = IDL.metadata.address as unknown as PublicKey

// Create the program instance using the IDL, program ID, and provider
const program = new Program<PdaAccount>(IDL, programId, provider)

1 Answer 1

2

Still haven't figured out how to use Anchor's .view() in a frontend without it prompting the connected wallet to sign for the simulated transaction.

As a workaround, you can manually build a versioned transaction which does not seem to require a signer when simulating. You can then manually parse out the return data from the program logs of the simulated transaction.

This isn't a great solution, but its an attempt to replicate what .view() does. Also thanks to SolAndy for the recent video on simulating transactions.

export default function ViewButton() {
  const { publicKey } = useWallet();
  const { connection } = useConnection();
  const { program } = useProgram();

  const onClick = async () => {
    if (!publicKey || !program) return;

    const [pda] = PublicKey.findProgramAddressSync(
      [Buffer.from("seed"), publicKey.toBuffer()],
      program.programId
    );

    // Build the instruction
    const ix = await program.methods
      .get()
      .accounts({ dataAccount: pda })
      .instruction();

    const data = await connection.getLatestBlockhash();

    // Build a versioned transaction with the instruction
    const msg = new TransactionMessage({
      payerKey: publicKey,
      recentBlockhash: data.blockhash,
      instructions: [ix],
    }).compileToV0Message();

    const tx = new VersionedTransaction(msg);

    // Simulate the transaction.
    const transactionSimulation = await connection.simulateTransaction(tx);
    console.log(transactionSimulation);

    // If value is "0", then return data returns null, so can't use this for bool 
    if (transactionSimulation.value.returnData?.data) {
      console.log(transactionSimulation.value.returnData.data[0]);
    }

    // Log all the transaction logs.
    const transactionLogs = transactionSimulation.value.logs;
    console.log(transactionLogs);

    // As workaround to avoid null return data when value is "0"
    // Extract the program return data directly from the logs.
    // Find log entry that starts with "Program return:"
    const returnPrefix = `Program return: ${program.programId} `;
    const returnLogEntry = transactionLogs!.find((log) =>
      log.startsWith(returnPrefix)
    );
    console.log(returnLogEntry);

    if (returnLogEntry) {
      // Slice out the prefix to get the base64 return data
      const encodedReturnData = returnLogEntry.slice(returnPrefix.length);
      console.log(encodedReturnData);

      // Convert the Base64 return data
      const decodedBuffer = Buffer.from(encodedReturnData, "base64");
      alert(decodedBuffer[0]);
    }
  };

  return <Button onClick={onClick}>View</Button>;

For additional context, console.log(transactionLogs); logs

"Program GwnWG8hjzBBnXRtQAmr3Cbo6ShYjv5s6NHTAdsmURXKa invoke [1]"
"Program GwnWG8hjzBBnXRtQAmr3Cbo6ShYjv5s6NHTAdsmURXKa consumed 310 of 200000 compute units"
"Program return: GwnWG8hjzBBnXRtQAmr3Cbo6ShYjv5s6NHTAdsmURXKa AA=="
"Program GwnWG8hjzBBnXRtQAmr3Cbo6ShYjv5s6NHTAdsmURXKa success"

console.log(returnLogEntry); finds the log with the prefix

Program return: GwnWG8hjzBBnXRtQAmr3Cbo6ShYjv5s6NHTAdsmURXKa AA==

console.log(encodedReturnData); slices out the prefix

AA==

Your Answer

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

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