solana-developers / solana-actions

https://solana-developers.github.io/solana-actions/
Apache License 2.0
96 stars 40 forks source link

TypeError: Cannot destructure property 'header' of 'message' as it is undefined #31

Open bestmycode010 opened 1 week ago

bestmycode010 commented 1 week ago

I got this error in Next.js - Transfer SOL example.

TypeError: Cannot destructure property 'header' of 'message' as it is undefined.
    at TransactionMessage.decompile (/var/task/node_modules/@solana/actions/node_modules/@solana/web3.js/lib/index.cjs.js:1985:7)
    at prepareVersionedTransaction (file:///var/task/node_modules/@solana/actions/lib/esm/createPostResponse.js:40:38)
    at createPostResponse (file:///var/task/node_modules/@solana/actions/lib/esm/createPostResponse.js:33:16)
    at handlePost (/var/task/frontend/.next/server/pages/api/claimsri.js:166:98)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async handler (/var/task/frontend/.next/server/pages/api/claimsri.js:55:13)
    at async Object.apiResolver (/var/task/node_modules/next/dist/server/api-utils/node.js:366:9)
    at async NextNodeServer.runApi (/var/task/node_modules/next/dist/server/next-server.js:481:9)
    at async Object.fn (/var/task/node_modules/next/dist/server/next-server.js:735:37)
    at async Router.execute (/var/task/node_modules/next/dist/server/router.js:247:36)
    at async NextNodeServer.run (/var/task/node_modules/next/dist/server/base-server.js:347:29)
    at async NextNodeServer.handleRequest (/var/task/node_modules/next/dist/server/base-server.js:285:20)
    at async Server.<anonymous> (/var/task/frontend/___next_launcher.cjs:26:5)
    at async Server.<anonymous> (/opt/rust/nodejs.js:8:4243)
nickfrosty commented 3 days ago

where do you get this error?

can you share code snippets or a repo of what you are experiencing?

bestmycode010 commented 3 days ago
import {
  clusterApiUrl,
  Connection,
  LAMPORTS_PER_SOL,
  PublicKey,
  SystemProgram,
  Transaction,
} from "@solana/web3.js";

import {
  ActionPostResponse,
  createPostResponse,
  ActionGetResponse,
  ActionPostRequest,
  createActionHeaders,
  ActionError,
} from "@solana/actions";
import { DEFAULT_SOL_ADDRESS, DEFAULT_SOL_AMOUNT } from "@/utils/constants";

// create the standard headers for this route (including CORS)
const headers = createActionHeaders();

const customizeResponse = (res: NextApiResponse) => {
  Object.entries(headers).forEach(([key, value]) => {
    res.setHeader(key, value);
  });
  return res;
};

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const { method } = req;

  const customRes = customizeResponse(res);

  switch (method) {
    case "GET":
      await handleGet(req, customRes);
      break;
    case "POST":
      await handlePost(req, customRes);
      break;
    case "OPTIONS":
      handleOptions(customRes);
      break;
    default:
      customRes.setHeader("Allow", ["GET", "POST", "OPTIONS"]);
      customRes.status(405).end(`Method ${method} Not Allowed`);
  }
}

const handleGet = async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    const protocol = req.headers.host?.includes("localhost")
      ? "http://"
      : "https://";
    const requestUrl = new URL(req.url!, `${protocol}${req.headers.host}`);
    const { toPubkey } = validatedQueryParams(requestUrl);

    const baseHref = new URL(
      `/api/claimsri?to=${toPubkey.toBase58()}`,
      requestUrl.origin
    ).toString();

    const payload: ActionGetResponse = {
      type: "action",
      title: "Transfer Native SOL",
      icon: new URL("/images/sns.svg", requestUrl.origin).toString(),
      description: "Transfer SOL to another Solana wallet",
      label: "Transfer", // this value will be ignored since `links.actions` exists
      links: {
        actions: [
          {
            label: "Send 0.1 SOL", // button text
            href: `${baseHref}&amount=${"0.1"}`,
          },
          {
            label: "Send 0.5 SOL", // button text
            href: `${baseHref}&amount=${"0.5"}`,
          },
          {
            label: "Send 1 SOL", // button text
            href: `${baseHref}&amount=${"1"}`,
          },
          {
            label: "Send SOL", // button text
            href: `${baseHref}&amount={amount}`, // this href will have a text input
            parameters: [
              {
                name: "amount", // parameter name in the `href` above
                label: "Enter the amount of SOL to send", // placeholder of the text input
                required: true,
              },
            ],
          },
        ],
      },
    };

    res.status(200).json(payload);
  } catch (err) {
    console.log(err);
    let actionError: ActionError = { message: "An unknown error occurred" };
    if (typeof err == "string") actionError.message = err;
    res.status(400).json(actionError);
  }
};

