Web3 新玩法:Solana Nonce Account 让你交易无忧

Web3新玩法:SolanaNonceAccount让你交易无忧想在Web3世界玩出新花样?Solana的交易速度快到飞起,但你有没有遇到过这样的烦恼:交易刚签名,转眼就因区块哈希过期失效?别急,SolanaNonceAccount来了!它就像一个“交易时间胶囊”,让你随时签名

Web3 新玩法:Solana Nonce Account 让你交易无忧

想在 Web3 世界玩出新花样?Solana 的交易速度快到飞起,但你有没有遇到过这样的烦恼:交易刚签名,转眼就因区块哈希过期失效?别急,Solana Nonce Account 来了!它就像一个“交易时间胶囊”,让你随时签名、随时提交,再也不用担心时间限制。本文将带你揭开这个 Web3 新玩法的面纱,手把手教你如何用它让交易无忧,轻松迈入 Solana 开发前沿!

在 Solana 区块链中,Nonce Account 是一个隐藏的“交易神器”。Nonce Account(nonce 账户) 是一种特殊的账户类型,用于支持 Durable Nonce(持久化 nonce) 机制。它用一个持久化的 nonce 值替代短命的区块哈希,让你的交易签名可以“长寿”无忧,随时提交不失效。本文不仅深入讲解了 Nonce Account 的原理和特性,还通过代码实战演示了如何创建、签名和发送持久化交易。对比传统方式,你会发现它的妙处:离线签名、定时转账,统统不在话下。无论你是 Web3 新手还是 Solana 开发者,这篇指南都能让你快速上手,玩转交易新姿势!

Nonce Account

什么是 Nonce Account?

Nonce Account 是 Solana 系统程序(System Program)拥有的一种账户,用于存储一个 nonce 值(一个唯一的标识符,通常是一个 32 字节的哈希值)。这个 nonce 值可以替代常规交易中的 recent blockhash(最近区块哈希),从而实现 持久化交易(Durable Transactions),也就是允许交易在签名后延迟提交而不失效。

  • 普通交易的限制: 在 Solana 中,常规交易需要包含一个最近的区块哈希(recent blockhash),这个哈希的有效期大约是 150 个区块(约 1-2 分钟)。如果交易未能在有效期内提交到网络,就会被拒绝。
  • Nonce Account 的作用: 通过使用 Nonce Account 存储的 nonce 值,交易可以预先签名并存储,之后在任意时间提交,而无需担心区块哈希过期。

Nonce Account 的关键特性

  1. 存储内容:

    • Nonce 值: 一个唯一的哈希值,替代交易中的 recent blockhash。

    • Authority(授权者): 一个公钥,负责管理该 Nonce Account(例如推进 nonce 或提取资金)。

    • 状态: 表示账户是否已初始化。

  2. 租免(Rent-Exempt):

  • Nonce Account 需要维持一个最低余额(大约 0.0015 SOL),以保持租免状态,确保数据在链上持久存储。
  1. 控制权:
  • 创建 Nonce Account 的账户默认是其 Nonce Authority(nonce 授权者),但可以通过指令将授权转移给其他账户。
  • Nonce Authority 可以执行以下操作:
    • Advance Nonce(推进 nonce): 更新 nonce 值。
    • Withdraw(提取资金): 从账户提取 SOL。
    • Authorize(更改授权): 将控制权转移给其他公钥。
  1. 交易要求
  • 使用 Durable Nonce 的交易必须以 AdvanceNonceAccount 指令 开头,这会更新 Nonce Account 中的 nonce 值,确保每个交易的 nonce 是唯一的,防止重放攻击。

实操 Solana 创建 NonceAccount

并发送交易

第一步:实现 prepareAccount 代码

