閱讀時間 8 分鐘
概覽
像Solana Explorer這樣的工具讓查詢交易變得輕而易舉,但單純的價值轉移往往無法完整呈現全貌。有時您需要為交易附加背景資訊,例如人可讀的備註、發票編號,或是您自身系統中某筆訂單的參考編號。
Solana 的 Memo 程式透過將任意 UTF-8 文字作為交易的一部分寫入區塊鏈,來解決這個問題。在本指南中,您將使用基於 Pinocchio 的新版 Memo 程式,將備註附加至交易並讀取該備註(p-memo) 以及 Solana 套件 用於客戶端程式碼。
- Memo 程式會將任意的 UTF-8 文字寫入區塊鏈中,作為交易中的指令。
p-memo這是基於 Pinocchio 的 Memo 程式重新實作版本,其運算單元消耗量僅為原版的零頭(約 513 CU,相較於單簽署人 Memo 的 13,525 CU)。- 您將使用 Solana Kit 以及
@solana-program/memo請客戶發送一份備忘錄,內容如下:p-memo, 然後從已確認的交易中將其解碼回來。
什麼是 p-memo?
p-memo 這是基於以下內容重新編譯的 SPL Memo 程式: 木偶奇遇記,這是一個用於編寫高效能 Solana 程式的、無依賴項的 Rust 框架。透過使用較低階的 Pinocchio 原始元件,而非較高階的抽象概念, p-memo 大幅減少了每次備忘錄所需的運算單元(CU):
| 簽署人 | p-memo (CU) | SPL 備忘錄 (CU) | p-memo 成本與 SPL Memo 之比較 |
|---|---|---|---|
| 0 | 287 | 2,022 | ~14% |
| 1 | 513 | 13,525 | ~4% |
| 2 | 628 | 25,111 | ~3% |
| 3 | 743 | 36,406 | ~2% |
一份典型的備忘錄通常只有一位簽署者(費用支付方),因此實際上您只需花費約 513 CU 而非 13,525 CU,成本約低 26 倍。當您在計算預算固定的交易中,將備忘錄與其他指令一併打包時,這項 CU 優化就顯得至關重要。
本指南所使用的 Memo 程式版本已部署於 Memo4c2pN8afCj432Lb7RMVKi9PbQnnW7ewFFaV3oAH. 請注意,此網址與舊版 Memo 程式不同(MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr).
您將負責的工作內容
- 使用 Solana Kit 和 Memo 程式客戶端建立一個 TypeScript 專案
- 使用 Solana CLI 產生並為 Devnet 金鑰對充值
- 使用 來建立並傳送一筆附有備註的交易
p-memo - 閱讀已確認交易的回覆備忘錄
您需要準備的物品
本指南假設讀者對 Solana 和 TypeScript 已有基本了解。如果您是初次使用 Solana Kit 發送交易,建議先閱讀《如何使用 Solana Kit 發送交易》指南,作為入門指引。
開始之前,請確認您已備妥以下項目:
- 已安裝Node.js(v24 以上版本,可直接執行 TypeScript 檔案)
- 已安裝Solana CLI,用於產生並為密鑰對充值
- 一個 Quicknode 帳戶,用於存取 Solana 開發網(Devnet)端點。若您尚未擁有,可在此免費建立一個。
| 依賴關係 | 版本 |
|---|---|
| 節點 | 24.8.0 |
| solana-cli | 3.0.6 |
| @solana/kit | 6.9.0 |
| @solana/kit-plugin-rpc | 0.11.1 |
| @solana/kit-plugin-signer | 0.10.0 |
| @solana-program/memo | 0.11.1 |
建立專案
建立一個新的專案目錄,並將其初始化為 Node.js 專案:
mkdir solana-memo && cd solana-memo
npm init -y
安裝 Solana Kit、其 RPC 及簽署者外掛程式、Memo 程式客戶端,以及 Node 類型定義:
npm 安裝 @solana/kit@6.9.0 @solana/kit-plugin-rpc@0.11.1 @solana/kit-plugin-signer@0.10.0 @solana-program/memo@0.11.1
npm 安裝 --save-dev @types/node@25
設定 "type": "module" 欄位在 package.json 因此,Node 會將您的程式碼視為 ES 模組:
npm pkg set 類型=模組
建立一個空的 app.ts 位於專案根目錄下的檔案。您將在後面的章節中加入交易代碼:
touch app.ts
接下來,請生成並為一組金鑰對充值——該金鑰對將用於簽署並支付您的備註交易。
產生並為密鑰對充值
您需要一個已存入資金的帳戶,才能支付交易手續費並簽署備註。請使用 Solana CLI 將新的金鑰對產生並儲存至專案本地的檔案中:
solana-keygen new --no-bip39-passphrase --outfile ./my-keypair.json
透過空投向其發放 1 SOL(開發網版),以便支付手續費:
Solana 空投 1 --keypair ./my-keypair.json --url devnet
注意:Devnet SOL 沒有任何財務價值,僅供測試使用。空投端點設有速率限制,因此若指令執行失敗,請稍等片刻後重試,或使用網頁水龍頭。如需進一步了解,請參閱《Solana 測試版 SOL 空投完整指南》。
由於這組金鑰對屬於機密資訊,請勿將其納入版本控制。請建立一個 .gitignore 檔案,以免您不小心將金鑰或環境變數提交至版本控制系統:
node_modules
my-keypair.json
.env
設定 Quicknode 端點
歡迎您使用 Solana 公共節點,但公共節點在負載較高時會實施速率限制並出現延遲。若要獲得更快、更可靠的回應,請建立一個 Quicknode 端點:
- 請在此註冊免費帳戶。
- 建立一個端點,選擇Solana以及Devnet網路,並複製 HTTP 提供者 URL。

