Cypher uses key exchange combined with Arcium’s RescueCipher to encrypt bet inputs before they reach the blockchain. The scheme ensures that no one - not even Solana validators - can read a position’s stake or side until the Arcium nodes run the reveal circuit.
createUserKeypair() uses crypto.getRandomValues - browser-safe and works in Node 20+.Use a fresh keypair per bet. Reusing a keypair across bets doesn’t break the cipher (nonces rotate), but it leaks that two positions belong to the same wallet to anyone watching EncryptedPosition.userPubkey on-chain.The high-level placeBetAction generates a keypair automatically. Only call createUserKeypair manually if you want to pre-generate and show it to the user before the bet is submitted.
import { deriveSharedSecret } from "@cypher-zk/sdk";const sharedSecret = deriveSharedSecret(userPrivateKey, mxePublicKey);
This is standard x25519 . The user’s private key + the MXE’s (Arcium node’s) public key → a 32-byte shared secret. Only the MXE can derive the same secret from its side, so only Arcium nodes can decrypt positions.
amount is the net stake after fees, not the gross bet. placeBetAction computes this automatically. If you are building instructions directly, compute fees via computeFees(grossAmount, { protocolFeeRateBps, lpFeeRateBps }).
Use saveSecret / loadSecret to persist the private key in localStorage. The key format is cypher:pos:{marketBase58}:{betIndex}.
TypeScript
// Save immediately after placeBetsaveSecret(result.position!.market, result.betIndex, result.userKeypair.privateKey);// Load to decryptconst privateKey = loadSecret(marketPda, betIndex);
Call saveSecret in onSuccess of placeBet, before any navigation or redirect. If the tab closes before saving, the user can still claim their payout (that doesn’t need the key), but the bet side and stake won’t be visible in the UI until the market resolves and pools are revealed.