- Uma transferência privada move tokens em um anel entre carteiras privadas.
- As transferências privadas são enviadas em uma única transação Solana para um endereço de carteira Solana.
Transferência: o que é privado
- Permissionless Confidential Ring
- Custom Rings
| Campo | Visibilidade | Motivo |
|---|---|---|
| Ativo | Privado | O ativo é criptografado na blockchain |
| Quantidade | Privado | A quantidade transferida é criptografada na blockchain |
| Carteira privada de origem | Público | Em um Ring confidencial, a carteira privada de origem é visível na blockchain. |
| Destinatário | Público | Em um Ring confidencial, o destinatário é visível na blockchain. |
Transferências de e dentro de um ring revelam o ID do programa do Ring customizado.Um saldo em um Ring customizado pode sair para uma conta de token SPL, para o Ring padrão, ou para outro Ring, desde que a política do Ring de origem permita.
O Ring padrão é sem permissões e não tem política ou autoridade.Uma transação pode combinar saldos do Anel Padrão e de um Anel Personalizado. Uma transferência entre dois Anéis Personalizados passa pelo Anel Padrão.
| Transferência privada | Rings confidenciais customizados | Rings anônimos customizados | Confidencial (padrão ou customizado) para ou de confidencial customizado | Confidencial (padrão ou customizado) para anônimo customizado | Anônimo customizado para confidencial (padrão ou customizado) |
|---|---|---|---|---|---|
| Quantidade | Privado | Privado | Privado | Privado | Privado |
| Ativo | Privado | Privado | Privado | Privado | Privado |
| Carteira privada de origem | Público | Privado. Um relayer submete a transação, então o ledger público não revela a carteira privada de origem. | Público | Público | Privado. Um relayer submete a transação, então o ledger público não revela a carteira privada de origem. |
| Destinatário | Público | Privado | Público | Privado | Público |
| ID do programa do Ring customizado | Público | Público | Público | Público | Público |
O anel sem permissão é confidencial com quantidade e ativo criptografados.
Um anel personalizado pode ser configurado como confidencial ou anônimo (remetente, destinatário, ativo e quantia criptografados).
Como Funcionamento de uma Transferência
Uma transferência privada se comporta de forma semelhante a uma transferência pública da Solana:- O saldo SOL ou SPL do usuário é criptografado no blockchain.
-
O usuário descriptografa o estado privado, a carteira constrói uma transferência e o proprietário assina.
- Buscar estado criptografado com métodos dedicados RPC. Apenas o usuário pode descriptografar saldos localmente.
- A carteira define a quantidade e o destinatário, então solicita uma prova ZK. O provedor RPC gera a prova ZK por padrão e a retorna.
- O runtime Solana verifica as assinaturas e invoca o Solana Privacy Program, que verifica a prova ZK sem revelar o estado criptografado.
- O aplicativo rastreia o status por meio do hash da transação Solana.
Comparar com Transferência Solana
Comparar com Transferência Solana
- O saldo SOL ou SPL do usuário é público no blockchain.
- A carteira lê o estado público, constrói uma transferência e o proprietário assina.
- O runtime Solana verifica as assinaturas e invoca o System Program ou Token Program, que atualiza o saldo público.
- O aplicativo rastreia o status por meio do hash da transação Solana.
Este é o fluxo de transação em alto nível para o anel confidencial sem permissão.
Compare com anéis personalizados em conceitos.
Como Começar
- Cliente TypeScript
- Cliente Rust
1
Pré-requisitos
Os exemplos em TypeScript exigem Node.js 24 ou superior, pnpm 11.18.0 e o Solana CLI.
pnpm add @heliuslabs/zolana@^0.3.1-alpha @solana/kit@^8.3.0
Conectar aos Endpoints
Conectar aos 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",
});
No localnet, o SDK inicia o validador de teste local (
:8899), o indexador Photon (:8784) e o provedor (:3001), e o
cliente se conecta a eles automaticamente, sem a necessidade de configuração de 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
Transferir para um Saldo Privado
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 };
};
}
- O SDK retorna instruções. O aplicativo as assina e as envia.
sendAndConfirmFactoryconstrói uma transação Kit, submete-a e retorna a assinatura mais o slot atingido.
- SOL
- SPL
import { LocalKeys } from "@heliuslabs/zolana/client";
import { SOL_MINT } from "@heliuslabs/zolana";
import { transactInstruction } from "@heliuslabs/zolana/interface";
import {
ConfidentialTransfer,
ProofInputUtxo,
} from "@heliuslabs/zolana/transaction";
import { sendAndConfirmFactory } from "../src/lib.js";
const sendAndConfirm = sendAndConfirmFactory(
client,
senderSigner,
);
const transferUtxo = depositBalance.utxos[0]!;
const transferInput =
ProofInputUtxo.fromKeypair(
transferUtxo,
sender,
);
const transfer = new ConfidentialTransfer(
senderAddress,
[transferInput],
senderSigner.address,
);
transfer.send(
recipient.shieldedAddress(),
SOL_MINT,
TRANSFER_AMOUNT,
);
const transferProofInputs = transfer.sign(
sender,
assets,
);
const senderKeys = LocalKeys.fromKeypair(sender, client.proofService);
const transferData = await client.proveTransact(
transferProofInputs,
senderKeys,
);
const transferInstruction = await transactInstruction(
{
payer: senderSigner,
inputTree: client.tree,
outputTree: client.tree,
data: transferData,
},
);
const transferTx = await sendAndConfirm([
transferInstruction,
]);
import { LocalKeys } from "@heliuslabs/zolana/client";
import { transactInstruction } from "@heliuslabs/zolana/interface";
import {
ConfidentialTransfer,
ProofInputUtxo,
} from "@heliuslabs/zolana/transaction";
import { sendAndConfirmFactory } from "../src/lib.js";
const sendAndConfirm = sendAndConfirmFactory(
client,
senderSigner,
);
const transferUtxo = depositBalance.utxos[0]!;
const transferInput =
ProofInputUtxo.fromKeypair(
transferUtxo,
sender,
);
const transfer = new ConfidentialTransfer(
senderAddress,
[transferInput],
senderSigner.address,
);
transfer.send(
recipient.shieldedAddress(),
spl.mint,
TRANSFER_AMOUNT,
);
const transferProofInputs = transfer.sign(
sender,
assets,
);
const senderKeys = LocalKeys.fromKeypair(sender, client.proofService);
const transferData = await client.proveTransact(
transferProofInputs,
senderKeys,
);
const transferInstruction = await transactInstruction(
{
payer: senderSigner,
inputTree: client.tree,
outputTree: client.tree,
data: transferData,
},
);
const transferTx = await sendAndConfirm([
transferInstruction,
]);
1. Selecionar contas de token privadas para gastar
1. Selecionar contas de token privadas para gastar
import { SOL_MINT } from "@heliuslabs/zolana";
const transferUtxo = depositBalance.utxos[0]!;
- O exemplo gasta a conta de token privada da Solana criada pelo depósito anterior. Uma transferência pode gastar vários UTXOs.
transferUtxoseleciona uma conta de token privada da Solana desse saldo.
2. Preparar as entradas de prova
2. Preparar as entradas de prova
import { ProofInputUtxo } from "@heliuslabs/zolana/transaction";
const transferInput =
ProofInputUtxo.fromKeypair(
transferUtxo,
sender,
);
ProofInputUtxo.fromKeypairprepara o UTXO selecionado como uma entrada de prova com o par de chaves da carteira privada do remetente.- O par de chaves deriva o anulador que marca o UTXO de entrada como gasto enquanto o ativo e a quantidade permanecem criptografados.
3. Construir e assinar a transferência confidencial
3. Construir e assinar a transferência confidencial
import { SOL_MINT } from "@heliuslabs/zolana";
import { ConfidentialTransfer } from "@heliuslabs/zolana/transaction";
const transfer = new ConfidentialTransfer(
senderAddress,
[transferInput],
senderSigner.address,
);
transfer.send(
recipient.shieldedAddress(),
SOL_MINT,
TRANSFER_AMOUNT,
);
const transferProofInputs = transfer.sign(
sender,
assets,
);
senderAddressé o do remetente. A transferência gasta fundos dessa carteira.[transferInput]lista os UTXOs selecionados pelo remetente. Uma transferência pode gastar vários UTXOs.senderSigner.addressé o endereço Solana do pagador da taxa. Um patrocinador de gas pode pagar a taxa.recipient.shieldedAddress()é o Endereço Protegido do destinatário. A saída transferida é criptografada com a chave de visualização do destinatário.SOL_MINTseleciona SOL. Uma transferência de SPL ou Token 2022 informa o mint do token.TRANSFER_AMOUNTé denominado nas unidades básicas do ativo. SOL usa lamports. Ativos SPL e Token 2022 usam as unidades básicas do token.transfer.signautoriza a transição de estado, criptografa o ativo e o valor e produz as entradas para o provador de conhecimento zero.assetsé o registro de ativos usado para identificar os ativos privados compatíveis.
4. Obter a prova de conhecimento zero
4. Obter a prova de conhecimento zero
import { LocalKeys } from "@heliuslabs/zolana/client";
const senderKeys = LocalKeys.fromKeypair(sender, client.proofService);
const transferData = await client.proveTransact(
transferProofInputs,
senderKeys,
);
senderKeysusaLocalKeys.fromKeypair(sender, client.proofService)para autorizar a geração da prova com as chaves do remetente.client.proveTransactgera a prova de conhecimento zero a partir da transferência assinada e retorna os dados serializados da instrução.- A prova demonstra que o remetente possui e pode gastar as entradas sem revelar o ativo criptografado nem o valor.
5. Construir a instrução de transferência
5. Construir a instrução de transferência
import { transactInstruction } from "@heliuslabs/zolana/interface";
const transferInstruction = await transactInstruction(
{
payer: senderSigner,
inputTree: client.tree,
outputTree: client.tree,
data: transferData,
},
);
payerassina e paga pela transação Solana. Um patrocinador de gas pode pagar a taxa.inputTreeeoutputTreesãoclient.tree, a árvore de Merkle de estado que contém os UTXOs gastos e recebe a saída do destinatário e o troco do remetente.await transactInstructionderiva localmente os endereços das contas de nullifier e retorna a instrução. As contas de nullifier marcam os UTXOs de entrada como gastos para que um saldo privado não possa ser gasto duas vezes.datacontém a prova de conhecimento zero e as saídas criptografadas produzidas na etapa anterior.- Uma transferência privada movimenta o ativo somente entre saldos privados. Ela não informa contas Solana públicas nem contas de token.
6. Envie como qualquer transação Solana
6. Envie como qualquer transação Solana
import { sendAndConfirmFactory } from "../src/lib.js";
const transferTx = await sendAndConfirm([
transferInstruction,
]);
sendAndConfirmassina e submetetransferInstructioncomo uma transação Solana.- A confirmação produz o slot atingido usado para limitar a busca do indexador.
Exemplo Completo de Código
Clone e execute o exemplo: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
Os exemplos usam um Ring confidencial em local/devnet aqui.
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
Pré-requisitos
Os exemplos em Rust exigem o Rust 1.98.1 e a CLI da Solana v4.0.2. Consulte o guia de instalação da 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" }
Conectar aos Endpoints
Conectar aos Endpoints
- Devnet
- Localnet
Adicione uma chave da Helius API:Os exemplos usam a carteira Solana CLI como a pagadora por padrão. A pagadora deve possuir SOL de devnet. Veja Como Obter SOL de 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
Transferir para um Saldo Privado
use zolana_program::instruction::Transact;
use zolana_transaction::{instructions::transact::ConfidentialTransaction, SOL_MINT};
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();
let mut transfer = ConfidentialTransaction::new(vec![transfer_utxo], sender.pubkey())?;
transfer.transfer_sol(&recipient.shielded_address()?, TRANSFER_AMOUNT)?;
// SPL: transfer.transfer(&recipient.shielded_address()?, spl.mint, TRANSFER_AMOUNT)?;
let proof_inputs = transfer.encrypt(&sender)?;
let transfer_data = client.prove_transact(proof_inputs, None, &sender)?;
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();
1. Selecionar contas de token privadas para gastar
1. Selecionar contas de token privadas para gastar
use zolana_transaction::SOL_MINT;
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();
- O exemplo gasta a conta de token privada da Solana criada pelo depósito anterior. Uma transferência pode gastar vários UTXOs.
transfer_utxoé o primeiro UTXO utilizável para esse ativo. O comentário// SPL:mostraget_balance(spl.mint).
2. Preparar as entradas de prova
2. Preparar as entradas de prova
use zolana_transaction::instructions::transact::ConfidentialTransaction;
let mut transfer = ConfidentialTransaction::new(vec![transfer_utxo], sender.pubkey())?;
ConfidentialTransaction::newrecebe diretamente as UTXOs selecionadas.vec![transfer_utxo]lista as entradas.sender.pubkey()é o pagador da taxa da transação.
3. Build and encrypt the confidential transfer
3. Build and encrypt the confidential transfer
use solana_signer::Signer;
transfer.transfer_sol(&recipient.shielded_address()?, TRANSFER_AMOUNT)?;
// SPL: transfer.transfer(&recipient.shielded_address()?, spl.mint, TRANSFER_AMOUNT)?;
let proof_inputs = transfer.encrypt(&sender)?;
recipient.shielded_address()?é o endereço protegido do destinatário. A saída transferida é criptografada com a chave de visualização do destinatário.transfer.transfer_solseleciona SOL. O comentário de// SPL:mostra a emissão do token para ativos SPL e Token 2022.TRANSFER_AMOUNTé denominado nas unidades base do ativo. SOL usa lamports. Ativos SPL e Token 2022 usam as unidades base do token.transfer.encrypt(&sender)criptografa as saídas e produz as entradas para o provador de conhecimento zero.
4. Obter a prova de conhecimento zero
4. Obter a prova de conhecimento zero
use zolana_client::Rpc;
let transfer_data = client.prove_transact(proof_inputs, None, &sender)?;
client.prove_transactgera a prova de conhecimento zero a partir da transferência criptografada e retorna os dados serializados da instrução.senderfornece as chaves usadas para autorizar a prova. As UTXOs de entrada identificam suas árvores de estado.- A prova demonstra que o remetente possui e pode gastar as entradas sem revelar o ativo criptografado nem o valor.
5. Construir a instrução de transferência
5. Construir a instrução de transferência
use zolana_program::instruction::Transact;
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();
payerassina e paga pela transação Solana. Um patrocinador de gas pode pagar a taxa.input_treesidentifica a árvore de Merkle de estado que contém os UTXOs gastos.output_treeidentifica a árvore de Merkle de estado que recebe os compromissos da saída do destinatário e do troco do remetente.interface_transfer_accountsestá vazio porque uma transferência privada movimenta o ativo somente entre saldos privados e não interage com saldos públicos em contas Solana nem em contas de token.owner_signersestá vazio para esta transferência confidencial.datacontém a prova de conhecimento zero e as saídas criptografadas produzidas na etapa anterior.
6. Envie como qualquer transação Solana
6. Envie como qualquer transação Solana
use zolana_client::Rpc;
let signature = client.create_and_send_transaction(
&[transfer_ix],
sender.pubkey(),
&[&sender],
client.compute_budget(),
)?;
let slot = landed_slot(&client, signature)?;
create_and_send_transactionassina e enviatransfer_ixcomo uma transação Solana.landed_slotlê o slot de confirmação usado para controlar a busca do indexador.senderpaga a taxa e autoriza a transferência.
Exemplo Completo de Código
Clone e execute o exemplo: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
Os exemplos usam um Ring confidencial em local/devnet aqui.
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(())
}