From 3471170c2fff42773f7d3626e8a0160b1f9f8266 Mon Sep 17 00:00:00 2001 From: majizzy Date: Sat, 16 Aug 2025 14:57:08 +0100 Subject: [PATCH] Create randomness-tutorial.md Design and deploy randomness-powered dApps on NEAR with focus on user experience, business logic integration, and cross-chain compatibility patterns. --- docs/tutorials/randomness-tutorial.md | 855 ++++++++++++++++++++++++++ 1 file changed, 855 insertions(+) create mode 100644 docs/tutorials/randomness-tutorial.md diff --git a/docs/tutorials/randomness-tutorial.md b/docs/tutorials/randomness-tutorial.md new file mode 100644 index 00000000000..e9d552bb291 --- /dev/null +++ b/docs/tutorials/randomness-tutorial.md @@ -0,0 +1,855 @@ +--- +id: randomness-ecosystem-guide +title: "Building Randomness-Driven Applications" +description: "Design and deploy randomness-powered dApps on NEAR with focus on user experience, business logic integration, and cross-chain compatibility patterns." +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import {CodeTabs, Language, Github} from "@site/src/components/codetabs" + +Randomness isn't just a technical challenge—it's the foundation for entire categories of blockchain applications. From gaming ecosystems to DeFi protocols, understanding how to architect randomness-driven systems determines the success of your dApp. Let's explore how to build these systems effectively using NEAR's randomness capabilities. + +![img](/docs/assets/examples/coin-flip.png) + +--- + +## Randomness as a Product Feature + +Most developers think about randomness as a technical implementation detail. Successful dApp builders think about it as a core product feature that drives user engagement and business value. + +Consider the difference between: +- "We need random numbers for our game mechanics" +- "We're building excitement through unpredictable rewards that keep users coming back" + +The second mindset leads to better products. + +### User Psychology and Randomness + +Random outcomes trigger powerful psychological responses: +- **Variable ratio reinforcement** (most addictive reward schedule) +- **Loss aversion** (fear of missing rare outcomes) +- **FOMO mechanics** (time-limited random events) +- **Social proof** (sharing lucky outcomes) + +Understanding these patterns helps you design better randomness experiences. + +--- + +## Architectural Patterns for Randomness Systems + + + + + + + + + + +### Event-Driven Random Systems + +Instead of generating randomness on-demand, architect systems around random events: + +```rust +#[derive(BorshSerialize, BorshDeserialize, Clone)] +pub struct RandomEvent { + pub event_id: u64, + pub event_type: EventType, + pub participants: Vec, + pub outcomes: Vec, + pub resolution_block: u64, + pub metadata: EventMetadata, +} + +#[derive(BorshSerialize, BorshDeserialize, Clone)] +pub enum EventType { + LootDrop { rarity_weights: Vec }, + TournamentBracket { team_count: u32 }, + MarketFluctuation { volatility: f32 }, + ResourceSpawn { spawn_rate: f32 }, +} + +impl Contract { + pub fn schedule_random_event(&mut self, event_type: EventType, delay_blocks: u64) -> u64 { + let event_id = self.next_event_id(); + let resolution_block = env::block_height() + delay_blocks; + + let event = RandomEvent { + event_id, + event_type, + participants: Vec::new(), + outcomes: Vec::new(), + resolution_block, + metadata: EventMetadata::new(), + }; + + self.pending_events.insert(&event_id, &event); + self.schedule_callback(resolution_block, event_id); + + env::log_str(&format!("Scheduled random event {} for block {}", event_id, resolution_block)); + event_id + } + + pub fn join_random_event(&mut self, event_id: u64) -> bool { + let mut event = self.pending_events.get(&event_id) + .expect("Event not found"); + + require!(env::block_height() < event.resolution_block, "Event already resolved"); + require!(!event.participants.contains(&env::predecessor_account_id()), "Already joined"); + + event.participants.push(env::predecessor_account_id()); + self.pending_events.insert(&event_id, &event); + + true + } + + pub fn resolve_random_event(&mut self, event_id: u64) -> Vec { + let mut event = self.pending_events.get(&event_id) + .expect("Event not found"); + + require!(env::block_height() >= event.resolution_block, "Too early to resolve"); + require!(event.outcomes.is_empty(), "Already resolved"); + + let entropy = env::random_seed(); + event.outcomes = self.generate_event_outcomes(&event.event_type, &event.participants, &entropy); + + self.process_event_rewards(&event); + self.completed_events.insert(&event_id, &event); + self.pending_events.remove(&event_id); + + event.outcomes + } +} +``` + +### Layered Randomness Architecture + +Build randomness systems in layers for flexibility and scalability: + +```rust +pub struct RandomnessLayer { + pub base_entropy: BaseEntropy, + pub game_logic: GameLogic, + pub reward_system: RewardSystem, + pub user_experience: UserExperience, +} + +impl RandomnessLayer { + pub fn execute_random_action(&mut self, action: UserAction) -> ActionResult { + // Layer 1: Base entropy generation + let base_random = self.base_entropy.generate(); + + // Layer 2: Game logic processing + let game_result = self.game_logic.process(action, base_random); + + // Layer 3: Reward calculation + let rewards = self.reward_system.calculate(game_result); + + // Layer 4: User experience enhancement + let enhanced_result = self.user_experience.enhance(rewards); + + ActionResult { + base_outcome: game_result, + rewards, + user_experience: enhanced_result, + transaction_id: env::block_height(), + } + } +} + +pub struct BaseEntropy; +impl BaseEntropy { + pub fn generate(&self) -> EntropyPackage { + let seed = env::random_seed(); + + EntropyPackage { + primary: seed[0], + secondary: seed[1], + tertiary: u16::from_le_bytes([seed[2], seed[3]]), + extended: u32::from_le_bytes([seed[4], seed[5], seed[6], seed[7]]), + timestamp_salt: env::block_timestamp(), + } + } +} + +pub struct GameLogic; +impl GameLogic { + pub fn process(&self, action: UserAction, entropy: EntropyPackage) -> GameResult { + match action.action_type { + ActionType::CoinFlip => { + let outcome = if entropy.primary % 2 == 0 { "heads" } else { "tails" }; + let success = outcome == action.prediction; + + GameResult { + outcome: outcome.to_string(), + success, + multiplier: if success { 2.0 } else { 0.0 }, + bonus_triggered: entropy.secondary % 100 < 5, // 5% bonus chance + } + }, + ActionType::SlotMachine => { + self.process_slot_machine(entropy) + }, + ActionType::LootBox => { + self.process_loot_box(entropy, action.rarity_boost) + }, + } + } +} +``` + +--- + +## Cross-Chain Randomness Patterns + + + + + + + + + + +### Rainbow Bridge Integration + +Synchronize randomness across NEAR and Ethereum: + +```rust +use near_sdk::json_types::U128; + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct CrossChainRandomEvent { + pub near_entropy: [u8; 32], + pub ethereum_block_hash: [u8; 32], + pub combined_seed: [u8; 32], + pub participants_near: Vec, + pub participants_eth: Vec, // Ethereum addresses +} + +impl Contract { + pub fn initiate_cross_chain_event(&mut self, eth_participants: Vec) -> Promise { + let near_entropy = env::random_seed(); + + // Request Ethereum block hash through Rainbow Bridge + ext_rainbow_bridge::get_latest_block_hash( + "rainbow-bridge.near".parse().unwrap(), + 0, + Gas(50_000_000_000_000) + ) + .then(ext_self::finalize_cross_chain_event( + near_entropy, + eth_participants, + env::current_account_id(), + 0, + Gas(50_000_000_000_000) + )) + } + + pub fn finalize_cross_chain_event( + &mut self, + near_entropy: [u8; 32], + eth_participants: Vec, + #[callback_result] eth_result: Result, PromiseError> + ) -> CrossChainRandomEvent { + let eth_block_hash = eth_result + .unwrap_or_else(|_| env::sha256(b"fallback_ethereum_entropy")); + + let combined_input = [&near_entropy[..], ð_block_hash[..]].concat(); + let combined_seed = env::sha256(&combined_input); + + let event = CrossChainRandomEvent { + near_entropy, + ethereum_block_hash: eth_block_hash.try_into().unwrap_or([0u8; 32]), + combined_seed, + participants_near: vec![env::predecessor_account_id()], + participants_eth: eth_participants, + }; + + self.process_cross_chain_outcomes(&event); + event + } +} +``` + +### Multi-Chain Randomness Verification + +```rust +pub struct MultiChainVerifier { + pub chain_confirmations: HashMap, + pub required_chains: Vec, +} + +impl MultiChainVerifier { + pub fn verify_randomness_consensus(&self, event_id: u64) -> VerificationResult { + let mut entropy_sources = Vec::new(); + + for chain in &self.required_chains { + if let Some(data) = self.chain_confirmations.get(chain) { + entropy_sources.push(data.block_hash.clone()); + } + } + + if entropy_sources.len() < self.required_chains.len() { + return VerificationResult::Insufficient; + } + + let combined_entropy = self.combine_multi_chain_entropy(&entropy_sources); + let verification_hash = env::sha256(&combined_entropy); + + VerificationResult::Verified { + consensus_hash: verification_hash, + participating_chains: self.required_chains.clone(), + block_confirmations: self.chain_confirmations.clone(), + } + } +} +``` + +--- + +## Business Logic Integration Patterns + +### Dynamic Probability Systems + +Adjust randomness probabilities based on business metrics: + +```rust +#[derive(BorshSerialize, BorshDeserialize)] +pub struct DynamicProbabilityEngine { + pub base_rates: HashMap, + pub user_modifiers: HashMap, + pub time_modifiers: TimeBasedModifiers, + pub economic_modifiers: EconomicModifiers, +} + +impl DynamicProbabilityEngine { + pub fn calculate_success_probability(&self, user: &AccountId, action: &str) -> f32 { + let base_rate = self.base_rates.get(action).unwrap_or(&0.5); + let user_modifier = self.user_modifiers.get(user).unwrap_or(&1.0); + let time_modifier = self.time_modifiers.get_current_modifier(); + let economic_modifier = self.economic_modifiers.get_current_modifier(); + + let final_probability = base_rate * user_modifier * time_modifier * economic_modifier; + final_probability.min(0.95).max(0.05) // Cap between 5% and 95% + } + + pub fn update_user_modifier(&mut self, user: &AccountId, performance_metric: f32) { + let current_modifier = self.user_modifiers.get(user).unwrap_or(&1.0); + + // Implement streak bonuses, loyalty rewards, etc. + let new_modifier = match performance_metric { + x if x > 0.8 => current_modifier * 1.1, // Reward good performance + x if x < 0.3 => current_modifier * 1.05, // Slight boost for struggling users + _ => *current_modifier, + }; + + self.user_modifiers.insert(user.clone(), new_modifier.min(2.0)); + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct TimeBasedModifiers { + pub happy_hour_bonus: f32, + pub weekend_bonus: f32, + pub special_events: Vec, +} + +impl TimeBasedModifiers { + pub fn get_current_modifier(&self) -> f32 { + let current_time = env::block_timestamp(); + let hour_of_day = (current_time / 3600_000_000_000) % 24; // Convert to hours + let day_of_week = (current_time / 86400_000_000_000) % 7; // Convert to days + + let mut modifier = 1.0; + + // Happy hour bonus (6-8 PM UTC) + if hour_of_day >= 18 && hour_of_day <= 20 { + modifier *= self.happy_hour_bonus; + } + + // Weekend bonus + if day_of_week == 5 || day_of_week == 6 { // Friday/Saturday + modifier *= self.weekend_bonus; + } + + // Special events + for event in &self.special_events { + if current_time >= event.start_time && current_time <= event.end_time { + modifier *= event.bonus_multiplier; + } + } + + modifier + } +} +``` + +### Revenue-Optimized Randomness + +Balance user engagement with revenue generation: + +```rust +#[derive(BorshSerialize, BorshDeserialize)] +pub struct RevenueOptimizer { + pub target_house_edge: f32, + pub user_lifetime_values: HashMap, + pub retention_thresholds: RetentionThresholds, +} + +impl RevenueOptimizer { + pub fn optimize_outcome(&self, user: &AccountId, bet_amount: u128, base_random: u8) -> OptimizedOutcome { + let user_ltv = self.user_lifetime_values.get(user).unwrap_or(&0.0); + let retention_risk = self.calculate_retention_risk(user); + + let base_win_probability = 0.45; // Base 45% win rate + let optimized_probability = self.adjust_for_ltv_and_retention( + base_win_probability, + *user_ltv, + retention_risk + ); + + let threshold = (optimized_probability * 255.0) as u8; + let wins = base_random < threshold; + + let payout = if wins { + self.calculate_payout(bet_amount, optimized_probability) + } else { + 0 + }; + + OptimizedOutcome { + wins, + payout: U128(payout), + probability_used: optimized_probability, + house_edge_applied: 1.0 - optimized_probability, + } + } + + fn adjust_for_ltv_and_retention(&self, base_prob: f32, ltv: f32, retention_risk: f32) -> f32 { + let mut adjusted_prob = base_prob; + + // High-value users get slightly better odds + if ltv > 1000.0 { + adjusted_prob += 0.02; + } + + // Users at risk of churning get retention boost + if retention_risk > 0.7 { + adjusted_prob += 0.05; + } + + // New users get beginner's luck + if ltv < 50.0 { + adjusted_prob += 0.08; + } + + adjusted_prob.min(0.55).max(0.35) // Keep within reasonable bounds + } + + fn calculate_retention_risk(&self, user: &AccountId) -> f32 { + // Implement retention risk calculation based on: + // - Days since last activity + // - Recent loss streak + // - Engagement metrics + // - Spending patterns + 0.5 // Placeholder + } +} +``` + +--- + +## User Experience Enhancement + +### Progressive Disclosure of Randomness + +Don't just show outcomes—create anticipation: + +```rust +#[derive(BorshSerialize, BorshDeserialize)] +pub struct ProgressiveReveal { + pub stages: Vec, + pub current_stage: usize, + pub final_outcome: Option, +} + +impl ProgressiveReveal { + pub fn create_coin_flip_reveal(entropy: &[u8; 32]) -> Self { + let final_result = if entropy[0] % 2 == 0 { "heads" } else { "tails" }; + + Self { + stages: vec![ + RevealStage { + message: "The coin is spinning...".to_string(), + delay_ms: 1000, + partial_info: None, + }, + RevealStage { + message: "The coin is slowing down...".to_string(), + delay_ms: 800, + partial_info: Some("You can see it's going to land soon!".to_string()), + }, + RevealStage { + message: format!("The coin landed on {}!", final_result), + delay_ms: 0, + partial_info: Some(final_result.to_string()), + }, + ], + current_stage: 0, + final_outcome: Some(Outcome { + result: final_result.to_string(), + timestamp: env::block_timestamp(), + }), + } + } + + pub fn next_stage(&mut self) -> Option<&RevealStage> { + if self.current_stage < self.stages.len() { + let stage = &self.stages[self.current_stage]; + self.current_stage += 1; + Some(stage) + } else { + None + } + } +} +``` + +### Social Features for Random Events + +Make randomness social and shareable: + +```rust +#[derive(BorshSerialize, BorshDeserialize)] +pub struct SocialRandomEvent { + pub event_id: u64, + pub participants: Vec, + pub spectators: Vec, + pub social_modifiers: SocialModifiers, + pub sharing_incentives: SharingIncentives, +} + +impl SocialRandomEvent { + pub fn add_spectator(&mut self, account: AccountId) { + if !self.spectators.contains(&account) { + self.spectators.push(account.clone()); + + // Spectator bonus for participants + for participant in &mut self.participants { + participant.luck_bonus += 0.01; // 1% bonus per spectator + } + } + } + + pub fn generate_social_outcome(&self, base_entropy: &[u8; 32]) -> SocialOutcome { + let crowd_excitement = self.calculate_crowd_excitement(); + let viral_potential = self.calculate_viral_potential(); + + // Social factors influence randomness + let social_seed = self.generate_social_seed(base_entropy, crowd_excitement); + let outcomes = self.resolve_with_social_modifiers(&social_seed); + + SocialOutcome { + individual_results: outcomes, + crowd_reaction: self.generate_crowd_reaction(crowd_excitement), + sharing_rewards: self.calculate_sharing_rewards(viral_potential), + social_achievements: self.check_social_achievements(), + } + } + + fn calculate_crowd_excitement(&self) -> f32 { + let spectator_count = self.spectators.len() as f32; + let participant_count = self.participants.len() as f32; + + (spectator_count * 0.1 + participant_count * 0.3).min(2.0) + } +} +``` + +--- + +## Testing and Quality Assurance + +### User Journey Testing + +Test randomness from the user's perspective: + +```rust +#[cfg(test)] +mod user_journey_tests { + use super::*; + + #[test] + fn test_new_user_experience() { + let mut contract = Contract::new(); + let new_user: AccountId = "new_player.near".parse().unwrap(); + + // New users should have good initial experience + let mut wins = 0; + let mut total_games = 0; + + for _ in 0..10 { + testing_env!(VMContextBuilder::new() + .predecessor_account_id(new_user.clone()) + .build()); + + let result = contract.play_coin_flip("heads".to_string()); + total_games += 1; + + if result.contains("Correct") { + wins += 1; + } + } + + let win_rate = wins as f32 / total_games as f32; + + // New users should win more than 50% initially (beginner's luck) + assert!(win_rate >= 0.6, "New user win rate too low: {}", win_rate); + } + + #[test] + fn test_retention_mechanics() { + let mut contract = Contract::new(); + let at_risk_user: AccountId = "churning_user.near".parse().unwrap(); + + // Simulate a user who's been losing and might churn + contract.user_stats.insert(&at_risk_user, &UserStats { + total_games: 50, + total_wins: 15, // 30% win rate - at risk + last_activity: env::block_timestamp() - 86400_000_000_000, // 1 day ago + lifetime_value: U128(200_000_000_000_000_000_000_000), // $200 + }); + + testing_env!(VMContextBuilder::new() + .predecessor_account_id(at_risk_user.clone()) + .build()); + + // At-risk users should get retention boost + let mut recent_wins = 0; + for _ in 0..10 { + let result = contract.play_coin_flip("heads".to_string()); + if result.contains("Correct") { + recent_wins += 1; + } + } + + let recent_win_rate = recent_wins as f32 / 10.0; + assert!(recent_win_rate >= 0.6, "Retention boost not working: {}", recent_win_rate); + } + + #[test] + fn test_social_dynamics() { + let mut contract = Contract::new(); + let event_id = contract.create_social_event(); + + // Add multiple participants and spectators + let participants = vec!["player1.near", "player2.near", "player3.near"]; + let spectators = vec!["spectator1.near", "spectator2.near"]; + + for participant in participants { + contract.join_social_event(event_id, participant.parse().unwrap()); + } + + for spectator in spectators { + contract.add_spectator_to_event(event_id, spectator.parse().unwrap()); + } + + let outcome = contract.resolve_social_event(event_id); + + // Social events should have enhanced outcomes + assert!(outcome.social_achievements.len() > 0, "No social achievements generated"); + assert!(outcome.sharing_rewards.total_bonus > U128(0), "No sharing rewards generated"); + } +} +``` + +--- + +## Deployment and Monitoring + +### Production Monitoring Dashboard + +```rust +#[derive(BorshSerialize, BorshDeserialize)] +pub struct RandomnessMetrics { + pub total_random_calls: u64, + pub average_entropy_quality: f32, + pub user_satisfaction_score: f32, + pub revenue_per_random_event: U128, + pub retention_impact: f32, +} + +impl Contract { + pub fn get_business_metrics(&self) -> RandomnessMetrics { + RandomnessMetrics { + total_random_calls: self.stats.total_random_calls, + average_entropy_quality: self.calculate_entropy_quality(), + user_satisfaction_score: self.calculate_user_satisfaction(), + revenue_per_random_event: self.calculate_revenue_per_event(), + retention_impact: self.calculate_retention_impact(), + } + } + + pub fn health_check(&self) -> HealthStatus { + let metrics = self.get_business_metrics(); + + HealthStatus { + randomness_quality: if metrics.average_entropy_quality > 0.8 { "Good" } else { "Warning" }, + user_experience: if metrics.user_satisfaction_score > 0.7 { "Good" } else { "Needs Attention" }, + business_performance: if metrics.revenue_per_random_event.0 > 1_000_000_000_000_000_000_000_000 { "Good" } else { "Below Target" }, + overall_status: "Operational", + } + } +} +``` + +### A/B Testing Framework + +```rust +#[derive(BorshSerialize, BorshDeserialize)] +pub struct ABTestConfig { + pub test_name: String, + pub variant_a_probability: f32, + pub variant_b_probability: f32, + pub user_allocation: HashMap, // "A" or "B" + pub results: ABTestResults, +} + +impl Contract { + pub fn run_randomness_ab_test(&mut self, user: &AccountId, base_entropy: u8) -> ABTestOutcome { + let variant = self.get_user_test_variant(user); + + let outcome = match variant.as_str() { + "A" => self.apply_variant_a_logic(base_entropy), + "B" => self.apply_variant_b_logic(base_entropy), + _ => self.apply_control_logic(base_entropy), + }; + + self.record_ab_test_result(user, &variant, &outcome); + outcome + } + + fn get_user_test_variant(&mut self, user: &AccountId) -> String { + if let Some(variant) = self.ab_test_config.user_allocation.get(user) { + variant.clone() + } else { + // Assign new user to variant based on deterministic hash + let user_hash = env::sha256(user.as_bytes()); + let assignment = if user_hash[0] % 2 == 0 { "A" } else { "B" }; + self.ab_test_config.user_allocation.insert(user.clone(), assignment.clone()); + assignment + } + } +} +``` + +--- + +## Future-Proofing Randomness Systems + +### Modular Randomness Architecture + +Design systems that can evolve with new randomness sources: + +```rust +pub trait RandomnessSource { + fn generate_entropy(&self) -> Vec; + fn get_security_level(&self) -> SecurityLevel; + fn get_latency_profile(&self) -> LatencyProfile; +} + +pub struct NearNativeRandomness; +impl RandomnessSource for NearNativeRandomness { + fn generate_entropy(&self) -> Vec { + env::random_seed().to_vec() + } + + fn get_security_level(&self) -> SecurityLevel { + SecurityLevel::Medium + } + + fn get_latency_profile(&self) -> LatencyProfile { + LatencyProfile::Immediate + } +} + +pub struct OracleRandomness { + pub oracle_account: AccountId, +} + +impl RandomnessSource for OracleRandomness { + fn generate_entropy(&self) -> Vec { + // Would make cross-contract call to oracle + // For now, return placeholder + vec![0u8; 32] + } + + fn get_security_level(&self) -> SecurityLevel { + SecurityLevel::High + } + + fn get_latency_profile(&self) -> LatencyProfile { + LatencyProfile::Delayed + } +} + +pub struct RandomnessManager { + pub sources: Vec>, + pub fallback_strategy: FallbackStrategy, +} + +impl RandomnessManager { + pub fn get_best_entropy_for_use_case(&self, use_case: UseCase) -> Vec { + let required_security = use_case.security_requirement(); + let max_latency = use_case.latency_tolerance(); + + for source in &self.sources { + if source.get_security_level() >= required_security + && source.get_latency_profile() <= max_latency { + return source.generate_entropy(); + } + } + + // Fallback to NEAR native if no source meets requirements + env::random_seed().to_vec() + } +} +``` + +--- + +## Conclusion + +Building successful randomness-driven applications requires thinking beyond technical implementation to user psychology, business logic, and ecosystem integration. NEAR's randomness provides a solid foundation, but the real value comes from how you architect systems around it. + +Key takeaways for randomness-powered dApps: + +**Design for Engagement**: Use randomness to create compelling user experiences, not just fair outcomes. + +**Think in Systems**: Build layered architectures that separate concerns and enable future evolution. + +**Optimize for Business Goals**: Balance user satisfaction with business metrics through dynamic probability systems. + +**Plan for Scale**: Design cross-chain compatibility and social features from day one. + +The future of blockchain applications lies in sophisticated randomness systems that feel magical to users while maintaining technical rigor underneath. + +:::note Production Readiness + +This architecture supports: + +- NEAR CLI: `4.0.13` +- Node.js: `18.19.1` +- Rust: `1.77.0` +- near-sdk-rs: `4.1.0` + +:::