本文通过可运行示例,系统地讲解如何利用 Solana Web3.js 与 Devnet 交互:获取链上数据、转账、分配账户、构建程序化地址、质押 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);
})();关键词: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 取消质押并提款
依次执行 Deactivate → Withdraw 即可在适当周期后回收 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.allocate 与 assign 有什么区别?
A:allocate 只扩展数据空间,不加 owner;assign 更换账户所属程序,空间不变。
Q4:如何确认质押开始生效?
A:connection.getStakeActivation(seedPubkey) 返回 state 字段:
inactive→ 未委托activating→ 转换期effective→ 已生效
Q5:为什么转账后账户不存在?
A:接收方若低于最小免租金额(minBalanceForRentExemption)且无后续存储占用,会被网结回收。
结语
通过本文分步骤的 Solana Web3.js 示例 代码,你已经掌握了:
- Devnet 连接、slot 读取
- KEYPAIR 创建与传递
- Solana 链上转账、Nonce、PDA、质押
- 跨链 Secp256k1 签名验证
把这些片段集成到你自己的 Node 或 前端 项目,即可快速上线下一个 Solana DApp。祝你开发顺利!