您將在下一步中將此 URL 加入環境變數中。Solana Kit 會從同一個 URL 推導出確認交易所需的 WebSocket 端點,因此您只需保留 HTTP Provider 的連結即可。
設定環境變數
與其將端點和金鑰路徑硬編碼,不如將它們儲存於一個 .env 位於專案根目錄中的檔案:
KEYPAIR_PATH=./my-keypair.json
SOLANA_RPC_URL=https://example.solana-devnet.quiknode.pro/0123456/
MEMO_PROGRAM_ADDRESS=Memo4c2pN8afCj432Lb7RMVKi9PbQnnW7ewFFaV3oAH
# 僅用於 Explorer 連結的叢集(例如:devnet、mainnet-beta、testnet)。
EXPLORER_CLUSTER=devnet
替換 SOLANA_RPC_URL 並填入您剛才複製的 HTTP 提供者 URL。Node.js 可以在執行時透過 --env-file 標誌,因此無需額外的依賴項。
開發應用程式
開啟 app.ts 並分段逐步建構:最上方是一個共用設定區塊,接著是兩個函式及其執行程式碼,依序添加於下方。只要按照每個步驟操作,最終便會得到完整的檔案。
首先從導入與設定開始。這段程式碼會讀取您的環境變數,接著建置一個單一的 Kit 客戶端,將 RPC 連線、WebSocket 訂閱和簽署器整合在一起,並定義幾個您將在各函式中重複使用的常數:
import {
address,
createClient,
getBase58Encoder,
type Signature,
} from "@solana/kit";
import {
getAddMemoInstruction,
getAddMemoInstructionDataDecoder,
} from "@solana-program/memo";
import { signerFromFile } from "@solana/kit-plugin-signer";
import { solanaRpc } from "@solana/kit-plugin-rpc";
const { KEYPAIR_PATH, SOLANA_RPC_URL, MEMO_PROGRAM_ADDRESS } = process.env;
if (!KEYPAIR_PATH || !SOLANA_RPC_URL || !MEMO_PROGRAM_ADDRESS) {
throw new Error(
"Set KEYPAIR_PATH, SOLANA_RPC_URL, and MEMO_PROGRAM_ADDRESS in your environment"
);
}
// A Kit client bundles the RPC connection, WebSocket subscriptions, and signer.
// signerFromFile loads the keypair JSON as the client's payer/identity, and
// solanaRpc wires up the RPC URL (the subscriptions URL is derived by swapping
// http -> ws). The client also gains a high-level sendTransaction helper.
const client = await createClient()
.use(signerFromFile(KEYPAIR_PATH))
.use(solanaRpc({ rpcUrl: SOLANA_RPC_URL }));
// The memo text to attach to the transaction.
const MEMO_TEXT = "Learning about the Memo program with Quicknode";
// Resolve the program address once, here where the env var is known to be set.
const MEMO_PROGRAM = address(MEMO_PROGRAM_ADDRESS);
// Cluster used only for the Explorer link below. Set EXPLORER_CLUSTER in your
// environment if SOLANA_RPC_URL points at a non-devnet network.
const explorerCluster = process.env.EXPLORER_CLUSTER ?? "devnet";
新增 sendMemo 功能
sendMemo 負責在區塊鏈上撰寫備忘錄。客戶的 sendTransaction 此輔助程式會為您處理所有後端作業——包括取得最新的區塊哈希、設定手續費支付者、簽名、發送及確認。
請將其新增至設定下方:
// Send a single Memo instruction and confirm it. The client handles fetching a
// blockhash, setting the fee payer, signing, sending, and confirming. Returns
// the confirmed transaction's signature.
async function sendMemo(memo: string): Promise<Signature> {
const result = await client.sendTransaction(
getAddMemoInstruction(
{ memo, signers: [client.payer] },
{ programAddress: MEMO_PROGRAM }
)
);
return result.context.signature;
}
有幾點值得特別指出:
client.sendTransaction整合了整個「建置-簽署-傳送-確認」的工作流程,否則您必須透過 Solana Kit 的低階交易函式手動組建此流程。getAddMemoInstruction源自生成的@solana-program/memo客戶。通過簽署人:[客戶/付款人]將費用支付人登記為備註指示上的簽署人(即上表中的「單一簽署人」情況),並且程式位址:MEMO_PROGRAM目標p-memo而非舊版程式。- 該筆已確認交易的簽名可於以下位置查閱:
result.context.signature.
新增 fetchMemo 功能
fetchMemo 透過從鏈上讀取備註內容來驗證此往返過程。它根據簽名擷取已確認的交易,找出屬於 Memo 程式的指令,並將該指令的資料解碼回原始文字。
請在下方加入 sendMemo:
// Fetch a transaction by signature and decode the plain-text memo it contains.
async function fetchMemo(signature: Signature): Promise<string> {
const transaction = await client.rpc
.getTransaction(signature, {
commitment: "confirmed",
maxSupportedTransactionVersion: 0,
encoding: "json",
})
.send();
if (!transaction) {
throw new Error(`Transaction not found: ${signature}`);
}
const { accountKeys, instructions } = transaction.transaction.message;
const memoInstruction = instructions.find(
(ix) => accountKeys[ix.programIdIndex] === MEMO_PROGRAM
);
if (!memoInstruction) {
throw new Error("No memo instruction found in transaction");
}
// The RPC returns instruction data as a base58 string. A base58 "encoder"
// turns that string into raw bytes (its .encode() = string -> bytes), and the
// memo decoder then reads those bytes back into the original memo text.
const data = getBase58Encoder().encode(memoInstruction.data);
return getAddMemoInstructionDataDecoder().decode(data).memo;
}
如果您之前曾使用過原始的「備忘錄」程式,您可能曾從 備忘錄 由...所回傳的欄位 getSignaturesForAddress. 該欄位僅由 RPC 節點在以下情況下填入: 被認可的 記錄程式識別碼,而且因為 p-memo 若該程式部署於新網址,該欄位將不會包含您的文字。取而代之的是,您需自行擷取交易紀錄並解碼指令資料,此方法適用於任何版本的備註程式。
傳送並閱讀備忘錄
最後,請依序執行以下兩個步驟:傳送備忘錄、記錄 Explorer 連結,然後擷取並記錄已解碼的備忘錄。因為 app.ts 是一個 ES 模組,你可以 等待 在頂層——無需封裝函式。
將此內容加入至 app.ts:
// Write a memo onchain, then read it back to prove the round-trip.
const signature = await sendMemo(MEMO_TEXT);
console.log(
`https://explorer.solana.com/tx/${signature}?cluster=${explorerCluster}`
);
const memo = await fetchMemo(signature);
console.log(`Memo: ${memo}`);
執行您的程式碼
執行此腳本,載入您的 .env 使用 Node 的 --env-file flag:
節點 --env-file=.env app.ts
您應能在 Solana Explorer 上看到該筆交易的連結,其後會顯示解碼後的備註:
https://explorer.solana.com/tx/5Pp...Xy?cluster=devnet
備忘錄:透過 Quicknode 了解「備忘錄」計畫
開啟「資源管理員」連結,您便會在「備忘錄」程式的說明及程式日誌中找到您的訊息。

