> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inco.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Client Integration

> TypeScript usage examples for the Private Raffle

# Client Integration

This guide shows how to interact with the Private Raffle program using TypeScript.

## Setup

```typescript theme={null}
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { PrivateLottery } from "../target/types/private_lottery";
import { 
  PublicKey, 
  Keypair, 
  SystemProgram, 
  Connection,
  SYSVAR_INSTRUCTIONS_PUBKEY,
  Transaction 
} from "@solana/web3.js";
import nacl from "tweetnacl";
import { encryptValue } from "@inco/solana-sdk/encryption";
import { decrypt } from "@inco/solana-sdk/attested-decrypt";
import { hexToBuffer, handleToBuffer, plaintextToBuffer } from "@inco/solana-sdk/utils";

const INCO_LIGHTNING_PROGRAM_ID = new PublicKey(
  "5sjEbPiqgZrYwR31ahR6Uk9wf5awoX61YGg7jExQSwaj"
);

const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);

const program = anchor.workspace.privateLottery as Program<PrivateLottery>;
const wallet = (provider.wallet as any).payer as Keypair;
```

## Derive PDAs

```typescript theme={null}
const lotteryId = Math.floor(Date.now() / 1000);
const idBuffer = Buffer.alloc(8);
idBuffer.writeBigUInt64LE(BigInt(lotteryId));

const [lotteryPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("lottery"), idBuffer],
  program.programId
);

const [vaultPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("vault"), lotteryPda.toBuffer()],
  program.programId
);

const [ticketPda] = PublicKey.findProgramAddressSync(
  [Buffer.from("ticket"), lotteryPda.toBuffer(), wallet.publicKey.toBuffer()],
  program.programId
);
```

## Helper Functions

### Derive Allowance PDA

```typescript theme={null}
function deriveAllowancePda(handle: bigint): [PublicKey, number] {
  const buf = Buffer.alloc(16);
  let v = handle;
  for (let i = 0; i < 16; i++) {
    buf[i] = Number(v & BigInt(0xff));
    v >>= BigInt(8);
  }
  return PublicKey.findProgramAddressSync(
    [buf, wallet.publicKey.toBuffer()],
    INCO_LIGHTNING_PROGRAM_ID
  );
}
```

### Decrypt Handle

```typescript theme={null}
async function decryptHandle(handle: string): Promise<{
  plaintext: string;
  ed25519Instructions: any[];
} | null> {
  await new Promise(r => setTimeout(r, 2000));
  try {
    const result = await decrypt([handle], {
      address: wallet.publicKey,
      signMessage: async (msg: Uint8Array) => 
        nacl.sign.detached(msg, wallet.secretKey),
    });
    return {
      plaintext: result.plaintexts[0],
      ed25519Instructions: result.ed25519Instructions,
    };
  } catch {
    return null;
  }
}
```

### Get Handle from Simulation

```typescript theme={null}
async function getHandleFromSimulation(
  tx: Transaction,
  prefix: string
): Promise<bigint | null> {
  const { blockhash } = await connection.getLatestBlockhash();
  tx.recentBlockhash = blockhash;
  tx.feePayer = wallet.publicKey;
  tx.sign(wallet);

  const sim = await connection.simulateTransaction(tx);
  for (const log of sim.value.logs || []) {
    if (log.includes(prefix)) {
      const match = log.match(/(\d+)/);
      if (match) return BigInt(match[1]);
    }
  }
  return null;
}
```

***

## Step-by-Step Flow

### 1. Create Raffle

```typescript theme={null}
const TICKET_PRICE = 10_000_000; // 0.01 SOL

await program.methods
  .createLottery(new anchor.BN(lotteryId), new anchor.BN(TICKET_PRICE))
  .accounts({
    authority: wallet.publicKey,
    lottery: lotteryPda,
    vault: vaultPda,
    systemProgram: SystemProgram.programId,
  })
  .rpc();

console.log("Raffle created! Guess a number 1-100");
```

### 2. Buy Ticket with Encrypted Guess

```typescript theme={null}
const MY_GUESS = 42;
console.log("My guess:", MY_GUESS, "(encrypted, nobody sees this!)");

const encryptedGuess = await encryptValue(BigInt(MY_GUESS));

await program.methods
  .buyTicket(hexToBuffer(encryptedGuess))
  .accounts({
    buyer: wallet.publicKey,
    lottery: lotteryPda,
    ticket: ticketPda,
    vault: vaultPda,
    systemProgram: SystemProgram.programId,
    incoLightningProgram: INCO_LIGHTNING_PROGRAM_ID,
  })
  .rpc();

console.log("Ticket purchased!");
```

### 3. Authority Draws Winning Number

```typescript theme={null}
const WINNING_NUMBER = 42;
console.log("Winning number:", WINNING_NUMBER, "(encrypted, nobody sees!)");

const encryptedWinning = await encryptValue(BigInt(WINNING_NUMBER));

await program.methods
  .drawWinner(hexToBuffer(encryptedWinning))
  .accounts({
    authority: wallet.publicKey,
    lottery: lotteryPda,
    systemProgram: SystemProgram.programId,
    incoLightningProgram: INCO_LIGHTNING_PROGRAM_ID,
  })
  .rpc();

console.log("Winning number set!");
```

