- Penarikan memindahkan token dari saldo privat ke saldo Solana publik.
- Penarikan dikirim dalam satu transaksi Solana ke alamat dompet Solana.
Penarikan: Bagian yang Privat
| Bidang | Visibilitas | Alasan |
|---|---|---|
| Dompet privat sumber | Publik | Dalam Ring rahasia, dompet privat sumber terlihat secara onchain. |
| Aset | Publik | Aset terlihat secara onchain |
| Jumlah | Publik | Jumlah yang ditarik terlihat secara onchain |
| Dompet publik tujuan | Publik | Alamat dompet tujuan terlihat secara onchain |
| Saldo publik yang dihasilkan | Publik | Saldo publik yang dihasilkan terlihat secara onchain |
| Saldo privat yang tersisa | Privat | Saldo yang tersisa dienkripsi secara onchain |
Ring tanpa izin bersifat rahasia, dengan jumlah dan aset yang dienkripsi.
Ring khusus dapat dikonfigurasi sebagai rahasia atau anonim (pengirim, penerima, aset, dan jumlah dienkripsi).
Cara Kerja Penarikan
Penarikan bekerja serupa dengan transfer Solana publik:- Saldo SOL atau SPL milik pengguna dienkripsi secara onchain.
-
Pengguna mendekripsi status privat, dompet membuat penarikan, dan pemilik menandatanganinya.
- Ambil status terenkripsi dengan metode RPC khusus. Hanya pengguna yang dapat mendekripsi saldo secara lokal.
- Dompet menetapkan jumlah dan penerima, lalu meminta bukti ZK. Secara default, penyedia RPC menghasilkan bukti ZK dan mengembalikannya.
- Runtime Solana memverifikasi tanda tangan dan memanggil Solana Privacy Program, yang memverifikasi bukti ZK tanpa mengungkapkan status terenkripsi.
- Aplikasi melacak status melalui hash transaksi Solana.
Compare to Solana Transfer
Compare to Solana Transfer
- Saldo SOL atau SPL milik pengguna bersifat publik secara onchain.
- Dompet membaca status publik, membuat transfer, dan pemilik menandatanganinya.
- Runtime Solana memverifikasi tanda tangan dan memanggil System Program atau Token Program, yang memperbarui saldo publik.
- Aplikasi melacak status melalui hash transaksi Solana.
Ini adalah alur transaksi tingkat tinggi untuk Ring rahasia tanpa izin.
Bandingkan dengan Ring khusus di konsep.
Mulai
- TypeScript Client
- Rust Client
1
Prasyarat
Contoh TypeScript memerlukan Node.js 24 atau versi yang lebih baru, pnpm 11.18.0, dan 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",
});
Di localnet, SDK memulai validator pengujian lokal (
:8899), pengindeks Photon (:8784), dan prover (:3001), lalu
klien terhubung secara otomatis tanpa memerlukan konfigurasi endpoint.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
Tarik ke Saldo Publik
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 mengembalikan instruksi. Aplikasi menandatangani dan mengirimkannya.
sendAndConfirmFactorymembuat transaksi Kit, mengirimkannya, serta mengembalikan tanda tangan dan slot tempat transaksi berhasil masuk.
- 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]!;
- Contoh ini membelanjakan Private Solana Token Account yang tersisa setelah transfer sebelumnya. Penarikan dapat membelanjakan beberapa UTXO.
withdrawalUtxomemilih satu Private Solana Token Account dari saldo tersebut.
2. Prepare proof inputs
2. Prepare proof inputs
import { ProofInputUtxo } from "@heliuslabs/zolana/transaction";
const withdrawalInput =
ProofInputUtxo.fromKeypair(
withdrawalUtxo,
sender,
);
ProofInputUtxo.fromKeypairmenyiapkan UTXO yang dipilih sebagai input bukti dengan pasangan kunci dompet privat milik pengirim.- Pasangan kunci tersebut memperoleh nullifier yang menandai UTXO input sebagai telah dibelanjakan, sementara aset dan jumlah input tetap terenkripsi.
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,
);
senderAddressadalah milik pengirim. Penarikan membelanjakan saldo dari dompet ini.[withdrawalInput]mencantumkan UTXO pilihan pengirim. Penarikan dapat membelanjakan beberapa UTXO.senderSigner.addressadalah alamat Solana milik pembayar biaya. Sponsor gas dapat membayar biaya tersebut.WithdrawalTarget.soladalah penerima Solana publik. Penerima dapat berupa pemilik atau pihak ketiga.SOL_MINTmemilih SOL. Penarikan SPL atau Token 2022 meneruskan mint token.WITHDRAW_AMOUNTdinyatakan dalam unit dasar aset. SOL menggunakan lamport. Aset SPL dan Token 2022 menggunakan unit dasar token.withdrawal.signmengotorisasi transisi status, mengenkripsi kembalian privat yang tersisa, dan menghasilkan input untuk pembukti zero-knowledge.assetsadalah registri aset yang digunakan untuk menemukan aset privat yang didukung.
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,
);
senderKeysmenggunakanLocalKeys.fromKeypair(sender, client.proofService)untuk mengotorisasi pembuatan bukti dengan kunci pengirim.client.proveTransactmenghasilkan bukti zero-knowledge dari penarikan yang ditandatangani dan mengembalikan data instruksi yang diserialisasi.- Bukti tersebut menunjukkan bahwa pengirim memiliki dan dapat membelanjakan input. Aset dan jumlah yang ditarik bersifat publik. Jumlah input dan kembalian tetap terenkripsi.
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,
});
payermenandatangani dan membayar transaksi Solana. Sponsor gas dapat membayar biaya tersebut.inputTreedanoutputTreeadalahclient.tree, pohon Merkle status yang berisi UTXO yang telah dibelanjakan dan menerima kembalian privat milik pengirim.withdrawaladalahTransactWithdrawal.sol, akun penerima Solana publik.await transactInstructionmemperoleh alamat akun nullifier secara lokal dan mengembalikan instruksi. Akun nullifier menandai UTXO input sebagai telah dibelanjakan agar saldo privat tidak dapat dibelanjakan dua kali.databerisi bukti zero-knowledge dan kembalian terenkripsi yang dihasilkan pada langkah sebelumnya.- Penarikan memindahkan aset dari saldo privat ke akun Solana publik. Penarikan tersebut meneruskan akun penerima publik.
6. Send like any Solana transaction
6. Send like any Solana transaction
import { sendAndConfirmFactory } from "../src/lib.js";
const withdrawalTx = await sendAndConfirm([
withdrawalInstruction,
]);
sendAndConfirmmenandatangani dan mengirimkanwithdrawalInstructionsebagai transaksi Solana.- Konfirmasi menghasilkan slot tempat transaksi berhasil masuk, yang digunakan untuk membatasi pengambilan data oleh pengindeks.
Contoh Kode Lengkap
Kloning dan jalankan contoh: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
Contoh tersebut menggunakan Ring rahasia pada local/devnet di sini.
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
Prasyarat
Contoh Rust memerlukan Rust 1.98.1 dan Solana CLI v4.0.2. Lihat panduan instalasi 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
Tambahkan kunci API Helius:Contoh-contoh ini menggunakan dompet Solana CLI sebagai pembayar secara default. Pembayar harus memiliki SOL devnet. Lihat Cara Mendapatkan SOL 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
Tarik ke Saldo Publik
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();
- Contoh ini membelanjakan Private Solana Token Account yang tersisa setelah transfer sebelumnya. Penarikan dapat membelanjakan beberapa UTXO.
withdrawal_utxoadalah UTXO pertama yang dapat dibelanjakan untuk aset tersebut. Komentar// SPL:menampilkanget_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::newmenerima UTXO yang dipilih secara langsung.vec![withdrawal_utxo]mencantumkan input.sender.pubkey()adalah pembayar biaya transaksi.
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()adalah penerima SOL publik. Penerima dapat berupa pemilik atau pihak ketiga. Untuk SPL dan Token 2022, teruskan akun token penerima kewithdraw.withdrawal.withdraw_solmemilih SOL. Komentar// SPL:menampilkan mint token untuk aset SPL dan Token 2022.WITHDRAW_AMOUNTdinyatakan dalam unit dasar aset. SOL menggunakan lamport. Aset SPL dan Token 2022 menggunakan unit dasar token.withdrawal.encrypt(&sender)mengenkripsi output dan menghasilkan input untuk pembukti zero-knowledge.
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_transactmenghasilkan bukti zero-knowledge dari penarikan terenkripsi dan mengembalikan data instruksi yang diserialisasi.sendermenyediakan kunci yang digunakan untuk mengotorisasi bukti. UTXO input mengidentifikasi pohon statusnya.- Bukti tersebut menunjukkan bahwa pengirim memiliki dan dapat membelanjakan input. Aset dan jumlah yang ditarik bersifat publik. Jumlah input dan kembalian tetap terenkripsi.
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();
payermenandatangani dan membayar transaksi Solana. Sponsor gas dapat membayar biaya tersebut.input_treesmengidentifikasi pohon Merkle status yang berisi UTXO yang telah dibelanjakan.output_treemengidentifikasi pohon Merkle status yang menerima komitmen untuk kembalian privat milik pengirim.interface_transfer_accountsmenyediakanTransactInterfaceTransferAccounts::Sol, penerima Solana publik. Komentar// SPL:menampilkanSplWithdrawal.owner_signerskosong untuk penarikan rahasia ini.databerisi bukti zero-knowledge dan kembalian terenkripsi yang dihasilkan pada langkah sebelumnya.
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_transactionmenandatangani dan mengirimkanwithdraw_ixsebagai transaksi Solana.landed_slotmembaca slot konfirmasi yang digunakan untuk membatasi pengambilan data oleh pengindeks.sendermembayar biaya dan mengotorisasi penarikan.
Contoh Kode Lengkap
Kloning dan jalankan contoh: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
Contoh tersebut menggunakan Ring rahasia pada local/devnet di sini.
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(())
}