Was ist Memo-Transfer?
Die MemoTransfer-Kontoerweiterung des Token Extensions Program erfordert
eine Memo-Anweisung unmittelbar vor jedem eingehenden Transfer auf dieses
Token-Konto. Ausgehende Transfers von diesem Token-Konto erfordern kein Memo.
Die Erweiterung wird auf dem Ziel-Token-Konto konfiguriert, nicht auf dem Mint.
Sobald aktiviert, schlagen Transfers ohne Memo mit TokenError::NoMemo
fehl, bis der Token-Kontoinhaber die Anforderung deaktiviert.
So erstellen Sie ein Token-Konto mit aktiviertem MemoTransfer
So erstellen Sie ein Token-Konto mit aktiviertem MemoTransfer:
- Erstellen und initialisieren Sie einen Mint.
- Berechnen Sie die Token-Kontogröße und die benötigte Miete für ein
Token-Konto mit
MemoTransfer. - Erstellen Sie das Token-Konto und aktivieren Sie
MemoTransferauf diesem Konto. Transfer- undTransferChecked- Anweisungen auf dieses Token-Konto müssen eine Memo-Anweisung unmittelbar vor der Transfer-Anweisung enthalten.
Mint erstellen und initialisieren
Erstellen und initialisieren Sie den Mint, den das Token-Konto verwenden wird.
Token-Kontogröße berechnen
Berechnen Sie die Token-Kontogröße für das Basis-Token-Konto zuzüglich der
MemoTransfer-Erweiterung. Dies ist die Größe, die in CreateAccount
verwendet wird.
Miete berechnen
Berechnen Sie die Miete unter Verwendung der Token-Kontogröße, die für die
MemoTransfer-Erweiterung benötigt wird.
Erstellen und Initialisieren des Token-Kontos
Erstellen Sie das Token-Konto mit dem berechneten Speicherplatz und den Lamports und initialisieren Sie es dann für die Mint.
MemoTransfer aktivieren
Aktivieren Sie MemoTransfer auf dem Token-Konto.
Quellenreferenz
| Element | Beschreibung | Quelle |
|---|---|---|
MemoTransfer | Kontenerweiterung, die speichert, ob eingehende Überweisungen ein Memo erfordern. | Quelle |
RequiredMemoTransfersInstruction::Enable | Anweisungen, die Überweisungen mit Memo-Pflicht auf einem Token-Konto aktivieren. | Quelle |
RequiredMemoTransfersInstruction::Disable | Anweisungen, die Überweisungen mit Memo-Pflicht auf einem Token-Konto deaktivieren. | Quelle |
process_toggle_required_memo_transfers | Prozessorlogik, die die MemoTransfer-Erweiterung initialisiert oder neu schreibt, nachdem die Eigentümerschaft überprüft wurde. | Quelle |
Typescript
Das Beispiel Kit unten verwendet die generierten Anweisungen direkt.
Legacy-Beispiele mit @solana/web3.js, @solana/spl-token und
@solana/spl-memo sind als Referenz enthalten.
Kit
import {lamports,createClient,generateKeyPairSigner,unwrapOption} from "@solana/kit";import { solanaRpc, rpcAirdrop } from "@solana/kit-plugin-rpc";import { generatedPayer, airdropPayer } from "@solana/kit-plugin-signer";import { getAddMemoInstruction } from "@solana-program/memo";import { getCreateAccountInstruction } from "@solana-program/system";import {extension,fetchToken,findAssociatedTokenPda,getCreateAssociatedTokenInstructionAsync,getDisableMemoTransfersInstruction,getEnableMemoTransfersInstruction,getInitializeAccountInstruction,getInitializeMintInstruction,getMintSize,getMintToCheckedInstruction,getTokenSize,getTransferCheckedInstruction,isExtension,TOKEN_2022_PROGRAM_ADDRESS} from "@solana-program/token-2022";const client = await createClient().use(generatedPayer()).use(solanaRpc({rpcUrl: "http://localhost:8899",rpcSubscriptionsUrl: "ws://localhost:8900"})).use(rpcAirdrop()).use(airdropPayer(lamports(1_000_000_000n)));const mint = await generateKeyPairSigner();const tokenOwner = await generateKeyPairSigner();const tokenAccount = await generateKeyPairSigner();const tokenAmount = 1n;const mintSpace = BigInt(getMintSize());const mintRent = await client.rpc.getMinimumBalanceForRentExemption(mintSpace).send();await client.sendTransaction([getCreateAccountInstruction({payer: client.payer, // Account funding the new mint account.newAccount: mint, // New mint account to create.lamports: mintRent, // Lamports funding the mint account rent.space: mintSpace, // Account size in bytes for the mint.programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the mint account.}),getInitializeMintInstruction({mint: mint.address, // Mint account to initialize.decimals: 0, // Number of decimals for the token.mintAuthority: client.payer.address, // Authority allowed to mint new tokens.freezeAuthority: client.payer.address // Authority allowed to freeze token accounts.})]);const [sourceToken] = await findAssociatedTokenPda({mint: mint.address, // Mint for the source token account.owner: client.payer.address, // Owner of the source token account.tokenProgram: TOKEN_2022_PROGRAM_ADDRESS // Token program that owns the token account.});const memoTransferAccountExtension = extension("MemoTransfer", {requireIncomingTransferMemos: false});const tokenSpace = BigInt(getTokenSize([memoTransferAccountExtension]));const tokenRent = await client.rpc.getMinimumBalanceForRentExemption(tokenSpace).send();await client.sendTransaction([await getCreateAssociatedTokenInstructionAsync({payer: client.payer, // Account funding the associated token account creation.mint: mint.address, // Mint for the associated token account.owner: client.payer.address // Owner of the associated token account.}),getMintToCheckedInstruction({mint: mint.address, // Mint account that issues the tokens.token: sourceToken, // Token account receiving the newly minted tokens.mintAuthority: client.payer, // Signer authorized to mint new tokens.amount: tokenAmount, // Token amount in base units.decimals: 0 // Decimals defined on the mint.}),getCreateAccountInstruction({payer: client.payer, // Account funding the new token account.newAccount: tokenAccount, // New token account to create.lamports: tokenRent, // Lamports funding the token account rent.space: tokenSpace, // Account size in bytes for the token account plus MemoTransfer.programAddress: TOKEN_2022_PROGRAM_ADDRESS // Program that owns the token account.}),getInitializeAccountInstruction({account: tokenAccount.address, // Token account to initialize.mint: mint.address, // Mint for the token account.owner: tokenOwner.address // Owner of the token account.})]);await client.sendTransaction([getEnableMemoTransfersInstruction({token: tokenAccount.address, // Token account that stores the MemoTransfer extension.owner: tokenOwner // Token account owner authorized to toggle memo requirements.})]);await client.sendTransaction([getAddMemoInstruction({memo: "memo required" // Memo string required by the destination token account.}),getTransferCheckedInstruction({source: sourceToken, // Token account sending the transfer.mint: mint.address, // Mint for the transfer.destination: tokenAccount.address, // Token account receiving the transfer.authority: client.payer, // Owner of the source token account.amount: tokenAmount, // Token amount in base units.decimals: 0 // Decimals defined on the mint.})]);await client.sendTransaction([getDisableMemoTransfersInstruction({token: tokenAccount.address, // Token account that stores the MemoTransfer extension.owner: tokenOwner // Token account owner authorized to toggle memo requirements.})]);const tokenAccountData = await fetchToken(client.rpc, tokenAccount.address);const memoTransferExtension = (unwrapOption(tokenAccountData.data.extensions) ?? []).find((item) => isExtension("MemoTransfer", item));console.log("Mint Address:", mint.address);console.log("Token Account:", tokenAccount.address);console.log("Destination Amount:", tokenAccountData.data.amount);console.log("MemoTransfer Extension:", memoTransferExtension);
Web3.js
import {Connection,Keypair,sendAndConfirmTransaction,SystemProgram,Transaction,LAMPORTS_PER_SOL} from "@solana/web3.js";import {ASSOCIATED_TOKEN_PROGRAM_ID,getAssociatedTokenAddressSync,createAssociatedTokenAccountInstruction,createDisableRequiredMemoTransfersInstruction,createEnableRequiredMemoTransfersInstruction,createInitializeAccountInstruction,createInitializeMintInstruction,getAccount,getAccountLen,getMemoTransfer,createMintToCheckedInstruction,createTransferCheckedInstruction,ExtensionType,getMintLen,TOKEN_2022_PROGRAM_ID} from "@solana/spl-token";import { createMemoInstruction } from "@solana/spl-memo";const connection = new Connection("http://localhost:8899", "confirmed");const feePayer = Keypair.generate();const tokenOwner = Keypair.generate();const tokenAmount = 1;const airdropSignature = await connection.requestAirdrop(feePayer.publicKey,5 * LAMPORTS_PER_SOL);await connection.confirmTransaction(airdropSignature, "confirmed");const mint = Keypair.generate();const mintLength = getMintLen([]);const mintRent = await connection.getMinimumBalanceForRentExemption(mintLength);const extensions = [ExtensionType.MemoTransfer];const tokenAccount = Keypair.generate();const tokenAccountLen = getAccountLen(extensions);const tokenAccountRent =await connection.getMinimumBalanceForRentExemption(tokenAccountLen);const createMintAccountInstruction = SystemProgram.createAccount({fromPubkey: feePayer.publicKey, // Account funding the new mint account.newAccountPubkey: mint.publicKey, // New mint account to create.space: mintLength, // Account size in bytes for the mint.lamports: mintRent, // Lamports funding the mint account rent.programId: TOKEN_2022_PROGRAM_ID // Program that owns the mint account.});const initializeMintInstruction = createInitializeMintInstruction(mint.publicKey, // Mint account to initialize.0, // Number of decimals for the token.feePayer.publicKey, // Authority allowed to mint new tokens.feePayer.publicKey, // Authority allowed to freeze token accounts.TOKEN_2022_PROGRAM_ID // Token program that owns the mint account.);const sourceToken = getAssociatedTokenAddressSync(mint.publicKey, // Mint for the source token account.feePayer.publicKey, // Owner of the source token account.false, // Whether the owner is a PDA.TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.ASSOCIATED_TOKEN_PROGRAM_ID // Associated Token Program that derives the ATA.);const createSourceTokenInstruction = createAssociatedTokenAccountInstruction(feePayer.publicKey, // Account funding the associated token account creation.sourceToken, // Associated token account address to create.feePayer.publicKey, // Owner of the associated token account.mint.publicKey, // Mint for the associated token account.TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.ASSOCIATED_TOKEN_PROGRAM_ID // Associated Token Program that creates the account.);const mintToInstruction = createMintToCheckedInstruction(mint.publicKey, // Mint account that issues the tokens.sourceToken, // Token account receiving the newly minted tokens.feePayer.publicKey, // Signer authorized to mint new tokens.tokenAmount, // Token amount in base units.0, // Decimals defined on the mint.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that owns the mint and token account.);const createTokenAccountInstruction = SystemProgram.createAccount({fromPubkey: feePayer.publicKey, // Account funding the new token account.newAccountPubkey: tokenAccount.publicKey, // New token account to create.space: tokenAccountLen, // Account size in bytes for the token account plus MemoTransfer.lamports: tokenAccountRent, // Lamports funding the token account rent.programId: TOKEN_2022_PROGRAM_ID // Program that owns the token account.});const initializeTokenAccountInstruction = createInitializeAccountInstruction(tokenAccount.publicKey, // Token account to initialize.mint.publicKey, // Mint for the token account.tokenOwner.publicKey, // Owner of the token account.TOKEN_2022_PROGRAM_ID // Token program that owns the token account.);await sendAndConfirmTransaction(connection,new Transaction().add(createMintAccountInstruction,initializeMintInstruction),[feePayer, mint]);await sendAndConfirmTransaction(connection,new Transaction().add(createSourceTokenInstruction,mintToInstruction,createTokenAccountInstruction,initializeTokenAccountInstruction),[feePayer, tokenAccount]);const enableMemoTransferExtensionInstruction =createEnableRequiredMemoTransfersInstruction(tokenAccount.publicKey, // Token account that stores the MemoTransfer extension.tokenOwner.publicKey, // Token account owner authorized to toggle memo requirements.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that owns the token account.);await sendAndConfirmTransaction(connection,new Transaction().add(enableMemoTransferExtensionInstruction),[feePayer, tokenOwner]);const memoIx = createMemoInstruction("memo required", // Memo string required by the destination token account.[feePayer.publicKey] // Accounts to include in the memo instruction.);const transferInstruction = createTransferCheckedInstruction(sourceToken, // Token account sending the transfer.mint.publicKey, // Mint for the transfer.tokenAccount.publicKey, // Token account receiving the transfer.feePayer.publicKey, // Owner of the source token account.tokenAmount, // Token amount in base units.0, // Decimals defined on the mint.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that processes the transfer.);await sendAndConfirmTransaction(connection,new Transaction().add(memoIx, transferInstruction),[feePayer]);const disableMemoTransferExtensionInstruction =createDisableRequiredMemoTransfersInstruction(tokenAccount.publicKey, // Token account that stores the MemoTransfer extension.tokenOwner.publicKey, // Token account owner authorized to toggle memo requirements.[], // Additional multisig signers.TOKEN_2022_PROGRAM_ID // Token program that owns the token account.);await sendAndConfirmTransaction(connection,new Transaction().add(disableMemoTransferExtensionInstruction),[feePayer, tokenOwner]);const tokenAccountData = await getAccount(connection,tokenAccount.publicKey,"confirmed",TOKEN_2022_PROGRAM_ID);const memoTransferExtension = getMemoTransfer(tokenAccountData);console.log("Mint Address:", mint.publicKey.toBase58());console.log("Token Account:", tokenAccount.publicKey.toBase58());console.log("Destination Amount:", tokenAccountData.amount.toString());console.log("MemoTransfer Extension:", memoTransferExtension);
Rust
use anyhow::Result;use solana_client::nonblocking::rpc_client::RpcClient;use solana_commitment_config::CommitmentConfig;use solana_sdk::{program_pack::Pack,signature::{Keypair, Signer},transaction::Transaction,};use solana_system_interface::instruction::create_account;use spl_associated_token_account_interface::{address::get_associated_token_address_with_program_id,instruction::create_associated_token_account,};use spl_memo_interface::{instruction::build_memo, v3::ID as MEMO_PROGRAM_ID};use spl_token_2022_interface::{extension::{memo_transfer::{instruction::{disable_required_transfer_memos, enable_required_transfer_memos},MemoTransfer,},BaseStateWithExtensions, ExtensionType, StateWithExtensions,},instruction::{initialize_account, initialize_mint, mint_to_checked, transfer_checked},state::{Account, Mint},ID as TOKEN_2022_PROGRAM_ID,};#[tokio::main]async fn main() -> Result<()> {let client = RpcClient::new_with_commitment(String::from("http://localhost:8899"),CommitmentConfig::confirmed(),);let fee_payer = Keypair::new();let token_owner = Keypair::new();let token_amount = 1u64;let airdrop_signature = client.request_airdrop(&fee_payer.pubkey(), 5_000_000_000).await?;loop {let confirmed = client.confirm_transaction(&airdrop_signature).await?;if confirmed {break;}}let mint = Keypair::new();let mint_space = Mint::LEN;let mint_rent = client.get_minimum_balance_for_rent_exemption(mint_space).await?;let create_mint_account_instruction = create_account(&fee_payer.pubkey(), // Account funding the new mint account.&mint.pubkey(), // New mint account to create.mint_rent, // Lamports funding the mint account rent.mint_space as u64, // Account size in bytes for the mint.&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.);let initialize_mint_instruction = initialize_mint(&TOKEN_2022_PROGRAM_ID, // Program that owns the mint account.&mint.pubkey(), // Mint account to initialize.&fee_payer.pubkey(), // Authority allowed to mint new tokens.Some(&fee_payer.pubkey()), // Authority allowed to freeze token accounts.0, // Number of decimals for the token.)?;let create_mint_transaction = Transaction::new_signed_with_payer(&[create_mint_account_instruction, initialize_mint_instruction],Some(&fee_payer.pubkey()),&[&fee_payer, &mint],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&create_mint_transaction).await?;let source_token = get_associated_token_address_with_program_id(&fee_payer.pubkey(), // Owner of the associated token account.&mint.pubkey(), // Mint for the associated token account.&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.);let create_source_token_instruction = create_associated_token_account(&fee_payer.pubkey(), // Account funding the associated token account creation.&fee_payer.pubkey(), // Owner of the associated token account.&mint.pubkey(), // Mint for the associated token account.&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.);let mint_to_instruction = mint_to_checked(&TOKEN_2022_PROGRAM_ID, // Token program that owns the mint and token account.&mint.pubkey(), // Mint account that issues the tokens.&source_token, // Token account receiving the newly minted tokens.&fee_payer.pubkey(), // Signer authorized to mint new tokens.&[], // Additional multisig signers.token_amount, // Token amount in base units.0, // Decimals defined on the mint.)?;let token_account = Keypair::new();let token_account_space =ExtensionType::try_calculate_account_len::<Account>(&[ExtensionType::MemoTransfer])?;let token_account_rent = client.get_minimum_balance_for_rent_exemption(token_account_space).await?;let create_token_account_instruction = create_account(&fee_payer.pubkey(), // Account funding the new token account.&token_account.pubkey(), // New token account to create.token_account_rent, // Lamports funding the token account rent.token_account_space as u64, // Account size in bytes for the token account plus MemoTransfer.&TOKEN_2022_PROGRAM_ID, // Program that owns the token account.);let initialize_token_account = initialize_account(&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.&token_account.pubkey(), // Token account to initialize.&mint.pubkey(), // Mint for the token account.&token_owner.pubkey(), // Owner of the token account.)?;let setup_transaction = Transaction::new_signed_with_payer(&[create_source_token_instruction,mint_to_instruction,create_token_account_instruction,initialize_token_account,],Some(&fee_payer.pubkey()),&[&fee_payer, &token_account],client.get_latest_blockhash().await?,);client.send_and_confirm_transaction(&setup_transaction).await?;let enable_memo_transfer_instruction = enable_required_transfer_memos(&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.&token_account.pubkey(), // Token account that stores the MemoTransfer extension.&token_owner.pubkey(), // Token account owner authorized to toggle memo requirements.&[], // Additional multisig signers.)?;let enable_blockhash = client.get_latest_blockhash().await?;let enable_transaction = Transaction::new_signed_with_payer(&[enable_memo_transfer_instruction],Some(&fee_payer.pubkey()),&[&fee_payer, &token_owner],enable_blockhash,);client.send_and_confirm_transaction(&enable_transaction).await?;let memo_instruction = build_memo(&MEMO_PROGRAM_ID, // Memo program that records the memo string.b"memo required", // Memo string required by the destination token account.&[&fee_payer.pubkey()], // Accounts to include in the memo instruction.);let transfer_instruction = transfer_checked(&TOKEN_2022_PROGRAM_ID, // Token program that processes the transfer.&source_token, // Token account sending the transfer.&mint.pubkey(), // Mint for the transfer.&token_account.pubkey(), // Token account receiving the transfer.&fee_payer.pubkey(), // Owner of the source token account.&[], // Additional multisig signers.token_amount, // Token amount in base units.0, // Decimals defined on the mint.)?;let transfer_blockhash = client.get_latest_blockhash().await?;let transfer_transaction = Transaction::new_signed_with_payer(&[memo_instruction, transfer_instruction],Some(&fee_payer.pubkey()),&[&fee_payer],transfer_blockhash,);client.send_and_confirm_transaction(&transfer_transaction).await?;let disable_memo_transfer_instruction = disable_required_transfer_memos(&TOKEN_2022_PROGRAM_ID, // Token program that owns the token account.&token_account.pubkey(), // Token account that stores the MemoTransfer extension.&token_owner.pubkey(), // Token account owner authorized to toggle memo requirements.&[], // Additional multisig signers.)?;let disable_blockhash = client.get_latest_blockhash().await?;let disable_transaction = Transaction::new_signed_with_payer(&[disable_memo_transfer_instruction],Some(&fee_payer.pubkey()),&[&fee_payer, &token_owner],disable_blockhash,);client.send_and_confirm_transaction(&disable_transaction).await?;let token_account_data = client.get_account(&token_account.pubkey()).await?;let token_account_state = StateWithExtensions::<Account>::unpack(&token_account_data.data)?;let memo_transfer_extension = token_account_state.get_extension::<MemoTransfer>()?;println!("Mint Address: {}", mint.pubkey());println!("Token Account: {}", token_account.pubkey());println!("Destination Amount: {}", token_account_state.base.amount);println!("MemoTransfer Extension: {:#?}", memo_transfer_extension);Ok(())}
Is this page helpful?