### 4. Check If Won (Encrypted Comparison)

<Note>
  Currently, checking the result requires a transaction. When attested compute is introduced, this will be done off-chain without a transaction.
</Note>

```typescript theme={null}
// First simulate to get the result handle
const txForSim = await program.methods
  .checkWinner()
  .accounts({
    checker: wallet.publicKey,
    lottery: lotteryPda,
    ticket: ticketPda,
    systemProgram: SystemProgram.programId,
    incoLightningProgram: INCO_LIGHTNING_PROGRAM_ID,
  })
  .transaction();

const resultHandle = await getHandleFromSimulation(txForSim, "Result handle:");

if (resultHandle) {
  // Derive allowance PDA and submit with remaining accounts
  const [allowancePda] = deriveAllowancePda(resultHandle);

  await program.methods
    .checkWinner()
    .accounts({
      checker: wallet.publicKey,
      lottery: lotteryPda,
      ticket: ticketPda,
      systemProgram: SystemProgram.programId,
      incoLightningProgram: INCO_LIGHTNING_PROGRAM_ID,
    })
    .remainingAccounts([
      { pubkey: allowancePda, isSigner: false, isWritable: true },
      { pubkey: wallet.publicKey, isSigner: false, isWritable: false },
    ])
    .rpc();

  // Decrypt the result
  const result = await decryptHandle(resultHandle.toString());
  if (result) {
    const won = result.plaintext === "1";
    console.log("Did I win?", won ? "YES!" : "No");
  }
}
```

### 5. Claim Prize

```typescript theme={null}
const txForSim = await program.methods
  .claimPrize()
  .accounts({
    claimer: wallet.publicKey,
    lottery: lotteryPda,
    ticket: ticketPda,
    vault: vaultPda,
    systemProgram: SystemProgram.programId,
    incoLightningProgram: INCO_LIGHTNING_PROGRAM_ID,
  })
  .transaction();

const prizeHandle = await getHandleFromSimulation(txForSim, "Prize handle:");

if (prizeHandle) {
  const [allowancePda] = deriveAllowancePda(prizeHandle);

  await program.methods
    .claimPrize()
    .accounts({
      claimer: wallet.publicKey,
      lottery: lotteryPda,
      ticket: ticketPda,
      vault: vaultPda,
      systemProgram: SystemProgram.programId,
      incoLightningProgram: INCO_LIGHTNING_PROGRAM_ID,
    })
    .remainingAccounts([
      { pubkey: allowancePda, isSigner: false, isWritable: true },
      { pubkey: wallet.publicKey, isSigner: false, isWritable: false },
    ])
    .rpc();

  console.log("Claim processed!");
}
```

### 6. Withdraw Prize (with On-Chain Verification)

```typescript theme={null}
// Fetch ticket to get prize handle
const ticket = await program.account.ticket.fetch(ticketPda);
const prizeHandle = ticket.prizeHandle.toString();

if (prizeHandle === "0") {
  console.log("No prize to claim");
  return;
}

// Decrypt prize amount (includes Ed25519 instructions for verification)
const result = await decryptHandle(prizeHandle);
if (!result) {
  console.log("Failed to decrypt");
  return;
}

const prize = BigInt(result.plaintext);
console.log("Prize amount:", Number(prize) / 1e9, "SOL");

if (prize > 0) {
  // Build transaction with Ed25519 signature verification
  const withdrawIx = await program.methods
    .withdrawPrize(
      handleToBuffer(prizeHandle),
      plaintextToBuffer(result.plaintext)
    )
    .accounts({
      winner: wallet.publicKey,
      lottery: lotteryPda,
      ticket: ticketPda,
      vault: vaultPda,
      instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
      systemProgram: SystemProgram.programId,
      incoLightningProgram: INCO_LIGHTNING_PROGRAM_ID,
    })
    .instruction();

  const tx = new Transaction();
  
  // Add Ed25519 signature instructions FIRST
  result.ed25519Instructions.forEach(ix => tx.add(ix));
  
  // Then add withdraw instruction
  tx.add(withdrawIx);

  const { blockhash } = await connection.getLatestBlockhash();
  tx.recentBlockhash = blockhash;
  tx.feePayer = wallet.publicKey;

  const signedTx = await provider.wallet.signTransaction(tx);
  const sig = await connection.sendRawTransaction(signedTx.serialize());
  await connection.confirmTransaction(sig, "confirmed");

  console.log("Prize withdrawn!", sig);
} else {
  console.log("Not a winner - prize is 0");
}
```

***

## Non-Winner Flow

When a player doesn't win:

1. `check_winner` returns encrypted `false` (decrypts to "0")
2. `claim_prize` uses `e_select` to set `prize_handle` to encrypted 0
3. `withdraw_prize` fails with `NotWinner` error (prize amount is 0)

```typescript theme={null}
// Prize is 0 - cannot withdraw
try {
  await program.methods
    .withdrawPrize(handleToBuffer(prizeHandle), plaintextToBuffer("0"))
    .accounts({...})
    .rpc();
} catch (e) {
  // Error: NotWinner - this is expected!
  console.log("Correctly rejected - not a winner");
}
```

***

## Running Tests

```bash theme={null}
anchor test --provider.cluster devnet
```

The test file includes both winner and non-winner flows to verify the entire system works correctly.
