Solana Web3.js 实战示例:节点交互、链上交易与质押全流程

·

本文通过可运行示例,系统地讲解如何利用 Solana Web3.jsDevnet 交互:获取链上数据、转账、分配账户、构建程序化地址、质押 SOL 等。所有代码片段都能在 Node 环境直接运行,关键字已在文中自然分布,搜索与阅读体验并重。

1. 与 Devnet 建立连接并读取区块信息

const web3 = require("@solana/web3.js");

(async () => {
  const connection = new web3.Connection(
    web3.clusterApiUrl("devnet"),
    "confirmed"
  );

  const slot = await connection.getSlot();
  console.log("当前 slot:", slot);            // e.g. 93186439

  const blockTime = await connection.getBlockTime(slot);
  console.log("区块时间戳:", blockTime);        // e.g. 1630747045

  const block = await connection.getBlock(slot);
  console.log("区块详情:", block);

  const slotLeader = await connection.getSlotLeader();
  console.log("时隙出块人:", slotLeader);
})();

关键词:Solana 节点、Devnet、最新区块


2. 申请测试币并发送第一笔 Solana 转账

想在 Devnet 无成本体验链上交易?跟着这一节 3 分钟即可完成。
const web3 = require("@solana/web3.js");

(async () => {
  const payer = web3.Keypair.generate();
  const toAccount = web3.Keypair.generate();
  const connection = new web3.Connection(
    web3.clusterApiUrl("devnet"),
    "confirmed"
  );

  // 1. 申请 1 SOL
  const sig = await connection.requestAirdrop(
    payer.publicKey,
    web3.LAMPORTS_PER_SOL
  );
  await connection.confirmTransaction(sig);

  // 2. 构建转账指令
  const tx = new web3.Transaction().add(
    web3.SystemProgram.transfer({
      fromPubkey: payer.publicKey,
      toPubkey: toAccount.publicKey,
      lamports: 1000,
    })
  );

  // 3. 发送并确认交易
  const txHash = await web3.sendAndConfirmTransaction(connection, tx, [payer]);
  console.log("转账 Tx:", txHash);
})();

👉 复制这段代码,一键跑通你的首笔 Devnet 转账 →

关键词:Solana 转账、Airdrop、Lamports、Web3.js sendAndConfirmTransaction


3. 程序化 Keypair 与地址管理

3.1 随机生成密钥对

const { Keypair } = require("@solana/web3.js");
const kp = Keypair.generate();
console.log("地址:", kp.publicKey.toBase58());
console.log("私钥 Uint8Array:", kp.secretKey);

3.2 从已知种子恢复一致地址

通过 Keypair.fromSeed(seed),可复现实验数据:

const seed = new Uint8Array(32).fill(0x42); // 恰好 32 字节
const deterministic = Keypair.fromSeed(seed);
console.log("种子地址:", deterministic.publicKey.toBase58());

3.3 通过 Base58 公钥快速实例化

const pk = new web3.PublicKey("5xot9PVkphiX2adznghwrAuxGs2zeWisNSxMW6hU6Hkj");
console.log("重新反序列化:", pk.toBase58());

4. 找到可预测的程序派生地址 (PDA)

借助 PublicKey.findProgramAddress 可在不消耗用户密匙的情况下,为程序计算稳定地址:

const { PublicKey } = require("@solana/web3.js");

(async () => {
  const baseKey = new PublicKey(
    "5xot9PVkphiX2adznghwrAuxGs2zeWisNSxMW6hU6Hkj"
  );
  const [pda, bump] = await PublicKey.findProgramAddress(
    [Buffer.from("","utf8")],
    baseKey
  );
  console.log("PDA:", pda.toBase58(), "Bump:", bump);
})();

5. Nonce 账户:让离线签名变得可行

Nonce 提供了一个可预测的近期块哈希,避免交易过期。

(async () => {
  const conn = new web3.Connection(web3.clusterApiUrl("devnet"), "confirmed");
  const payer = web3.Keypair.generate();
  await conn.requestAirdrop(payer.publicKey, web3.LAMPORTS_PER_SOL);

  // 1. 创建 Nonce 账户
  const nonceKp = web3.Keypair.generate();
  const minLamps = await conn.getMinimumBalanceForRentExemption(
    web3.NONCE_ACCOUNT_LENGTH
  );
  const createTx = new web3.Transaction().add(
    web3.SystemProgram.createNonceAccount({
      fromPubkey: payer.publicKey,
      noncePubkey: nonceKp.publicKey,
      authorizedPubkey: payer.publicKey,
      lamports: minLamps,
    })
  );
  await web3.sendAndConfirmTransaction(conn, createTx, [payer, nonceKp]);

  // 2. 读取 Nonce
  const nonceData = await conn.getNonce(nonceKp.publicKey);
  console.log("实时 nonce 值:", nonceData.nonce);
})();

