
Transações da Solana: nonces duráveis (2023)
Introdução
Ao enviar uma transação na Solana como desenvolvedor, você precisa buscar o blockhash mais recente por meio do RPC da Solana. Essa etapa essencial ajuda a evitar ataques de repetição, garantindo que, após uma transação ser assinada e enviada usando um blockhash específico, ninguém possa usar o mesmo hash para repetir ou reenviar essa transação.
Imagine que você precise enviar uma transação que exige uma assinatura de um armazenamento frio offline ou de uma carteira de hardware como a Ledger. No entanto, os blockhashes expiram rapidamente, o que pode invalidar sua transação. É aí que entram os nonces duráveis, que permitem realizar transações offline com segurança.
Ao final deste guia, você entenderá:
- O que é um nonce durável.
- A finalidade de um nonce durável.
- Como usar nonces duráveis em transações.
Transações: pré-requisitos
Antes de começar, verifique se você tem:
- Conhecimentos básicos de JavaScript.
- NodeJS instalado.
- Solana CLI instalada.
- Git instalado
Configuração do ambiente
- Clone nosso repositório de exemplo com utilitários existentes:
git clone- Acesse a pasta do projeto e instale o npm:
cd durable-noncenpm install- Acesse a pasta de carteiras dentro da pasta de nonce. Ela armazenará nossas chaves locais para testes:
cd wallets- Com a Solana CLI instalada, crie uma carteira para o par de chaves pagador:
solana-keygen new -o ./wallet.json- Agora, defina essa carteira como sua carteira na CLI para receber Solana via airdrop:
solana config set --keypair ./wallet.json- Agora você pode fazer um airdrop de Solana para esse endereço executando o seguinte comando:
solana aidrop 2- Também precisamos criar e financiar outra carteira para a autoridade do nonce:
solana-keygen new -o ./nonceAuth.json- Para a chave pública gerada, podemos usar um site de faucet para enviar 1 SOL por airdrop, disponível aqui.
Agora que nosso ambiente está configurado, podemos avançar para as próximas etapas.
O que é um nonce durável?
Uma conta de nonce durável na Solana pode ser vista como um cofre. Quando você cria essa conta, a Solana atribui a ela um código exclusivo e estável chamado "nonce durável". Diferentemente dos nonces comuns, que mudam a cada transação, esse permanece estável e serve como uma referência consistente.
Isso é especialmente útil para transações "offline". Ao criar uma transação, você referencia esse nonce da sua conta. A Solana o valida comparando-o com o valor armazenado e, se houver correspondência, a transação é aprovada. Portanto, uma conta de nonce durável funciona tanto como mecanismo de armazenamento quanto de validação, garantindo a autenticidade das transações e, ao mesmo tempo, atendendo ao ritmo acelerado e aos cenários offline da rede Solana.
Os nonces duráveis podem ser usados em vários casos, como:
- Transações agendadas: você pode configurar transações para ocorrerem em um momento específico no futuro. Os nonces duráveis garantem que essas transações agendadas sejam executadas com segurança.
- Carteiras multisig: no contexto de carteiras com múltiplas assinaturas, os nonces duráveis oferecem uma camada adicional de segurança e coordenação entre vários signatários.
- Programas que precisam de interação futura: alguns programas na Solana exigem interações com outros programas ou serviços em intervalos específicos. Os nonces duráveis ajudam a manter a integridade dessas interações.
- Interação com outras blockchains: quando a Solana interage com outras blockchains, os nonces duráveis ajudam a garantir a validade das transações entre diferentes blockchains.
Agora podemos começar a desenvolver nosso exemplo.
Transações da Solana: etapas de desenvolvimento
Etapa 1: configure as dependências e constantes
Nesta etapa, você importará os módulos e utilitários necessários e definirá as constantes e os pares de chaves do exemplo. Essas dependências e constantes serão usadas durante todo o processo da transação.
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;Etapa 2: crie a função sendTransaction
A função sendTransaction coordena o processo de envio de uma transação usando um nonce durável. Essa função lida com a criação e a confirmação do nonce e com a execução da transação.
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);
}
}Etapa 3: crie a função nonce
A função nonce é responsável por criar e inicializar a conta de nonce durável. Isso envolve calcular o aluguel necessário para a conta, buscar o blockhash mais recente e construir transações para criar e inicializar a conta de nonce.
- Antes de criar a conta de nonce, precisamos calcular o aluguel necessário para armazenar os dados da conta e buscar o blockhash mais recente.
async function nonce() {
const rent = await connection.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH);
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();- Agora, construiremos uma transação para criar a conta de nonce. Isso envolve usar a instrução
SystemProgram.createAccountpara alocar espaço para a conta de nonce.
const createNonceTx = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: nonceAuthKeypair.publicKey,
newAccountPubkey: nonceKeypair.publicKey,
lamports: rent,
space: NONCE_ACCOUNT_LENGTH,
programId: SystemProgram.programId,
})
);- Assinaremos a transação com os pares de chaves da autoridade e a enviaremos para a rede Solana. Essa transação cria a conta de nonce durável.
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" });- Após enviar a transação, confirmaremos seu status para garantir que a conta de nonce tenha sido criada com sucesso.
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);- Para usar plenamente a conta de nonce, precisamos inicializar seu valor. Criaremos uma nova transação para executar a instrução
SystemProgram.nonceInitialize.
// Initialize the nonce value within the account
const initializeNonceTx = new Transaction().add(
SystemProgram.nonceInitialize({
noncePubkey: nonceKeypair.publicKey,
authorizedPubkey: nonceAuthKeypair.publicKey,
})
);- Assim como na etapa anterior, assinaremos a transação e a enviaremos para a rede a fim de inicializar a conta de 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" });- Por fim, confirmaremos o status da transação de inicialização para garantir que a conta de nonce tenha sido inicializada corretamente.
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;
}
}A função completa deve ser semelhante a esta:
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;
}
}Etapa 4: crie a função getNonce
Defina a função getNonce, responsável por buscar o valor do nonce na conta de nonce criada.
async function getNonce() {
const nonceAccount = await fetchNonceInfo();
return nonceAccount.nonce;
}Etapa 5: crie a função createTx
Defina a função createTx, que cria uma transação de exemplo contendo a instrução de avanço do nonce e uma instrução de transferência. Ela usa o nonce buscado anteriormente para garantir a autenticidade da transação.
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;
}Etapa 6: crie a função signOffline
Defina a função signOffline, responsável por assinar a transação offline. Ela simula um atraso offline antes de assinar a transação com os pares de chaves do remetente e da autoridade do 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;
}Etapa 7: crie a função executeTx
A função executeTx é responsável por enviar a transação assinada para execução na rede Solana. Essa é a etapa final do processo, quando a transação é transmitida para a rede.
async function executeTx() {
const signedTx = await readAndDecodeTransaction("./signedTxn.json");
const sig = await connection.sendRawTransaction(signedTx.serialize());
console.log("Tx sent: ", sig);
}Etapa 8: crie a função fetchNonceInfo
A função fetchNonceInfo busca informações do nonce na conta de nonce criada, repetindo a tentativa até três vezes, se necessário. Isso ajuda a garantir que o nonce usado na transação esteja atualizado e seja válido.
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");
}Etapa 9: chame a função sendTransaction
Por fim, chame a função sendTransaction para iniciar o processo da transação. Essa função reúne todas as etapas definidas anteriormente para criar, assinar e executar uma transação usando um nonce durável.
ts-node mainA execução de sendTransaction gerará uma assinatura para uma transação bem-sucedida. Essa assinatura é uma informação essencial para rastrear e verificar a transação na rede Solana.
Tx written to ./unsignedTxn.jsonTx written to ./signedTxn.jsonTx sent: 64vBuSbN8SJZo74r8KoRFF6GJD7iszdckER2NkmFfYzHCN1H9Q3iC2Z3CP7NsoAgrP2jdyQrVeSzVx6vsbxNEE5UAgora você usou um nonce durável em uma transação bem-sucedida!
Código completo
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();Transações da Solana: como usar os RPCs da Helius
A Helius pode atuar como uma intermediária poderosa para interagir com os RPCs da Solana, simplificando o processo de busca das informações de blockhash necessárias para nonces duráveis. Com a Helius, você pode gerenciar o ciclo de vida das suas transações da Solana com mais confiabilidade, especialmente em cenários offline. Ela oferece acesso simplificado aos blockhashes, ajudando os desenvolvedores a tornar suas aplicações mais resistentes à expiração de transações.
Em resumo, os nonces duráveis nas transações da Solana oferecem uma maneira segura e confiável de lidar com transações offline e garantir sua autenticidade. Seguindo as etapas apresentadas neste guia, os desenvolvedores podem implementar nonces duráveis em suas aplicações da Solana, aumentando a segurança e a flexibilidade.
Artigos relacionados
Assine a Helius
Acompanhe as novidades mais recentes do desenvolvimento Solana e receba atualizações quando publicarmos


