Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Solana development skill covering @solana/kit v5, Anchor programs, LiteSVM testing, and security patterns.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/confidential-transfers.md
1---2title: Confidential Transfers3description: Implement private, encrypted token balances on Solana using the Token-2022 confidential transfers extension.4---56# Confidential Transfers (Token-2022 Extension)78## When to use this guidance910Use this guidance when the user asks about:1112- Private/encrypted token balances13- Confidential transfers or balances on Solana14- Zero-knowledge proofs for token transfers15- Token-2022 confidential transfer extension(s)16- ElGamal encryption for tokens1718## Current Network Availability1920**Important:** Confidential transfers are currently only available on a TXTX cluster.2122- RPC endpoint: `https://zk-edge.surfnet.dev/`23- Mainnet availability expected in a few months2425When building for confidential transfers, always use the ZK-Edge RPC for testing. Plan for mainnet migration by abstracting the RPC endpoint configuration. Ensure the user is aware of this.2627## Key Concepts2829### What are Confidential Transfers?3031Confidential transfers encrypt token balances and transfer amounts using zero-knowledge cryptography. onchain observers cannot see actual amounts, but the system still verifies:3233- Sender has sufficient balance34- Transfer amounts are non-negative35- No tokens are created or destroyed3637### Balance Types3839Each confidential-enabled account has three balance types:4041- **Public**: Standard visible SPL balance42- **Pending**: Encrypted incoming transfers awaiting application43- **Available**: Encrypted balance ready for outgoing transfers4445### Encryption Keys4647Two keys are derived deterministically from the account owner's keypair:4849- **ElGamal keypair**: Used for transfer encryption (asymmetric)50- **AES key**: Used for balance decryption by owner (symmetric)5152### Privacy Levels5354Mints can configure four privacy modes:5556- `Disabled`: No confidential transfers57- `Whitelisted`: Only approved accounts58- `OptIn`: Accounts choose to enable59- `Required`: All transfers must be confidential6061## Dependencies6263```toml64[dependencies]65# Solana core66solana-sdk = "3.0.0"67solana-client = "3.1.6"68solana-zk-sdk = "5.0.0"69solana-commitment-config = "3.1.0"7071# Token-202272spl-token-2022 = { version = "10.0.0", features = ["zk-ops"] }73spl-token-client = "0.18.0"74spl-associated-token-account = "8.0.0"7576# Confidential transfer proofs77spl-token-confidential-transfer-proof-generation = "0.5.1"78spl-token-confidential-transfer-proof-extraction = "0.5.1"7980# Async runtime81tokio = { version = "1", features = ["full"] }82```8384## Common Types8586```rust87use solana_sdk::signature::Signature;88use std::error::Error;8990pub type CtResult<T> = Result<T, Box<dyn Error>>;91pub type SigResult = CtResult<Signature>;92pub type MultiSigResult = CtResult<Vec<Signature>>;93```9495## Operation Flow9697The typical flow for confidential transfers:98991. **Configure** - Enable confidential transfers on a token account1002. **Deposit** - Move tokens from public to pending balance1013. **Apply Pending** - Move pending to available balance1024. **Transfer** - Send from available balance (encrypted)1035. **Withdraw** - Move from available back to public balance104105## Key Operations106107### 1. Configure Account for Confidential Transfers108109Before using confidential transfers, accounts must be configured with encryption keys:110111```rust112use solana_client::rpc_client::RpcClient;113use solana_sdk::{signature::Signer, transaction::Transaction};114use spl_associated_token_account::get_associated_token_address_with_program_id;115use spl_token_2022::{116extension::{117confidential_transfer::instruction::{configure_account, PubkeyValidityProofData},118ExtensionType,119},120instruction::reallocate,121solana_zk_sdk::encryption::{auth_encryption::AeKey, elgamal::ElGamalKeypair},122};123use spl_token_confidential_transfer_proof_extraction::instruction::ProofLocation;124125pub async fn configure_account_for_confidential_transfers(126client: &RpcClient,127payer: &dyn Signer,128authority: &dyn Signer,129mint: &solana_sdk::pubkey::Pubkey,130) -> SigResult {131let token_account = get_associated_token_address_with_program_id(132&authority.pubkey(),133mint,134&spl_token_2022::id(),135);136137// Derive encryption keys deterministically from authority138let elgamal_keypair = ElGamalKeypair::new_from_signer(139authority,140&token_account.to_bytes(),141)?;142let aes_key = AeKey::new_from_signer(143authority,144&token_account.to_bytes(),145)?;146147// Maximum pending deposits before apply_pending_balance must be called148let max_pending_balance_credit_counter = 65536u64;149150// Initial decryptable balance (encrypted with AES)151let decryptable_balance = aes_key.encrypt(0);152153// Generate proof that we control the ElGamal public key154let proof_data = PubkeyValidityProofData::new(&elgamal_keypair)155.map_err(|_| "Failed to generate pubkey validity proof")?;156157// Proof will be in the next instruction (offset 1)158let proof_location = ProofLocation::InstructionOffset(1591.try_into().unwrap(),160&proof_data,161);162163let mut instructions = vec![];164165// 1. Reallocate to add ConfidentialTransferAccount extension166instructions.push(reallocate(167&spl_token_2022::id(),168&token_account,169&payer.pubkey(),170&authority.pubkey(),171&[&authority.pubkey()],172&[ExtensionType::ConfidentialTransferAccount],173)?);174175// 2. Configure account (includes proof instruction)176instructions.extend(configure_account(177&spl_token_2022::id(),178&token_account,179mint,180&decryptable_balance.into(),181max_pending_balance_credit_counter,182&authority.pubkey(),183&[],184proof_location,185)?);186187let recent_blockhash = client.get_latest_blockhash()?;188let transaction = Transaction::new_signed_with_payer(189&instructions,190Some(&payer.pubkey()),191&[authority, payer],192recent_blockhash,193);194195let signature = client.send_and_confirm_transaction(&transaction)?;196Ok(signature)197}198```199200### 2. Deposit to Confidential Balance201202Move tokens from public balance to pending confidential balance:203204```rust205use solana_client::rpc_client::RpcClient;206use solana_sdk::{signature::Signer, transaction::Transaction};207use spl_associated_token_account::get_associated_token_address_with_program_id;208use spl_token_2022::extension::confidential_transfer::instruction::deposit;209210pub async fn deposit_to_confidential(211client: &RpcClient,212payer: &dyn Signer,213authority: &dyn Signer,214mint: &solana_sdk::pubkey::Pubkey,215amount: u64,216decimals: u8,217) -> SigResult {218let token_account = get_associated_token_address_with_program_id(219&authority.pubkey(),220mint,221&spl_token_2022::id(),222);223224let deposit_ix = deposit(225&spl_token_2022::id(),226&token_account,227mint,228amount,229decimals,230&authority.pubkey(),231&[&authority.pubkey()],232)?;233234let recent_blockhash = client.get_latest_blockhash()?;235let transaction = Transaction::new_signed_with_payer(236&[deposit_ix],237Some(&payer.pubkey()),238&[payer, authority],239recent_blockhash,240);241242let signature = client.send_and_confirm_transaction(&transaction)?;243Ok(signature)244}245```246247### 3. Apply Pending Balance248249Move tokens from pending to available (spendable) balance:250251```rust252use solana_client::rpc_client::RpcClient;253use solana_sdk::{signature::Signer, transaction::Transaction};254use spl_associated_token_account::get_associated_token_address_with_program_id;255use spl_token_2022::{256extension::{257confidential_transfer::{258instruction::apply_pending_balance as apply_pending_balance_instruction,259ConfidentialTransferAccount,260},261BaseStateWithExtensions, StateWithExtensions,262},263solana_zk_sdk::encryption::{auth_encryption::AeKey, elgamal::ElGamalKeypair},264state::Account as TokenAccount,265};266267pub async fn apply_pending_balance(268client: &RpcClient,269payer: &dyn Signer,270authority: &dyn Signer,271mint: &solana_sdk::pubkey::Pubkey,272) -> SigResult {273let token_account = get_associated_token_address_with_program_id(274&authority.pubkey(),275mint,276&spl_token_2022::id(),277);278279// Derive encryption keys280let elgamal_keypair = ElGamalKeypair::new_from_signer(281authority,282&token_account.to_bytes(),283)?;284let aes_key = AeKey::new_from_signer(285authority,286&token_account.to_bytes(),287)?;288289// Fetch account state290let account_data = client.get_account(&token_account)?;291let account = StateWithExtensions::<TokenAccount>::unpack(&account_data.data)?;292let ct_extension = account.get_extension::<ConfidentialTransferAccount>()?;293294// Decrypt current balances - note: decrypt_u32 is called ON the ciphertext295let pending_balance_lo: spl_token_2022::solana_zk_sdk::encryption::elgamal::ElGamalCiphertext =296ct_extension.pending_balance_lo.try_into()297.map_err(|_| "Failed to convert pending_balance_lo")?;298let pending_balance_hi: spl_token_2022::solana_zk_sdk::encryption::elgamal::ElGamalCiphertext =299ct_extension.pending_balance_hi.try_into()300.map_err(|_| "Failed to convert pending_balance_hi")?;301let available_balance: spl_token_2022::solana_zk_sdk::encryption::elgamal::ElGamalCiphertext =302ct_extension.available_balance.try_into()303.map_err(|_| "Failed to convert available_balance")?;304305// Decrypt using ciphertext.decrypt_u32(secret)306let pending_lo = pending_balance_lo.decrypt_u32(elgamal_keypair.secret())307.ok_or("Failed to decrypt pending_balance_lo")?;308let pending_hi = pending_balance_hi.decrypt_u32(elgamal_keypair.secret())309.ok_or("Failed to decrypt pending_balance_hi")?;310let current_available = available_balance.decrypt_u32(elgamal_keypair.secret())311.ok_or("Failed to decrypt available_balance")?;312313// Calculate new available balance314let pending_total = pending_lo + (pending_hi << 16);315let new_available = current_available + pending_total;316317// Encrypt new available balance with AES for owner318let new_decryptable_balance = aes_key.encrypt(new_available);319320// Get expected pending balance credit counter321let expected_counter: u64 = ct_extension.pending_balance_credit_counter.into();322323let apply_ix = apply_pending_balance_instruction(324&spl_token_2022::id(),325&token_account,326expected_counter,327&new_decryptable_balance.into(),328&authority.pubkey(),329&[&authority.pubkey()],330)?;331332let recent_blockhash = client.get_latest_blockhash()?;333let transaction = Transaction::new_signed_with_payer(334&[apply_ix],335Some(&payer.pubkey()),336&[payer, authority],337recent_blockhash,338);339340let signature = client.send_and_confirm_transaction(&transaction)?;341Ok(signature)342}343```344345### 4. Confidential Transfer346347Transfer tokens between accounts using zero-knowledge proofs. This is the most complex operation requiring multiple transactions and proof context state accounts:348349```rust350use solana_client::rpc_client::RpcClient;351use solana_client::nonblocking::rpc_client::RpcClient as AsyncRpcClient;352use solana_commitment_config::CommitmentConfig;353use solana_sdk::signature::{Keypair, Signer};354use spl_associated_token_account::get_associated_token_address_with_program_id;355use spl_token_2022::{356extension::{357confidential_transfer::{358account_info::TransferAccountInfo,359ConfidentialTransferAccount, ConfidentialTransferMint,360},361BaseStateWithExtensions, StateWithExtensions,362},363solana_zk_sdk::encryption::{364auth_encryption::AeKey,365elgamal::ElGamalKeypair,366pod::elgamal::PodElGamalPubkey,367},368state::{Account as TokenAccount, Mint},369};370use spl_token_client::{371client::{ProgramRpcClient, ProgramRpcClientSendTransaction, RpcClientResponse},372token::{ProofAccountWithCiphertext, Token},373};374use std::sync::Arc;375376fn extract_signature(response: RpcClientResponse) -> Result<solana_sdk::signature::Signature, Box<dyn std::error::Error>> {377match response {378RpcClientResponse::Signature(sig) => Ok(sig),379_ => Err("Expected Signature response".into()),380}381}382383pub async fn transfer_confidential(384client: &RpcClient,385_payer: &dyn Signer,386sender: &Keypair, // Must be Keypair for token client387mint: &solana_sdk::pubkey::Pubkey,388recipient: &solana_sdk::pubkey::Pubkey,389amount: u64,390) -> MultiSigResult {391let sender_token_account = get_associated_token_address_with_program_id(392&sender.pubkey(),393mint,394&spl_token_2022::id(),395);396let recipient_token_account = get_associated_token_address_with_program_id(397recipient,398mint,399&spl_token_2022::id(),400);401402// Get recipient's ElGamal public key403let recipient_account_data = client.get_account(&recipient_token_account)?;404let recipient_account = StateWithExtensions::<TokenAccount>::unpack(&recipient_account_data.data)?;405let recipient_ct_extension = recipient_account.get_extension::<ConfidentialTransferAccount>()?;406let recipient_elgamal_pubkey: spl_token_2022::solana_zk_sdk::encryption::elgamal::ElGamalPubkey =407recipient_ct_extension.elgamal_pubkey.try_into()408.map_err(|_| "Failed to convert recipient ElGamal pubkey")?;409410// Get auditor ElGamal public key from mint (if configured)411let mint_account_data = client.get_account(mint)?;412let mint_account = StateWithExtensions::<Mint>::unpack(&mint_account_data.data)?;413let mint_ct_extension = mint_account.get_extension::<ConfidentialTransferMint>()?;414let auditor_elgamal_pubkey: Option<spl_token_2022::solana_zk_sdk::encryption::elgamal::ElGamalPubkey> =415Option::<PodElGamalPubkey>::from(mint_ct_extension.auditor_elgamal_pubkey)416.map(|pk| pk.try_into())417.transpose()418.map_err(|_| "Failed to convert auditor ElGamal pubkey")?;419420// Derive sender's encryption keys421let sender_elgamal = ElGamalKeypair::new_from_signer(422sender,423&sender_token_account.to_bytes(),424)?;425let sender_aes = AeKey::new_from_signer(426sender,427&sender_token_account.to_bytes(),428)?;429430// Fetch sender account and create transfer info431let account_data = client.get_account(&sender_token_account)?;432let account = StateWithExtensions::<TokenAccount>::unpack(&account_data.data)?;433let ct_extension = account.get_extension::<ConfidentialTransferAccount>()?;434let transfer_info = TransferAccountInfo::new(ct_extension);435436// Verify sufficient balance437let available_balance: spl_token_2022::solana_zk_sdk::encryption::elgamal::ElGamalCiphertext =438transfer_info.available_balance.try_into()439.map_err(|_| "Failed to convert available_balance")?;440let current_available = available_balance.decrypt_u32(sender_elgamal.secret())441.ok_or("Failed to decrypt available balance")?;442443if current_available < amount {444return Err(format!(445"Insufficient balance: have {}, need {}",446current_available, amount447).into());448}449450// Generate split transfer proofs (equality, ciphertext validity, range)451let proof_data = transfer_info.generate_split_transfer_proof_data(452amount,453&sender_elgamal,454&sender_aes,455&recipient_elgamal_pubkey,456auditor_elgamal_pubkey.as_ref(),457)?;458459// Create async client for Token operations460let rpc_url = client.url();461let async_client = Arc::new(AsyncRpcClient::new_with_commitment(462rpc_url,463CommitmentConfig::confirmed(),464));465let program_client = Arc::new(ProgramRpcClient::new(466async_client,467ProgramRpcClientSendTransaction,468));469470// Clone sender for Arc (Token client requires ownership)471let sender_clone = Keypair::new_from_array(*sender.secret_bytes());472let sender_arc: Arc<dyn Signer> = Arc::new(sender_clone);473474let token = Token::new(475program_client,476&spl_token_2022::id(),477mint,478None,479sender_arc,480);481482// Create proof context state accounts483let equality_proof_account = Keypair::new();484let ciphertext_validity_proof_account = Keypair::new();485let range_proof_account = Keypair::new();486487let mut signatures = Vec::new();488489// 1. Create equality proof context account490let response = token.confidential_transfer_create_context_state_account(491&equality_proof_account.pubkey(),492&sender.pubkey(),493&proof_data.equality_proof_data,494false,495&[&equality_proof_account],496).await?;497signatures.push(extract_signature(response)?);498499// 2. Create ciphertext validity proof context account500let response = token.confidential_transfer_create_context_state_account(501&ciphertext_validity_proof_account.pubkey(),502&sender.pubkey(),503&proof_data.ciphertext_validity_proof_data_with_ciphertext.proof_data,504false,505&[&ciphertext_validity_proof_account],506).await?;507signatures.push(extract_signature(response)?);508509// 3. Create range proof context account510let response = token.confidential_transfer_create_context_state_account(511&range_proof_account.pubkey(),512&sender.pubkey(),513&proof_data.range_proof_data,514true, // Range proof uses batched verification515&[&range_proof_account],516).await?;517signatures.push(extract_signature(response)?);518519// 4. Execute the confidential transfer520let ciphertext_validity_proof = ProofAccountWithCiphertext {521context_state_account: ciphertext_validity_proof_account.pubkey(),522ciphertext_lo: proof_data.ciphertext_validity_proof_data_with_ciphertext.ciphertext_lo,523ciphertext_hi: proof_data.ciphertext_validity_proof_data_with_ciphertext.ciphertext_hi,524};525526let response = token.confidential_transfer_transfer(527&sender_token_account,528&recipient_token_account,529&sender.pubkey(),530Some(&equality_proof_account.pubkey()),531Some(&ciphertext_validity_proof),532Some(&range_proof_account.pubkey()),533amount,534None,535&sender_elgamal,536&sender_aes,537&recipient_elgamal_pubkey,538auditor_elgamal_pubkey.as_ref(),539&[sender],540).await?;541signatures.push(extract_signature(response)?);542543// 5. Close proof context accounts to reclaim rent544let response = token.confidential_transfer_close_context_state_account(545&equality_proof_account.pubkey(),546&sender_token_account,547&sender.pubkey(),548&[sender],549).await?;550signatures.push(extract_signature(response)?);551552let response = token.confidential_transfer_close_context_state_account(553&ciphertext_validity_proof_account.pubkey(),554&sender_token_account,555&sender.pubkey(),556&[sender],557).await?;558signatures.push(extract_signature(response)?);559560let response = token.confidential_transfer_close_context_state_account(561&range_proof_account.pubkey(),562&sender_token_account,563&sender.pubkey(),564&[sender],565).await?;566signatures.push(extract_signature(response)?);567568Ok(signatures)569}570```571572### 5. Withdraw from Confidential Balance573574Move tokens from available confidential balance back to public balance:575576```rust577use solana_client::rpc_client::RpcClient;578use solana_sdk::{signature::Signer, transaction::Transaction};579use spl_associated_token_account::get_associated_token_address_with_program_id;580use spl_token_2022::{581extension::{582confidential_transfer::{583account_info::WithdrawAccountInfo,584instruction::withdraw,585ConfidentialTransferAccount,586},587BaseStateWithExtensions, StateWithExtensions,588},589solana_zk_sdk::encryption::{auth_encryption::AeKey, elgamal::ElGamalKeypair},590state::Account as TokenAccount,591};592use spl_token_confidential_transfer_proof_extraction::instruction::ProofLocation;593594pub async fn withdraw_from_confidential(595client: &RpcClient,596payer: &dyn Signer,597authority: &dyn Signer,598mint: &solana_sdk::pubkey::Pubkey,599amount: u64,600decimals: u8,601) -> SigResult {602let token_account = get_associated_token_address_with_program_id(603&authority.pubkey(),604mint,605&spl_token_2022::id(),606);607608// Derive encryption keys609let elgamal_keypair = ElGamalKeypair::new_from_signer(610authority,611&token_account.to_bytes(),612)?;613let aes_key = AeKey::new_from_signer(614authority,615&token_account.to_bytes(),616)?;617618// Fetch account state619let account_data = client.get_account(&token_account)?;620let account = StateWithExtensions::<TokenAccount>::unpack(&account_data.data)?;621let ct_extension = account.get_extension::<ConfidentialTransferAccount>()?;622623// Create withdraw account info helper624let withdraw_info = WithdrawAccountInfo::new(ct_extension);625626// Decrypt available balance to verify sufficiency627let available_balance: spl_token_2022::solana_zk_sdk::encryption::elgamal::ElGamalCiphertext =628withdraw_info.available_balance.try_into()629.map_err(|_| "Failed to convert available_balance")?;630let current_available = available_balance.decrypt_u32(elgamal_keypair.secret())631.ok_or("Failed to decrypt available balance")?;632633if current_available < amount {634return Err(format!(635"Insufficient confidential balance: have {}, need {}",636current_available, amount637).into());638}639640// Generate withdrawal proofs using the helper641let proof_data = withdraw_info.generate_proof_data(642amount,643&elgamal_keypair,644&aes_key,645)?;646647// Calculate new decryptable available balance after withdrawal648let new_available = current_available - amount;649let new_decryptable_balance = aes_key.encrypt(new_available);650651// Build withdraw instruction with two proof locations (equality + range)652let withdraw_instructions = withdraw(653&spl_token_2022::id(),654&token_account,655mint,656amount,657decimals,658&new_decryptable_balance.into(),659&authority.pubkey(),660&[&authority.pubkey()],661ProofLocation::InstructionOffset(1.try_into().unwrap(), &proof_data.equality_proof_data),662ProofLocation::InstructionOffset(2.try_into().unwrap(), &proof_data.range_proof_data),663)?;664665let recent_blockhash = client.get_latest_blockhash()?;666let transaction = Transaction::new_signed_with_payer(667&withdraw_instructions,668Some(&payer.pubkey()),669&[payer, authority],670recent_blockhash,671);672673let signature = client.send_and_confirm_transaction(&transaction)?;674Ok(signature)675}676```677678## Reading Balances679680To read and decrypt all balance types:681682```rust683pub fn get_confidential_balances(684client: &RpcClient,685authority: &dyn Signer,686mint: &solana_sdk::pubkey::Pubkey,687) -> Result<(u64, u64, u64), Box<dyn std::error::Error>> {688let token_account = get_associated_token_address_with_program_id(689&authority.pubkey(),690mint,691&spl_token_2022::id(),692);693694let elgamal_keypair = ElGamalKeypair::new_from_signer(authority, &token_account.to_bytes())?;695let aes_key = AeKey::new_from_signer(authority, &token_account.to_bytes())?;696697let account_data = client.get_account(&token_account)?;698let account = StateWithExtensions::<TokenAccount>::unpack(&account_data.data)?;699let ct_extension = account.get_extension::<ConfidentialTransferAccount>()?;700701// Public balance (visible to all)702let public_balance = account.base.amount;703704// Pending balance (decrypt with ElGamal) - note method is on ciphertext705let pending_lo_ct: spl_token_2022::solana_zk_sdk::encryption::elgamal::ElGamalCiphertext =706ct_extension.pending_balance_lo.try_into()?;707let pending_hi_ct: spl_token_2022::solana_zk_sdk::encryption::elgamal::ElGamalCiphertext =708ct_extension.pending_balance_hi.try_into()?;709710let pending_lo = pending_lo_ct.decrypt_u32(elgamal_keypair.secret()).unwrap_or(0) as u64;711let pending_hi = pending_hi_ct.decrypt_u32(elgamal_keypair.secret()).unwrap_or(0) as u64;712let pending_balance = pending_lo + (pending_hi << 16);713714// Available balance (decrypt with AES - only owner can see)715let available_balance = aes_key.decrypt(&ct_extension.decryptable_available_balance.try_into()?)?;716717Ok((public_balance, pending_balance, available_balance))718}719```720721## Security Considerations722723- **Key derivation is deterministic**: The same keypair always produces the same encryption keys for a given token account. This enables recovery but means keypair compromise exposes all confidential balances.724- **Auditor keys**: Mints can configure an auditor ElGamal public key that can decrypt all transfer amounts (but not balances).725- **Pending balance limits**: The `max_pending_balance_credit_counter` limits how many incoming transfers can accumulate before `apply_pending` must be called.726- **Proof verification**: All proofs are verified by the ZK ElGamal Proof Program onchain (`ZkE1Gama1Proof11111111111111111111111111111`).727728## Reference Implementation729730For complete working examples including mint creation, see:731https://github.com/gitteri/confidential-balances-exploration (Rust) and732https://github.com/catmcgee/confidential-transfers-explorer (TypeScript)733734## Limitations735736- Currently only works on ZK-Edge testnet (`https://zk-edge.surfnet.dev/`)737- Transfer operations require multiple transactions (7 total) due to proof size. This will be lower when larger transactions are merged into mainnet738- Proof generation can be computationally intensive (client-side)739- Sender must be a `Keypair` (not generic `Signer`) for transfers due to token client requirements740