6. 质押 SOL:从创建 Stake 账户到提款全链路

6.1 创建与充值 Stake 账户

(async () => {
  const conn = new web3.Connection(web3.clusterApiUrl("devnet"), "confirmed");
  const payer = web3.Keypair.generate();
  await conn.requestAirdrop(payer.publicKey, web3.LAMPORTS_PER_SOL);

  const stakeKp = web3.Keypair.generate();
  const authorized = new web3.Authorized(payer.publicKey, payer.publicKey);
  const lockup = new web3.Lockup(0, 0, payer.publicKey);
  const minStake =
    (await conn.getMinimumBalanceForRentExemption(web3.StakeProgram.space)) + 50;

  const ixCreate = web3.StakeProgram.createAccount({
    fromPubkey: payer.publicKey,
    stakePubkey: stakeKp.publicKey,
    authorized,
    lockup,
    lamports: minStake,
  });
  await web3.sendAndConfirmTransaction(
    conn,
    new web3.Transaction().add(ixCreate),
    [payer, stakeKp]
  );
})();

6.2 选择验证者节点并委托权益

const voteAccounts = await conn.getVoteAccounts();
const voteKey = new web3.PublicKey(voteAccounts.current[0].votePubkey);

const delegateIx = web3.StakeProgram.delegate({
  stakePubkey: stakeKp.publicKey,
  authorizedPubkey: payer.publicKey,
  votePubkey: voteKey,
});
await web3.sendAndConfirmTransaction(
  conn,
  new web3.Transaction().add(delegateIx),
  [payer]
);

6.3 取消质押并提款

依次执行 DeactivateWithdraw 即可在适当周期后回收 SOL:

await web3.sendAndConfirmTransaction(
  conn,
  new web3.Transaction().add(
    web3.StakeProgram.deactivate({
      stakePubkey: stakeKp.publicKey,
      authorizedPubkey: payer.publicKey,
    })
  ),
  [payer]
);

const balance = await conn.getBalance(stakeKp.publicKey);
await web3.sendAndConfirmTransaction(
  conn,
  new web3.Transaction().add(
    web3.StakeProgram.withdraw({
      stakePubkey: stakeKp.publicKey,
      authorizedPubkey: payer.publicKey,
      toPubkey: payer.publicKey,
      lamports: balance,
    })
  ),
  [payer]
);

7. 使用以太坊私钥的 Secp256k1 签名验证

const secp256k1 = require("secp256k1");
const secp256k1PrivateKey = // 32 字节
  web3.Keypair.generate().secretKey.slice(0, 32);
const secp256k1PublicKey = secp256k1.publicKeyCreate(
  secp256k1PrivateKey,
  false
).slice(1);

const ethAddress = web3.Secp256k1Program.publicKeyToEthAddress(
  secp256k1PublicKey
);
console.log("ETH 地址: 0x" + ethAddress.toString("hex"));

👉 立即运行,体验跨链密钥签名 →


FAQ

Q1:Devnet 上的 1 SOL 主网能兑换吗?
A:Devnet 代币仅作测试用途,没有真实价值,也不能提现到主网。

Q2:为什么需要 Nonce 账户?
A:链上交易的 recentBlockhash 会在 150 个 slot 后失效。Nonce 账户提供永不失效的“长周期”最近块哈希,适配离线或批量签名场景。

Q3:SystemProgram.allocateassign 有什么区别?
A:allocate 只扩展数据空间,不加 owner;assign 更换账户所属程序,空间不变。

Q4:如何确认质押开始生效?
A:connection.getStakeActivation(seedPubkey) 返回 state 字段:

Q5:为什么转账后账户不存在?
A:接收方若低于最小免租金额(minBalanceForRentExemption)且无后续存储占用,会被网结回收。


结语

通过本文分步骤的 Solana Web3.js 示例 代码,你已经掌握了:

把这些片段集成到你自己的 Node前端 项目,即可快速上线下一个 Solana DApp。祝你开发顺利!