A confidential raffle system built on Solana using Inco Lightning rust SDK for encrypted compute on Solana. Players submit encrypted guesses, and the winning number remains hidden until verification, ensuring complete privacy throughout the game.
This program implements a simple number-guessing raffle (1-100) where:
- Player guesses are encrypted and hidden from everyone
- The winning number is encrypted and hidden from everyone
- Winner determination happens through encrypted comparison
- Only the ticket owner can decrypt their result
| Data | Visibility |
|---|---|
| Player's guess | Encrypted (only player can decrypt) |
| Winning number | Encrypted (set by authority) |
| Win/loss result | Encrypted (only ticket owner can decrypt) |
| Prize amount | Encrypted (only ticket owner can decrypt) |
1. create_raffle -> Authority creates raffle with ticket price
2. buy_ticket -> Player submits encrypted guess (1-100)
3. draw_winner -> Authority sets encrypted winning number
4. check_winner -> Encrypted comparison: guess == winning_number
5. claim_prize -> e_select(is_winner, prize, 0) computes encrypted prize
6. withdraw_prize -> On-chain signature verification, transfer if prize > 0
new_euint128: Create encrypted value from ciphertexte_eq: Encrypted equality comparisone_select: Encrypted conditional selectionallow: Grant decryption permission to specific addressis_validsignature: Verify decryption proof on-chain
pub struct Raffle {
pub authority: Pubkey,
pub raffle_id: u64,
pub ticket_price: u64,
pub participant_count: u32,
pub is_open: bool,
pub prize_claimed: bool, // True when a winner has withdrawn
pub winning_number_handle: u128, // Encrypted winning number (1-100)
pub bump: u8,
}pub struct Ticket {
pub raffle: Pubkey,
pub owner: Pubkey,
pub guess_handle: u128, // Encrypted guess (1-100)
pub is_winner_handle: u128, // Encrypted: guess == winning?
pub claimed: bool, // Whether this ticket holder has withdrawn
pub bump: u8,
}- Rust 1.70+
- Solana CLI 1.18+
- Anchor 0.31.1
- Node.js 18+
- Yarn
# Clone repository
git clone https://github.com/Inco-fhevm/raffle-example-solana
cd raffle-example-solana
# Install dependencies
yarn install
# Build program
anchor build# Get program keypair address
solana address -k target/deploy/keypair.json
# Update program ID in lib.rs and Anchor.toml with the address above
# Rebuild with correct program ID
anchor build
# Deploy to devnet
anchor deploy --provider.cluster devnet# Run tests (after deployment)
anchor test --skip-deployThe test suite covers two scenarios:
- Winner Flow: Player guesses correctly and withdraws prize
- Non-Winner Flow: Player guesses incorrectly and withdrawal fails
import { encryptValue } from "@inco/solana-sdk/encryption";
import { decrypt } from "@inco/solana-sdk/attested-decrypt";
import { hexToBuffer } from "@inco/solana-sdk/utils";
// Encrypt guess
const myGuess = 42;
const encryptedGuess = await encryptValue(BigInt(myGuess));
// Buy ticket
await program.methods
.buyTicket(hexToBuffer(encryptedGuess))
.accounts({...})
.rpc();
// Decrypt result after checking
const result = await decrypt([resultHandle], {
address: wallet.publicKey,
signMessage: async (msg) => nacl.sign.detached(msg, wallet.secretKey),
});
const isWinner = result.plaintexts[0] === "1";To decrypt encrypted values, the program must grant permission via the allow instruction. This is done through remaining accounts:
const [allowancePda] = PublicKey.findProgramAddressSync(
[handleBuffer, walletPublicKey.toBuffer()],
INCO_LIGHTNING_PROGRAM_ID
);
await program.methods
.checkWinner()
.accounts({...})
.remainingAccounts([
{ pubkey: allowancePda, isSigner: false, isWritable: true },
{ pubkey: wallet.publicKey, isSigner: false, isWritable: false },
])
.rpc();Prize withdrawal requires on-chain verification of the decryption proof:
const result = await decrypt([prizeHandle], {...});
// Build transaction with Ed25519 signature + withdraw instruction
const tx = new Transaction();
result.ed25519Instructions.forEach(ix => tx.add(ix));
tx.add(withdrawInstruction);[dependencies]
anchor-lang = "0.31.1"
inco-lightning = { version = "0.1.4", features = ["cpi"] }Navigate to the app folder:
cd appInstall the dependencies:
bun installStart the app:
bun run devThe app will start on localhost:3000