E2E Confidential Voting Demo
Pre-Alpha Disclaimer: This is an early pre-alpha release for exploring the SDK and starting development only. There is no real encryption — all data is completely public and stored as plaintext on-chain. Do not submit any sensitive or real data. Encryption keys and the trust model are not final; do not rely on any encryption guarantees or key material until mainnet. All interfaces, APIs, and data formats are subject to change without notice. The Solana program and all on-chain data will be wiped periodically and everything will be deleted when we transition to Encrypt Alpha 1. This software is provided “as is” without warranty of any kind; use is entirely at your own risk and dWallet Labs assumes no liability for any damages arising from its use.
Run a full confidential voting scenario against the Encrypt pre-alpha on Solana devnet.
Prerequisites
- Rust toolchain (edition 2024)
- just command runner
- bun (for TypeScript demos)
- Solana CLI (for airdrop)
Install JS dependencies:
bun install
Quick Start
The demos connect to the pre-alpha executor on devnet automatically:
just demo-web3 <ENCRYPT_PROGRAM_ID> <VOTING_PROGRAM_ID>
Available Demos
| Command | SDK | File |
|---|---|---|
just demo-web3 <ENC> <VOTE> | @solana/web3.js (v1) | e2e-voting-web3.ts |
just demo-kit <ENC> <VOTE> | @solana/kit (v2) | e2e-voting-kit.ts |
just demo-gill <ENC> <VOTE> | gill | e2e-voting-gill.ts |
just demo-rust <ENC> <VOTE> | solana-sdk (Rust) | e2e-voting-rust/ |
just demo <ENC> <VOTE> | All four sequentially |
What the Demo Does
- Create proposal — initializes encrypted yes/no tally counters (both start at 0)
- Cast 5 votes — 3 YES + 2 NO, each as an encrypted boolean via
execute_graph - Close proposal — authority locks voting
- Request decryption — asks executor to decrypt the tally ciphertexts
- Reveal results — reads decrypted values on-chain
Expected output:
═══ Results ═══
→ Total votes: 5
→ Yes votes: 3
→ No votes: 2
Proposal PASSED (3 yes / 2 no)
How It Works
The demo script acts as a user client only — it encrypts values client-side, submits them via gRPC, and sends on-chain transactions. The pre-alpha executor running on devnet handles everything else:
- Script encrypts vote value and submits via gRPC
CreateInput→ executor verifies proof + creates ciphertext on-chain → returns account address - Script sends
cast_voteon voting program (CPI toexecute_graph) - Executor detects
GraphExecutedevent, evaluates the graph, and callscommit_ciphertext - Script sends
request_decryptionon voting program (CPI torequest_decryption) - Executor detects
DecryptionRequestedevent, decrypts, and callsrespond_decryption
No authority keypair needed — the client never touches the executor’s keys.
Client SDK Usage
Rust:
#![allow(unused)]
fn main() {
let mut encrypt = EncryptClient::connect().await?;
let vote_ct = encrypt.create_input::<Bool>(true, &program_id, &network_key).await?;
}
TypeScript:
const encrypt = createEncryptClient(); // connects to pre-alpha endpoint
const { ciphertextIdentifiers } = await encrypt.createInput({
chain: Chain.Solana,
inputs: [{ ciphertextBytes: mockCiphertext(1n), fheType: 0 }],
authorized: programId.toBytes(),
networkEncryptionPublicKey: networkKey,
});
Reading Ciphertexts Off-Chain
Rust:
#![allow(unused)]
fn main() {
let result = client.read_ciphertext(&ct_pubkey, &reencryption_key, epoch, &keypair).await?;
// result.value = plaintext bytes (mock) or re-encrypted ct (production)
}
TypeScript:
import { encodeReadCiphertextMessage } from "@encrypt.xyz/pre-alpha-solana-client/grpc";
const msg = encodeReadCiphertextMessage(Chain.Solana, ctId, new Uint8Array(), 1n);
const result = await encrypt.readCiphertext({
message: msg,
signature: Buffer.alloc(64), // not needed for public ciphertexts
signer: Buffer.alloc(32),
});
For private ciphertexts, sign the BCS message with your ed25519 keypair.
Troubleshooting
Airdrop failed — Solana devnet faucet may be rate-limited. Wait a few seconds and retry, or fund the keypair manually.
Transaction simulation failed — the program may have been wiped (pre-alpha data is reset periodically). Check the program ID is still deployed.
Timeout waiting for executor — the pre-alpha executor may be restarting. Wait a minute and retry.