
Transaksi Solana: Nonce Tahan Lama (2023)
Pendahuluan
Saat mengirimkan transaksi di Solana sebagai developer, Anda perlu mengambil blockhash terbaru melalui RPC Solana. Langkah penting ini membantu mengurangi serangan replay. Setelah transaksi ditandatangani dan dikirimkan menggunakan blockhash tertentu, tidak seorang pun dapat menggunakan hash yang sama untuk memutar ulang atau mengirimkan kembali transaksi tersebut.
Bayangkan Anda perlu mengirimkan transaksi yang memerlukan tanda tangan dari cold storage offline atau hardware wallet seperti Ledger. Namun, blockhash cepat kedaluwarsa sehingga transaksi Anda berpotensi menjadi tidak valid. Di sinilah nonce tahan lama berperan untuk memungkinkan transaksi offline yang aman.
Setelah menyelesaikan panduan ini, Anda akan memahami:
- Apa itu nonce tahan lama.
- Tujuan nonce tahan lama.
- Cara menggunakan nonce tahan lama dalam transaksi.
Transaksi: Prasyarat
Sebelum memulai, pastikan Anda memiliki:
- Pengetahuan dasar JavaScript.
- NodeJS yang sudah terinstal.
- Solana CLI yang sudah terinstal.
- Git yang sudah terinstal
Penyiapan Lingkungan
- Kloning repositori contoh kami yang sudah dilengkapi utilitas:
git clone- Buka folder proyek dan instal npm:
cd durable-noncenpm install- Buka folder wallets di dalam folder nonce. Folder ini akan menyimpan kunci lokal untuk pengujian. Kemudian, masuk ke folder tersebut:
cd wallets- Setelah Solana CLI terinstal, buat satu wallet untuk keypair pembayar:
solana-keygen new -o ./wallet.json- Sekarang, tetapkan wallet ini sebagai wallet Anda di CLI untuk menerima airdrop Solana:
solana config set --keypair ./wallet.json- Sekarang Anda dapat melakukan airdrop Solana ke alamat ini dengan menjalankan perintah berikut:
solana aidrop 2- Kita juga perlu membuat dan mendanai wallet lain untuk otoritas nonce:
solana-keygen new -o ./nonceAuth.json- Untuk public key yang dihasilkan ini, kita dapat menggunakan situs faucet untuk melakukan airdrop 1 SOL, di sini.
Setelah lingkungan siap, kita dapat melanjutkan ke langkah berikutnya.
Apa Itu Nonce Tahan Lama?
Akun nonce tahan lama di Solana dapat dianggap sebagai kotak penyimpanan aman. Saat Anda membuat akun ini, Solana memberinya kode unik dan stabil yang disebut "nonce tahan lama". Tidak seperti nonce biasa yang berubah pada setiap transaksi, nonce ini tetap sama dan berfungsi sebagai referensi yang konsisten.
Hal ini sangat berguna untuk transaksi "offline". Saat membuat transaksi, Anda merujuk nonce ini dari akun Anda. Solana memvalidasinya dengan nilai yang tersimpan. Jika nilainya cocok, transaksi akan disetujui. Dengan demikian, akun nonce tahan lama berfungsi sebagai mekanisme penyimpanan sekaligus validasi yang memastikan keaslian transaksi, sembari mengakomodasi kecepatan tinggi dan skenario offline di jaringan Solana.
Nonce tahan lama dapat digunakan dalam berbagai kasus penggunaan, seperti:
- Transaksi terjadwal: Anda dapat mengatur transaksi agar berlangsung pada waktu tertentu di masa mendatang. Nonce tahan lama memastikan transaksi terjadwal tersebut dieksekusi dengan aman.
- Wallet multisig: Dalam konteks wallet multisignature, nonce tahan lama menyediakan lapisan keamanan dan koordinasi tambahan di antara beberapa penanda tangan.
- Program yang memerlukan interaksi di masa mendatang: Beberapa program di Solana perlu berinteraksi dengan program atau layanan lain pada interval tertentu. Nonce tahan lama membantu menjaga integritas interaksi tersebut.
- Interaksi dengan blockchain lain: Saat Solana berinteraksi dengan blockchain lain, nonce tahan lama membantu memastikan validitas transaksi lintas chain.
Sekarang, kita dapat mulai membuat contoh ini.
Transaksi Solana: Langkah-Langkah Pembuatan
Langkah 1: Siapkan Dependensi dan Konstanta
Pada langkah ini, Anda akan mengimpor modul dan utilitas yang diperlukan, lalu menentukan konstanta dan keypair untuk contoh ini. Dependensi dan konstanta tersebut akan digunakan sepanjang proses transaksi.
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
NonceAccount,
NONCE_ACCOUNT_LENGTH,
SystemProgram,
Transaction,
} from "@solana/web3.js";
import { encodeAndWriteTransaction, loadWallet, readAndDecodeTransaction } from "./utils";
const nonceAuthKeypair = loadWallet('./wallets/nonceAuth.json');
const nonceKeypair = Keypair.generate();
const senderKeypair = loadWallet('./wallets/wallet.json');
const connection = new Connection("https://devnet.helius-rpc.com/?api-key=");
const waitTime = 120000;
const TranferAmount = LAMPORTS_PER_SOL * 0.01;Langkah 2: Buat Fungsi sendTransaction
Fungsi sendTransaction mengatur proses pengiriman transaksi menggunakan nonce tahan lama. Fungsi ini menangani pembuatan nonce, konfirmasi, dan eksekusi transaksi.
async function sendTransaction() {
console.log("Starting Nonce Transaction")
try {
const nonceCreationTxSig = await nonce();
const confirmationStatus = await connection.confirmTransaction(nonceCreationTxSig);
if (!confirmationStatus.value.err) {
console.log("Nonce account creation confirmed.");
const nonce = await getNonce();
await createTx(nonce);
await signOffline(waitTime);
await executeTx();
} else {
console.error("Nonce account creation transaction failed:", confirmationStatus.value.err);
}
} catch (error) {
console.error(error);
}
}Langkah 3: Buat Fungsi nonce
Fungsi nonce bertanggung jawab untuk membuat dan menginisialisasi akun nonce tahan lama. Proses ini mencakup penghitungan biaya sewa yang diperlukan untuk akun, pengambilan blockhash terbaru, serta pembuatan transaksi untuk membuat dan menginisialisasi akun nonce.
- Sebelum membuat akun nonce, kita perlu menghitung biaya sewa yang diperlukan untuk penyimpanan data akun dan mengambil blockhash terbaru.
async function nonce() {
const rent = await connection.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH);
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();- Sekarang, kita akan membuat transaksi untuk membuat akun nonce. Proses ini menggunakan instruksi
SystemProgram.createAccountuntuk mengalokasikan ruang bagi akun nonce.
const createNonceTx = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: nonceAuthKeypair.publicKey,
newAccountPubkey: nonceKeypair.publicKey,
lamports: rent,
space: NONCE_ACCOUNT_LENGTH,
programId: SystemProgram.programId,
})
);- Kita akan menandatangani transaksi dengan keypair otoritas, lalu mengirimkannya ke jaringan Solana. Transaksi ini membuat akun nonce tahan lama.
createNonceTx.feePayer = nonceAuthKeypair.publicKey;
createNonceTx.recentBlockhash = blockhash;
createNonceTx.lastValidBlockHeight = lastValidBlockHeight;
createNonceTx.sign(nonceAuthKeypair, nonceKeypair);
try {
const signature = await connection.sendRawTransaction(createNonceTx.serialize(), { skipPreflight: false, preflightCommitment: "single" });- Setelah mengirimkan transaksi, kita akan mengonfirmasi statusnya untuk memastikan akun nonce berhasil dibuat.
const confirmationStatus = await connection.confirmTransaction(signature);
if (confirmationStatus.value.err) {
throw new Error("Nonce account creation transaction failed: " + confirmationStatus.value.err);
}
console.log("Nonce account created:", signature);- Agar akun nonce dapat digunakan sepenuhnya, kita perlu menginisialisasi nilainya. Kita akan membuat transaksi baru untuk menjalankan instruksi
SystemProgram.nonceInitialize.
// Initialize the nonce value within the account
const initializeNonceTx = new Transaction().add(
SystemProgram.nonceInitialize({
noncePubkey: nonceKeypair.publicKey,
authorizedPubkey: nonceAuthKeypair.publicKey,
})
);- Seperti pada langkah sebelumnya, kita akan menandatangani transaksi dan mengirimkannya ke jaringan untuk menginisialisasi akun nonce.
const { blockhash: initBlockhash, lastValidBlockHeight: initLastValidBlockHeight } = await connection.getLatestBlockhash();
initializeNonceTx.feePayer = nonceAuthKeypair.publicKey;
initializeNonceTx.recentBlockhash = initBlockhash;
initializeNonceTx.lastValidBlockHeight = initLastValidBlockHeight;
initializeNonceTx.sign(nonceAuthKeypair);
const initSignature = await connection.sendRawTransaction(initializeNonceTx.serialize(), { skipPreflight: false, preflightCommitment: "single" });- Terakhir, kita akan mengonfirmasi status transaksi inisialisasi untuk memastikan akun nonce telah diinisialisasi dengan benar.
const initConfirmationStatus = await connection.confirmTransaction(initSignature);
if (initConfirmationStatus.value.err) {
throw new Error("Nonce initialization transaction failed: " + initConfirmationStatus.value.err);
}
console.log("Nonce initialized:", initSignature);
return initSignature;
} catch (error) {
console.error("Failed in createNonce function: ", error);
throw error;
}
}Keseluruhan fungsi akan terlihat seperti ini:
async function nonce() {
// For creating the nonce account
const rent = await connection.getMinimumBalanceForRentExemption(
NONCE_ACCOUNT_LENGTH
);
const { blockhash, lastValidBlockHeight } =
await connection.getLatestBlockhash();
const createNonceTx = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: nonceAuthKeypair.publicKey,
newAccountPubkey: nonceKeypair.publicKey,
lamports: rent,
space: NONCE_ACCOUNT_LENGTH,
programId: SystemProgram.programId,
})
);
createNonceTx.feePayer = nonceAuthKeypair.publicKey;
createNonceTx.recentBlockhash = blockhash;
createNonceTx.lastValidBlockHeight = lastValidBlockHeight;
createNonceTx.sign(nonceAuthKeypair, nonceKeypair);
try {
// Create the nonce account
const signature = await connection.sendRawTransaction(
createNonceTx.serialize(),
{ skipPreflight: false, preflightCommitment: "single" }
);
const confirmationStatus = await connection.confirmTransaction(signature);
if (confirmationStatus.value.err) {
throw new Error(
"Nonce account creation transaction failed: " +
confirmationStatus.value.err
);
}
console.log("Nonce account created:", signature);
// Now, initialize the nonce
const initializeNonceTx = new Transaction().add(
SystemProgram.nonceInitialize({
noncePubkey: nonceKeypair.publicKey,
authorizedPubkey: nonceAuthKeypair.publicKey,
})
);
const {
blockhash: initBlockhash,
lastValidBlockHeight: initLastValidBlockHeight,
} = await connection.getLatestBlockhash();
initializeNonceTx.feePayer = nonceAuthKeypair.publicKey;
initializeNonceTx.recentBlockhash = initBlockhash;
initializeNonceTx.lastValidBlockHeight = initLastValidBlockHeight;
initializeNonceTx.sign(nonceAuthKeypair); // Only sign with nonceAuthKeypair
const initSignature = await connection.sendRawTransaction(
initializeNonceTx.serialize(),
{ skipPreflight: false, preflightCommitment: "single" }
);
const initConfirmationStatus = await connection.confirmTransaction(
initSignature
);
if (initConfirmationStatus.value.err) {
throw new Error(
"Nonce initialization transaction failed: " +
initConfirmationStatus.value.err
);
}
console.log("Nonce initialized:", initSignature);
return initSignature;
} catch (error) {
console.error("Failed in createNonce function: ", error);
throw error;
}
}Langkah 4: Buat Fungsi getNonce
Tentukan fungsi getNonce yang bertanggung jawab mengambil nilai nonce dari akun nonce yang telah dibuat.
async function getNonce() {
const nonceAccount = await fetchNonceInfo();
return nonceAccount.nonce;
}Langkah 5: Buat Fungsi createTx
Tentukan fungsi createTx yang membuat contoh transaksi berisi instruksi untuk memajukan nonce dan instruksi transfer. Fungsi ini menggunakan nonce yang sebelumnya diambil untuk memastikan keaslian transaksi.
async function createTx(nonce) {
const destination = Keypair.generate();
const advanceNonceIx = SystemProgram.nonceAdvance({
noncePubkey: nonceKeypair.publicKey,
authorizedPubkey: nonceAuthKeypair.publicKey
});
const transferIx = SystemProgram.transfer({
fromPubkey: senderKeypair.publicKey,
toPubkey: destination.publicKey,
lamports: TranferAmount,
});
const sampleTx = new Transaction();
sampleTx.add(advanceNonceIx, transferIx);
sampleTx.recentBlockhash = nonce; // Use the nonce fetched earlier
sampleTx.feePayer = senderKeypair.publicKey;
const serialisedTx = encodeAndWriteTransaction(sampleTx, "./unsignedTxn.json", false);
return serialisedTx;
}Langkah 6: Buat Fungsi signOffline
Tentukan fungsi signOffline yang bertanggung jawab menandatangani transaksi secara offline. Fungsi ini menyimulasikan penundaan offline sebelum menandatangani transaksi dengan keypair pengirim dan otoritas nonce.
async function createTx(nonce) {
const destination = Keypair.generate();
const advanceNonceIx = SystemProgram.nonceAdvance({
noncePubkey: nonceKeypair.publicKey,
authorizedPubkey: nonceAuthKeypair.publicKey
});
const transferIx = SystemProgram.transfer({
fromPubkey: senderKeypair.publicKey,
toPubkey: destination.publicKey,
lamports: TranferAmount,
});
const sampleTx = new Transaction();
sampleTx.add(advanceNonceIx, transferIx);
sampleTx.recentBlockhash = nonce; // Use the nonce fetched earlier
sampleTx.feePayer = senderKeypair.publicKey;
const serialisedTx = encodeAndWriteTransaction(sampleTx, "./unsignedTxn.json", false);
return serialisedTx;
}Langkah 7: Buat Fungsi executeTx
Fungsi executeTx bertanggung jawab mengirimkan transaksi yang telah ditandatangani ke jaringan Solana untuk dieksekusi. Ini adalah langkah terakhir dalam proses transaksi, yaitu saat transaksi disiarkan ke jaringan.
async function executeTx() {
const signedTx = await readAndDecodeTransaction("./signedTxn.json");
const sig = await connection.sendRawTransaction(signedTx.serialize());
console.log("Tx sent: ", sig);
}Langkah 8: Buat Fungsi fetchNonceInfo
Fungsi fetchNonceInfo mengambil informasi nonce dari akun nonce yang telah dibuat dan mencoba kembali hingga tiga kali jika diperlukan. Langkah ini membantu memastikan nonce yang digunakan dalam transaksi merupakan nonce terbaru dan valid.
async function fetchNonceInfo(retries = 3) {
while (retries > 0) {
const accountInfo = await connection.getAccountInfo(nonceKeypair.publicKey);
if (accountInfo) {
const nonceAccount = NonceAccount.fromAccountData(accountInfo.data);
return nonceAccount;
}
retries--;
if (retries > 0) {
console.log(`Retry fetching nonce in 3 seconds. ${retries} retries left.`);
await new Promise(res => setTimeout(res, 3000)); // wait for 3 seconds
}
}
throw new Error("No account info found");
}Langkah 9: Panggil Fungsi sendTransaction
Terakhir, panggil fungsi sendTransaction untuk memulai proses transaksi. Fungsi ini menyatukan semua langkah yang telah ditentukan sebelumnya untuk membuat, menandatangani, dan mengeksekusi transaksi menggunakan nonce tahan lama.
ts-node mainMenjalankan sendTransaction akan menghasilkan tanda tangan transaksi untuk transaksi yang berhasil. Tanda tangan ini merupakan informasi penting untuk melacak dan memverifikasi transaksi di jaringan Solana.
Tx written to ./unsignedTxn.jsonTx written to ./signedTxn.jsonTx sent: 64vBuSbN8SJZo74r8KoRFF6GJD7iszdckER2NkmFfYzHCN1H9Q3iC2Z3CP7NsoAgrP2jdyQrVeSzVx6vsbxNEE5UAnda kini telah berhasil menggunakan nonce tahan lama dalam sebuah transaksi!
Kode Lengkap
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
NonceAccount,
NONCE_ACCOUNT_LENGTH,
SystemProgram,
Transaction,
} from "@solana/web3.js";
import {
encodeAndWriteTransaction,
loadWallet,
readAndDecodeTransaction,
} from "./utils";
const TranferAmount = LAMPORTS_PER_SOL * 0.01;
const nonceAuthKeypair = loadWallet("./wallets/nonceAuth.json");
const nonceKeypair = Keypair.generate();
const senderKeypair = loadWallet("./wallets/wallet.json");
const connection = new Connection(
"https://devnet.helius-rpc.com/?api-key="
);
const waitTime = 120000;
async function sendTransaction() {
try {
// Create nonce and get its creation transaction signature
const nonceCreationTxSig = await nonce();
// Ensure nonce account creation is confirmed before moving forward
const confirmationStatus = await connection.confirmTransaction(
nonceCreationTxSig
);
if (!confirmationStatus.value.err) {
console.log("Nonce account creation confirmed.");
const nonce = await getNonce();
await createTx(nonce);
await signOffline(waitTime);
await executeTx();
} else {
console.error(
"Nonce account creation transaction failed:",
confirmationStatus.value.err
);
}
} catch (error) {
console.error(error);
}
}
async function nonce() {
// For creating the nonce account
const rent = await connection.getMinimumBalanceForRentExemption(
NONCE_ACCOUNT_LENGTH
);
const { blockhash, lastValidBlockHeight } =
await connection.getLatestBlockhash();
const createNonceTx = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: nonceAuthKeypair.publicKey,
newAccountPubkey: nonceKeypair.publicKey,
lamports: rent,
space: NONCE_ACCOUNT_LENGTH,
programId: SystemProgram.programId,
})
);
createNonceTx.feePayer = nonceAuthKeypair.publicKey;
createNonceTx.recentBlockhash = blockhash;
createNonceTx.lastValidBlockHeight = lastValidBlockHeight;
createNonceTx.sign(nonceAuthKeypair, nonceKeypair);
try {
// Create the nonce account
const signature = await connection.sendRawTransaction(
createNonceTx.serialize(),
{ skipPreflight: false, preflightCommitment: "single" }
);
const confirmationStatus = await connection.confirmTransaction(signature);
if (confirmationStatus.value.err) {
throw new Error(
"Nonce account creation transaction failed: " +
confirmationStatus.value.err
);
}
console.log("Nonce account created:", signature);
// Now, initialize the nonce
const initializeNonceTx = new Transaction().add(
SystemProgram.nonceInitialize({
noncePubkey: nonceKeypair.publicKey,
authorizedPubkey: nonceAuthKeypair.publicKey,
})
);
const {
blockhash: initBlockhash,
lastValidBlockHeight: initLastValidBlockHeight,
} = await connection.getLatestBlockhash();
initializeNonceTx.feePayer = nonceAuthKeypair.publicKey;
initializeNonceTx.recentBlockhash = initBlockhash;
initializeNonceTx.lastValidBlockHeight = initLastValidBlockHeight;
initializeNonceTx.sign(nonceAuthKeypair); // Only sign with nonceAuthKeypair
const initSignature = await connection.sendRawTransaction(
initializeNonceTx.serialize(),
{ skipPreflight: false, preflightCommitment: "single" }
);
const initConfirmationStatus = await connection.confirmTransaction(
initSignature
);
if (initConfirmationStatus.value.err) {
throw new Error(
"Nonce initialization transaction failed: " +
initConfirmationStatus.value.err
);
}
console.log("Nonce initialized:", initSignature);
return initSignature;
} catch (error) {
console.error("Failed in createNonce function: ", error);
throw error;
}
}
async function getNonce() {
const nonceAccount = await fetchNonceInfo();
return nonceAccount.nonce;
}
async function createTx(nonce: string) {
const destination = Keypair.generate();
const advanceNonceIx = SystemProgram.nonceAdvance({
noncePubkey: nonceKeypair.publicKey,
authorizedPubkey: nonceAuthKeypair.publicKey,
});
const transferIx = SystemProgram.transfer({
fromPubkey: senderKeypair.publicKey,
toPubkey: destination.publicKey,
lamports: TranferAmount,
});
const sampleTx = new Transaction();
sampleTx.add(advanceNonceIx, transferIx);
sampleTx.recentBlockhash = nonce; // Use the nonce fetched earlier
sampleTx.feePayer = senderKeypair.publicKey;
const serialisedTx = encodeAndWriteTransaction(
sampleTx,
"./unsigned.json",
false
);
return serialisedTx;
}
async function signOffline(waitTime = 120000): Promise {
await new Promise((resolve) => setTimeout(resolve, waitTime));
const unsignedTx = readAndDecodeTransaction("./unsigned.json");
unsignedTx.sign(senderKeypair, nonceAuthKeypair); // Sign with both keys
const serialisedTx = encodeAndWriteTransaction(unsignedTx, "./signed.json");
return serialisedTx;
}
async function executeTx() {
const signedTx = readAndDecodeTransaction("./signed.json");
const sig = await connection.sendRawTransaction(signedTx.serialize());
console.log(" Tx sent: ", sig);
}
async function fetchNonceInfo(retries = 3): Promise {
while (retries > 0) {
const accountInfo = await connection.getAccountInfo(nonceKeypair.publicKey);
if (accountInfo) {
const nonceAccount = NonceAccount.fromAccountData(accountInfo.data);
return nonceAccount;
}
retries--;
if (retries > 0) {
console.log(
`Retry fetching nonce in 3 seconds. ${retries} retries left.`
);
await new Promise((res) => setTimeout(res, 3000)); // wait for 3 seconds
}
}
throw new Error("No account info found");
}
sendTransaction();Transaksi Solana: Menggunakan RPC Helius
Helius dapat berfungsi sebagai perantara andal untuk berinteraksi dengan RPC Solana, sehingga menyederhanakan proses pengambilan informasi blockhash yang diperlukan untuk nonce tahan lama. Melalui Helius, Anda dapat mengelola siklus hidup transaksi Solana dengan lebih andal, terutama untuk skenario offline. Helius menyediakan akses yang lebih ringkas ke blockhash, sehingga membantu developer membuat aplikasi yang lebih tangguh terhadap transaksi yang kedaluwarsa.
Singkatnya, nonce tahan lama dalam transaksi Solana menawarkan cara yang aman dan andal untuk menangani transaksi offline serta memastikan keaslian transaksi. Dengan mengikuti langkah-langkah dalam panduan ini, developer dapat menerapkan nonce tahan lama pada aplikasi Solana mereka untuk meningkatkan keamanan dan fleksibilitas.
Artikel Terkait
Berlangganan Helius
Ikuti perkembangan terbaru dalam pengembangan Solana dan dapatkan pembaruan saat kami memublikasikan postingan


