本指南详细介绍了如何在Solana区块链上使用Solana-Web3.js、Solana CLI和Solana Rust SDK与Wrapped SOL (wSOL)进行交互,包括SOL的包装、转移和解包操作。文中提供了系统化的步骤和代码示例,适合希望深入了解Solana生态系统的开发者。
Wrapped SOL (wSOL) 是一种在 Solana 区块链上代表原生 SOL 形式的 SPL 代币。 本指南将教你如何使用 Solana-Web3.js JavaScript 库、Solana CLI 和 Solana Rust SDK 与 wrapped SOL 进行交互,包括包装、转移和解包操作。
让我们开始吧!
编写脚本以使用 Solana Web3.js、Solana CLI 和 Solana Rust SDK 执行以下操作:
依赖项 | 版本 |
---|---|
@solana/web3.js | ^1.91.1 |
@solana/spl-token | ^0.3.9 |
typescript | ^5.0.0 |
ts-node | ^10.9.1 |
solana cli | 1.18.8 |
spl-token cli | >3.3.0 |
rust | 1.75.0 |
让我们开始吧!
Wrapped SOL (wSOL) 是一种在 Solana 区块链上表示原生 SOL 的 SPL 代币(Solana 上的原生代币协议)。允许你在需要 SPL 代币的上下文中使用 SOL,例如去中心化交易所或与代币池交互的其他 DeFi 应用程序。通过包装 SOL,你可以将原生 SOL 无缝集成到其应用程序中的其他 SPL 代币中,从而在 Solana 生态系统内启用更广泛的功能和交互。这种灵活性使得 wrapped SOL 成为在 Solana 上创建更复杂且可互操作的去中心化应用程序的重要工具。
包装 SOL,你所需做的就是将 SOL 发送到一个 关联代币账户 在原生铸币(So11111111111111111111111111111111111111112
:可以通过 @solana/spl-token
包中的 NATIVE_MINT
常量访问)上,并调用来自 SPL 代币程序的 syncNative 指令(源)。syncNative
更新代币账户上的金额字段,以匹配可用的包装 SOL 的数量。只有通过关闭代币账户并选择所需的地址发送代币账户的 lamports,那些 SOL 才能被提取。
让我们创建一个示例脚本,演示 wrapped SOL 交易的完整生命周期。
首先,让我们设置项目:
mkdir wrapped-sol-demo && cd wrapped-sol-demo && echo > app.ts
使用你最喜欢的包管理器初始化一个新项目(在本指南中我们将使用 npm):
npm init -y
并安装必要的依赖项:
npm install @solana/web3.js@1 @solana/spl-token
打开 app.ts
并导入所需的依赖项:
import {
NATIVE_MINT,
createAssociatedTokenAccountInstruction,
getAssociatedTokenAddress,
createSyncNativeInstruction,
getAccount,
createTransferInstruction,
createCloseAccountInstruction,
} from "@solana/spl-token";
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
PublicKey,
} from "@solana/web3.js;
这些导入提供了与 Solana 区块链和 SPL 代币程序进行交互所需的函数和类型。
现在,继续定义一个 main
函数来组织我们的操作。将以下代码添加到 app.ts
导入下面的内容:
async function main() {
const connection = new Connection("http://127.0.0.1:8899", "confirmed");
const wallet1 = Keypair.generate();
const wallet2 = Keypair.generate();
await requestAirdrop(connection, wallet1);
const tokenAccount1 = await wrapSol(connection, wallet1);
const tokenAccount2 = await transferWrappedSol(connection, wallet1, wallet2, tokenAccount1);
await unwrapSol(connection, wallet1, tokenAccount1);
await printBalances(connection, wallet1, wallet2, tokenAccount2);
}
此函数概述了我们的步骤,并调用几个函数以执行每个步骤。我们将在下一部分定义每个函数。对于这个示例,我们将使用一个 本地测试验证器 来运行我们的代码;但是,你可以修改连接以便在实际应用中使用 devnet 或 mainnet。当你准备开始与 devnet 或 mainnet 交互时,你将需要一个 RPC 端点。你可以在 QuickNode.com 免费获取一个,然后你只需将连接更改为指向你的端点,如下所示:
const connection = new Connection("https://example.solana-mainnet.quiknode.pro/123/", "confirmed");
让我们为每个要执行的操作创建函数。
添加以下函数来请求 airdrop SOL:
async function requestAirdrop(connection: Connection, wallet: Keypair): Promise<void> {
const airdropSignature = await connection.requestAirdrop(
wallet.publicKey,
2 * LAMPORTS_PER_SOL
);
while (true) {
const { value: statuses } = await connection.getSignatureStatuses([airdropSignature]);
if (!statuses || statuses.length === 0) {
await new Promise(resolve => setTimeout(resolve, 1000));
continue;
}
if (statuses[0] && statuses[0].confirmationStatus === 'confirmed' || statuses[0].confirmationStatus === 'finalized') {
break;
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log("✅ - 步骤 1: Airdrop 完成");
}
此函数请求向指定钱包的 2 SOL airdrop。
接下来,让我们创建一个函数来包装 SOL:
async function wrapSol(connection: Connection, wallet: Keypair): Promise<PublicKey> {
const associatedTokenAccount = await getAssociatedTokenAddress(
NATIVE_MINT,
wallet.publicKey
);
const wrapTransaction = new Transaction().add(
createAssociatedTokenAccountInstruction(
wallet.publicKey,
associatedTokenAccount,
wallet.publicKey,
NATIVE_MINT
),
SystemProgram.transfer({
fromPubkey: wallet.publicKey,
toPubkey: associatedTokenAccount,
lamports: LAMPORTS_PER_SOL,
}),
createSyncNativeInstruction(associatedTokenAccount)
);
await sendAndConfirmTransaction(connection, wrapTransaction, [wallet]);
console.log("✅ - 步骤 2: SOL 已包装");
return associatedTokenAccount;
}
让我们看一下这个函数:
syncNative
指令以更新 ATA 上的金额字段,使其与可用的包装 SOL 数量匹配。现在,让我们创建一个函数来转移包装的 SOL:
async function transferWrappedSol(
connection: Connection,
fromWallet: Keypair,
toWallet: Keypair,
fromTokenAccount: PublicKey
): Promise<PublicKey> {
const toTokenAccount = await getAssociatedTokenAddress(
NATIVE_MINT,
toWallet.publicKey
);
const transferTransaction = new Transaction().add(
createAssociatedTokenAccountInstruction(
fromWallet.publicKey,
toTokenAccount,
toWallet.publicKey,
NATIVE_MINT
),
createTransferInstruction(
fromTokenAccount,
toTokenAccount,
fromWallet.publicKey,
LAMPORTS_PER_SOL / 2
)
);
await sendAndConfirmTransaction(connection, transferTransaction, [fromWallet]);
console.log("✅ - 步骤 3: 转移了包装的 SOL");
return toTokenAccount;
}
由于包装的 SOL 只是一个 SPL 代币,因此这个函数实际上是一个简单的 SPL 代币转移(查看我们的指南:如何在 Solana 上转移 SPL 代币)。此函数为接收方创建一个 ATA(以 NATIVE_MINT
为种子),然后将一半的包装 SOL 转移到它。
让我们添加一个函数来解包 SOL。为此,我们只需关闭关联代币账户,并选择所需的地址来发送代币账户的 lamports。
async function unwrapSol(
connection: Connection,
wallet: Keypair,
tokenAccount: PublicKey
): Promise<void> {
const unwrapTransaction = new Transaction().add(
createCloseAccountInstruction(
tokenAccount,
wallet.publicKey,
wallet.publicKey
)
);
await sendAndConfirmTransaction(connection, unwrapTransaction, [wallet]);
console.log("✅ - 步骤 4: SOL 解包完成");
}
此函数关闭包装的 SOL 关联代币账户,有效地解包 SOL。我们通过传入钱包的公钥来选择 lamports 的目标。
最后,让我们添加一个函数来打印余额:
async function printBalances(
connection: Connection,
wallet1: Keypair,
wallet2: Keypair,
tokenAccount2: PublicKey
): Promise<void> {
const [wallet1Balance, wallet2Balance, tokenAccount2Info] = await Promise.all([
connection.getBalance(wallet1.publicKey),
connection.getBalance(wallet2.publicKey),
connection.getTokenAccountBalance(tokenAccount2)
]);
console.log(` - 钱包 1 SOL 余额: ${wallet1Balance / LAMPORTS_PER_SOL}`);
console.log(` - 钱包 2 SOL 余额: ${wallet2Balance / LAMPORTS_PER_SOL}`);
console.log(` - 钱包 2 包装 SOL: ${Number(tokenAccount2Info.value.amount) / LAMPORTS_PER_SOL}`);
}
此函数获取并显示两个钱包的 SOL 和包装 SOL 余额。
最后,在 app.ts
文件底部添加对 main
的调用:
main().catch(console.error);
要在本地网络上运行我们的代码,请打开一个终端并启动本地 Solana 验证器:
solana-test-validator -r
验证器启动后,在另一个终端中运行你的脚本:
ts-node app.ts
你应该在终端中看到每个步骤的输出。我们应该预期钱包 1 有大约 1.5 SOL(2 SOL 被空投,减去 1 SOL 转移,加上 0.5 SOL 解包,减去租金和交易费用),钱包 2 有 0 SOL(因为我们没有向钱包 2 发送任何 SOL),而钱包 2 有 0.5 的包装 SOL:
ts-node app
✅ - 步骤 1: Airdrop 完成
✅ - 步骤 2: SOL 已包装
✅ - 步骤 3: 转移了包装的 SOL
✅ - 步骤 4: SOL 解包完成
- 钱包 1 SOL 余额: 1.49794572
- 钱包 2 SOL 余额: 0
- 钱包 2 包装 SOL: 0.5
干得好!
让我们尝试使用 Solana CLI 做同样的事情。确保通过运行以下命令安装了最新版本的 Solana CLI 和 SPL Token CLI:
solana -V
以及
spl-token -V
如果你在此部分的命令中遇到任何问题,可以随时运行 solana COMMAND_NAME -h
或 spl-token COMMAND_NAME -h
来获取有关如何使用 CLI 的帮助。
首先,确保你的 CLI 配置为使用 localnet:
solana config set -ul
现在,创建钱包 1 和钱包 2 的密钥对(你可以在同一 JS 项目目录中执行此操作,以保持简单):
solana-keygen new --no-bip39-passphrase -s -o wallet1.json
和
solana-keygen new --no-bip39-passphrase -s -o wallet2.json
你应该在项目目录中看到创建了两个文件:wallet1.json
和 wallet2.json
。这些文件包含我们在本示例中使用的两个钱包的私钥。
在终端中,向 wallet1.json
进行 2 SOL 的空投:
solana airdrop -k wallet1.json 2
由于我们使用的是相对路径,请确保你的终端位于与 wallet1.json
相同的目录中(如果你按照本指南中的说明操作,这应该是正确的)。
要通过 CLI 包装 SOL,你只需使用 spl-token wrap
命令。就像我们之前的示例一样,我们将从 wallet1.json
中包装 1 SOL 为 wSOL:
spl-token wrap 1 wallet1.json
此命令将为 wSOL 创建一个新的代币账户并铸造 1 个 wSOL 到钱包中。
要使用 CLI 转移 SPL 代币(如 wSOL),你必须首先为接收方创建一个关联代币账户。这是通过 spl-token create-account
命令完成的。让我们为 wallet2.json
创建一个关联代币账户:
spl-token create-account So11111111111111111111111111111111111111112 --owner wallet2.json --fee-payer wallet1.json
请注意,我们将 NATIVE_MINT
用作关联代币账户的代币铸币,并将 wallet1.json
用作费用支付者。终端应输出新关联代币账户的地址,例如,Creating account 2MhZKqyeLY1X2g7jFb7CQ4D4ySHMvGkTfUYZFWGe7fXw
。我们稍后需要这个地址。
现在,让我们从 wallet1.json
转移 0.5 wSOL 到我们创建的新的关联代币账户(将 2MhZKqyeLY1X2g7jFb7CQ4D4ySHMvGkTfUYZFWGe7fXw
替换为你创建的新关联代币账户的地址):
spl-token transfer So11111111111111111111111111111111111111112 0.5 2MhZKqyeLY1X2g7jFb7CQ4D4ySHMvGkTfUYZFWGe7fXw --owner wallet1.json --fee-payer wallet1.json
此命令将 0.5 wSOL 从 wallet1.json
转移到新创建的关联代币账户。--owner
标志指定将拥有源 wSOL 账户的钱包,而 --fee-payer
标志指定将支付交易费用的钱包。
要解包 wSOL,你可以使用 spl-token unwrap
命令。让我们通过关闭关联代币账户来解包我们的 wSOL。请在你的终端中运行:
spl-token unwrap --owner wallet1.json
此命令将从关联代币账户中解包 wSOL,并将包装的 SOL 发送到拥有关联代币账户的钱包。
最后,我们可以双重检查我们的余额,以确保一切正常。在你的终端中运行:
solana balance -k wallet1.json
我们预计显示的余额约为 ~1.5 SOL。
solana balance -k wallet2.json
我们预计显示余额为 0。
spl-token balance --address 2MhZKqyeLY1X2g7jFb7CQ4D4ySHMvGkTfUYZFWGe7fXw
将地址替换为你创建的新关联代币账户的地址。我们预计显示余额为 0.5。
干得好!你现在知道如何使用 Solana CLI 来包装、转移、解包和检查余额。
对于更喜欢使用 Rust 的开发者,以下是如何使用 Solana Rust SDK 执行相同的包装 SOL 操作。
首先,创建一个新的 Rust 项目:
cargo new wrapped-sol-rust
cd wrapped-sol-rust
在你的 Cargo.toml
文件中添加以下依赖项:
[dependencies]
solana-sdk = "2.0.3"
solana-client = "2.0.3"
spl-token = "6.0.0"
spl-associated-token-account = "4.0.0"
让我们尝试在 Rust 中重现 JavaScript 代码。首先导入必要的 crate,并创建一个主函数来概述空投、包装、转移和解包 SOL 的步骤。在 src/main.rs
中,将内容替换为以下代码:
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig,
pubkey::Pubkey,
signature::{Keypair, Signer},
system_instruction,
transaction::Transaction,
};
use spl_associated_token_account::get_associated_token_address;
use spl_token::{instruction as spl_instruction, native_mint};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rpc_client = RpcClient::new_with_commitment(
"http://127.0.0.1:8899".to_string(),
CommitmentConfig::processed(),
);
let wallet1 = Keypair::new();
let wallet2 = Keypair::new();
request_airdrop(&rpc_client, &wallet1)?;
let token_account1 = wrap_sol(&rpc_client, &wallet1)?;
let token_account2 = transfer_wrapped_sol(&rpc_client, &wallet1, &wallet2, &token_account1)?;
unwrap_sol(&rpc_client, &wallet1, &token_account1)?;
print_balances(&rpc_client, &wallet1, &wallet2, &token_account2)?;
Ok(())
}
此函数概述了我们的步骤,并调用几个函数以执行每个步骤。我们将在下一部分定义每个函数。对于这个示例,我们将使用一个 本地测试验证器 来运行我们的代码;但是,你可以修改连接以便在实际应用中使用 devnet 或 mainnet。当你准备开始与 devnet 或 mainnet 交互时,你将需要一个 RPC 端点。你可以在 QuickNode.com 免费获得一个,你只需将连接更改为指向你的端点,例如:
let rpc_client = RpcClient::new_with_commitment(
"https://example.solana-mainnet.quiknode.pro/123/".to_string(),
CommitmentConfig::processed(),
);
让我们为每个要执行的操作创建函数。
fn request_airdrop(
rpc_client: &RpcClient,
wallet: &Keypair,
) -> Result<(), Box<dyn std::error::Error>> {
let airdrop_signature = rpc_client.request_airdrop(&wallet.pubkey(), 2 * 1_000_000_000)?;
let recent_blockhash: solana_sdk::hash::Hash = rpc_client.get_latest_blockhash()?;
rpc_client.confirm_transaction_with_spinner(
&airdrop_signature,
&recent_blockhash,
CommitmentConfig::processed(),
)?;
println!("✅ - 步骤 1: Airdrop 完成");
Ok(())
}
我们仅使用 request_airdrop
函数请求 2 SOL 空投到指定钱包。我们将使用 confirm_transaction_with_spinner
函数通过终端中的旋转效果确认空投交易。
接下来,让我们创建一个函数来包装 SOL:
fn wrap_sol(
rpc_client: &RpcClient,
wallet: &Keypair,
) -> Result<Pubkey, Box<dyn std::error::Error>> {
let associated_token_account =
get_associated_token_address(&wallet.pubkey(), &native_mint::id());
let instructions = vec![
spl_associated_token_account::instruction::create_associated_token_account(
&wallet.pubkey(),
&wallet.pubkey(),
&native_mint::id(),
&spl_token::id(),
),
system_instruction::transfer(&wallet.pubkey(), &associated_token_account, 1_000_000_000),
spl_instruction::sync_native(&spl_token::id(), &associated_token_account)?,
];
let recent_blockhash: solana_sdk::hash::Hash = rpc_client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&wallet.pubkey()),
&[wallet],
recent_blockhash,
);
rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
println!("✅ - 步骤 2: SOL 已包装");
Ok(associated_token_account)
}
让我们看一下这个函数:
sync_native
指令以更新 ATA 上的金额字段,使其与可用的包装 SOL 数量匹配。现在,创建一个函数来转移包装的 SOL:
fn transfer_wrapped_sol(
rpc_client: &RpcClient,
from_wallet: &Keypair,
to_wallet: &Keypair,
from_token_account: &Pubkey,
) -> Result<Pubkey, Box<dyn std::error::Error>> {
let to_token_account = get_associated_token_address(&to_wallet.pubkey(), &native_mint::id());
let instructions = vec![
spl_associated_token_account::instruction::create_associated_token_account(
&from_wallet.pubkey(),
&to_wallet.pubkey(),
&native_mint::id(),
&spl_token::id(),
),
spl_instruction::transfer(
&spl_token::id(),
from_token_account,
&to_token_account,
&from_wallet.pubkey(),
&[&from_wallet.pubkey()],
500_000_000,
)?,
];
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&instructions,
Some(&from_wallet.pubkey()),
&[from_wallet],
recent_blockhash,
);
rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
println!("✅ - 步骤 3: 转移了包装的 SOL");
Ok(to_token_account)
}
由于包装的 SOL 只是一个 SPL 代币,因此这个函数实际上是一个简单的 SPL 代币转移。此函数为接收方创建一个 ATA(以 native_mint
为种子),然后将一半的包装 SOL 转移到它。
让我们添加一个函数来解包 SOL。为此,我们只需关闭关联代币账户,并选择所需的地址来发送代币账户的 lamports。
fn unwrap_sol(
rpc_client: &RpcClient,
wallet: &Keypair,
token_account: &Pubkey,
) -> Result<(), Box<dyn std::error::Error>> {
let instruction = spl_instruction::close_account(
&spl_token::id(),
token_account,
&wallet.pubkey(),
&wallet.pubkey(),
&[&wallet.pubkey()],
)?;
let recent_blockhash = rpc_client.get_latest_blockhash()?;
let transaction = Transaction::new_signed_with_payer(
&[instruction],
Some(&wallet.pubkey()),
&[wallet],
recent_blockhash,
);
rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
println!("✅ - 步骤 4: SOL 解包完成");
Ok(())
}
此函数关闭包装的 SOL 关联代币账户,有效地解包 SOL。我们选择 lamports 的目标通过传入钱包的公钥。
最后,让我们添加一个函数来打印余额:
fn print_balances(
rpc_client: &RpcClient,
wallet1: &Keypair,
wallet2: &Keypair,
token_account2: &Pubkey,
) -> Result<(), Box<dyn std::error::Error>> {
let wallet1_balance = rpc_client.get_balance(&wallet1.pubkey())?;
let wallet2_balance = rpc_client.get_balance(&wallet2.pubkey())?;
let token_account2_balance = rpc_client.get_token_account_balance(token_account2)?;
println!(
" - 钱包 1 SOL 余额: {}",
wallet1_balance as f64 / 1_000_000_000.0
);
println!(
" - 钱包 2 SOL 余额: {}",
wallet2_balance as f64 / 1_000_000_000.0
);
println!(
" - 钱包 2 包装 SOL: {}",
token_account2_balance.ui_amount.unwrap()
);
Ok(())
}
此函数获取并显示两个钱包的 SOL 和包装 SOL 余额。
要运行 Rust 脚本,请确保正在运行本地 Solana 验证器:
solana-test-validator -r
然后,在另一个终端中,导航到你的 Rust 项目目录并运行:
cargo run
你应该看到与 JavaScript 版本类似的输出,显示空投、包装、转移和解包 SOL 的步骤,最后是最终余额:
cargo run
Compiling wrapped-sol-rust v0.1.0 (/wrapped-sol-rust)
Finished dev [unoptimized + debuginfo] target(s) in 2.45s
Running `target/debug/wrapped-sol-rust`
✅ - 步骤 1: Airdrop 完成
✅ - 步骤 2: SOL 已包装
✅ - 步骤 3: 转移了包装的 SOL
✅ - 步骤 4: SOL 解包完成
- 钱包 1 SOL 余额: 1.49794572
- 钱包 2 SOL 余额: 0
- 钱包 2 包装 SOL: 0.5
这个 Rust 实现提供了与 JavaScript 和 CLI 版本相同的功能,演示了如何使用 Solana Rust SDK 与 wrapped SOL 进行交互。干得好!
本指南教你如何使用 Solana-Web3.js、Solana CLI 和 Solana Rust SDK 与 wrapped SOL 进行交互。你创建了函数来空投 SOL、包装 SOL、转移包装 SOL、解包 SOL 和检查余额。这些知识构成了开发更复杂的涉及 SOL 和 SPL 代币的 Solana 应用程序的基础。
请记住,虽然我们在本指南中使用了本地验证器,但你可以修改连接以便在实际应用中使用 devnet 或 mainnet。当你准备开始与 devnet 或 mainnet 交互时,你将需要一个 RPC 端点。你可以在 QuickNode.com 免费获取一个。在处理真实资金时,请务必谨慎!
告诉我们 如果你有任何反馈或新主题请求。我们很乐意听取你的意见。
- 原文链接: quicknode.com/guides/sol...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!