export function prepareAccount(params: any) {
    const {
        authorAddress,
        nonceAccountAddress,
        recentBlockhash,
        minBalanceForRentExemption,
        privs,
    } = params;

    const authorPrivateKey = privs?.find(
        (ele: { address: any }) => ele.address === authorAddress
    )?.key;
    if (!authorPrivateKey) throw new Error("authorPrivateKey 为空");

    const nonceAcctPrivateKey = privs?.find(
        (ele: { address: any }) => ele.address === nonceAccountAddress
    )?.key;
    if (!nonceAcctPrivateKey) throw new Error("nonceAcctPrivateKey 为空");

    const author = Keypair.fromSecretKey(
        new Uint8Array(Buffer.from(authorPrivateKey, "hex"))
    );
    const nonceAccount = Keypair.fromSecretKey(
        new Uint8Array(Buffer.from(nonceAcctPrivateKey, "hex"))
    );

    const tx = new Transaction();
    tx.add(
        SystemProgram.createAccount({
            fromPubkey: author.publicKey,
            newAccountPubkey: nonceAccount.publicKey,
            lamports: minBalanceForRentExemption,
            space: NONCE_ACCOUNT_LENGTH,
            programId: SystemProgram.programId,
        }),

        SystemProgram.nonceInitialize({
            noncePubkey: nonceAccount.publicKey,
            authorizedPubkey: author.publicKey,
        })
    );
    tx.recentBlockhash = recentBlockhash;

    tx.sign(author, nonceAccount);
    return tx.serialize().toString("base64");
}

第二步:获取最新区块哈希(getLatestBlockhash)

Request:

{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getLatestBlockhash",
    "params": [
        {
            "commitment": "processed"
        }
    ]
}

Response:

{
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "context": {
            "apiVersion": "2.1.11",
            "slot": 361716547
        },
        "value": {
            "blockhash": "7kcpQ8Hfpi35gTKtmkVW5XBdYH5Zrw4e9Pg27YyCxkcP",
            "lastValidBlockHeight": 349703424
        }
    }
}

第三步:编写测试代码并执行测试

 test('prepareAccount test', async () => {
        const params = {
            authorAddress: from,
            nonceAccountAddress: nonce_account_addr,
            recentBlockhash: "7kcpQ8Hfpi35gTKtmkVW5XBdYH5Zrw4e9Pg27YyCxkcP",
            minBalanceForRentExemption: 1647680,
            privs: [
                {
                    address: from,
                    key: fromPrivateKey
                },
                {
                    address: nonce_account_addr,
                    key: noncePrivateKey
                }
            ]
        }
        let tx_msg = await prepareAccount(params)
        console.log("prepareAccount-tx_msg===", tx_msg)
    });

测试输出

 console.log
    prepareAccount-tx_msg=== AovVQR0MBuqhv9g/Z13s7ZxLGWUJJT3Oo93lbpmuv9f/liETgjOJVoJyRGRnwp3lo+rb45SCPzcoubMn4we/mwNNRDoya6WDYE9FkcZOGZAgLHSJ8+e0+H5D8pDxHAEnHgDCqWPguVrIyTulfaDxplezPdql6xQHFT9ggyzc9EgPAgADBVDS9aJBo99CnaEp9zsUQTrBn951FTSzXyb0W7DUWub2xEp9ziCqAhF18Ho7eXUKwhKbLjja+jESCZydfVfC/UsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAan1RcZLFaO4IqEX3PSl4jPA1wxRbIas0TYBi6pQAAABqfVFxksXFEhjMlMPUrxf1ja7gibof1E49vZigAAAABkUphSZcxpdk94aUysDsPylqN3s1PWrP/RYWsLIxSGGAICAgABNAAAAABAJBkAAAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAwEDBCQGAAAAUNL1okGj30KdoSn3OxRBOsGf3nUVNLNfJvRbsNRa5vY=

第四步:发送交易(sendTransaction)

Request:


{
    "method": "sendTransaction",
    "params": [
        "AovVQR0MBuqhv9g/Z13s7ZxLGWUJJT3Oo93lbpmuv9f/liETgjOJVoJyRGRnwp3lo+rb45SCPzcoubMn4we/mwNNRDoya6WDYE9FkcZOGZAgLHSJ8+e0+H5D8pDxHAEnHgDCqWPguVrIyTulfaDxplezPdql6xQHFT9ggyzc9EgPAgADBVDS9aJBo99CnaEp9zsUQTrBn951FTSzXyb0W7DUWub2xEp9ziCqAhF18Ho7eXUKwhKbLjja+jESCZydfVfC/UsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAan1RcZLFaO4IqEX3PSl4jPA1wxRbIas0TYBi6pQAAABqfVFxksXFEhjMlMPUrxf1ja7gibof1E49v...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
寻月隐君
寻月隐君
0x89EE...a439
不要放弃,如果你喜欢这件事,就不要放弃。如果你不喜欢,那这也不好,因为一个人不应该做自己不喜欢的事。