- При выводе токены перемещаются с приватного баланса на публичный баланс Solana.
- Вывод выполняется одной транзакцией Solana на адрес кошелька Solana.
Вывод средств: что остаётся приватным
| Поле | Видимость |
|---|---|
| Актив | Публично. Отображается в публичной операции вывода. |
| Сумма | Публично. Отображается в публичной операции вывода. |
| Отправитель | Публично в конфиденциальном кольце; скрыто в анонимном кольце. |
| Получатель | Публично. Адрес кошелька назначения отображается в публичной операции вывода. |
| Примечание (необязательно) | Скрыто, если прикреплено к зашифрованному выходу. |
| Итоговый публичный баланс | Публично. Хранится в публичном аккаунте получателя. |
| Оставшийся приватный баланс | Скрыто. Зашифровано в блокчейне. |
Общедоступное кольцо обеспечивает конфиденциальность благодаря шифрованию суммы и актива.
Пользовательское кольцо можно настроить как конфиденциальное или анонимное (с шифрованием отправителя, получателя, актива и суммы).
Как работает вывод средств
Вывод средств выполняется аналогично публичному переводу в Solana:- Баланс SOL или SPL пользователя зашифрован в блокчейне.
-
Пользователь расшифровывает приватное состояние, кошелёк формирует операцию вывода, а владелец подписывает её.
- Получите зашифрованное состояние с помощью специальных методов RPC. Только пользователь может локально расшифровать баланс.
- Кошелёк задаёт сумму и получателя, а затем запрашивает доказательство с нулевым разглашением. По умолчанию провайдер RPC создаёт и возвращает это доказательство.
- Среда выполнения Solana проверяет подписи и вызывает программу конфиденциальности Solana, которая проверяет доказательство с нулевым разглашением, не раскрывая зашифрованное состояние.
- Приложение отслеживает статус по хешу транзакции Solana.
Compare to Solana Transfer
Compare to Solana Transfer
- Баланс SOL или SPL пользователя общедоступен в блокчейне.
- Кошелёк считывает публичное состояние, формирует перевод, а владелец подписывает его.
- Среда выполнения Solana проверяет подписи и вызывает System Program или Token Program, которая обновляет публичный баланс.
- Приложение отслеживает статус по хешу транзакции Solana.
Это высокоуровневый процесс транзакции для общедоступного конфиденциального кольца.
Сравните его с пользовательскими кольцами в разделе Высокоуровневый процесс транзакции.
Начало работы
- TypeScript Client
- Rust Client
1
Предварительные требования
Для примеров на TypeScript требуются Node.js 24 или более поздней версии, pnpm 11.18.0 и Solana CLI.
pnpm add @heliuslabs/zolana@^0.4.0-alpha @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",
});
В localnet SDK запускает локальный тестовый валидатор (
:8899), индексатор Photon (:8784) и средство доказательства (:3001), после чего
клиент автоматически подключается к ним без необходимости настраивать конечные точки.cargo install --git https://github.com/helius-labs/zolana --tag v0.4.0-alpha zolana-cli
zolana dev start
import { createZolanaClient } from "@heliuslabs/zolana";
const client = await createZolanaClient({});
2
Вывод на публичный баланс
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 возвращает инструкции. Приложение подписывает и отправляет их.
sendAndConfirmFactoryформирует транзакцию Kit, отправляет её и возвращает подпись вместе со слотом, в который она была включена.
- 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]!;
- В примере расходуется остаток на приватном токен-аккаунте Solana после предыдущего перевода. Для вывода можно использовать несколько UTXO.
withdrawalUtxoвыбирает один приватный токен-аккаунт Solana из этого баланса.
2. Prepare proof inputs
2. Prepare proof inputs
import { ProofInputUtxo } from "@heliuslabs/zolana/transaction";
const withdrawalInput =
ProofInputUtxo.fromKeypair(
withdrawalUtxo,
sender,
);
ProofInputUtxo.fromKeypairподготавливает выбранный UTXO в качестве входных данных доказательства, используя приватную пару ключей кошелька отправителя.- Из пары ключей выводится нуллификатор, который помечает входной UTXO как потраченный, при этом актив и сумма на входе остаются зашифрованными.
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,
);
senderAddress— это отправителя. Средства для вывода списываются с этого кошелька.[withdrawalInput]содержит список выбранных UTXO отправителя. Для вывода можно использовать несколько UTXO.senderSigner.address— адрес Solana плательщика комиссии. Комиссию может оплатить спонсор газа.WithdrawalTarget.sol— публичный получатель в Solana. Получателем может быть владелец или третье лицо.SOL_MINTвыбирает SOL. При выводе SPL или Token 2022 передаётся адрес выпуска токена.WITHDRAW_AMOUNTуказывается в базовых единицах актива. Для SOL используются лампорты. Для активов SPL и Token 2022 используются базовые единицы токена.withdrawal.signавторизует переход состояния, шифрует оставшуюся приватную сдачу и создаёт входные данные для системы доказательства с нулевым разглашением.assets— реестр активов, используемый для определения поддерживаемых приватных активов.
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,
);
senderKeysиспользуетLocalKeys.fromKeypair(sender, client.proofService), чтобы авторизовать создание доказательства с помощью ключей отправителя.client.proveTransactсоздаёт доказательство с нулевым разглашением на основе подписанной операции вывода и возвращает сериализованные данные инструкции.- Доказательство подтверждает, что отправитель владеет входными средствами и может их потратить. Выводимый актив и сумма общедоступны. Суммы на входе и сдача остаются зашифрованными.
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,
});
payerподписывает и оплачивает транзакцию Solana. Комиссию может оплатить спонсор газа.inputTreeиoutputTree— этоclient.tree, дерево Меркла состояния, которое содержит потраченные UTXO и получает приватную сдачу отправителя.withdrawal— этоTransactWithdrawal.sol, публичный аккаунт получателя в Solana.await transactInstructionлокально выводит адреса аккаунтов нуллификаторов и возвращает инструкцию. Аккаунты нуллификаторов помечают входные UTXO как потраченные, чтобы приватный баланс нельзя было потратить дважды.dataсодержит доказательство с нулевым разглашением и зашифрованную сдачу, созданные на предыдущем шаге.- При выводе актив перемещается с приватного баланса на публичный аккаунт Solana. В операцию передаётся публичный аккаунт получателя.
6. Send like any Solana transaction
6. Send like any Solana transaction
import { sendAndConfirmFactory } from "../src/lib.js";
const withdrawalTx = await sendAndConfirm([
withdrawalInstruction,
]);
sendAndConfirmподписывает и отправляетwithdrawalInstructionкак транзакцию Solana.- После подтверждения возвращается слот, в который попала транзакция. Он используется для ограничения выборки индексатора.
Полный пример кода
Клонируйте и запустите пример: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
В примерах используется конфиденциальное кольцо в локальной сети/devnet, доступное здесь.
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,
} 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 client = await createZolanaClient({
solanaRpcUrl: `https://devnet.helius-rpc.com/?api-key=${process.env.API_KEY}`,
});
// localnet: const client = await createZolanaClient({});
// 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 outputs.
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 this transaction's outputs, gated on its confirmed slot.
const depositResponse =
await client.getShieldedTransactionsBySignature(
depositTx.signature,
atSlot(depositTx.slot),
);
// 4. The sender decrypts the transaction outputs locally to read the funds deposited in this run.
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 this transaction's outputs, gated on its confirmed slot.
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 this transaction's outputs, gated on its confirmed slot.
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
Предварительные требования
Для примеров на Rust требуются Rust 1.98.1 и Solana CLI v4.0.2. См. руководство по установке Solana.
Cargo.toml
[dependencies]
zolana-client = { git = "https://github.com/helius-labs/zolana", tag = "v0.4.0-alpha", features = ["indexer-api", "solana-rpc"] }
zolana-interface = { git = "https://github.com/helius-labs/zolana", tag = "v0.4.0-alpha" }
zolana-program = { git = "https://github.com/helius-labs/zolana", tag = "v0.4.0-alpha" }
zolana-keypair = { git = "https://github.com/helius-labs/zolana", tag = "v0.4.0-alpha" }
zolana-transaction = { git = "https://github.com/helius-labs/zolana", tag = "v0.4.0-alpha" }
zolana-wallet = { git = "https://github.com/helius-labs/zolana", tag = "v0.4.0-alpha" }
Connect to Endpoints
Connect to Endpoints
- Devnet
- Localnet
Добавьте API-ключ Helius:По умолчанию в примерах в качестве плательщика используется кошелёк Solana CLI. На балансе плательщика должны быть SOL в сети devnet. См. Как получить 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 url = "https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY";
let client = ZolanaClient::from_urls(SolanaRpc::new(url), url, url)?;
cargo install --git https://github.com/helius-labs/zolana --tag v0.4.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
Вывод на публичный баланс
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();
- В примере расходуется остаток на приватном токен-аккаунте Solana после предыдущего перевода. Для вывода можно использовать несколько UTXO.
withdrawal_utxo— первый доступный для расходования UTXO этого актива. Комментарий// SPL:показываетget_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::newпринимает выбранные UTXO напрямую.vec![withdrawal_utxo]содержит список входных данных.sender.pubkey()— плательщик комиссии за транзакцию.
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()— публичный получатель SOL. Получателем может быть владелец или третье лицо. Для SPL и Token 2022 передайте токен-аккаунт получателя вwithdraw.withdrawal.withdraw_solвыбирает SOL. Комментарий// SPL:показывает адрес выпуска токена для активов SPL и Token 2022.WITHDRAW_AMOUNTуказывается в базовых единицах актива. Для SOL используются лампорты. Для активов SPL и Token 2022 используются базовые единицы токена.withdrawal.encrypt(&sender)шифрует выходные данные и создаёт входные данные для системы доказательства с нулевым разглашением.
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_transactсоздаёт доказательство с нулевым разглашением на основе зашифрованной операции вывода и возвращает сериализованные данные инструкции.senderпредоставляет ключи для авторизации доказательства. Входные UTXO определяют свои деревья состояния.- Доказательство подтверждает, что отправитель владеет входными средствами и может их потратить. Выводимый актив и сумма общедоступны. Суммы на входе и сдача остаются зашифрованными.
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();
payerподписывает и оплачивает транзакцию Solana. Комиссию может оплатить спонсор газа.input_treesопределяет дерево Меркла состояния, содержащее потраченные UTXO.output_treeопределяет дерево Меркла состояния, которое получает обязательство для приватной сдачи отправителя.interface_transfer_accountsпредоставляетTransactInterfaceTransferAccounts::Sol, публичного получателя в Solana. Комментарий// SPL:показываетSplWithdrawal.owner_signersпуст для этого конфиденциального вывода.dataсодержит доказательство с нулевым разглашением и зашифрованную сдачу, созданные на предыдущем шаге.
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_transactionподписывает и отправляетwithdraw_ixкак транзакцию Solana.landed_slotсчитывает слот подтверждения, используемый для ограничения выборки индексатора.senderоплачивает комиссию и авторизует вывод.
Полный пример кода
Клонируйте и запустите пример: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
В примерах используется конфиденциальное кольцо в локальной сети/devnet, доступное здесь.
deposit_transfer_withdraw.rs
use anyhow::{anyhow, Result};
use rust_client_example::{cli_keypair, connect, landed_slot};
use solana_keypair::Keypair;
use solana_signer::Signer;
use zolana_client::{IndexerRpcConfig, Rpc};
use zolana_interface::pda;
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 client = connect("https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY")?;
let tree = pda::tree(0);
// 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 this transaction's outputs, gated on its confirmed slot.
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 read the funds deposited in this run.
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 this transaction's outputs, gated on its confirmed slot.
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 this transaction's outputs, gated on its confirmed slot.
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 the funds remaining from this run 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(())
}
Связанные руководства
Deposit
Transfer
Read a Private Balance
Read Private History
Не нашли то, что искали?
Свяжитесь с нами! Telegram | Электронная почта | Контакты