- Thao tác rút tiền chuyển token từ số dư riêng tư sang số dư Solana công khai.
- Khoản rút được gửi trong một giao dịch Solana duy nhất đến địa chỉ ví Solana.
Rút tiền: Thông tin nào là riêng tư
| Trường | Khả năng hiển thị | Lý do |
|---|---|---|
| Ví riêng tư nguồn | Công khai | Trong một Ring bảo mật, ví riêng tư nguồn hiển thị trên chuỗi. |
| Tài sản | Công khai | Tài sản hiển thị trên chuỗi |
| Số tiền | Công khai | Số tiền đã rút hiển thị trên chuỗi |
| Ví công khai đích | Công khai | Địa chỉ ví đích hiển thị trên chuỗi |
| Số dư công khai sau giao dịch | Công khai | Số dư công khai sau giao dịch hiển thị trên chuỗi |
| Số dư riêng tư còn lại | Riêng tư | Số dư còn lại được mã hóa trên chuỗi |
Ring không cần cấp quyền có tính bảo mật, trong đó số lượng và tài sản được mã hóa.
Có thể cấu hình Ring tùy chỉnh ở chế độ bảo mật hoặc ẩn danh (mã hóa người gửi, người nhận, tài sản và số lượng).
Cách hoạt động của thao tác rút tiền
Thao tác rút tiền hoạt động tương tự một giao dịch chuyển Solana công khai:- Số dư SOL hoặc SPL của người dùng được mã hóa onchain.
-
Người dùng giải mã trạng thái riêng tư, ví tạo giao dịch rút tiền và chủ sở hữu ký.
- Truy xuất trạng thái mã hóa bằng các phương thức RPC chuyên dụng. Chỉ người dùng mới có thể giải mã số dư cục bộ.
- Ví thiết lập số lượng và người nhận, sau đó yêu cầu bằng chứng ZK. Theo mặc định, nhà cung cấp RPC tạo và trả về bằng chứng ZK.
- Môi trường thực thi Solana xác minh các chữ ký và gọi Solana Privacy Program. Chương trình này xác minh bằng chứng ZK mà không tiết lộ trạng thái mã hóa.
- Ứng dụng theo dõi trạng thái thông qua hàm băm giao dịch Solana.
Compare to Solana Transfer
Compare to Solana Transfer
- Số dư SOL hoặc SPL của người dùng được công khai onchain.
- Ví đọc trạng thái công khai, tạo giao dịch chuyển và chủ sở hữu ký.
- Môi trường thực thi Solana xác minh các chữ ký và gọi System Program hoặc Token Program để cập nhật số dư công khai.
- Ứng dụng theo dõi trạng thái thông qua hàm băm giao dịch Solana.
Đây là luồng giao dịch cấp cao cho Ring bảo mật không cần cấp quyền.
So sánh với các Ring tùy chỉnh trong phần khái niệm.
Bắt đầu
- TypeScript Client
- Rust Client
1
Điều kiện tiên quyết
Các ví dụ TypeScript yêu cầu Node.js 24 trở lên, pnpm 11.18.0 và Solana CLI.
pnpm add 'git+ssh://git@github.com/helius-labs/zolana.git#v0.3.0-alpha&path:/sdk-libs/ts' @solana/kit@^8.3.0
Connect to Endpoints
Connect to Endpoints
- Devnet
- Localnet
pnpm install
cp .env.example .env
.env
API_KEY=YOUR_API_KEY
ZOLANA_PAYER_KEYPAIR=~/.config/solana/id.json
import { createZolanaClient } from "@heliuslabs/zolana";
const client = await createZolanaClient({
solanaRpcUrl: "https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY",
indexerUrl: "https://d2xah7tnhdhcom.cloudfront.net",
proverUrl: "https://d21ni15goiip6l.cloudfront.net",
});
Trên localnet, SDK khởi động trình xác thực kiểm thử cục bộ (
:8899), trình lập chỉ mục Photon (:8784) và trình chứng minh (:3001), đồng thời
máy khách tự động kết nối với chúng mà không cần cấu hình điểm cuối.cargo install --git https://github.com/helius-labs/zolana --tag v0.3.0-alpha zolana-cli
zolana dev start
import { createZolanaClient } from "@heliuslabs/zolana";
const client = await createZolanaClient({});
2
Rút tiền về số dư công khai
Solana Kit send helper
Solana Kit send helper
import {
appendTransactionMessageInstructions,
assertIsTransactionWithBlockhashLifetime,
createTransactionMessage,
getSignatureFromTransaction,
pipe,
sendTransactionWithoutConfirmingFactory,
setTransactionMessageConfig,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
type Instruction,
type Signature,
type TransactionSigner,
} from "@solana/kit";
import { createZolanaClient } from "@heliuslabs/zolana";
type Client = Awaited<ReturnType<typeof createZolanaClient>>;
export interface ConfirmedTransaction {
readonly signature: Signature;
readonly slot: bigint;
}
export function sendAndConfirmFactory(
client: Client,
feePayer: TransactionSigner,
): (instructions: readonly Instruction[]) => Promise<ConfirmedTransaction> {
const sendTransaction = sendTransactionWithoutConfirmingFactory({
rpc: client.solanaRpc,
});
return async function sendAndConfirm(
instructions: readonly Instruction[],
): Promise<ConfirmedTransaction> {
const { value: lifetime } = await client.solanaRpc
.getLatestBlockhash()
.send();
const signed = await signTransactionMessageWithSigners(
pipe(
createTransactionMessage({ version: 1 }),
(message) => setTransactionMessageFeePayerSigner(feePayer, message),
(message) =>
setTransactionMessageLifetimeUsingBlockhash(lifetime, message),
(message) =>
setTransactionMessageConfig(
{
computeUnitLimit: 450_000,
loadedAccountsDataSizeLimit: 64 * 1024 * 1024,
},
message,
),
(message) =>
appendTransactionMessageInstructions(instructions, message),
),
);
assertIsTransactionWithBlockhashLifetime(signed);
await sendTransaction(signed, { commitment: "confirmed" });
const signature = getSignatureFromTransaction(signed);
const slot = await client.confirmTransaction(signature);
return { signature, slot };
};
}
- SDK trả về các chỉ thị. Ứng dụng ký và gửi chúng.
sendAndConfirmFactorytạo một giao dịch Kit, gửi giao dịch đó rồi trả về chữ ký cùng slot đã được ghi nhận.
- SOL
- SPL
import { LocalKeys } from "@heliuslabs/zolana/client";
import { SOL_MINT } from "@heliuslabs/zolana";
import {
transactInstruction,
TransactWithdrawal,
} from "@heliuslabs/zolana/interface";
import {
ConfidentialTransfer,
ProofInputUtxo,
WithdrawalTarget,
} from "@heliuslabs/zolana/transaction";
const withdrawalUtxo =
transferBalance.utxos[0]!;
const withdrawalInput =
ProofInputUtxo.fromKeypair(
withdrawalUtxo,
sender,
);
const withdrawal = new ConfidentialTransfer(
senderAddress,
[withdrawalInput],
senderSigner.address,
);
withdrawal.withdraw(
SOL_MINT,
WITHDRAW_AMOUNT,
WithdrawalTarget.sol({
recipient: senderSigner.address,
}),
);
const withdrawalProofInputs = withdrawal.sign(
sender,
assets,
);
const senderKeys = LocalKeys.fromKeypair(sender, client.proofService);
const withdrawalData =
await client.proveTransact(
withdrawalProofInputs,
senderKeys,
);
const withdrawalInstruction =
await transactInstruction({
payer: senderSigner,
inputTree: client.tree,
outputTree: client.tree,
withdrawal: TransactWithdrawal.sol({
recipient: senderSigner.address,
}),
data: withdrawalData,
});
const withdrawalTx = await sendAndConfirm([
withdrawalInstruction,
]);
import { LocalKeys } from "@heliuslabs/zolana/client";
import {
transactInstruction,
TransactWithdrawal,
} from "@heliuslabs/zolana/interface";
import {
ConfidentialTransfer,
ProofInputUtxo,
WithdrawalTarget,
} from "@heliuslabs/zolana/transaction";
const withdrawalUtxo =
transferBalance.utxos[0]!;
const withdrawalInput =
ProofInputUtxo.fromKeypair(
withdrawalUtxo,
sender,
);
const withdrawal = new ConfidentialTransfer(
senderAddress,
[withdrawalInput],
senderSigner.address,
);
withdrawal.withdraw(
spl.mint,
WITHDRAW_AMOUNT,
WithdrawalTarget.spl({
recipientTokenAccount: spl.userTokenAccount,
splTokenInterface: spl.splTokenInterface,
splInterfaceBump: spl.splInterfaceBump,
}),
);
const withdrawalProofInputs = withdrawal.sign(
sender,
assets,
);
const senderKeys = LocalKeys.fromKeypair(sender, client.proofService);
const withdrawalData =
await client.proveTransact(
withdrawalProofInputs,
senderKeys,
);
const withdrawalInstruction =
await transactInstruction({
payer: senderSigner,
inputTree: client.tree,
outputTree: client.tree,
withdrawal: TransactWithdrawal.spl({
mint: spl.mint,
splTokenInterface: spl.splTokenInterface,
recipientTokenAccount: spl.userTokenAccount,
tokenProgram: spl.tokenProgram,
}),
data: withdrawalData,
});
const withdrawalTx = await sendAndConfirm([
withdrawalInstruction,
]);
1. Select private token accounts to spend
1. Select private token accounts to spend
import { SOL_MINT } from "@heliuslabs/zolana";
const withdrawalUtxo =
transferBalance.utxos[0]!;
- Ví dụ sử dụng Tài khoản token Solana riêng tư còn lại sau giao dịch chuyển trước đó. Một lần rút tiền có thể sử dụng nhiều UTXO.
withdrawalUtxochọn một Tài khoản token Solana riêng tư từ số dư đó.
2. Prepare proof inputs
2. Prepare proof inputs
import { ProofInputUtxo } from "@heliuslabs/zolana/transaction";
const withdrawalInput =
ProofInputUtxo.fromKeypair(
withdrawalUtxo,
sender,
);
ProofInputUtxo.fromKeypairchuẩn bị UTXO đã chọn làm đầu vào bằng chứng bằng cặp khóa ví riêng tư của người gửi.- Cặp khóa tạo nullifier để đánh dấu UTXO đầu vào là đã được sử dụng, trong khi tài sản và số lượng đầu vào vẫn được mã hóa.
3. Build and sign the withdrawal
3. Build and sign the withdrawal
import { SOL_MINT } from "@heliuslabs/zolana";
import {
ConfidentialTransfer,
WithdrawalTarget,
} from "@heliuslabs/zolana/transaction";
const withdrawal = new ConfidentialTransfer(
senderAddress,
[withdrawalInput],
senderSigner.address,
);
withdrawal.withdraw(
SOL_MINT,
WITHDRAW_AMOUNT,
WithdrawalTarget.sol({
recipient: senderSigner.address,
}),
);
const withdrawalProofInputs = withdrawal.sign(
sender,
assets,
);
senderAddresslà của người gửi. Khoản rút được lấy từ ví này.[withdrawalInput]liệt kê các UTXO đã chọn của người gửi. Một lần rút tiền có thể sử dụng nhiều UTXO.senderSigner.addresslà địa chỉ Solana của người trả phí. Nhà tài trợ phí gas có thể thanh toán khoản phí này.WithdrawalTarget.sollà người nhận Solana công khai. Người nhận có thể là chủ sở hữu hoặc bên thứ ba.SOL_MINTchọn SOL. Khi rút SPL hoặc Token 2022, hãy truyền địa chỉ mint của token.WITHDRAW_AMOUNTđược biểu thị bằng đơn vị cơ sở của tài sản. SOL sử dụng lamport. Tài sản SPL và Token 2022 sử dụng đơn vị cơ sở của token.withdrawal.signcấp quyền chuyển đổi trạng thái, mã hóa khoản tiền thừa riêng tư còn lại và tạo đầu vào cho trình chứng minh không kiến thức.assetslà sổ đăng ký tài sản dùng để phân giải các tài sản riêng tư được hỗ trợ.
4. Fetch the zero-knowledge proof
4. Fetch the zero-knowledge proof
import { LocalKeys } from "@heliuslabs/zolana/client";
const senderKeys = LocalKeys.fromKeypair(sender, client.proofService);
const withdrawalData =
await client.proveTransact(
withdrawalProofInputs,
senderKeys,
);
senderKeyssử dụngLocalKeys.fromKeypair(sender, client.proofService)để cấp quyền tạo bằng chứng bằng các khóa của người gửi.client.proveTransacttạo bằng chứng không kiến thức từ giao dịch rút tiền đã ký và trả về dữ liệu chỉ thị đã tuần tự hóa.- Bằng chứng cho thấy người gửi sở hữu và có thể sử dụng các đầu vào. Tài sản và số lượng được rút là công khai. Số lượng đầu vào và tiền thừa vẫn được mã hóa.
5. Build the withdrawal instruction
5. Build the withdrawal instruction
import {
transactInstruction,
TransactWithdrawal,
} from "@heliuslabs/zolana/interface";
const withdrawalInstruction =
await transactInstruction({
payer: senderSigner,
inputTree: client.tree,
outputTree: client.tree,
withdrawal: TransactWithdrawal.sol({
recipient: senderSigner.address,
}),
data: withdrawalData,
});
payerký và thanh toán phí cho giao dịch Solana. Nhà tài trợ phí gas có thể thanh toán khoản phí này.inputTreevàoutputTreelàclient.tree, cây Merkle trạng thái chứa các UTXO đã sử dụng và nhận khoản tiền thừa riêng tư của người gửi.withdrawallàTransactWithdrawal.sol, tài khoản Solana công khai của người nhận.await transactInstructiontạo cục bộ các địa chỉ tài khoản nullifier và trả về chỉ thị. Các tài khoản nullifier đánh dấu UTXO đầu vào là đã được sử dụng để ngăn việc chi tiêu hai lần từ một số dư riêng tư.datachứa bằng chứng không kiến thức và khoản tiền thừa mã hóa được tạo ở bước trước.- Thao tác rút tiền chuyển tài sản từ số dư riêng tư sang tài khoản Solana công khai. Thao tác này truyền tài khoản công khai của người nhận.
6. Send like any Solana transaction
6. Send like any Solana transaction
import { sendAndConfirmFactory } from "../src/lib.js";
const withdrawalTx = await sendAndConfirm([
withdrawalInstruction,
]);
sendAndConfirmký và gửiwithdrawalInstructiondưới dạng một giao dịch Solana.- Quá trình xác nhận trả về slot đã được ghi nhận, dùng để kiểm soát thời điểm truy xuất từ trình lập chỉ mục.
Ví dụ mã đầy đủ
Sao chép kho lưu trữ và chạy ví dụ:git clone https://github.com/helius-labs/zolana-examples.git
cd zolana-examples
git checkout v0.3.0-alpha
cd typescript-client
pnpm install
pnpm example examples/deposit_transfer_withdraw.ts
Các ví dụ sử dụng một Ring bảo mật trên local/devnet tại đây.
deposit_transfer_withdraw.ts
import {
SOL_MINT,
ShieldedKeypair,
createZolanaClient,
} from "@heliuslabs/zolana";
import {
LocalKeys,
atSlot,
} from "@heliuslabs/zolana/client";
import {
depositInstruction,
transactInstruction,
DepositAsset,
TransactWithdrawal,
} from "@heliuslabs/zolana/interface";
import {
AssetRegistry,
ConfidentialTransfer,
ProofInputUtxo,
decryptToBalances,
WithdrawalTarget,
} from "@heliuslabs/zolana/transaction";
import {
cliKeypair,
sendAndConfirmFactory,
setup,
} from "../src/lib.js";
const DEPOSIT_AMOUNT = 10_000_000n;
const TRANSFER_AMOUNT = 3_000_000n;
const WITHDRAW_AMOUNT = 3_000_000n;
async function main(): Promise<void> {
const { clientConfig } = await setup();
// Connect to Helius devnet RPC plus the Photon indexer and prover.
const client =
await createZolanaClient(clientConfig);
// Initialize the sender's private wallet and local authority
// to decrypt transactions and sync balances.
// The Solana signer and private wallet are derived from the same Ed25519 seed.
const sender = ShieldedKeypair.fromKeypair(
await cliKeypair(),
);
const recipient = ShieldedKeypair.generate();
const senderSigner = sender.toSolanaSigner();
const senderAddress = sender.shieldedAddress();
const senderKeys = LocalKeys.fromKeypair(
sender,
client.proofService,
);
// The SDK hands back instructions; the app owns signing and sending.
const sendAndConfirm = sendAndConfirmFactory(
client,
senderSigner,
);
// Mints that are registered with Solana Rings for privacy.
const assets = new AssetRegistry();
// Deposit SOL into the sender's private balance.
// A deposit from a public balance reveals
// sender, recipient, asset and amount.
// Alternatively, you can onramp fiat directly to a private balance.
// 1. Move public SOL into the sender's private balance.
// The view tag is the sender's Solana public key in confidential rings.
// Used by the indexer to fetch the sender's UTXOs.
const senderViewTag =
senderAddress.confidentialViewTag();
const depositIx = await depositInstruction({
tree: client.tree,
depositor: senderSigner,
deposits: [
{
asset: DepositAsset.sol(),
viewTag: senderViewTag,
recipientOwnerHash:
senderAddress.ownerHash(),
amount: DEPOSIT_AMOUNT,
},
],
});
// 2. Send and confirm like any Solana transaction; confirmation yields the landed slot.
const depositTx = await sendAndConfirm([
depositIx,
]);
// 3. Fetch transaction outputs from the indexer, gated on the deposit's slot.
// The indexer returns encrypted outputs by transaction signature.
const depositResponse =
await client.getShieldedTransactionsBySignature(
depositTx.signature,
atSlot(depositTx.slot),
);
// 4. The sender decrypts the transaction outputs locally to read the private balance.
const balancesAfterDeposit =
await decryptToBalances({
keypair: sender,
registry: assets,
transactions:
depositResponse.transactions.map(
({ transaction }) => transaction,
),
});
const depositBalance =
balancesAfterDeposit.balance(SOL_MINT);
if (depositBalance.amount !== DEPOSIT_AMOUNT) {
throw new Error(
`expected deposit amount ${DEPOSIT_AMOUNT}, got ${depositBalance.amount}`,
);
}
if (depositBalance.utxos.length !== 1) {
throw new Error(
`expected 1 deposit utxo, got ${depositBalance.utxos.length}`,
);
}
// Confidential SOL transfer to the recipient's private balance.
// A confidential transfer reveals only sender and recipient,
// not the asset or amount.
// 1. Select private token accounts (UTXOs) that make up the private balance for the transfer.
const transferUtxo = depositBalance.utxos[0]!;
// 2. Prepare the selected UTXOs as inputs for the zero-knowledge proof.
const transferInput =
ProofInputUtxo.fromKeypair(
transferUtxo,
sender,
);
// 3. Build and sign the confidential transfer.
// Signing encrypts the asset and amount and produces the proof inputs for the ZK prover.
const transfer = new ConfidentialTransfer(
senderAddress,
[transferInput],
senderSigner.address,
);
transfer.send(
recipient.shieldedAddress(),
SOL_MINT,
TRANSFER_AMOUNT,
);
const transferProofInputs = transfer.sign(
sender,
assets,
);
// 4. Fetch the ZK proof to prove the sender can spend the balance without revealing asset and amount.
const transferData = await client.proveTransact(
transferProofInputs,
senderKeys,
);
// 5. Build the instruction with the state Merkle tree and Solana accounts required for the transfer.
// Private transfers move balances only between private token accounts, not public token accounts.
const transferInstruction =
await transactInstruction({
payer: senderSigner,
inputTree: client.tree,
outputTree: client.tree,
data: transferData,
});
// 6. Send and confirm like any Solana transaction; confirmation yields the landed slot.
const transferTx = await sendAndConfirm([
transferInstruction,
]);
// 7. Fetch the sender's UTXOs from this transaction, gated on the transfer's slot,
// and read the remaining private balance.
const transferResponse =
await client.getShieldedTransactionsBySignature(
transferTx.signature,
atSlot(transferTx.slot),
);
const balancesAfterTransfer =
await decryptToBalances({
keypair: sender,
registry: assets,
transactions:
transferResponse.transactions.map(
({ transaction }) => transaction,
),
});
const transferBalance =
balancesAfterTransfer.balance(SOL_MINT);
if (
transferBalance.amount !==
DEPOSIT_AMOUNT - TRANSFER_AMOUNT
) {
throw new Error(
`expected remaining amount from this run ${DEPOSIT_AMOUNT - TRANSFER_AMOUNT}, got ${transferBalance.amount}`,
);
}
if (transferBalance.utxos.length !== 1) {
throw new Error(
`expected 1 transfer utxo, got ${transferBalance.utxos.length}`,
);
}
// Withdraw SOL from the sender's private balance to their public balance.
// A withdrawal reveals the sender, recipient, asset, and amount.
// 1. Select private token accounts (UTXOs) that make up the private balance for the withdrawal.
const withdrawalUtxo =
transferBalance.utxos[0]!;
// 2. Prepare the selected UTXOs as inputs for the zero-knowledge proof.
const withdrawalInput =
ProofInputUtxo.fromKeypair(
withdrawalUtxo,
sender,
);
// 3. Build and sign the private-to-public withdrawal.
// Signing encrypts the asset and amount of the remaining private balance
// and produces the proof inputs for the ZK prover.
const withdrawal = new ConfidentialTransfer(
senderAddress,
[withdrawalInput],
senderSigner.address,
);
withdrawal.withdraw(
SOL_MINT,
WITHDRAW_AMOUNT,
WithdrawalTarget.sol({
recipient: senderSigner.address,
}),
);
const withdrawalProofInputs = withdrawal.sign(
sender,
assets,
);
// 4. Fetch the ZK proof to prove the sender can spend the balance.
const withdrawalData =
await client.proveTransact(
withdrawalProofInputs,
senderKeys,
);
// 5. Build the instruction with the state Merkle tree and Solana accounts required for the withdrawal.
const withdrawalInstruction =
await transactInstruction({
payer: senderSigner,
inputTree: client.tree,
outputTree: client.tree,
withdrawal: TransactWithdrawal.sol({
recipient: senderSigner.address,
}),
data: withdrawalData,
});
// 6. Send and confirm like any Solana transaction; confirmation yields the landed slot.
const withdrawalTx = await sendAndConfirm([
withdrawalInstruction,
]);
// 7. Fetch the sender's UTXOs from this transaction, gated on the withdrawal's slot,
// and read the remaining private balance.
const withdrawalResponse =
await client.getShieldedTransactionsBySignature(
withdrawalTx.signature,
atSlot(withdrawalTx.slot),
);
const balancesAfterWithdrawal =
await decryptToBalances({
keypair: sender,
registry: assets,
transactions:
withdrawalResponse.transactions.map(
({ transaction }) => transaction,
),
});
const withdrawalBalance =
balancesAfterWithdrawal.balance(SOL_MINT);
if (
withdrawalBalance.amount !==
DEPOSIT_AMOUNT -
TRANSFER_AMOUNT -
WITHDRAW_AMOUNT
) {
throw new Error(
`expected remaining amount from this run ${DEPOSIT_AMOUNT - TRANSFER_AMOUNT - WITHDRAW_AMOUNT}, got ${withdrawalBalance.amount}`,
);
}
if (withdrawalBalance.utxos.length !== 1) {
throw new Error(
`expected 1 withdrawal utxo, got ${withdrawalBalance.utxos.length}`,
);
}
// 8. Read remaining private balance and the public balance.
const solanaBalance = await client.getBalance(
senderSigner.address,
);
console.log(
`withdraw private_balance=${withdrawalBalance.amount} ` +
`solana_balance=${solanaBalance} tx=${withdrawalTx.signature}`,
);
}
await main();
1
Điều kiện tiên quyết
Các ví dụ Rust yêu cầu Rust 1.98.1 và Solana CLI v4.0.2. Xem hướng dẫn cài đặt Solana.
Cargo.toml
[dependencies]
zolana-client = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha", features = ["indexer-api", "solana-rpc"] }
zolana-interface = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha" }
zolana-program = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha" }
zolana-keypair = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha" }
zolana-transaction = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha" }
zolana-wallet = { git = "https://github.com/helius-labs/zolana", tag = "v0.3.0-alpha" }
Connect to Endpoints
Connect to Endpoints
- Devnet
- Localnet
Thêm khóa API Helius:Theo mặc định, các ví dụ sử dụng ví Solana CLI làm tài khoản thanh toán. Tài khoản thanh toán phải có SOL trên devnet. Xem Cách nhận SOL trên devnet.
.env
API_KEY=YOUR_API_KEY
ZOLANA_PAYER_KEYPAIR=~/.config/solana/id.json
use zolana_client::{SolanaRpc, ZolanaClient};
use zolana_interface::pda;
let tree = pda::tree(0);
let client = ZolanaClient::from_urls(
SolanaRpc::new("https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY"),
"https://d2xah7tnhdhcom.cloudfront.net",
"https://d21ni15goiip6l.cloudfront.net",
)?;
cargo install --git https://github.com/helius-labs/zolana --tag v0.3.0-alpha zolana-cli
zolana dev start
use zolana_client::{SolanaRpc, ZolanaClient};
use zolana_interface::pda;
let tree = pda::tree(0);
let client = ZolanaClient::from_urls(
SolanaRpc::new("http://127.0.0.1:8899"),
"http://127.0.0.1:8784",
"http://127.0.0.1:3001",
)?;
2
Rút tiền về số dư công khai
use zolana_program::instruction::{
Transact, TransactInterfaceTransferAccounts, TransactSolTransferAccounts,
};
use zolana_transaction::{instructions::transact::ConfidentialTransaction, SOL_MINT};
let withdrawal_utxo = sender_balances_after_transfer
.get_balance(SOL_MINT)
// SPL: .get_balance(spl.mint)
.and_then(|balance| balance.utxos.first())
.expect("failed to fetch sender's utxo")
.clone();
let mut withdrawal = ConfidentialTransaction::new(vec![withdrawal_utxo], sender.pubkey())?;
withdrawal.withdraw_sol(WITHDRAW_AMOUNT, sender.pubkey())?;
// SPL: withdrawal.withdraw(spl.mint, WITHDRAW_AMOUNT, spl.user_token_account)?;
let proof_inputs = withdrawal.encrypt(&sender)?;
let withdrawal_data = client.prove_transact(proof_inputs, None, &sender)?;
let withdraw_ix = Transact {
payer: sender.pubkey(),
input_trees: vec![tree],
output_tree: tree,
owner_signers: Vec::new(),
interface_transfer_accounts: vec![TransactInterfaceTransferAccounts::Sol(
TransactSolTransferAccounts {
recipient: sender.pubkey(),
},
)],
// SPL: interface_transfer_accounts: vec![
// SPL: TransactInterfaceTransferAccounts::SplWithdrawal(
// SPL: zolana_program::instruction::TransactSplWithdrawalAccounts {
// SPL: mint: spl.mint,
// SPL: spl_interface: spl.vault,
// SPL: user_token_account: spl.user_token_account,
// SPL: token_program: spl.token_program,
// SPL: },
// SPL: ),
// SPL: ],
data: withdrawal_data,
}
.instruction();
1. Select private token accounts to spend
1. Select private token accounts to spend
use zolana_transaction::SOL_MINT;
let withdrawal_utxo = sender_balances_after_transfer
.get_balance(SOL_MINT)
// SPL: .get_balance(spl.mint)
.and_then(|balance| balance.utxos.first())
.expect("failed to fetch sender's utxo")
.clone();
- Ví dụ sử dụng Tài khoản token Solana riêng tư còn lại sau giao dịch chuyển trước đó. Một lần rút tiền có thể sử dụng nhiều UTXO.
withdrawal_utxolà UTXO đầu tiên có thể sử dụng cho tài sản đó. Chú thích// SPL:minh họaget_balance(spl.mint).
2. Prepare proof inputs
2. Prepare proof inputs
use zolana_transaction::instructions::transact::ConfidentialTransaction;
let mut withdrawal = ConfidentialTransaction::new(vec![withdrawal_utxo], sender.pubkey())?;
ConfidentialTransaction::newnhận trực tiếp các UTXO đã chọn.vec![withdrawal_utxo]liệt kê các đầu vào.sender.pubkey()là người trả phí giao dịch.
3. Build and encrypt the withdrawal
3. Build and encrypt the withdrawal
use solana_signer::Signer;
withdrawal.withdraw_sol(WITHDRAW_AMOUNT, sender.pubkey())?;
// SPL: withdrawal.withdraw(spl.mint, WITHDRAW_AMOUNT, spl.user_token_account)?;
let proof_inputs = withdrawal.encrypt(&sender)?;
sender.pubkey()là người nhận SOL công khai. Người nhận có thể là chủ sở hữu hoặc bên thứ ba. Đối với SPL và Token 2022, hãy truyền tài khoản token của người nhận vàowithdraw.withdrawal.withdraw_solchọn SOL. Chú thích// SPL:minh họa địa chỉ mint của token cho tài sản SPL và Token 2022.WITHDRAW_AMOUNTđược biểu thị bằng đơn vị cơ sở của tài sản. SOL sử dụng lamport. Tài sản SPL và Token 2022 sử dụng đơn vị cơ sở của token.withdrawal.encrypt(&sender)mã hóa các đầu ra và tạo đầu vào cho trình chứng minh không kiến thức.
4. Fetch the zero-knowledge proof
4. Fetch the zero-knowledge proof
use zolana_client::Rpc;
let withdrawal_data = client.prove_transact(proof_inputs, None, &sender)?;
client.prove_transacttạo bằng chứng không kiến thức từ giao dịch rút tiền đã mã hóa và trả về dữ liệu chỉ thị đã tuần tự hóa.sendercung cấp các khóa dùng để cấp quyền cho bằng chứng. Các UTXO đầu vào xác định cây trạng thái của chúng.- Bằng chứng cho thấy người gửi sở hữu và có thể sử dụng các đầu vào. Tài sản và số lượng được rút là công khai. Số lượng đầu vào và tiền thừa vẫn được mã hóa.
5. Build the withdrawal instruction
5. Build the withdrawal instruction
use zolana_program::instruction::{
Transact, TransactInterfaceTransferAccounts, TransactSolTransferAccounts,
};
let withdraw_ix = Transact {
payer: sender.pubkey(),
input_trees: vec![tree],
output_tree: tree,
owner_signers: Vec::new(),
interface_transfer_accounts: vec![TransactInterfaceTransferAccounts::Sol(
TransactSolTransferAccounts {
recipient: sender.pubkey(),
},
)],
// SPL: interface_transfer_accounts: vec![
// SPL: TransactInterfaceTransferAccounts::SplWithdrawal(
// SPL: zolana_program::instruction::TransactSplWithdrawalAccounts {
// SPL: mint: spl.mint,
// SPL: spl_interface: spl.vault,
// SPL: user_token_account: spl.user_token_account,
// SPL: token_program: spl.token_program,
// SPL: },
// SPL: ),
// SPL: ],
data: withdrawal_data,
}
.instruction();
payerký và thanh toán phí cho giao dịch Solana. Nhà tài trợ phí gas có thể thanh toán khoản phí này.input_treesxác định cây Merkle trạng thái chứa các UTXO đã sử dụng.output_treexác định cây Merkle trạng thái nhận cam kết về khoản tiền thừa riêng tư của người gửi.interface_transfer_accountscung cấpTransactInterfaceTransferAccounts::Sol, người nhận Solana công khai. Chú thích// SPL:minh họaSplWithdrawal.owner_signersđể trống cho giao dịch rút tiền bảo mật này.datachứa bằng chứng không kiến thức và khoản tiền thừa mã hóa được tạo ở bước trước.
6. Send like any Solana transaction
6. Send like any Solana transaction
use zolana_client::Rpc;
let signature = client.create_and_send_transaction(
&[withdraw_ix],
sender.pubkey(),
&[&sender],
client.compute_budget(),
)?;
let slot = landed_slot(&client, signature)?;
create_and_send_transactionký và gửiwithdraw_ixdưới dạng một giao dịch Solana.landed_slotđọc slot xác nhận dùng để kiểm soát thời điểm truy xuất từ trình lập chỉ mục.senderthanh toán phí và cấp quyền rút tiền.
Ví dụ mã đầy đủ
Sao chép kho lưu trữ và chạy ví dụ:git clone https://github.com/helius-labs/zolana-examples.git
cd zolana-examples
git checkout v0.3.0-alpha
cd rust-client
cargo run -p rust-client-example --example deposit_transfer_withdraw
Các ví dụ sử dụng một Ring bảo mật trên local/devnet tại đây.
deposit_transfer_withdraw.rs
use anyhow::{anyhow, Result};
use rust_client_example::{cli_keypair, landed_slot, setup, SetupContext};
use solana_keypair::Keypair;
use solana_signer::Signer;
use zolana_client::{IndexerRpcConfig, Rpc, SolanaRpc, ZolanaClient};
use zolana_keypair::ShieldedKeypair;
use zolana_program::instruction::{
AssetDeposit, Deposit, DepositAsset, Transact, TransactInterfaceTransferAccounts,
TransactSolTransferAccounts,
};
use zolana_transaction::{
decrypt_spendable, instructions::transact::ConfidentialTransaction, AssetRegistry, SOL_MINT,
};
const DEPOSIT_AMOUNT: u64 = 10_000_000;
const TRANSFER_AMOUNT: u64 = 3_000_000;
const WITHDRAW_AMOUNT: u64 = 3_000_000;
fn main() -> Result<()> {
let SetupContext {
rpc_url,
indexer_url,
prover_url,
tree,
} = setup()?;
// Connect to the RPC, indexer, and prover.
let client = ZolanaClient::from_urls(SolanaRpc::new(rpc_url), &indexer_url, prover_url)?;
// Mints that are registered with Solana Rings for privacy.
let assets = AssetRegistry::default();
// SPL: assets.insert(spl.asset_id, spl.mint)?;
// Initialize the sender's private wallet and local authority
// to decrypt transactions and sync balances.
// The Solana signer and private wallet are derived from the same Ed25519 seed.
let sender = ShieldedKeypair::from_keypair(&cli_keypair()?)?;
let recipient = ShieldedKeypair::from_keypair(&Keypair::new())?;
let sender_shielded_address = sender.shielded_address()?;
// Deposit SOL into the sender's private balance.
// A deposit from a public balance reveals
// sender, recipient, asset and amount.
// Alternatively, you can onramp fiat directly to a private balance.
// 1. Move public SOL into the sender's private balance.
let sender_balances_after_deposit = {
let deposit_ix = Deposit {
tree,
depositor: sender.pubkey(),
deposits: vec![AssetDeposit {
asset: DepositAsset::Sol,
// SPL: asset: DepositAsset::Spl(zolana_program::instruction::DepositSplAccounts {
// SPL: mint: spl.mint,
// SPL: user_token: spl.user_token_account,
// SPL: token_program: spl.token_program,
// SPL: }),
view_tag: sender_shielded_address.confidential_view_tag()?,
owner: sender_shielded_address.owner_hash()?,
amount: DEPOSIT_AMOUNT,
memo: None,
}],
}
.instruction()?;
// 2. Send and confirm like any Solana transaction; the landed slot gates
// the indexer fetch below.
let signature = client.create_and_send_transaction(
&[deposit_ix],
sender.pubkey(),
&[&sender],
client.compute_budget(),
)?;
let slot = landed_slot(&client, signature)?;
// 3. Fetch transaction outputs from the indexer, gated on the deposit's slot.
// The indexer returns encrypted outputs by transaction signature.
let response = client.get_shielded_transactions_by_signature(
signature,
Some(IndexerRpcConfig::at_slot(slot)),
)?;
let transactions = response
.transactions
.into_iter()
.map(|indexed| indexed.transaction)
.collect::<Vec<_>>();
// 4. The sender decrypts the transaction outputs locally to update the private balance.
let balances = decrypt_spendable(&sender, &transactions, &assets)
.map_err(|e| anyhow!("decrypt sender transactions: {e:?}"))?
.balances;
let sender_balance = balances
.get_balance(SOL_MINT)
// SPL: .get_balance(spl.mint)
.expect("failed to fetch sender's utxo");
assert_eq!(sender_balance.amount, DEPOSIT_AMOUNT);
assert_eq!(sender_balance.utxos.len(), 1);
balances
};
// Confidential SOL transfer to the recipient's private balance.
// A confidential transfer reveals only sender and recipient,
// not the asset or amount.
let sender_balances_after_transfer = {
// 1. Select UTXOs that make up the private balance for the transfer.
let transfer_utxo = sender_balances_after_deposit
.get_balance(SOL_MINT)
// SPL: .get_balance(spl.mint)
.and_then(|balance| balance.utxos.first())
.expect("failed to fetch deposited utxo")
.clone();
// 2. Prepare the selected UTXOs as inputs for the zero-knowledge proof.
let mut transfer = ConfidentialTransaction::new(vec![transfer_utxo], sender.pubkey())?;
// 3. Build and encrypt the confidential transfer.
// Encryption hides the asset and amount and produces the proof inputs for the ZK prover.
transfer.transfer_sol(&recipient.shielded_address()?, TRANSFER_AMOUNT)?;
// SPL: transfer.transfer(&recipient.shielded_address()?, spl.mint, TRANSFER_AMOUNT)?;
let proof_inputs = transfer.encrypt(&sender)?;
// 4. Fetch the zk proof to prove the sender can spend the balance without revealing asset and amount.
let transfer_data = client.prove_transact(proof_inputs, None, &sender)?;
// 5. Construct the instruction.
let transfer_ix = Transact {
payer: sender.pubkey(),
input_trees: vec![tree],
output_tree: tree,
owner_signers: Vec::new(),
interface_transfer_accounts: Vec::new(),
data: transfer_data,
}
.instruction();
// 6. Send and confirm like any Solana transaction; confirmation yields the landed slot.
let signature = client.create_and_send_transaction(
&[transfer_ix],
sender.pubkey(),
&[&sender],
client.compute_budget(),
)?;
let slot = landed_slot(&client, signature)?;
// 7. Fetch the sender's UTXOs from this transaction, gated on the transfer's slot,
// and read the remaining private balance.
let response = client.get_shielded_transactions_by_signature(
signature,
Some(IndexerRpcConfig::at_slot(slot)),
)?;
let transactions = response
.transactions
.into_iter()
.map(|indexed| indexed.transaction)
.collect::<Vec<_>>();
let sender_balances = decrypt_spendable(&sender, &transactions, &assets)
.map_err(|e| anyhow!("decrypt sender transactions: {e:?}"))?
.balances;
let sender_balance = sender_balances
.get_balance(SOL_MINT)
// SPL: .get_balance(spl.mint)
.expect("failed to fetch sender's utxo");
assert_eq!(sender_balance.amount, DEPOSIT_AMOUNT - TRANSFER_AMOUNT);
assert_eq!(sender_balance.utxos.len(), 1);
sender_balances
};
// Withdraw SOL back to the sender's public balance.
// A withdrawal from a confidential balance reveals
// sender, recipient, asset and amount.
{
// 1. Select UTXOs that make up the private balance for the withdrawal.
let withdrawal_utxo = sender_balances_after_transfer
.get_balance(SOL_MINT)
// SPL: .get_balance(spl.mint)
.and_then(|balance| balance.utxos.first())
.expect("failed to fetch sender's utxo")
.clone();
// 2. Prepare the selected UTXOs as inputs for the zero-knowledge proof.
let mut withdrawal = ConfidentialTransaction::new(vec![withdrawal_utxo], sender.pubkey())?;
// 3. Build and encrypt the confidential withdrawal.
// Encryption hides the private change and produces the ZK prover inputs.
withdrawal.withdraw_sol(WITHDRAW_AMOUNT, sender.pubkey())?;
// SPL: withdrawal.withdraw(spl.mint, WITHDRAW_AMOUNT, spl.user_token_account)?;
let proof_inputs = withdrawal.encrypt(&sender)?;
// 4. Fetch the ZK proof to prove the sender can spend the balance.
let withdrawal_data = client.prove_transact(proof_inputs, None, &sender)?;
// 5. Combine the proof and withdrawal accounts in a single instruction.
let withdraw_ix = Transact {
payer: sender.pubkey(),
input_trees: vec![tree],
output_tree: tree,
owner_signers: Vec::new(),
interface_transfer_accounts: vec![TransactInterfaceTransferAccounts::Sol(
TransactSolTransferAccounts {
recipient: sender.pubkey(),
},
)],
// SPL: interface_transfer_accounts: vec![
// SPL: TransactInterfaceTransferAccounts::SplWithdrawal(
// SPL: zolana_program::instruction::TransactSplWithdrawalAccounts {
// SPL: mint: spl.mint,
// SPL: spl_interface: spl.vault,
// SPL: user_token_account: spl.user_token_account,
// SPL: token_program: spl.token_program,
// SPL: },
// SPL: ),
// SPL: ],
data: withdrawal_data,
}
.instruction();
// 6. Send and confirm like any Solana transaction.
let signature = client.create_and_send_transaction(
&[withdraw_ix],
sender.pubkey(),
&[&sender],
client.compute_budget(),
)?;
let slot = landed_slot(&client, signature)?;
// 7. Fetch the sender's UTXOs from this transaction, gated on the withdrawal's slot,
// and read the remaining private balance.
let response = client.get_shielded_transactions_by_signature(
signature,
Some(IndexerRpcConfig::at_slot(slot)),
)?;
let transactions = response
.transactions
.into_iter()
.map(|indexed| indexed.transaction)
.collect::<Vec<_>>();
let sender_balances = decrypt_spendable(&sender, &transactions, &assets)
.map_err(|e| anyhow!("decrypt sender transactions: {e:?}"))?
.balances;
let sender_balance = sender_balances
.get_balance(SOL_MINT)
// SPL: .get_balance(spl.mint)
.expect("failed to fetch sender's utxo");
assert_eq!(
sender_balance.amount,
DEPOSIT_AMOUNT - TRANSFER_AMOUNT - WITHDRAW_AMOUNT
);
assert_eq!(sender_balance.utxos.len(), 1);
// 8. Read remaining private balance and the public SOL balance.
let solana_balance = client.get_balance(sender.pubkey())?;
println!("withdraw solana_balance={solana_balance} tx={signature}");
// SPL: println!(
// SPL: "withdraw user_token={} tx={signature}",
// SPL: spl.user_token_account,
// SPL: );
}
Ok(())
}