placeBet action handles all of this automatically - you only need these utilities if you are building a custom bet submission flow, a position viewer, or a tool that needs to inspect encrypted positions client-side.
client.actions.placeBet calls createUserKeypair, fetchMxePublicKey, createCipher, and encryptBetInput internally. You do not need to call these functions manually for a standard bet placement. These utilities are documented here for advanced integrations such as batch bet tools, custom UI overlays, or automated resolvers that need to pre-check winner eligibility.Privacy model
When a bet is placed, the following steps happen client-side before any transaction is sent:createUserKeypair()- generates a fresh x25519 keypair. The public key is stored on-chain in the position account; the private key never leaves the client.fetchMxePublicKey(client)- fetches the Arcium MXE cluster’s public key (cached after first call).createCipher(privateKey, mxePublicKey)- derives a shared secret and constructs aRescueCipherinstance.encryptBetInput({ side, amount, ... })- encrypts the side and net amount into two 32-byte ciphertexts.- The ciphertexts, your x25519 public key, and a nonce are submitted on-chain. The Arcium MPC cluster later uses its private key to derive the same shared secret and decrypt your bet during settlement.
createUserKeypair
Generates a fresh x25519 keypair for encrypting a single bet. Each bet should use its own keypair.Promise<UserCryptoKeypair> - a UserCryptoKeypair wrapping the x25519 key pair used for ECDH encryption.
fetchMxePublicKey
Fetches the Arcium MXE (Multi-party eXecution Environment) cluster’s x25519 public key. The result is cached after the first call perCypherClient instance.
An initialised
CypherClient instance. The function reads client.cluster to target the correct Arcium deployment.Promise<Uint8Array | null> - 32-byte raw x25519 public key bytes, or null if the MXE account is not found.
createCipher
Derives the ECDH shared secret and returns aRescueCipher instance ready for encryption or decryption.
Raw 32-byte x25519 private key bytes (not the
CryptoKey object - export it first with crypto.subtle.exportKey("raw", key)).Raw 32-byte MXE x25519 public key bytes from
fetchMxePublicKey.RescueCipher - a cipher instance. Use it with encryptBetInput or decryptBetInput.
encryptBetInput
Encrypts a(side, amount) pair into two 32-byte ciphertexts. This is what gets stored in position.encryptedSide and position.encryptedAmount on-chain.
Net bet amount in USDC lamports (after fees). This is
netAmount from computeFees.Outcome index. Same value as the
side parameter you pass to placeBet.A
RescueCipher instance from createCipher.Optional 16-byte nonce. If omitted, a fresh random nonce is generated via
freshNonce().EncryptedBetInput:
32-byte ciphertext for the stake amount.
32-byte ciphertext for the outcome side.
16-byte nonce used for encryption. Stored on-chain in
position.nonce.decryptBetInput
Decrypts an on-chainEncryptedPosition to reveal the original side and amount. Only the user who placed the bet (holding the original private key) can decrypt.
The on-chain position account with
encryptedAmount, encryptedSide, and nonce fields.A
RescueCipher instance constructed with the user’s saved private key and the MXE public key.16-byte nonce. Derive from the on-chain
position.nonce (a bigint u128) using bigIntToLeBytes(position.nonce, 16).BetInput:
The original outcome index (0 = NO/first, 1 = YES/second, etc.).
The net USDC lamport amount that entered the pool.
bigIntToLeBytes
Converts abigint to a little-endian Uint8Array of a specified byte length. Used to prepare the nonce for decryption.
The
bigint value to convert.Output byte length. Use
16 for a u128 nonce.Uint8Array of the specified length.
Full custom bet flow
The following shows how to build a complete bet submission without usingclient.actions.placeBet, composing the raw primitives yourself:
