Skip to content

Commit 10d9822

Browse files
committed
rewrite the connection utils for memo and transfer sol
1 parent a39d5eb commit 10d9822

File tree

11 files changed

+468
-252
lines changed

11 files changed

+468
-252
lines changed

examples/next-js/package-lock.json

Lines changed: 14 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/next-js/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"tailwindcss-animate": "^1.0.7",
3434
"tweetnacl": "^1.0.3",
3535
"vaul": "^0.9.1",
36-
"zod": "^3.23.8"
36+
"zod": "^3.23.8",
37+
"zod-validation-error": "^3.4.0"
3738
},
3839
"devDependencies": {
3940
"@types/node": "^20",
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { TransactionInstruction, PublicKey } from "@solana/web3.js";
2+
3+
/**
4+
* Memo Program ID on Solana
5+
* This is a system program that allows writing arbitrary data to the blockchain
6+
*/
7+
export const MEMO_PROGRAM_ID = new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
8+
9+
/**
10+
* Creates an instruction to write a memo to the Solana blockchain
11+
*
12+
* @param message - The message to write to the blockchain
13+
* @param signers - Array of public keys that need to sign this instruction
14+
* @returns TransactionInstruction for the memo program
15+
*
16+
* Usage:
17+
* const instruction = createMemoInstruction("Hello Solana!", [signer.publicKey]);
18+
*/
19+
export function createMemoInstruction(
20+
message: string,
21+
signers: PublicKey[] = []
22+
): TransactionInstruction {
23+
return new TransactionInstruction({
24+
programId: MEMO_PROGRAM_ID,
25+
keys: signers.map((pubkey) => ({
26+
pubkey,
27+
isSigner: true,
28+
isWritable: false,
29+
})),
30+
data: Buffer.from(message, "utf-8"),
31+
});
32+
}

examples/next-js/src/app/api/actions/memo/route.ts

Lines changed: 85 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -5,95 +5,102 @@
55
import {
66
ActionPostResponse,
77
createPostResponse,
8-
MEMO_PROGRAM_ID,
98
ActionGetResponse,
10-
ActionPostRequest,
119
createActionHeaders,
12-
ActionError,
10+
Action,
11+
ActionPostRequest,
1312
} from "@solana/actions";
1413
import {
15-
clusterApiUrl,
16-
ComputeBudgetProgram,
17-
Connection,
1814
PublicKey,
19-
Transaction,
20-
TransactionInstruction,
15+
TransactionMessage,
16+
VersionedTransaction,
2117
} from "@solana/web3.js";
18+
import { createActionRoutes } from "../../utils/action-handler";
19+
import { getConnection } from "../../utils/connection";
20+
import { MemoQuerySchema } from "./schema";
21+
22+
// Create memo program instruction
23+
import { createMemoInstruction } from "./instruction";
2224

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

26-
export const GET = async (req: Request) => {
27-
const payload: ActionGetResponse = {
27+
async function handleGet(req: Request): Promise<ActionGetResponse> {
28+
const requestUrl = new URL(req.url);
29+
const baseHref = new URL("/api/actions/memo", requestUrl.origin).toString();
30+
31+
return {
2832
type: "action",
29-
title: "Actions Example - Simple On-chain Memo",
30-
icon: new URL("/solana_devs.jpg", new URL(req.url).origin).toString(),
31-
description: "Send a message on-chain using a Memo",
32-
label: "Send Memo",
33+
title: "Actions Example - Write Memo",
34+
icon: new URL("/solana_devs.jpg", requestUrl.origin).toString(),
35+
description: "Write a message to the Solana network",
36+
label: "Write",
37+
links: {
38+
actions: [
39+
{
40+
label: "Write Message",
41+
href: `${baseHref}?message={message}`,
42+
parameters: [
43+
{
44+
type: "textarea",
45+
name: "message",
46+
label: "Enter your message",
47+
required: true,
48+
min: 1,
49+
max: 256,
50+
},
51+
],
52+
},
53+
],
54+
},
3355
};
56+
}
57+
58+
async function handlePost(req: Request): Promise<ActionPostResponse> {
59+
const requestUrl = new URL(req.url);
60+
const { message } = parseQueryParams(requestUrl);
61+
62+
const body: ActionPostRequest = await req.json();
63+
const account = new PublicKey(body.account);
64+
65+
const connection = getConnection();
66+
const { blockhash } = await connection.getLatestBlockhash();
3467

35-
return Response.json(payload, {
36-
headers,
68+
// Create the memo instruction
69+
const memoInstruction = createMemoInstruction(message, [account]);
70+
71+
// Create a versioned transaction
72+
const messageV0 = new TransactionMessage({
73+
payerKey: account,
74+
recentBlockhash: blockhash,
75+
instructions: [memoInstruction],
76+
}).compileToV0Message();
77+
78+
const transaction = new VersionedTransaction(messageV0);
79+
80+
return createPostResponse({
81+
fields: {
82+
transaction,
83+
message: `Write memo: "${message}"`,
84+
},
3785
});
38-
};
39-
40-
// DO NOT FORGET TO INCLUDE THE `OPTIONS` HTTP METHOD
41-
// THIS WILL ENSURE CORS WORKS FOR BLINKS
42-
export const OPTIONS = async () => Response.json(null, { headers });
43-
44-
export const POST = async (req: Request) => {
45-
try {
46-
const body: ActionPostRequest = await req.json();
47-
48-
let account: PublicKey;
49-
try {
50-
account = new PublicKey(body.account);
51-
} catch (err) {
52-
throw 'Invalid "account" provided';
53-
}
54-
55-
const connection = new Connection(
56-
process.env.SOLANA_RPC! || clusterApiUrl("devnet"),
57-
);
58-
59-
const transaction = new Transaction().add(
60-
// note: `createPostResponse` requires at least 1 non-memo instruction
61-
ComputeBudgetProgram.setComputeUnitPrice({
62-
microLamports: 1000,
63-
}),
64-
new TransactionInstruction({
65-
programId: new PublicKey(MEMO_PROGRAM_ID),
66-
data: Buffer.from("this is a simple memo message2", "utf8"),
67-
keys: [],
68-
}),
69-
);
70-
71-
// set the end user as the fee payer
72-
transaction.feePayer = account;
73-
74-
transaction.recentBlockhash = (
75-
await connection.getLatestBlockhash()
76-
).blockhash;
77-
78-
const payload: ActionPostResponse = await createPostResponse({
79-
fields: {
80-
transaction,
81-
message: "Post this memo on-chain",
82-
},
83-
// no additional signers are required for this transaction
84-
// signers: [],
85-
});
86-
87-
return Response.json(payload, {
88-
headers,
89-
});
90-
} catch (err) {
91-
console.log(err);
92-
let actionError: ActionError = { message: "An unknown error occurred" };
93-
if (typeof err == "string") actionError.message = err;
94-
return Response.json(actionError, {
95-
status: 400,
96-
headers,
97-
});
86+
}
87+
88+
function parseQueryParams(requestUrl: URL) {
89+
const result = MemoQuerySchema.safeParse(
90+
Object.fromEntries(requestUrl.searchParams)
91+
);
92+
93+
if (!result.success) {
94+
throw result.error;
9895
}
99-
};
96+
97+
return result.data;
98+
}
99+
100+
export const { GET, POST, OPTIONS } = createActionRoutes(
101+
{
102+
GET: handleGet,
103+
POST: handlePost,
104+
},
105+
headers
106+
);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { z } from "zod";
2+
3+
export const MemoQuerySchema = z.object({
4+
message: z.string().min(1, "Message is required").max(256, "Message must be less than 256 characters"),
5+
});
6+
7+
export type MemoQuery = z.infer<typeof MemoQuerySchema>;
Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
import { PublicKey } from "@solana/web3.js";
2-
3-
export const DEFAULT_SOL_ADDRESS: PublicKey = new PublicKey(
4-
"nick6zJc6HpW3kfBm4xS2dmbuVRyb5F3AnUvj5ymzR5", // devnet wallet
5-
);
6-
71
export const DEFAULT_SOL_AMOUNT: number = 1.0;

0 commit comments

Comments
 (0)