const handlePost = async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    const protocol = req.headers.host?.includes("localhost")
      ? "http://"
      : "https://";
    const requestUrl = new URL(req.url!, `${protocol}${req.headers.host}`);
    const { amount, toPubkey } = validatedQueryParams(requestUrl);

    const body: ActionPostRequest = req.body;

    // validate the client provided input
    let account;
    try {
      account = new PublicKey(body.account);
    } catch (err) {
      return res.status(400).json({ message: 'Invalid "account" provided' });
    }

    const connection = new Connection(
      process.env.NEXT_PUBLIC_RPC_URL || clusterApiUrl("devnet")
    );

    // ensure the receiving account will be rent exempt
    const minimumBalance = await connection.getMinimumBalanceForRentExemption(
      0 // note: simple accounts that just store native SOL have `0` bytes of data
    );
    if (amount * LAMPORTS_PER_SOL < minimumBalance) {
      return res.status(400).json({
        message: `account may not be rent exempt: ${toPubkey.toBase58()}`,
      });
    }

    // create an instruction to transfer native SOL from one wallet to another
    const transferSolInstruction = SystemProgram.transfer({
      fromPubkey: account,
      toPubkey: toPubkey,
      lamports: amount * LAMPORTS_PER_SOL,
    });

    // get the latest blockhash amd block height
    const { blockhash, lastValidBlockHeight } =
      await connection.getLatestBlockhash();

    // create a legacy transaction
    const transaction = new Transaction({
      feePayer: account,
      blockhash,
      lastValidBlockHeight,
    }).add(transferSolInstruction);

    // versioned transactions are also supported
    // const transaction = new VersionedTransaction(
    //   new TransactionMessage({
    //     payerKey: account,
    //     recentBlockhash: blockhash,
    //     instructions: [transferSolInstruction],
    //   }).compileToV0Message(),
    //   // note: you can also use `compileToLegacyMessage`
    // );

    const payload: ActionPostResponse = await createPostResponse({
      fields: {
        // @ts-ignore
        transaction,
        message: `Send ${amount} SOL to ${toPubkey.toBase58()}`,
      },
      // note: no additional signers are needed
      // signers: [],
    });

    res.status(200).json(payload);
  } catch (err) {
    console.log(err);
    let actionError: ActionError = { message: "An unknown error occurred" };
    if (typeof err == "string") actionError.message = err;
    res.status(400).json(actionError);
  }
};

const handleOptions = (res: NextApiResponse) => {
  res.status(200).setHeader("Access-Control-Allow-Origin", "*").end();
};

function validatedQueryParams(requestUrl: URL) {
  let toPubkey: PublicKey = DEFAULT_SOL_ADDRESS;
  let amount: number = DEFAULT_SOL_AMOUNT;

  try {
    if (requestUrl.searchParams.get("to")) {
      toPubkey = new PublicKey(requestUrl.searchParams.get("to")!);
    }
  } catch (err) {
    throw "Invalid input query parameter: to";
  }

  try {
    if (requestUrl.searchParams.get("amount")) {
      amount = parseFloat(requestUrl.searchParams.get("amount")!);
    }

    if (amount <= 0) throw "amount is too small";
  } catch (err) {
    throw "Invalid input query parameter: amount";
  }

  return {
    amount,
    toPubkey,
  };
}

This is /src/pages/api/claimsri.page.ts content. Example is Next.js 14 but I am using Next.js 12.3.1