已收到備忘錄。做得很好!
總結
您現在已透過基於 Pinocchio 的 Memo 程式和 Solana Kit,將一則備註附加至 Solana 交易並成功讀取。您撰寫的備註僅是交易中的一項指令,這意味著您可以將其與其他指令(例如 SOL 轉帳或呼叫您自己的程式)一併加入,藉此在您正在發送的交易中記錄相關背景資訊。透過 p-memo 由於其運算成本僅為原始程式的一小部分,您便能將交易中的 CU 預算留出更多給其他指令。
常見問題
什麼是 Solana Memo 計畫?
Memo 程式是一個原生 Solana 程式,可將任意的 UTF-8 文字以交易指令的形式記錄在鏈上。它通常用於將人可讀的備註、發票編號或外部參考資訊附加至交易中,並會驗證傳遞給該指令的任何帳戶是否為簽署者。
什麼是 p-memo?它與原始的 Memo 程式有何不同?
p-memo 是基於 Pinocchio(一個零依賴性的 Rust 框架)重新實作的 SPL Memo 程式。其指令介面與原版完全相同(指令資料即為備忘錄文字),但所需的運算單元更少。 使用 p-memo 執行單一簽署者的備忘錄,約需 513 CU;而原始程式則需約 13,525 CU。其部署於不同位址:Memo4c2pN8afCj432Lb7RMVKi9PbQnnW7ewFFaV3oAH。
為什麼我無法閱讀來自 getSignaturesForAddress 的備忘錄?
getSignaturesForAddress 回傳的備註欄位,僅會在 RPC 節點識別出備註程式 ID 時才會填入內容。由於 p-memo 是部署在新的地址上,該欄位將不會包含您的文字。相反地,請使用 getTransaction 擷取交易,並直接解碼備註指令的資料,此方法適用於任何版本的備註程式。
我能否在已經執行其他操作的交易中新增備註?
是的。備註僅是交易中的一項指令,因此您可以將其附加在其他指令(例如 SOL 轉帳或呼叫您自己的程式)之後,藉此將上下文記錄到您正在發送的交易中。
我可以在主網(Mainnet)上使用這個嗎?
是的。各叢集的程式碼完全相同;僅有您的端點以及您已充值的主網金鑰對會有所不同。請將 SOLANA_RPC_URL 指向主網端點,並使用已充值的主網金鑰對。請注意,在主網中,SOL 具有實際價值,且您需支付實際的交易手續費。
資源
- Solana Memo 計畫 (p-memo) 儲存庫
- @solana-program/memo 客戶端
- SPL Memo 程式文件
- Solana 套件
- 如何使用 Solana Kit 發送交易
- 如何使用 Pinocchio 建置並部署 Solana 程式
我們 ❤️ 您的回饋!
如果您有任何意見回饋或對新主題的建議,請告訴我們。我們非常樂意聽取您的意見。
