miracle_api/
sdk.rs

1use serde::{Deserialize, Serialize};
2use solana_program::hash::hashv;
3use steel::*;
4
5use crate::{
6    consts::*,
7    dmt::{DailyParticipantData, EpochClaimData, SocialClaimData},
8    instruction::*,
9    state::{config_pda, metrics_pda, proof_pda, snapshot_pda, treasury_pda},
10};
11
12/// Builds a claim instruction.
13///
14/// The reward amount is calculated on-chain using the transparent formula:
15/// reward = (activity_count * customer_reward_pool) / total_customer_activity
16///
17/// Where:
18/// - `activity_count` is extracted from the participant data
19/// - `customer_reward_pool` and `total_customer_activity` are read from epoch data
20///
21/// ## Parameters
22/// - `signer`: The wallet signing the transaction
23/// - `beneficiary`: The wallet receiving the rewards
24/// - `epoch`: The epoch number for the claim
25/// - `participant_data`: Participant data including activity count and timestamps
26/// - `payment_proof`: Merkle proof path for payment verification
27/// - `payment_indices`: Merkle proof path indices (true = right, false = left)
28/// - `seal_proof`: Merkle proof path for seal verification
29/// - `seal_indices`: Merkle proof path indices (true = right, false = left)
30/// - `payment_root`: Payment merkle root for verification
31/// - `epoch_data`: Epoch-specific data for accurate reward calculation
32/// - `participant_type`: 0 for customer, 1 for merchant
33/// - `social_data`: Optional social media engagement data
34///
35/// ## Returns
36/// - Instruction for claiming rewards
37///
38/// ## Note
39/// Users can estimate their rewards off-chain using the transparent calculation functions
40/// in `consts.rs`. The on-chain calculation will match the off-chain estimation exactly.
41pub fn claim(
42    signer: Pubkey,
43    beneficiary: Pubkey,
44    epoch: u64,
45    participant_data: DailyParticipantData,
46    payment_proof: Vec<[u8; 32]>,
47    payment_indices: Vec<bool>,
48    seal_proof: Vec<[u8; 32]>,
49    seal_indices: Vec<bool>,
50    payment_root: [u8; 32],
51    epoch_data: EpochClaimData,
52    participant_type: u8,
53    social_data: Option<SocialClaimData>,
54) -> Instruction {
55    let mut data = Vec::new();
56
57    // Use the Claim struct's to_bytes() method for consistent serialization
58    let claim_data = Claim {
59        epoch: epoch.to_le_bytes(),
60        payment_proof_length: payment_proof.len() as u8,
61        seal_proof_length: seal_proof.len() as u8,
62        participant_type,
63        has_social: if social_data.is_some() { 1u8 } else { 0u8 },
64        _padding: [0u8; 4],
65    };
66    data.extend_from_slice(&claim_data.to_bytes());
67
68    // Append participant data
69    data.extend_from_slice(bytemuck::bytes_of(&participant_data));
70
71    // Append payment proof data
72    for path_node in payment_proof {
73        data.extend_from_slice(&path_node);
74    }
75
76    // Append payment indices data
77    for &is_right in &payment_indices {
78        data.push(if is_right { 1u8 } else { 0u8 });
79    }
80
81    // Append seal proof data
82    for path_node in seal_proof {
83        data.extend_from_slice(&path_node);
84    }
85
86    // Append seal indices data
87    for &is_right in &seal_indices {
88        data.push(if is_right { 1u8 } else { 0u8 });
89    }
90
91    // Append payment root data
92    data.extend_from_slice(&payment_root);
93
94    // Append epoch data
95    data.extend_from_slice(bytemuck::bytes_of(&epoch_data));
96
97    // Append social data if provided
98    if let Some(social) = social_data {
99        data.extend_from_slice(bytemuck::bytes_of(&social));
100    }
101
102    let proof_pda = proof_pda(signer);
103    Instruction {
104        program_id: crate::ID,
105        accounts: vec![
106            AccountMeta::new(signer, true),                     // [0] signer
107            AccountMeta::new(beneficiary, false),               // [1] beneficiary
108            AccountMeta::new(proof_pda.0, false),               // [2] proof PDA
109            AccountMeta::new(TREASURY_ADDRESS, false),          // [3] treasury
110            AccountMeta::new(TREASURY_TOKENS_ADDRESS, false),   // [4] treasury tokens
111            AccountMeta::new_readonly(spl_token::ID, false),    // [5] token program
112            AccountMeta::new_readonly(SNAPSHOT_ADDRESS, false), // [6] snapshot
113            AccountMeta::new_readonly(CONFIG_ADDRESS, false),   // [7] config
114        ],
115        data,
116    }
117}
118
119/// Create social claim data for social media engagement.
120///
121/// This function creates a `SocialClaimData` struct with the provided social media
122/// information. The oracle signature should be provided by the authorized oracle
123/// authority.
124///
125/// ## Parameters
126/// - `post_url`: Social media post URL (max 256 bytes)
127/// - `engagement_score`: Engagement score for the post (u32)
128/// - `post_timestamp`: Unix timestamp when post was made (i64)
129/// - `oracle_signature`: Ed25519 signature from authorized oracle
130///
131/// ## Oracle Signature Requirements
132/// The oracle must sign a message with the following format:
133///
134/// `epoch:user_pubkey:post_url:engagement_score:post_timestamp`
135///
136/// This format prevents replay attacks by including epoch and user context.
137///
138/// ## Returns
139/// - `SocialClaimData` struct ready for social claim
140///
141/// ## Example
142/// ```rust
143/// use miracle_api::sdk::create_social_claim_data;
144///
145/// let social_data = create_social_claim_data(
146///     "https://twitter.com/user/123",
147///     500,
148///     1640995200,
149///     &[0u8; 64], // Oracle signature
150/// );
151/// ```
152pub fn create_social_claim_data(
153    post_url: &str,
154    engagement_score: u32,
155    post_timestamp: i64,
156    oracle_signature: &[u8; 64],
157) -> SocialClaimData {
158    let mut post_url_bytes = [0u8; 256];
159    let url_bytes = post_url.as_bytes();
160    let copy_len = std::cmp::min(url_bytes.len(), 256);
161    post_url_bytes[..copy_len].copy_from_slice(&url_bytes[..copy_len]);
162
163    SocialClaimData {
164        post_url: post_url_bytes,
165        oracle_signature: *oracle_signature,
166        engagement_score: engagement_score.to_le_bytes(),
167        post_timestamp: post_timestamp.to_le_bytes(),
168        _padding: [0u8; 4],
169    }
170}
171
172/// Validate social media post URL for required content.
173///
174/// This function checks if the social media post URL contains the required
175/// hashtags and mentions for social marketing rewards.
176///
177/// ## Parameters
178/// - `post_url`: Social media post URL to validate
179///
180/// ## Returns
181/// - `true` if the URL contains required content, `false` otherwise
182///
183/// ## Required Content
184/// - Must contain `#MiracleRewards` hashtag
185/// - Must contain `@MiracleProtocol` mention
186/// - URL must be from supported platforms (Twitter, Instagram, etc.)
187pub fn validate_social_post_url(post_url: &str) -> bool {
188    let url_lower = post_url.to_lowercase();
189
190    // Check for required hashtag
191    if !url_lower.contains("#miraclerewards") {
192        return false;
193    }
194
195    // Check for required mention
196    if !url_lower.contains("@miracleprotocol") {
197        return false;
198    }
199
200    // Check for supported platforms
201    let supported_platforms = [
202        "twitter.com",
203        "x.com",
204        "instagram.com",
205        "facebook.com",
206        "linkedin.com",
207        "tiktok.com",
208    ];
209
210    supported_platforms
211        .iter()
212        .any(|platform| url_lower.contains(platform))
213}
214
215/// Calculate social reward amount based on engagement and base reward.
216///
217/// This function calculates the social reward amount based on the engagement
218/// score and the base reward per post. The calculation matches the on-chain
219/// logic used in the social claim instruction.
220///
221/// ## Parameters
222/// - `engagement_score`: Engagement metrics for the social post
223/// - `base_reward_per_post`: Base reward amount per post
224///
225/// ## Returns
226/// - Total reward amount (base reward + engagement bonus)
227///
228/// ## Formula
229/// - Base reward: `base_reward_per_post`
230/// - Engagement bonus: `(engagement_score - 1000) / 100` if score > 1000
231/// - Total reward: `base_reward + engagement_bonus`
232pub fn calculate_social_reward_amount(engagement_score: u32, base_reward_per_post: u32) -> u64 {
233    let base_reward = base_reward_per_post as u64;
234    let engagement_score_u64 = engagement_score as u64;
235
236    let engagement_bonus = if engagement_score_u64 > 1000 {
237        (engagement_score_u64 - 1000) / 100 // 1 MIRACLE per 100 engagement points above 1000
238    } else {
239        0
240    };
241
242    base_reward + engagement_bonus
243}
244
245/// Builds a social claim instruction.
246///
247/// This instruction allows users to claim social marketing rewards based on their
248/// social media engagement. Social rewards require payment activity in the same epoch
249/// and are verified through oracle signatures to prevent gaming.
250///
251/// ## Parameters
252/// - `signer`: The wallet signing the transaction
253/// - `beneficiary`: The wallet receiving the rewards
254/// - `epoch`: The epoch number for the claim (payment activity required)
255/// - `merkle_root`: Merkle root for payment verification
256/// - `merkle_path`: Merkle proof path for payment activity
257/// - `path_indices`: Merkle proof path indices (true = right, false = left)
258/// - `participant_type`: 0 for customer, 1 for merchant
259/// - `social_data`: Social media verification data
260///
261/// ## Returns
262/// - Instruction for claiming social rewards
263///
264/// ## Requirements
265
266/// Builds a close instruction.
267///
268/// This instruction allows a user to close their Proof account and recover the rent.
269/// The Proof account can only be closed if:
270/// 1. The signer is the Proof account owner (authority)
271/// 2. No rewards have been claimed (total_claimed_rewards == 0)
272///
273/// ## Accounts
274/// - `signer`: The Proof account owner (must be a signer)
275/// - `proof`: The Proof account to close (PDA derived from signer)
276/// - `system_program`: System program for rent recovery
277///
278/// ## Safety Features
279/// - **Owner-only**: Only the Proof account owner can close it
280/// - **No claims allowed**: Cannot close if rewards have been claimed
281/// - **Rent recovery**: All rent is returned to the signer
282///
283/// ## Use Cases
284/// - Recovering rent when no longer participating
285/// - Clean account management
286/// - Reopening with a fresh Proof account later
287///
288/// ## Returns
289/// - Instruction for closing the Proof account
290pub fn close(signer: Pubkey) -> Instruction {
291    let proof = proof_pda(signer).0;
292    Instruction {
293        program_id: crate::ID,
294        accounts: vec![
295            AccountMeta::new(signer, true),
296            AccountMeta::new(proof, false),
297            AccountMeta::new_readonly(solana_program::system_program::ID, false),
298        ],
299        data: Close {}.to_bytes(),
300    }
301}
302
303/// Builds an open instruction.
304pub fn open(signer: Pubkey, payer: Pubkey) -> Instruction {
305    let proof_pda = proof_pda(signer);
306    Instruction {
307        program_id: crate::ID,
308        accounts: vec![
309            AccountMeta::new(signer, true),
310            AccountMeta::new(payer, true),
311            AccountMeta::new(proof_pda.0, false),
312            AccountMeta::new_readonly(solana_program::system_program::ID, false),
313        ],
314        data: Open {}.to_bytes(),
315    }
316}
317
318/// Builds a reset instruction.
319///
320/// This instruction implements the Community-Driven Decay model by adjusting daily rewards
321/// based on community health metrics stored in the Metrics account.
322pub fn reset(signer: Pubkey) -> Instruction {
323    Instruction {
324        program_id: crate::ID,
325        accounts: vec![
326            AccountMeta::new(signer, true),
327            AccountMeta::new(CONFIG_ADDRESS, false),
328            AccountMeta::new_readonly(METRICS_ADDRESS, false),
329            AccountMeta::new(MINT_ADDRESS, false),
330            AccountMeta::new(TREASURY_ADDRESS, false),
331            AccountMeta::new(TREASURY_TOKENS_ADDRESS, false),
332            AccountMeta::new_readonly(spl_token::ID, false),
333        ],
334        data: Reset {}.to_bytes(),
335    }
336}
337
338// MI: abandoned for community trust/confidence
339// // Build an upgrade instruction.
340// pub fn upgrade(signer: Pubkey, beneficiary: Pubkey, sender: Pubkey, amount: u64) -> Instruction {
341//     Instruction {
342//         program_id: crate::ID,
343//         accounts: vec![
344//             AccountMeta::new(signer, true),
345//             AccountMeta::new(beneficiary, false),
346//             AccountMeta::new(MINT_ADDRESS, false),
347//             AccountMeta::new(MINT_V1_ADDRESS, false),
348//             AccountMeta::new(sender, false),
349//             AccountMeta::new(TREASURY_ADDRESS, false),
350//             AccountMeta::new_readonly(spl_token::ID, false),
351//             AccountMeta::new_readonly(CONFIG_ADDRESS, false),
352//         ],
353//         data: Upgrade {
354//             amount: amount.to_le_bytes(),
355//         }
356//         .to_bytes(),
357//     }
358// }
359
360// /// Build an evolve instruction.
361// pub fn evolve(signer: Pubkey, evolvable: bool) -> Instruction {
362//     Instruction {
363//         program_id: crate::ID,
364//         accounts: vec![
365//             AccountMeta::new(signer, true),
366//             AccountMeta::new(CONFIG_ADDRESS, false),
367//         ],
368//         data: Evolve {
369//             evolvable: evolvable as u8,
370//         }
371//         .to_bytes(),
372//     }
373// }
374
375/// Builds an initialize instruction.
376pub fn initialize(signer: Pubkey, project_wallet: Pubkey) -> Instruction {
377    let config_pda = config_pda();
378    let mint_pda = Pubkey::find_program_address(&[MINT, MINT_NOISE.as_slice()], &crate::ID);
379    let treasury_pda = treasury_pda();
380    let snapshot_pda = snapshot_pda();
381    let metrics_pda = metrics_pda();
382    let metadata_pda = Pubkey::find_program_address(
383        &[
384            METADATA,
385            mpl_token_metadata::ID.as_ref(),
386            mint_pda.0.as_ref(),
387        ],
388        &mpl_token_metadata::ID,
389    );
390    // let treasury_tokens_pda = Pubkey::find_program_address(
391    //     &[
392    //         treasury_pda.0.as_ref(),
393    //         spl_token::id().as_ref(),
394    //         MINT_ADDRESS.as_ref(),
395    //     ],
396    //     &spl_associated_token_account::id(),
397    // );
398    let project_tokens_pda = Pubkey::find_program_address(
399        &[
400            project_wallet.as_ref(),
401            spl_token::id().as_ref(),
402            MINT_ADDRESS.as_ref(),
403        ],
404        &spl_associated_token_account::id(),
405    );
406
407    Instruction {
408        program_id: crate::ID,
409        accounts: vec![
410            AccountMeta::new(signer, true),
411            AccountMeta::new(config_pda.0, false),
412            AccountMeta::new(snapshot_pda.0, false),
413            AccountMeta::new(metrics_pda.0, false),
414            AccountMeta::new(metadata_pda.0, false),
415            AccountMeta::new(mint_pda.0, false),
416            AccountMeta::new(treasury_pda.0, false),
417            AccountMeta::new(TREASURY_TOKENS_ADDRESS, false),
418            AccountMeta::new(project_wallet, false),
419            AccountMeta::new(project_tokens_pda.0, false),
420            AccountMeta::new_readonly(system_program::ID, false),
421            AccountMeta::new_readonly(spl_token::ID, false),
422            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
423            AccountMeta::new_readonly(mpl_token_metadata::ID, false),
424            AccountMeta::new_readonly(sysvar::rent::ID, false),
425        ],
426        data: Initialize { project_wallet }.to_bytes(),
427    }
428}
429
430/// Builds an update oracle instruction.
431///
432/// This instruction allows the current oracle authority to update itself to a new oracle authority.
433/// Only the current oracle authority can execute this instruction.
434///
435/// ## Parameters
436/// - `oracle`: The current oracle authority public key (must be a signer)
437/// - `new_oracle_authority`: The new oracle authority public key
438///
439/// ## Returns
440/// An instruction that can be included in a transaction to update the oracle authority.
441///
442/// ## Security
443/// - Only the current oracle authority can execute this instruction
444/// - New oracle authority must be different from current and not zero
445/// - Both main and social oracle authorities are updated consistently
446///
447/// ## Example
448/// ```rust,no_run
449/// use steel::Pubkey;
450/// let current_oracle = Pubkey::new_unique();
451/// let new_oracle = Pubkey::new_unique();
452/// let instruction = miracle_api::sdk::update_oracle(current_oracle, new_oracle);
453/// ```
454pub fn update_oracle(oracle: Pubkey, new_oracle_authority: Pubkey) -> Instruction {
455    Instruction {
456        program_id: crate::ID,
457        accounts: vec![
458            AccountMeta::new(oracle, true),
459            AccountMeta::new(CONFIG_ADDRESS, false),
460        ],
461        data: UpdateOracle {
462            new_oracle_authority,
463        }
464        .to_bytes(),
465    }
466}
467
468/// Builds an update snapshot instruction.
469pub fn update_snapshot(
470    oracle: Pubkey,
471    epoch: u64,
472    seal_merkle_root: [u8; 32],
473    payment_merkle_root: [u8; 32],
474    epoch_hash: [u8; 32],
475    customer_reward_pool: u64,
476    merchant_reward_pool: u64,
477    customer_participants: u32,
478    merchant_participants: u32,
479    total_customer_payments: u32,
480    total_merchant_payments: u32,
481) -> Instruction {
482    Instruction {
483        program_id: crate::ID,
484        accounts: vec![
485            AccountMeta::new(oracle, true),
486            AccountMeta::new(SNAPSHOT_ADDRESS, false),
487            AccountMeta::new_readonly(CONFIG_ADDRESS, false),
488            AccountMeta::new(TREASURY_ADDRESS, false),
489        ],
490        data: UpdateSnapshot {
491            epoch: epoch.to_le_bytes(),
492            seal_merkle_root,
493            payment_merkle_root,
494            epoch_hash,
495            customer_reward_pool: customer_reward_pool.to_le_bytes(),
496            merchant_reward_pool: merchant_reward_pool.to_le_bytes(),
497            customer_participants: customer_participants.to_le_bytes(),
498            merchant_participants: merchant_participants.to_le_bytes(),
499            total_customer_payments: total_customer_payments.to_le_bytes(),
500            total_merchant_payments: total_merchant_payments.to_le_bytes(),
501        }
502        .to_bytes(),
503    }
504}
505
506/// Builds an update community metrics instruction.
507///
508/// This function creates an instruction to update community health metrics used for
509/// the Enhanced Community-Driven Decay model. Only the oracle authority can execute this instruction.
510///
511/// ## Parameters
512/// - `oracle`: The oracle authority public key (must be a signer)
513/// - `weekly_active_users`: Number of active users in the past week
514/// - `weekly_retention_rate`: Retention rate in basis points (0-10000, 0-100%)
515/// - `user_weight`: User weight in basis points (0-10000) for weighted geometric mean
516/// - `volume_weight`: Volume weight in basis points (0-10000) for weighted geometric mean
517/// - `retention_weight`: Retention weight in basis points (0-10000) for weighted geometric mean
518///
519/// ## Returns
520/// An instruction that can be included in a transaction to update community metrics.
521///
522/// ## Security
523/// - The oracle must be a signer of the transaction
524/// - Input validation occurs on-chain
525/// - Only the authorized oracle can update metrics
526///
527/// ## Example
528/// ```rust,no_run
529/// use steel::Pubkey;
530/// let oracle_pubkey = Pubkey::new_unique();
531/// let instruction = miracle_api::sdk::update_metrics(
532///     oracle_pubkey,
533///     5000,        // 5,000 active users
534///     7500,        // 75% retention rate
535///     4000,        // 40% user weight
536///     3500,        // 35% activity weight
537///     2500,        // 25% retention weight
538///     50_000,      // 50,000 transactions
539///     7000,        // 70% customer reward share
540///     3000,        // 30% merchant reward share
541/// );
542/// ```
543pub fn update_metrics(
544    oracle: Pubkey,
545    weekly_active_users: u32,
546    weekly_retention_rate: u16,
547    user_weight: u16,
548    activity_weight: u16,
549    retention_weight: u16,
550    weekly_activity_count: u32,
551    customer_reward_share: u16,
552    merchant_reward_share: u16,
553) -> Instruction {
554    Instruction {
555        program_id: crate::ID,
556        accounts: vec![
557            AccountMeta::new(oracle, true),
558            AccountMeta::new(METRICS_ADDRESS, false),
559            AccountMeta::new_readonly(CONFIG_ADDRESS, false),
560        ],
561        data: UpdateMetrics {
562            weekly_active_users: weekly_active_users.to_le_bytes(),
563            weekly_retention_rate: weekly_retention_rate.to_le_bytes(),
564            user_weight: user_weight.to_le_bytes(),
565            activity_weight: activity_weight.to_le_bytes(),
566            retention_weight: retention_weight.to_le_bytes(),
567            weekly_activity_count: weekly_activity_count.to_le_bytes(),
568            customer_reward_share: customer_reward_share.to_le_bytes(),
569            merchant_reward_share: merchant_reward_share.to_le_bytes(),
570            _padding: [0u8; 4], // 4 bytes padding for 8-byte alignment
571        }
572        .to_bytes(),
573    }
574}
575
576/// Validates community metrics parameters before creating an instruction.
577///
578/// This function helps oracles validate their inputs before submitting transactions,
579/// providing better error handling and user experience.
580///
581/// ## Parameters
582/// - `weekly_active_users`: Number of active users in the past week
583/// - `weekly_retention_rate`: Retention rate in basis points (0-10000, 0-100%)
584/// - `user_weight`: User weight in basis points (0-10000) for weighted geometric mean
585/// - `activity_weight`: Activity weight in basis points (0-10000) for weighted geometric mean
586/// - `retention_weight`: Retention weight in basis points (0-10000) for weighted geometric mean
587/// - `weekly_activity_count`: Number of transactions in the past week
588/// - `customer_reward_share`: Customer reward percentage in basis points (0-10000)
589/// - `merchant_reward_share`: Merchant reward percentage in basis points (0-10000)
590///
591/// ## Returns
592/// - `Ok(())` if all parameters are valid
593/// - `Err(MiracleError)` with specific error if validation fails
594///
595/// ## Validation Rules
596/// - Weights must sum to 10000 (100%)
597/// - Reward shares must sum to 10000 (100%)
598/// - Retention rate must be 0-10000 (0-100%)
599
600/// - Activity count must be reasonable (0-1M transactions per week)
601/// - All numeric values must be reasonable
602pub fn validate_community_metrics(
603    weekly_active_users: u32,
604    weekly_retention_rate: u16,
605    user_weight: u16,
606    activity_weight: u16,
607    retention_weight: u16,
608    weekly_activity_count: u32,
609    customer_reward_share: u16,
610    merchant_reward_share: u16,
611) -> Result<(), crate::error::MiracleError> {
612    // Validate weights sum to 10000 (100%)
613    let total_weight = user_weight
614        .saturating_add(activity_weight)
615        .saturating_add(retention_weight);
616    if total_weight != 10000 {
617        return Err(crate::error::MiracleError::InvalidWeights);
618    }
619
620    // Validate reward shares sum to 10000 (100%)
621    let total_reward_share = customer_reward_share.saturating_add(merchant_reward_share);
622    if total_reward_share != 10000 {
623        return Err(crate::error::MiracleError::InvalidRewardSplit);
624    }
625
626    // Validate retention rate bounds (0-10000)
627    if weekly_retention_rate > 10000 {
628        return Err(crate::error::MiracleError::InvalidRetentionRate);
629    }
630
631    // Validate activity count bounds (reasonable bounds: 0-1M transactions per week)
632    if weekly_activity_count > 1_000_000 {
633        return Err(crate::error::MiracleError::InvalidActivityCount);
634    }
635
636    // Validate that weights are reasonable (not all zero)
637    if user_weight == 0 && activity_weight == 0 && retention_weight == 0 {
638        return Err(crate::error::MiracleError::InvalidWeights);
639    }
640
641    // Validate that weekly metrics are reasonable (not unreasonably large)
642    if weekly_active_users > 1_000_000 {
643        return Err(crate::error::MiracleError::InvalidInput);
644    }
645
646    Ok(())
647}
648
649/// Complete transparent calculation for customer rewards.
650/// This function combines all steps of the reward calculation for transparency.
651///
652/// ## Formula Chain
653/// 1. daily_rewards = calculate_daily_rewards(community_score, years_since_launch)
654/// 2. customer_pool = daily_rewards * customer_reward_share / 10000
655/// 3. customer_reward = (activity_count * customer_pool) / total_customer_activity
656///
657/// ## Parameters
658/// - `activity_count`: Number of activities performed by the customer
659/// - `community_score`: Community health score (0-10000 basis points)
660/// - `years_since_launch`: Number of years since project launch (for time decay)
661/// - `customer_reward_share`: Customer reward percentage in basis points (0-10000)
662/// - `total_customer_activity`: Total activity count across all customers
663///
664/// ## Returns
665/// - Individual customer reward amount in smallest token units
666///
667/// ## Usage
668/// ```rust
669/// use miracle_api::sdk::calculate_customer_reward_transparent;
670///
671/// let reward = calculate_customer_reward_transparent(
672///     5,           // activity_count
673///     8000,        // community_score
674///     2,           // years_since_launch
675///     7000,        // customer_reward_share
676///     1000,        // total_customer_activity
677/// );
678/// println!("Reward: {} MIRACLE", reward);
679/// ```
680///
681/// ## Note
682/// This function provides complete transparency from community metrics to individual rewards.
683/// It can be used both on-chain and off-chain for verification.
684pub fn calculate_customer_reward_transparent(
685    activity_count: u32,
686    community_score: u16,
687    years_since_launch: u32,
688    customer_reward_share: u16,
689    total_customer_activity: u32,
690) -> u64 {
691    crate::consts::calculate_customer_reward_transparent(
692        activity_count,
693        community_score,
694        years_since_launch,
695        customer_reward_share,
696        total_customer_activity,
697    )
698}
699
700/// Complete transparent calculation for customer rewards WITH activity capping.
701/// This function matches the on-chain calculation behavior exactly.
702///
703/// ## Formula Chain
704/// 1. daily_rewards = calculate_daily_rewards(community_score, years_since_launch)
705/// 2. customer_pool = daily_rewards * customer_reward_share / 10000
706/// 3. capped_activity = min(activity_count, max_customer_activity_per_epoch)
707/// 4. customer_reward = (capped_activity * customer_pool) / total_customer_activity
708///
709/// ## Parameters
710/// - `activity_count`: Number of activities performed by the customer
711/// - `community_score`: Community health score (0-10000 basis points)
712/// - `years_since_launch`: Number of years since project launch (for time decay)
713/// - `customer_reward_share`: Customer reward percentage in basis points (0-10000)
714/// - `total_customer_activity`: Total activity count across all customers
715/// - `max_customer_activity_per_epoch`: Maximum allowed customer activity per epoch
716///
717/// ## Returns
718/// - Individual customer reward amount in smallest token units (with activity capping)
719///
720/// ## Benefits
721/// - Matches on-chain calculation exactly
722/// - Includes activity capping to prevent gaming
723/// - Complete transparency for off-chain estimation
724/// - Deterministic calculation for verification
725///
726/// ## Usage
727/// ```rust
728/// use miracle_api::sdk::calculate_customer_reward_transparent_with_cap;
729///
730/// let reward = calculate_customer_reward_transparent_with_cap(
731///     15,          // activity_count (will be capped)
732///     8000,        // community_score
733///     2,           // years_since_launch
734///     7000,        // customer_reward_share
735///     1000,        // total_customer_activity
736///     10,          // max_customer_activity_per_epoch (caps activity at 10)
737/// );
738/// println!("Reward: {} MIRACLE", reward);
739/// ```
740///
741/// ## Note
742/// This function provides complete transparency from community metrics to individual rewards
743/// and matches the on-chain calculation behavior exactly.
744pub fn calculate_customer_reward_transparent_with_cap(
745    activity_count: u32,
746    community_score: u16,
747    years_since_launch: u32,
748    customer_reward_share: u16,
749    total_customer_activity: u32,
750    max_customer_activity_per_epoch: u32,
751) -> u64 {
752    crate::consts::calculate_customer_reward_transparent_with_cap(
753        activity_count,
754        community_score,
755        years_since_launch,
756        customer_reward_share,
757        total_customer_activity,
758        max_customer_activity_per_epoch,
759    )
760}
761
762/// Complete transparent calculation for merchant rewards.
763/// This function combines all steps of the reward calculation for transparency.
764///
765/// ## Formula Chain
766/// 1. daily_rewards = calculate_daily_rewards(community_score, years_since_launch)
767/// 2. merchant_pool = daily_rewards * merchant_reward_share / 10000
768/// 3. merchant_reward = (activity_count * merchant_pool) / total_merchant_activity
769///
770/// ## Parameters
771/// - `activity_count`: Number of activities performed by the merchant
772/// - `community_score`: Community health score (0-10000 basis points)
773/// - `years_since_launch`: Number of years since project launch (for time decay)
774/// - `merchant_reward_share`: Merchant reward percentage in basis points (0-10000)
775/// - `total_merchant_activity`: Total activity count across all merchants
776///
777/// ## Returns
778/// - Individual merchant reward amount in smallest token units
779///
780/// ## Usage
781/// ```rust
782/// use miracle_api::sdk::calculate_merchant_reward_transparent;
783///
784/// let reward = calculate_merchant_reward_transparent(
785///     3,           // activity_count
786///     8000,        // community_score
787///     2,           // years_since_launch
788///     3000,        // merchant_reward_share
789///     500,         // total_merchant_activity
790/// );
791/// println!("Reward: {} MIRACLE", reward);
792/// ```
793///
794/// ## Note
795/// This function provides complete transparency from community metrics to individual rewards.
796/// It can be used both on-chain and off-chain for verification.
797pub fn calculate_merchant_reward_transparent(
798    activity_count: u32,
799    community_score: u16,
800    years_since_launch: u32,
801    merchant_reward_share: u16,
802    total_merchant_activity: u32,
803) -> u64 {
804    crate::consts::calculate_merchant_reward_transparent(
805        activity_count,
806        community_score,
807        years_since_launch,
808        merchant_reward_share,
809        total_merchant_activity,
810    )
811}
812
813/// Complete transparent calculation for merchant rewards WITH activity capping.
814/// This function matches the on-chain calculation behavior exactly.
815///
816/// ## Formula Chain
817/// 1. daily_rewards = calculate_daily_rewards(community_score, years_since_launch)
818/// 2. merchant_pool = daily_rewards * merchant_reward_share / 10000
819/// 3. capped_activity = min(activity_count, max_merchant_activity_per_epoch)
820/// 4. merchant_reward = (capped_activity * merchant_pool) / total_merchant_activity
821///
822/// ## Parameters
823/// - `activity_count`: Number of activities performed by the merchant
824/// - `community_score`: Community health score (0-10000 basis points)
825/// - `years_since_launch`: Number of years since project launch (for time decay)
826/// - `merchant_reward_share`: Merchant reward percentage in basis points (0-10000)
827/// - `total_merchant_activity`: Total activity count across all merchants
828/// - `max_merchant_activity_per_epoch`: Maximum allowed merchant activity per epoch
829///
830/// ## Returns
831/// - Individual merchant reward amount in smallest token units (with activity capping)
832///
833/// ## Benefits
834/// - Matches on-chain calculation exactly
835/// - Includes activity capping to prevent gaming
836/// - Complete transparency for off-chain estimation
837/// - Deterministic calculation for verification
838///
839/// ## Usage
840/// ```rust
841/// use miracle_api::sdk::calculate_merchant_reward_transparent_with_cap;
842///
843/// let reward = calculate_merchant_reward_transparent_with_cap(
844///     25,          // activity_count (will be capped)
845///     8000,        // community_score
846///     2,           // years_since_launch
847///     3000,        // merchant_reward_share
848///     500,         // total_merchant_activity
849///     20,          // max_merchant_activity_per_epoch (caps activity at 20)
850/// );
851/// println!("Reward: {} MIRACLE", reward);
852/// ```
853///
854/// ## Note
855/// This function provides complete transparency from community metrics to individual rewards
856/// and matches the on-chain calculation behavior exactly.
857pub fn calculate_merchant_reward_transparent_with_cap(
858    activity_count: u32,
859    community_score: u16,
860    years_since_launch: u32,
861    merchant_reward_share: u16,
862    total_merchant_activity: u32,
863    max_merchant_activity_per_epoch: u32,
864) -> u64 {
865    crate::consts::calculate_merchant_reward_transparent_with_cap(
866        activity_count,
867        community_score,
868        years_since_launch,
869        merchant_reward_share,
870        total_merchant_activity,
871        max_merchant_activity_per_epoch,
872    )
873}
874
875/// Convert Unix timestamp to epoch number (day sequence since launch).
876///
877/// ## Formula
878/// epoch = (timestamp - START_AT) / EPOCH_DURATION
879///
880/// ## Parameters
881/// - `timestamp`: Unix timestamp in seconds
882///
883/// ## Returns
884/// - Epoch number (0-based day sequence since launch)
885///
886/// ## Epoch Alignment
887/// - Epoch 0: Sept 1, 2025 00:00:00 UTC to Sept 1, 2025 23:59:59 UTC
888/// - Epoch 1: Sept 2, 2025 00:00:00 UTC to Sept 2, 2025 23:59:59 UTC
889/// - Each epoch aligns with a natural UTC date (midnight to midnight)
890/// - Project launches at START_AT (10:00 AM UTC on Sept 1, 2025) within epoch 0
891///
892/// ## Example
893/// ```rust
894/// use miracle_api::sdk;
895/// let epoch = sdk::timestamp_to_epoch(1756756800); // Returns 0 (Sept 1, 2025 00:00:00 UTC)
896/// let epoch = sdk::timestamp_to_epoch(1756843200); // Returns 1 (Sept 2, 2025 00:00:00 UTC)
897/// ```
898pub fn timestamp_to_epoch(timestamp: i64) -> u64 {
899    // Align epochs with natural UTC dates (midnight to midnight)
900    // Formula: Find the start of the UTC day containing START_AT
901    // Then calculate epochs from that day boundary
902    let start_day_beginning = (START_AT / EPOCH_DURATION) * EPOCH_DURATION; // Start of UTC day containing START_AT
903
904    if timestamp < start_day_beginning {
905        return 0; // Return epoch 0 for timestamps before the start of epoch 0
906    }
907
908    let adjusted_timestamp = timestamp - start_day_beginning;
909    (adjusted_timestamp / EPOCH_DURATION) as u64
910}
911
912/// Calculate Unix timestamp from epoch number (day sequence).
913///
914/// ## Formula
915/// timestamp = START_AT + (epoch * EPOCH_DURATION)
916///
917/// ## Parameters
918/// - `epoch`: Epoch number (0-based day sequence since launch)
919///
920/// ## Returns
921/// - Unix timestamp in seconds (start of the epoch day)
922///
923/// ## Epoch Alignment
924/// - Epoch 0: Returns Sept 1, 2025 00:00:00 UTC
925/// - Epoch 1: Returns Sept 2, 2025 00:00:00 UTC
926/// - Each epoch starts at 00:00:00 UTC of the corresponding date
927/// - Project launches at START_AT (10:00 AM UTC on Sept 1, 2025) within epoch 0
928///
929/// ## Example
930/// ```rust
931/// use miracle_api::sdk;
932/// let timestamp = sdk::epoch_to_timestamp(0); // Returns 1756756800 (Sept 1, 2025 00:00:00 UTC)
933/// let timestamp = sdk::epoch_to_timestamp(1); // Returns 1756843200 (Sept 2, 2025 00:00:00 UTC)
934/// ```
935pub fn epoch_to_timestamp(epoch: u64) -> i64 {
936    // Align epochs with natural UTC dates (midnight to midnight)
937    // Formula: Find the start of the UTC day containing START_AT
938    // Then calculate timestamp from that day boundary
939    let start_day_beginning = (START_AT / EPOCH_DURATION) * EPOCH_DURATION; // Start of UTC day containing START_AT
940    start_day_beginning + (epoch as i64 * EPOCH_DURATION)
941}
942
943/// Convert a date range to a list of epochs.
944///
945/// ## Parameters
946/// - `start_date`: Start date as Unix timestamp
947/// - `end_date`: End date as Unix timestamp (inclusive)
948///
949/// ## Returns
950/// - Vector of epoch numbers from start_date to end_date (inclusive)
951///
952/// ## Example
953/// ```rust
954/// use miracle_api::sdk;
955/// let epochs = sdk::date_range_to_epochs(1756792800, 1756879199);
956/// // Returns [0] for Sept 1, 2025 (launch day)
957/// let epochs = sdk::date_range_to_epochs(1756792800, 1756965599);
958/// // Returns [0, 1] for Sept 1-2, 2025
959/// ```
960pub fn date_range_to_epochs(start_date: i64, end_date: i64) -> Vec<u64> {
961    if start_date > end_date {
962        return Vec::new(); // Return empty vector for invalid range
963    }
964
965    let start_epoch = timestamp_to_epoch(start_date);
966    let end_epoch = timestamp_to_epoch(end_date);
967
968    (start_epoch..=end_epoch).collect()
969}
970
971/// Builds a batch claim instruction for a date range.
972///
973/// This function converts the date range to epochs and creates a vector claim.
974/// It provides a user-friendly interface for claiming rewards across multiple days.
975///
976/// ## Parameters
977/// - `signer`: The wallet signing the transaction
978/// - `beneficiary`: The wallet receiving the rewards
979/// - `start_date`: Start date as Unix timestamp
980/// - `end_date`: End date as Unix timestamp (inclusive)
981/// - `participant_type`: 0 = customer, 1 = merchant
982/// - `max_batch_size`: Maximum epochs per batch (1-10)
983/// - `merkle_proofs`: Vector of Merkle proof data for each epoch
984///
985/// ## Returns
986/// - Instruction for batch claiming rewards across the date range
987///
988/// ## Note
989/// - The date range is converted to epochs using `date_range_to_epochs`
990/// - Each epoch requires its own Merkle proof in the `merkle_proofs` vector
991/// - The number of proofs must match the number of epochs in the range
992/// - If the date range is invalid or too large, returns an error
993///
994/// ## Example
995/// ```rust
996/// use miracle_api::sdk;
997/// use solana_program::pubkey::Pubkey;
998///
999/// let signer = Pubkey::new_unique();
1000/// let beneficiary = Pubkey::new_unique();
1001/// let merkle_proofs = vec![vec![1, 2, 3], vec![4, 5, 6]]; // Example proofs
1002///
1003/// let instruction = sdk::batch_claim_date_range(
1004///     signer,
1005///     beneficiary,
1006///     1756792800, // Sept 1, 2025 00:00:00 UTC
1007///     1756879199, // Sept 1, 2025 23:59:59 UTC
1008///     0, // customer
1009///     10, // max batch size
1010///     merkle_proofs, // proofs for epoch 0
1011///     None, // social_data (no social claims)
1012/// );
1013/// ```
1014///
1015/// Batch claim instruction for date range - uses correct 8-account structure
1016pub fn batch_claim_date_range(
1017    signer: Pubkey,
1018    beneficiary: Pubkey,
1019    start_date: i64,
1020    end_date: i64,
1021    participant_type: u8,
1022    max_batch_size: u8,
1023    merkle_proofs: Vec<Vec<u8>>,
1024    social_data: Option<Vec<Option<SocialClaimData>>>,
1025) -> Result<Instruction, crate::error::MiracleError> {
1026    // Convert date range to epochs
1027    let epochs = date_range_to_epochs(start_date, end_date);
1028
1029    if epochs.is_empty() {
1030        return Err(crate::error::MiracleError::InvalidInput);
1031    }
1032
1033    if epochs.len() > max_batch_size as usize {
1034        return Err(crate::error::MiracleError::InvalidInput);
1035    }
1036
1037    if merkle_proofs.len() != epochs.len() {
1038        return Err(crate::error::MiracleError::InvalidInput);
1039    }
1040
1041    // Validate social_data if provided
1042    if let Some(ref social_data_vec) = social_data {
1043        if social_data_vec.len() != epochs.len() {
1044            return Err(crate::error::MiracleError::InvalidInput);
1045        }
1046    }
1047
1048    // Create batch claim instruction using vector claim type
1049    let mut data = Vec::new();
1050
1051    // Add instruction discriminator for MiracleInstruction::BatchClaim (value = 0)
1052    data.push(MiracleInstruction::BatchClaim as u8);
1053
1054    // Manually serialize the BatchClaim struct fields in the exact order the program expects
1055    data.extend_from_slice(&[0u8; 8]); // start_epoch: 8 bytes (not used for date range)
1056    data.extend_from_slice(&[0u8; 8]); // end_epoch: 8 bytes (not used for date range)
1057    data.push(participant_type); // participant_type: 1 byte
1058    data.push(2u8); // claim_type: 1 byte (Date range claim = 2)
1059    data.push(epochs.len() as u8); // epoch_count: 1 byte
1060    data.push(max_batch_size); // max_batch_size: 1 byte
1061    data.extend_from_slice(&[0u8; 4]); // _padding: 4 bytes
1062
1063    // Append epoch list
1064    for &epoch in &epochs {
1065        data.extend_from_slice(&epoch.to_le_bytes());
1066    }
1067
1068    // Append Merkle proof data (consistent with single Claim)
1069    // For batch claims, we use the same merkle path and path indices for all epochs
1070    // since all epochs in a batch must use the same merkle root and structure
1071    let first_proof = &merkle_proofs[0];
1072    if first_proof.len() >= 37 {
1073        // Skip the first 33 bytes (merkle_root + merkle_path_length) and append the rest
1074        // This gives us: merkle_path + path_indices + activity_count
1075        data.extend_from_slice(&first_proof[33..]);
1076    } else {
1077        return Err(crate::error::MiracleError::InvalidInput);
1078    }
1079
1080    // Append social flags (1 byte per epoch indicating if social claim exists)
1081    let social_flags = social_data
1082        .as_ref()
1083        .map(|social_vec| {
1084            social_vec
1085                .iter()
1086                .map(|opt| if opt.is_some() { 1u8 } else { 0u8 })
1087                .collect::<Vec<u8>>()
1088        })
1089        .unwrap_or_else(|| vec![0u8; epochs.len()]);
1090
1091    data.extend_from_slice(&social_flags);
1092
1093    // Append social data for epochs that have social claims
1094    if let Some(social_data_vec) = social_data {
1095        for social_opt in social_data_vec {
1096            if let Some(social) = social_opt {
1097                // Serialize SocialClaimData using bytemuck::bytes_of for consistent formatting
1098                data.extend_from_slice(bytemuck::bytes_of(&social));
1099            }
1100        }
1101    }
1102
1103    // Create instruction with correct account structure matching program
1104    let proof_pda = proof_pda(signer);
1105    Ok(Instruction {
1106        program_id: crate::ID,
1107        accounts: vec![
1108            AccountMeta::new(signer, true),                     // [0] signer
1109            AccountMeta::new(beneficiary, false),               // [1] beneficiary
1110            AccountMeta::new(proof_pda.0, false),               // [2] proof PDA
1111            AccountMeta::new(TREASURY_ADDRESS, false),          // [3] treasury
1112            AccountMeta::new(TREASURY_TOKENS_ADDRESS, false),   // [4] treasury tokens
1113            AccountMeta::new_readonly(spl_token::ID, false),    // [5] token program
1114            AccountMeta::new_readonly(SNAPSHOT_ADDRESS, false), // [6] snapshot
1115            AccountMeta::new_readonly(CONFIG_ADDRESS, false),   // [7] config
1116        ],
1117        data,
1118    })
1119}
1120
1121/// Builds a batch claim instruction for a vector of epochs.
1122///
1123/// This is the underlying function used by `batch_claim_date_range`.
1124/// Uses correct 8-account structure matching program.
1125/// It allows claiming rewards for specific epochs in a single transaction.
1126/// Supports both payment rewards and optional social rewards per epoch.
1127///
1128/// ## Parameters
1129/// - `signer`: The wallet signing the transaction
1130/// - `beneficiary`: The wallet receiving the rewards
1131/// - `epochs`: Vector of epoch numbers to claim
1132/// - `participant_type`: 0 for customer, 1 for merchant
1133/// - `max_batch_size`: Maximum epochs per batch (1-10)
1134/// - `epoch_proofs`: Vector of proof data for each epoch
1135/// - `social_data`: Optional vector of social data for each epoch (None = no social claim)
1136///
1137/// ## Returns
1138/// - Instruction for batch claiming rewards for specific epochs
1139///
1140/// ## Note
1141/// - Each epoch requires its own proof data
1142/// - The number of proofs must match the number of epochs
1143/// - Social data is optional per epoch (None = payment only, Some = payment + social)
1144/// - Epochs must be in ascending order for optimal gas efficiency
1145///
1146/// ## Data Structure
1147/// Each epoch proof contains:
1148/// - `DailyParticipantData`: Participant-specific information
1149/// - `Vec<[u8; 32]>`: Payment proof path
1150/// - `Vec<bool>`: Payment proof indices
1151/// - `Vec<[u8; 32]>`: Seal proof path
1152/// - `Vec<bool>`: Seal proof indices
1153/// - `[u8; 32]`: Payment root
1154/// - `EpochClaimData`: Epoch-specific reward and activity data
1155/// - `DailyParticipantData`: Participant data for each epoch
1156pub fn batch_claim_vector(
1157    signer: Pubkey,
1158    beneficiary: Pubkey,
1159    epochs: Vec<u64>,
1160    participant_type: u8,
1161    max_batch_size: u8,
1162    epoch_proofs: Vec<(
1163        Vec<[u8; 32]>, // payment_proof
1164        Vec<bool>,     // payment_indices
1165        Vec<[u8; 32]>, // seal_proof
1166        Vec<bool>,     // seal_indices
1167        [u8; 32],      // payment_root
1168        EpochClaimData,
1169        DailyParticipantData, // participant data for each epoch
1170    )>,
1171    social_data: Option<Vec<Option<SocialClaimData>>>,
1172) -> Result<Instruction, crate::error::MiracleError> {
1173    if epochs.is_empty() {
1174        return Err(crate::error::MiracleError::InvalidInput);
1175    }
1176
1177    if epochs.len() > max_batch_size as usize {
1178        return Err(crate::error::MiracleError::InvalidInput);
1179    }
1180
1181    if epoch_proofs.len() != epochs.len() {
1182        return Err(crate::error::MiracleError::InvalidInput);
1183    }
1184
1185    // Validate social_data if provided
1186    if let Some(ref social_data_vec) = social_data {
1187        if social_data_vec.len() != epochs.len() {
1188            return Err(crate::error::MiracleError::InvalidInput);
1189        }
1190    }
1191
1192    // Create batch claim instruction
1193    let mut data = Vec::new();
1194
1195    // Use the BatchClaim struct's proper serialization like single claim does
1196    let batch_claim_data = BatchClaim {
1197        start_epoch: [0u8; 8], // Not used for vector claims
1198        end_epoch: [0u8; 8],   // Not used for vector claims
1199        participant_type,
1200        claim_type: 1u8, // Vector claim = 1
1201        epoch_count: epochs.len() as u8,
1202        max_batch_size,
1203        _padding: [0u8; 4],
1204    };
1205    data.extend_from_slice(&batch_claim_data.to_bytes());
1206
1207    // Add epoch list with proper endianness like single claim
1208    for &epoch in &epochs {
1209        data.extend_from_slice(&epoch.to_le_bytes());
1210    }
1211
1212    // Add proof data for each epoch
1213    for epoch_proof in &epoch_proofs {
1214        let (
1215            payment_proof,
1216            payment_indices,
1217            seal_proof,
1218            seal_indices,
1219            payment_root,
1220            epoch_data,
1221            participant_data,
1222        ) = epoch_proof;
1223
1224        // Add payment proof length
1225        data.push(payment_proof.len() as u8);
1226
1227        // Add payment proof path
1228        for path_node in payment_proof {
1229            data.extend_from_slice(path_node);
1230        }
1231
1232        // Add payment indices
1233        for &is_right in payment_indices {
1234            data.push(if is_right { 1u8 } else { 0u8 });
1235        }
1236
1237        // Add seal proof length
1238        data.push(seal_proof.len() as u8);
1239
1240        // Add seal proof path
1241        for path_node in seal_proof {
1242            data.extend_from_slice(path_node);
1243        }
1244
1245        // Add seal indices
1246        for &is_right in seal_indices {
1247            data.push(if is_right { 1u8 } else { 0u8 });
1248        }
1249
1250        // Add payment root
1251        data.extend_from_slice(payment_root);
1252
1253        // Add epoch data
1254        data.extend_from_slice(bytemuck::bytes_of(epoch_data));
1255
1256        // Add participant data for this epoch
1257        data.extend_from_slice(bytemuck::bytes_of(participant_data));
1258    }
1259
1260    // Add social flags (1 byte per epoch indicating if social claim exists)
1261    let social_flags = social_data
1262        .as_ref()
1263        .map(|social_vec| {
1264            social_vec
1265                .iter()
1266                .map(|opt| if opt.is_some() { 1u8 } else { 0u8 })
1267                .collect::<Vec<u8>>()
1268        })
1269        .unwrap_or_else(|| vec![0u8; epochs.len()]);
1270
1271    data.extend_from_slice(&social_flags);
1272
1273    // Add social data for epochs that have social claims
1274    if let Some(social_data_vec) = social_data {
1275        for social_opt in social_data_vec {
1276            if let Some(social) = social_opt {
1277                data.extend_from_slice(bytemuck::bytes_of(&social));
1278            }
1279        }
1280    }
1281
1282    // Create instruction with correct account structure
1283    let proof_pda = proof_pda(signer);
1284    Ok(Instruction {
1285        program_id: crate::ID,
1286        accounts: vec![
1287            AccountMeta::new(signer, true),                     // [0] signer
1288            AccountMeta::new(beneficiary, false),               // [1] beneficiary
1289            AccountMeta::new(proof_pda.0, false),               // [2] proof PDA
1290            AccountMeta::new(TREASURY_ADDRESS, false),          // [3] treasury
1291            AccountMeta::new(TREASURY_TOKENS_ADDRESS, false),   // [4] treasury tokens
1292            AccountMeta::new_readonly(spl_token::ID, false),    // [5] token program
1293            AccountMeta::new_readonly(SNAPSHOT_ADDRESS, false), // [6] snapshot
1294            AccountMeta::new_readonly(CONFIG_ADDRESS, false),   // [7] config
1295        ],
1296        data,
1297    })
1298}
1299
1300/// Build an UpdateTargets instruction to update community targets and activity limits.
1301/// This function allows the oracle to update configurable parameters based on community performance.
1302///
1303/// ## Parameters
1304/// - `oracle_authority`: The oracle authority that can update these parameters
1305/// - `target_weekly_users`: Target weekly active users (default: 500 for launch)
1306/// - `target_weekly_activity`: Target weekly activity count (default: 5,000 for launch)
1307/// - `target_retention_rate`: Target retention rate in basis points (default: 5,000 = 50%)
1308/// - `max_customer_activity_per_epoch`: Max customer activities per epoch (default: 5)
1309/// - `max_merchant_activity_per_epoch`: Max merchant activities per epoch (default: 50)
1310/// - `activity_cap_enabled`: Whether activity capping is enabled (default: 1 = enabled)
1311/// - `claim_cap_enabled`: Whether claim capping is enabled (default: 1 = enabled)
1312///
1313/// ## Returns
1314/// - Instruction to update community targets and activity limits
1315///
1316/// ## Authority
1317/// - Only the oracle authority can execute this instruction
1318/// - Oracle has the data to make informed decisions about community health
1319///
1320/// ## Benefits
1321/// - **Operational Flexibility**: Adjust parameters based on real community performance
1322/// - **Anti-Gaming**: Update activity limits to prevent new gaming strategies
1323/// - **Community Adaptation**: Adjust targets as community grows and evolves
1324/// - **Economic Balance**: Fine-tune parameters for sustainable tokenomics
1325/// - **Risk Management**: Adjust claim rewards threshold based on market conditions
1326///
1327/// ## Example
1328/// ```rust
1329/// use miracle_api::sdk;
1330/// use solana_program::pubkey::Pubkey;
1331///
1332/// let oracle_authority = Pubkey::new_unique();
1333/// let update_targets_ix = sdk::update_targets(
1334///     oracle_authority,
1335///     1000,   // target_weekly_users
1336///     10000,  // target_weekly_activity
1337///     6000,   // target_retention_rate (60%)
1338///     10,     // max_customer_activity_per_epoch
1339///     100,    // max_merchant_activity_per_epoch
1340///     1,      // activity_cap_enabled
1341///     1,      // claim_cap_enabled
1342///     1000000, // claim_rewards_threshold
1343/// );
1344/// ```
1345pub fn update_targets(
1346    oracle_authority: Pubkey,
1347    target_weekly_users: u32,
1348    target_weekly_activity: u32,
1349    target_retention_rate: u16,
1350    max_customer_activity_per_epoch: u32,
1351    max_merchant_activity_per_epoch: u32,
1352    activity_cap_enabled: u8,
1353    claim_cap_enabled: u8,
1354    claim_rewards_threshold: u64,
1355) -> Instruction {
1356    let data = UpdateTargets {
1357        target_weekly_users: target_weekly_users.to_le_bytes(),
1358        target_weekly_activity: target_weekly_activity.to_le_bytes(),
1359        target_retention_rate: target_retention_rate.to_le_bytes(),
1360        max_customer_activity_per_epoch: max_customer_activity_per_epoch.to_le_bytes(),
1361        max_merchant_activity_per_epoch: max_merchant_activity_per_epoch.to_le_bytes(),
1362        activity_cap_enabled,
1363        claim_cap_enabled,
1364        claim_rewards_threshold: claim_rewards_threshold.to_le_bytes(),
1365        _padding: [0; 4],
1366    }
1367    .to_bytes();
1368
1369    Instruction {
1370        program_id: crate::ID,
1371        accounts: vec![
1372            AccountMeta::new(oracle_authority, true),
1373            AccountMeta::new(CONFIG_ADDRESS, false),
1374        ],
1375        data,
1376    }
1377}
1378
1379// ===== MERKLE TREE CREATION MODULE =====
1380
1381/// Merkle tree creation result containing the root and proof generation capabilities.
1382///
1383/// This structure provides everything needed for off-chain processors to:
1384/// 1. Generate the merkle root for snapshot updates
1385/// 2. Generate merkle proofs for individual claims
1386/// 3. Verify merkle proofs for validation
1387///
1388/// ## Security
1389/// - **Deterministic**: Same input data always produces the same merkle root
1390/// - **Cryptographic**: Uses Solana hashv for collision resistance
1391/// - **Efficient**: O(n) construction time, O(log n) proof generation
1392/// - **Verifiable**: All proofs can be verified on-chain
1393#[repr(C)]
1394#[derive(Clone, Debug, Serialize, Deserialize)]
1395pub struct MerkleTree {
1396    /// The merkle root hash (32 bytes) - used for snapshot updates
1397    pub root: [u8; 32],
1398    /// Internal tree structure for proof generation
1399    tree: Vec<Vec<[u8; 32]>>,
1400    /// Original participant data for proof generation
1401    participants: Vec<DailyParticipantData>,
1402}
1403
1404impl MerkleTree {
1405    /// Create a new merkle tree from participant data.
1406    ///
1407    /// ## Parameters
1408    /// - `participants`: Vector of daily participant data
1409    ///
1410    /// ## Returns
1411    /// - `MerkleTree` instance with root and proof generation capabilities
1412    ///
1413    /// ## Panics
1414    /// - If participants vector is empty
1415    ///
1416    /// ## Example
1417    /// ```rust
1418    /// use miracle_api::{sdk, prelude::{DailyParticipantData, MerkleTree}};
1419    ///
1420    /// let participants = vec![
1421    ///     DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
1422    ///     DailyParticipantData::new([2u8; 32], 1723680000, 1723766400, [3u8; 8], [4u8; 8], 10, 1),
1423    /// ];
1424    ///
1425    /// let merkle_tree = MerkleTree::new(participants);
1426    /// println!("Merkle root: {:?}", merkle_tree.root());
1427    /// ```
1428    pub fn new(mut participants: Vec<DailyParticipantData>) -> Self {
1429        if participants.is_empty() {
1430            panic!("Cannot create merkle tree from empty participants list");
1431        }
1432
1433        // Validate all participants
1434        for participant in &participants {
1435            participant.validate().expect("Invalid participant data");
1436        }
1437
1438        // Sort participants deterministically by participant_id for consistent merkle tree construction
1439        // This ensures that the same participants always produce the same merkle root regardless of input order
1440        sort_participants_by_id(&mut participants);
1441
1442        // Generate leaf hashes from sorted participants
1443        let mut leaves: Vec<[u8; 32]> =
1444            participants.iter().map(|p| p.compute_leaf_hash()).collect();
1445
1446        // CRITICAL: Handle single leaf case - duplicate the leaf to force one hash operation
1447        // This matches Oracle's logic exactly for consistency
1448        if leaves.len() == 1 {
1449            leaves.push(leaves[0]); // Duplicate the single leaf
1450        }
1451
1452        // Build merkle tree bottom-up
1453        let mut tree = vec![leaves.clone()];
1454
1455        while leaves.len() > 1 {
1456            let mut new_level = Vec::new();
1457            for chunk in leaves.chunks(2) {
1458                if chunk.len() == 2 {
1459                    let combined = hashv(&[&chunk[0], &chunk[1]]).to_bytes();
1460                    new_level.push(combined);
1461                } else {
1462                    // Single element case - hash with itself for consistency
1463                    let combined = hashv(&[&chunk[0], &chunk[0]]).to_bytes();
1464                    new_level.push(combined);
1465                }
1466            }
1467            leaves = new_level;
1468            tree.push(leaves.clone());
1469        }
1470
1471        println!("🔍 Debug: Built tree with {} levels", tree.len());
1472        for (i, level) in tree.iter().enumerate() {
1473            println!("🔍 Debug: Level {} has {} nodes", i, level.len());
1474        }
1475
1476        let root = tree.last().unwrap()[0];
1477        println!("🔍 Debug: Root from tree: {:?}", root);
1478        println!("🔍 Debug: Root from leaves: {:?}", leaves[0]);
1479
1480        Self {
1481            root,
1482            tree,
1483            participants,
1484        }
1485    }
1486
1487    /// Get the merkle root hash.
1488    ///
1489    /// ## Returns
1490    /// - 32-byte merkle root hash for snapshot updates
1491    pub fn root(&self) -> [u8; 32] {
1492        self.root
1493    }
1494
1495    /// Generate a merkle proof for a specific participant.
1496    ///
1497    /// ## Parameters
1498    /// - `participant_index`: Index of the participant in the original data
1499    ///
1500    /// ## Returns
1501    /// - `MerkleProof` containing path and indices for verification
1502    ///
1503    /// ## Panics
1504    /// - If participant_index is out of bounds
1505    ///
1506    /// ## Example
1507    /// ```rust
1508    /// use miracle_api::{sdk, prelude::{DailyParticipantData, MerkleTree, EpochClaimData}};
1509    /// use solana_program::pubkey::Pubkey;
1510    ///
1511    /// let participants = vec![
1512    ///     DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
1513    ///     DailyParticipantData::new([2u8; 32], 1723680000, 1723766400, [3u8; 8], [4u8; 8], 10, 1),
1514    /// ];
1515    ///
1516    /// let merkle_tree = MerkleTree::new(participants);
1517    /// let payment_proof = merkle_tree.generate_proof(0);
1518    /// let seal_proof = sdk::SealProof::new_for_verification(vec![[2u8; 32]], vec![true], merkle_tree.root());
1519    ///
1520    /// // Use proof for claim instruction using dual merkle tree
1521    /// let customer_wallet = Pubkey::new_unique();
1522    /// let beneficiary = Pubkey::new_unique();
1523    /// let epoch = 0u64;
1524    /// let participant_type = 0u8;
1525    /// let claim_ix = sdk::claim(
1526    ///     customer_wallet,
1527    ///     beneficiary,
1528    ///     epoch,
1529    ///     DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
1530    ///     payment_proof.path,
1531    ///     payment_proof.indices,
1532    ///     seal_proof.path,
1533    ///     seal_proof.indices,
1534    ///     seal_proof.payment_root,
1535    ///     EpochClaimData {
1536    ///         customer_reward_pool: 1000000u64.to_le_bytes(),
1537    ///         merchant_reward_pool: 500000u64.to_le_bytes(),
1538    ///         total_customer_activity: 0u64.to_le_bytes(),
1539    ///         total_merchant_activity: 0u64.to_le_bytes(),
1540    ///     },
1541    ///     participant_type,
1542    ///     None, // social_data (no social claim)
1543    /// );
1544    /// ```
1545    pub fn generate_proof(&self, participant_index: usize) -> MerkleProof {
1546        if participant_index >= self.participants.len() {
1547            panic!("Participant index out of bounds");
1548        }
1549
1550        let mut path = Vec::new();
1551        let mut indices = Vec::new();
1552        let mut current_index = participant_index;
1553
1554        println!(
1555            "🔍 Debug: Generating proof for participant index {}",
1556            participant_index
1557        );
1558        println!("🔍 Debug: Tree has {} levels", self.tree.len());
1559
1560        // Traverse up the tree to build the proof
1561        for level in 0..self.tree.len() - 1 {
1562            let level_size = self.tree[level].len();
1563            println!(
1564                "🔍 Debug: Level {} has {} nodes, current_index = {}",
1565                level, level_size, current_index
1566            );
1567
1568            if current_index % 2 == 0 {
1569                // Left child - need right sibling
1570                if current_index + 1 < level_size {
1571                    let sibling = self.tree[level][current_index + 1];
1572                    path.push(sibling);
1573                    indices.push(true); // true = sibling is on the right
1574                    println!(
1575                        "🔍 Debug: Level {}: Left child, adding right sibling {:?}",
1576                        level, sibling
1577                    );
1578                }
1579            } else {
1580                // Right child - need left sibling
1581                let sibling = self.tree[level][current_index - 1];
1582                path.push(sibling);
1583                indices.push(false); // false = sibling is on the left
1584                println!(
1585                    "🔍 Debug: Level {}: Right child, adding left sibling {:?}",
1586                    level, sibling
1587                );
1588            }
1589
1590            current_index /= 2;
1591        }
1592
1593        println!(
1594            "🔍 Debug: Generated proof with {} path nodes and indices {:?}",
1595            path.len(),
1596            indices
1597        );
1598        MerkleProof { path, indices }
1599    }
1600
1601    /// Find participant by ID and generate proof.
1602    ///
1603    /// ## Parameters
1604    /// - `participant_id`: The participant ID to find
1605    ///
1606    /// ## Returns
1607    /// - `Some(MerkleProof)` if participant found
1608    /// - `None` if participant not found
1609    ///
1610    /// ## Example
1611    /// ```rust
1612    /// use miracle_api::{sdk, prelude::{DailyParticipantData, MerkleTree}};
1613    ///
1614    /// let participants = vec![
1615    ///     DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
1616    ///     DailyParticipantData::new([2u8; 32], 1723680000, 1723766400, [3u8; 8], [4u8; 8], 10, 1),
1617    /// ];
1618    ///
1619    /// let merkle_tree = MerkleTree::new(participants);
1620    ///
1621    /// if let Some(payment_proof) = merkle_tree.find_and_generate_proof([1u8; 32]) {
1622    ///     // Use payment proof for dual merkle tree claim
1623    /// }
1624    /// ```
1625    pub fn find_and_generate_proof(&self, participant_id: [u8; 32]) -> Option<MerkleProof> {
1626        self.participants
1627            .iter()
1628            .position(|p| p.participant_id == participant_id)
1629            .map(|index| self.generate_proof(index))
1630    }
1631
1632    /// Get the number of participants in the tree.
1633    ///
1634    /// ## Returns
1635    /// - Number of participants
1636    pub fn participant_count(&self) -> usize {
1637        self.participants.len()
1638    }
1639
1640    /// Get participant data by index.
1641    ///
1642    /// ## Parameters
1643    /// - `index`: Participant index
1644    ///
1645    /// ## Returns
1646    /// - `Some(&DailyParticipantData)` if index is valid
1647    /// - `None` if index is out of bounds
1648    pub fn get_participant(&self, index: usize) -> Option<&DailyParticipantData> {
1649        self.participants.get(index)
1650    }
1651
1652    /// Get all participant data.
1653    ///
1654    /// ## Returns
1655    /// - Reference to all participant data
1656    pub fn participants(&self) -> &[DailyParticipantData] {
1657        &self.participants
1658    }
1659}
1660
1661/// Merkle proof for a specific participant.
1662///
1663/// This structure contains the path and direction indices needed to verify
1664/// that a participant's data is included in the merkle tree.
1665///
1666/// ## Security
1667/// - **Cryptographic**: Uses Solana hashv for collision resistance
1668/// - **Deterministic**: Same participant always generates same proof
1669/// - **Verifiable**: Can be verified on-chain against merkle root
1670#[repr(C)]
1671#[derive(Clone, Debug, Serialize, Deserialize)]
1672pub struct MerkleProof {
1673    /// Merkle path nodes (sibling hashes)
1674    pub path: Vec<[u8; 32]>,
1675    /// Direction indices (false = left, true = right)
1676    pub indices: Vec<bool>,
1677}
1678
1679/// Seal proof structure for dual merkle tree verification.
1680/// Contains both the merkle proof path and the payment root being proven.
1681/// Enhanced with epoch-specific data for accurate historical reward calculations.
1682#[repr(C)]
1683#[derive(Clone, Debug, Serialize, Deserialize)]
1684pub struct SealProof {
1685    /// Seal merkle path nodes (sibling hashes)
1686    pub path: Vec<[u8; 32]>,
1687    /// Seal direction indices (false = left, true = right)
1688    pub indices: Vec<bool>,
1689    /// Payment root being proven (embedded in seal proof)
1690    pub payment_root: [u8; 32],
1691    /// Epoch-specific reward calculation data for accurate historical calculations
1692    pub customer_reward_pool: u64,
1693    pub merchant_reward_pool: u64,
1694    pub total_customer_activity: u64,
1695    pub total_merchant_activity: u64,
1696    pub community_score: u16,
1697    pub customer_reward_share: u16,
1698    pub merchant_reward_share: u16,
1699    pub weekly_active_users: u32,
1700    pub weekly_retention_rate: u16,
1701    pub weekly_activity_count: u32,
1702}
1703
1704impl MerkleProof {
1705    /// Create a new merkle proof.
1706    ///
1707    /// ## Parameters
1708    /// - `path`: Vector of merkle path nodes
1709    /// - `indices`: Vector of direction indices
1710    ///
1711    /// ## Returns
1712    /// - New MerkleProof instance
1713    pub fn new(path: Vec<[u8; 32]>, indices: Vec<bool>) -> Self {
1714        Self { path, indices }
1715    }
1716
1717    /// Verify this proof against a merkle root and participant data.
1718    ///
1719    /// ## Parameters
1720    /// - `participant`: The participant data to verify
1721    /// - `merkle_root`: The expected merkle root
1722    ///
1723    /// ## Returns
1724    /// - `true` if proof is valid
1725    /// - `false` if proof is invalid
1726    ///
1727    /// ## Example
1728    /// ```rust
1729    /// use miracle_api::{sdk, prelude::{DailyParticipantData, MerkleTree}};
1730    ///
1731    /// let participant = DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0);
1732    /// let participants = vec![participant.clone()];
1733    /// let merkle_tree = MerkleTree::new(participants);
1734    /// let proof = merkle_tree.generate_proof(0);
1735    ///
1736    /// assert!(proof.verify(&participant, merkle_tree.root()));
1737    /// ```
1738    pub fn verify(&self, participant: &DailyParticipantData, merkle_root: [u8; 32]) -> bool {
1739        if self.path.len() != self.indices.len() {
1740            return false;
1741        }
1742
1743        let mut current_hash = participant.compute_leaf_hash();
1744        println!(
1745            "🔍 Debug: Starting verification with leaf hash: {:?}",
1746            current_hash
1747        );
1748
1749        // Traverse the proof path
1750        for (i, &path_node) in self.path.iter().enumerate() {
1751            let is_right = self.indices[i];
1752            println!(
1753                "🔍 Debug: Step {}: is_right={}, path_node={:?}",
1754                i, is_right, path_node
1755            );
1756
1757            if is_right {
1758                // When is_right is true, the sibling is on the right
1759                // So current_hash should be on the left, path_node on the right
1760                // Use hashv(&[&left, &right]) to match tree construction
1761                current_hash = hashv(&[&current_hash, &path_node]).to_bytes();
1762            } else {
1763                // When is_right is false, the sibling is on the left
1764                // So path_node should be on the left, current_hash on the right
1765                // Use hashv(&[&left, &right]) to match tree construction
1766                current_hash = hashv(&[&path_node, &current_hash]).to_bytes();
1767            }
1768            println!("🔍 Debug: Step {}: current hash = {:?}", i, current_hash);
1769        }
1770
1771        println!(
1772            "🔍 Debug: Final hash: {:?}, Expected root: {:?}",
1773            current_hash, merkle_root
1774        );
1775        current_hash == merkle_root
1776    }
1777
1778    /// Get the proof length (number of path nodes).
1779    ///
1780    /// ## Returns
1781    /// - Number of nodes in the proof path
1782    pub fn length(&self) -> usize {
1783        self.path.len()
1784    }
1785
1786    /// Check if this is an empty proof (single leaf tree).
1787    ///
1788    /// ## Returns
1789    /// - `true` if proof is empty
1790    /// - `false` if proof has path nodes
1791    pub fn is_empty(&self) -> bool {
1792        self.path.is_empty()
1793    }
1794
1795    /// Serialize the MerkleProof to bytes (for use in transactions, batch claims, etc.)
1796    pub fn serialize(&self) -> Vec<u8> {
1797        bincode::serialize(self).expect("MerkleProof serialization failed")
1798    }
1799
1800    /// Deserialize a MerkleProof from bytes.
1801    pub fn deserialize(data: &[u8]) -> Self {
1802        bincode::deserialize(data).expect("MerkleProof deserialization failed")
1803    }
1804}
1805
1806/// Create a merkle tree from participant data.
1807///
1808/// This is a convenience function that creates a merkle tree from a vector
1809/// of participant data. It's the primary entry point for off-chain processors.
1810///
1811/// ## Parameters
1812/// - `participants`: Vector of daily participant data
1813///
1814/// ## Returns
1815/// - `MerkleTree` instance with root and proof generation capabilities
1816///
1817/// ## Panics
1818/// - If participants vector is empty
1819///
1820/// ## Example
1821/// ```rust
1822/// use miracle_api::{sdk, prelude::{DailyParticipantData, MerkleTree}};
1823///
1824/// // Off-chain processor aggregates daily payments
1825/// let participants = vec![
1826///     DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
1827///     DailyParticipantData::new([2u8; 32], 1723680000, 1723766400, [3u8; 8], [4u8; 8], 10, 1),
1828/// ];
1829///
1830/// // Create merkle tree
1831/// let merkle_tree = sdk::create_merkle_tree(participants);
1832/// println!("Merkle root: {:?}", merkle_tree.root());
1833/// ```
1834pub fn create_merkle_tree(participants: Vec<DailyParticipantData>) -> MerkleTree {
1835    MerkleTree::new(participants)
1836}
1837
1838/// Verify a merkle proof against a merkle root and participant data.
1839///
1840/// This is a convenience function for proof verification that can be used
1841/// both off-chain (for testing) and on-chain (in claim instructions).
1842///
1843/// ## Parameters
1844/// - `participant`: The participant data to verify
1845/// - `proof`: The merkle proof
1846/// - `merkle_root`: The expected merkle root
1847///
1848/// ## Returns
1849/// - `true` if proof is valid
1850/// - `false` if proof is invalid
1851///
1852/// ## Example
1853/// ```rust
1854/// use miracle_api::{sdk, prelude::DailyParticipantData};
1855///
1856/// let participant = DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0);
1857/// let participants = vec![participant.clone()];
1858/// let merkle_tree = sdk::create_merkle_tree(participants);
1859/// let proof = merkle_tree.generate_proof(0);
1860///
1861/// assert!(sdk::verify_merkle_proof(&participant, &proof, merkle_tree.root()));
1862/// ```
1863pub fn verify_merkle_proof(
1864    participant: &DailyParticipantData,
1865    proof: &MerkleProof,
1866    merkle_root: [u8; 32],
1867) -> bool {
1868    proof.verify(participant, merkle_root)
1869}
1870
1871/// Find the index of a participant in the participant list.
1872///
1873/// This helper function allows users to find their index in the Merkle tree
1874/// without needing to know the internal ordering. This is useful for:
1875/// - Generating proofs for claims
1876/// - Verifying participant inclusion
1877/// - Building user-friendly interfaces
1878///
1879/// ## Parameters
1880/// - `participants`: Vector of daily participant data
1881/// - `participant_id`: The participant ID to find
1882///
1883/// ## Returns
1884/// - `Some(index)` if participant found
1885/// - `None` if participant not found
1886///
1887/// ## Example
1888/// ```rust
1889/// use miracle_api::{sdk, prelude::{DailyParticipantData, MerkleTree, EpochClaimData}};
1890/// use solana_program::pubkey::Pubkey;
1891///
1892/// let participants = vec![
1893///     DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
1894///     DailyParticipantData::new([2u8; 32], 1723680000, 1723766400, [3u8; 8], [4u8; 8], 10, 1),
1895/// ];
1896///
1897/// let my_id = [1u8; 32];
1898/// if let Some(my_index) = sdk::find_participant_index(&participants, my_id) {
1899///     println!("My index in the Merkle tree: {}", my_index);
1900///     
1901///     // Generate proof using the index
1902///     let merkle_tree = sdk::create_merkle_tree(participants);
1903///     let proof = merkle_tree.generate_proof(my_index);
1904///     
1905///     // Use proof for claim
1906///     let customer_wallet = Pubkey::new_unique();
1907///     let beneficiary = Pubkey::new_unique();
1908///     let epoch = 0u64;
1909///     let activity_count = 5u32;
1910///     let participant_type = 0u8;
1911///     let payment_proof = merkle_tree.generate_proof(my_index);
1912///     let seal_proof = sdk::SealProof::new_for_verification(vec![[2u8; 32]], vec![true], merkle_tree.root());
1913///     let claim_ix = sdk::claim(
1914///         customer_wallet,
1915///         beneficiary,
1916///         epoch,
1917///         DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
1918///         payment_proof.path,
1919///         payment_proof.indices,
1920///         seal_proof.path,
1921///         seal_proof.indices,
1922///         seal_proof.payment_root,
1923///         EpochClaimData {
1924///             customer_reward_pool: 1000000u64.to_le_bytes(),
1925///             merchant_reward_pool: 500000u64.to_le_bytes(),
1926///             total_customer_activity: 0u64.to_le_bytes(),
1927///             total_merchant_activity: 0u64.to_le_bytes(),
1928///         },
1929///         participant_type,
1930///         None, // social_data (no social claim)
1931///     );
1932/// }
1933/// ```
1934///
1935/// ## Alternative Approaches
1936/// Instead of finding the index, you can also use:
1937/// ```rust
1938/// use miracle_api::{sdk, prelude::{DailyParticipantData, MerkleTree}};
1939///
1940/// let participants = vec![
1941///     DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
1942///     DailyParticipantData::new([2u8; 32], 1723680000, 1723766400, [3u8; 8], [4u8; 8], 10, 1),
1943/// ];
1944///
1945/// let my_id = [1u8; 32];
1946/// // Direct proof generation by participant ID
1947/// let merkle_tree = sdk::create_merkle_tree(participants);
1948/// if let Some(proof) = merkle_tree.find_and_generate_proof(my_id) {
1949///     // Use proof directly
1950/// }
1951/// ```
1952pub fn find_participant_index(
1953    participants: &[DailyParticipantData],
1954    participant_id: [u8; 32],
1955) -> Option<usize> {
1956    participants
1957        .iter()
1958        .position(|p| p.participant_id == participant_id)
1959}
1960
1961/// Helper to sort participants deterministically by participant_id (lexicographically).
1962/// This is recommended for trustless Merkle tree construction.
1963pub fn sort_participants_by_id(participants: &mut [DailyParticipantData]) {
1964    participants.sort_by(|a, b| a.participant_id.cmp(&b.participant_id));
1965}
1966
1967/// Convert wallet address to participant ID (32-byte hash).
1968///
1969/// This function creates a deterministic participant ID from a wallet address
1970/// by hashing the address bytes. This ensures consistent participant identification
1971/// across different systems while maintaining privacy.
1972///
1973/// ## Parameters
1974/// - `wallet_address`: Base58 encoded Solana wallet address
1975///
1976/// ## Returns
1977/// - 32-byte participant ID hash
1978///
1979/// ## Example
1980/// ```rust
1981/// use miracle_api::sdk;
1982///
1983/// let wallet = "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM";
1984/// let participant_id = sdk::wallet_to_participant_id(wallet);
1985/// println!("Participant ID: {:?}", participant_id);
1986/// ```
1987///
1988/// ## Note
1989/// - Uses Solana's hashv function for deterministic results
1990/// - Same wallet address always produces same participant ID
1991/// - 32-byte output matches DailyParticipantData::participant_id type
1992/// - Matches on-chain participant ID computation exactly
1993pub fn wallet_to_participant_id(wallet_address: &str) -> [u8; 32] {
1994    use solana_program::pubkey::Pubkey;
1995    use std::str::FromStr;
1996
1997    // Parse wallet address to Pubkey first, then hash the bytes (same as on-chain)
1998    match Pubkey::from_str(wallet_address) {
1999        Ok(pubkey) => {
2000            let wallet_bytes = pubkey.to_bytes();
2001            hashv(&[&wallet_bytes]).to_bytes()
2002        }
2003        Err(_) => {
2004            // Fallback to hash of string bytes if Pubkey parsing fails
2005            hashv(&[wallet_address.as_bytes()]).to_bytes()
2006        }
2007    }
2008}
2009
2010/// Find participant by wallet address in a participant list.
2011///
2012/// This function converts the wallet address to a participant ID and then
2013/// searches for it in the participant list. Useful for user-friendly lookups
2014/// when you have the wallet address but need to find the participant data.
2015///
2016/// ## Parameters
2017/// - `participants`: Vector of daily participant data
2018/// - `wallet_address`: Base58 encoded Solana wallet address
2019///
2020/// ## Returns
2021/// - `Some(index)` if participant found
2022/// - `None` if participant not found
2023///
2024/// ## Example
2025/// ```rust
2026/// use miracle_api::{sdk, prelude::{DailyParticipantData, MerkleTree, EpochClaimData}};
2027/// use solana_program::pubkey::Pubkey;
2028///
2029/// let participants = vec![
2030///     DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
2031///     DailyParticipantData::new([2u8; 32], 1723680000, 1723766400, [3u8; 8], [4u8; 8], 10, 1),
2032/// ];
2033///
2034/// let wallet = "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM";
2035/// if let Some(index) = sdk::find_participant_by_wallet(&participants, wallet) {
2036///     println!("Found participant at index: {}", index);
2037///     
2038///     // Generate proof using the index
2039///     let merkle_tree = sdk::create_merkle_tree(participants);
2040///     let proof = merkle_tree.generate_proof(index);
2041///     
2042///     // Use proof for claim
2043///     let customer_wallet = Pubkey::new_unique();
2044///     let beneficiary = Pubkey::new_unique();
2045///     let epoch = 0u64;
2046///     let activity_count = 5u32;
2047///     let participant_type = 0u8;
2048///     let payment_proof = merkle_tree.generate_proof(index);
2049///     let seal_proof = sdk::SealProof::new_for_verification(vec![[2u8; 32]], vec![true], merkle_tree.root());
2050///     let claim_ix = sdk::claim(
2051///         customer_wallet,
2052///         beneficiary,
2053///         epoch,
2054///         DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
2055///         payment_proof.path,
2056///         payment_proof.indices,
2057///         seal_proof.path,
2058///         seal_proof.indices,
2059///         seal_proof.payment_root,
2060///         EpochClaimData {
2061///             customer_reward_pool: 1000000u64.to_le_bytes(),
2062///             merchant_reward_pool: 500000u64.to_le_bytes(),
2063///             total_customer_activity: 0u64.to_le_bytes(),
2064///             total_merchant_activity: 0u64.to_le_bytes(),
2065///         },
2066///         participant_type,
2067///         None, // social_data (no social claim)
2068///     );
2069/// }
2070/// ```
2071pub fn find_participant_by_wallet(
2072    participants: &[DailyParticipantData],
2073    wallet_address: &str,
2074) -> Option<usize> {
2075    let participant_id = wallet_to_participant_id(wallet_address);
2076    participants
2077        .iter()
2078        .position(|p| p.participant_id == participant_id)
2079}
2080
2081/// Create DailyParticipantData from wallet address and payment information.
2082///
2083/// This convenience function creates a DailyParticipantData instance from
2084/// a wallet address, automatically converting it to the required participant ID.
2085/// Useful for off-chain processors when aggregating payment data.
2086///
2087/// ## Parameters
2088/// - `wallet_address`: Base58 encoded Solana wallet address
2089/// - `first_payment_timestamp`: Unix timestamp of first payment
2090/// - `last_payment_timestamp`: Unix timestamp of last payment
2091/// - `first_payment_tx_sig_short`: First payment transaction signature (short)
2092/// - `last_payment_tx_sig_short`: Last payment transaction signature (short)
2093/// - `payment_count`: Number of transactions for this participant
2094/// - `participant_type`: 0 for customer, 1 for merchant
2095///
2096/// ## Returns
2097/// - New DailyParticipantData instance
2098///
2099/// ## Example
2100/// ```rust
2101/// use miracle_api::{sdk, prelude::{DailyParticipantData, MerkleTree}};
2102///
2103/// // Off-chain processor aggregating daily payments
2104/// let customer_wallet = "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM";
2105/// let customer_data = sdk::create_participant_from_wallet(
2106///     customer_wallet,
2107///     1723680000,     // first payment timestamp
2108///     1723766400,     // last payment timestamp
2109///     [1u8; 8],       // first payment tx sig short
2110///     [2u8; 8],       // last payment tx sig short
2111///     5,              // 5 transactions
2112///     0,              // customer type
2113/// );
2114///
2115/// let merchant_wallet = "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU";
2116/// let merchant_data = sdk::create_participant_from_wallet(
2117///     merchant_wallet,
2118///     1723680000,     // first payment timestamp
2119///     1723766400,     // last payment timestamp
2120///     [3u8; 8],       // first payment tx sig short
2121///     [4u8; 8],       // last payment tx sig short
2122///     10,             // 10 transactions
2123///     1,              // merchant type
2124/// );
2125///
2126/// // Create merkle tree from aggregated data
2127/// let participants = vec![customer_data, merchant_data];
2128/// let merkle_tree = sdk::create_merkle_tree(participants);
2129/// println!("Merkle root: {:?}", merkle_tree.root());
2130/// ```
2131pub fn create_participant_from_wallet(
2132    wallet_address: &str,
2133    first_payment_timestamp: i64,
2134    last_payment_timestamp: i64,
2135    first_payment_tx_sig_short: [u8; 8],
2136    last_payment_tx_sig_short: [u8; 8],
2137    payment_count: u32,
2138    participant_type: u8,
2139) -> DailyParticipantData {
2140    DailyParticipantData::new(
2141        wallet_to_participant_id(wallet_address),
2142        first_payment_timestamp,
2143        last_payment_timestamp,
2144        first_payment_tx_sig_short,
2145        last_payment_tx_sig_short,
2146        payment_count,
2147        participant_type,
2148    )
2149}
2150
2151/// Generate merkle proof for a wallet address.
2152///
2153/// This convenience function combines wallet address lookup and proof generation.
2154/// It finds the participant by wallet address and generates their merkle proof
2155/// in one step. Useful for user-facing applications where you have the wallet
2156/// address but need the proof for claiming.
2157///
2158/// ## Parameters
2159/// - `participants`: Vector of daily participant data
2160/// - `wallet_address`: Base58 encoded Solana wallet address
2161///
2162/// ## Returns
2163/// - `Some(MerkleProof)` if participant found
2164/// - `None` if participant not found
2165///
2166/// ## Example
2167/// ```rust
2168/// use miracle_api::{sdk, prelude::{DailyParticipantData, MerkleTree, EpochClaimData}};
2169/// use solana_program::pubkey::Pubkey;
2170///
2171/// let participants = vec![
2172///     DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
2173///     DailyParticipantData::new([2u8; 32], 1723680000, 1723766400, [3u8; 8], [4u8; 8], 10, 1),
2174/// ];
2175///
2176/// let wallet = "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM";
2177/// if let Some(proof) = sdk::generate_proof_for_wallet(&participants, wallet) {
2178///     println!("Generated proof with {} path nodes", proof.length());
2179///     
2180///     // Use proof for claim
2181///     let merkle_tree = sdk::create_merkle_tree(participants);
2182///     let customer_wallet = Pubkey::new_unique();
2183///     let beneficiary = Pubkey::new_unique();
2184///     let epoch = 0u64;
2185///     let activity_count = 5u32;
2186///     let participant_type = 0u8;
2187///     let payment_proof = proof;
2188///     let seal_proof = sdk::SealProof::new_for_verification(vec![[2u8; 32]], vec![true], merkle_tree.root());
2189///     let claim_ix = sdk::claim(
2190///         customer_wallet,
2191///         beneficiary,
2192///         epoch,
2193///         DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0),
2194///         payment_proof.path,
2195///         payment_proof.indices,
2196///         seal_proof.path,
2197///         seal_proof.indices,
2198///         seal_proof.payment_root,
2199///         EpochClaimData {
2200///             customer_reward_pool: 1000000u64.to_le_bytes(),
2201///             merchant_reward_pool: 500000u64.to_le_bytes(),
2202///             total_customer_activity: 0u64.to_le_bytes(),
2203///             total_merchant_activity: 0u64.to_le_bytes(),
2204///         },
2205///         participant_type,
2206///         None, // social_data (no social claim)
2207///     );
2208/// }
2209/// ```
2210pub fn generate_proof_for_wallet(
2211    participants: &[DailyParticipantData],
2212    wallet_address: &str,
2213) -> Option<MerkleProof> {
2214    let merkle_tree = create_merkle_tree(participants.to_vec());
2215    merkle_tree.find_and_generate_proof(wallet_to_participant_id(wallet_address))
2216}
2217
2218// ===== CLAIMS TRACKING MODULE =====
2219
2220/// Generate a unique claim key for tracking claims.
2221///
2222/// The claim key is a hash of participant ID, epoch, participant type, and claim type.
2223/// This ensures each claim is uniquely identifiable and prevents double-spending.
2224///
2225/// ## Parameters
2226/// - `participant_id`: 32-byte participant ID (wallet hash)
2227/// - `epoch`: Epoch number
2228/// - `participant_type`: 0 = customer, 1 = merchant
2229/// - `claim_type`: 1 = payment only, 3 = payment + social
2230///
2231/// ## Returns
2232/// - 32-byte claim key hash
2233///
2234/// ## Security
2235/// - **Unique**: Each (participant, epoch, type) combination has unique key
2236/// - **Collision Resistant**: Uses SHA-256 for cryptographic security
2237/// - **Deterministic**: Same inputs always produce same key
2238pub fn generate_claim_key(
2239    participant_id: [u8; 32],
2240    epoch: u64,
2241    participant_type: u8,
2242    claim_type: u8,
2243) -> [u8; 32] {
2244    use solana_program::hash::hashv;
2245
2246    hashv(&[
2247        &participant_id,
2248        &epoch.to_le_bytes(),
2249        &participant_type.to_le_bytes(),
2250        &claim_type.to_le_bytes(),
2251    ])
2252    .to_bytes()
2253}
2254
2255// ===== DUAL MERKLE TREE VERIFICATION FUNCTIONS =====
2256
2257/// Verify a dual merkle claim using both payment and seal proofs.
2258///
2259/// This function implements the core security verification for the Dual Merkle Tree system.
2260/// It verifies both the payment merkle proof (user activity within epoch) and the seal
2261/// merkle proof (payment root authenticity from oracle), with the payment root embedded
2262/// in the seal proof data.
2263///
2264/// ## Parameters
2265/// - `payment_proof`: Merkle proof for user's activity within the epoch
2266/// - `seal_proof`: Merkle proof that payment root is in the seal tree (contains payment root)
2267/// - `on_chain_seal_root`: Current seal merkle root stored on-chain
2268/// - `participant_data`: User's participant data to verify
2269///
2270/// ## Returns
2271/// - `Ok(())` if both proofs are valid
2272/// - `Err(InvalidPaymentProof)` if payment proof is invalid
2273/// - `Err(InvalidSealProof)` if seal proof is invalid
2274///
2275/// ## Security Features
2276/// - **Payment Proof**: Ensures claim data integrity within an epoch
2277/// - **Seal Proof**: Ensures payment merkle root is authentic (from oracle)
2278/// - **Payment Root**: Embedded in seal proof for explicit verification
2279/// - **Dual Verification**: Both proofs must pass for a valid claim
2280///
2281/// ## Example
2282/// ```rust
2283/// use miracle_api::{sdk, prelude::{DailyParticipantData, MerkleProof, SealProof}};
2284///
2285/// let participant = DailyParticipantData::new([1u8; 32], 1723680000, 1723766400, [1u8; 8], [2u8; 8], 5, 0);
2286/// let payment_proof = MerkleProof::new(vec![[2u8; 32]], vec![true]);
2287/// let seal_proof = SealProof::new_for_verification(vec![[4u8; 32]], vec![false], [3u8; 32]);
2288/// let on_chain_seal_root = [5u8; 32];
2289///
2290/// let result = sdk::verify_dual_merkle_claim(
2291///     &payment_proof,
2292///     &seal_proof,
2293///     on_chain_seal_root,
2294///     &participant,
2295/// );
2296///
2297/// match result {
2298///     Ok(()) => println!("Dual merkle verification successful"),
2299///     Err(e) => println!("Verification failed: {:?}", e),
2300/// }
2301/// ```
2302pub fn verify_dual_merkle_claim(
2303    payment_proof: &MerkleProof,
2304    seal_proof: &SealProof,
2305    on_chain_seal_root: [u8; 32],
2306    participant_data: &DailyParticipantData,
2307) -> Result<(), crate::error::MiracleError> {
2308    // Rule 1: Verify seal proof - payment root must be in seal merkle tree
2309    if !verify_seal_proof(
2310        &seal_proof.path,
2311        &seal_proof.indices,
2312        seal_proof.payment_root,
2313        on_chain_seal_root,
2314    ) {
2315        return Err(crate::error::MiracleError::InvalidSealProof);
2316    }
2317
2318    // Rule 2: Verify payment proof against payment root
2319    if !payment_proof.verify(participant_data, seal_proof.payment_root) {
2320        return Err(crate::error::MiracleError::InvalidPaymentProof);
2321    }
2322
2323    Ok(())
2324}
2325
2326impl SealProof {
2327    /// Create a new seal proof with path, indices, payment root, and epoch-specific data.
2328    ///
2329    /// This constructor is required for:
2330    /// - Historical reward calculations
2331    /// - Production claim verification
2332    /// - Accurate epoch-specific data
2333    ///
2334    /// Use this for all production claims that require historical accuracy.
2335    ///
2336    /// ## Parameters
2337    /// - `path`: Seal merkle path nodes (sibling hashes)
2338    /// - `indices`: Seal direction indices (false = left, true = right)
2339    /// - `payment_root`: Payment root being proven (embedded in seal proof)
2340    /// - `customer_reward_pool`: Epoch-specific customer reward pool
2341    /// - `merchant_reward_pool`: Epoch-specific merchant reward pool
2342    /// - `total_customer_activity`: Epoch-specific total customer activity
2343    /// - `total_merchant_activity`: Epoch-specific total merchant activity
2344    /// - `community_score`: Epoch-specific community score
2345    /// - `customer_reward_share`: Epoch-specific customer reward share
2346    /// - `merchant_reward_share`: Epoch-specific merchant reward share
2347    /// - `weekly_active_users`: Epoch-specific weekly active users
2348    /// - `weekly_retention_rate`: Epoch-specific weekly retention rate
2349    /// - `weekly_activity_count`: Epoch-specific weekly activity count
2350    pub fn new(
2351        path: Vec<[u8; 32]>,
2352        indices: Vec<bool>,
2353        payment_root: [u8; 32],
2354        customer_reward_pool: u64,
2355        merchant_reward_pool: u64,
2356        total_customer_activity: u64,
2357        total_merchant_activity: u64,
2358        community_score: u16,
2359        customer_reward_share: u16,
2360        merchant_reward_share: u16,
2361        weekly_active_users: u32,
2362        weekly_retention_rate: u16,
2363        weekly_activity_count: u32,
2364    ) -> Self {
2365        Self {
2366            path,
2367            indices,
2368            payment_root,
2369            customer_reward_pool,
2370            merchant_reward_pool,
2371            total_customer_activity,
2372            total_merchant_activity,
2373            community_score,
2374            customer_reward_share,
2375            merchant_reward_share,
2376            weekly_active_users,
2377            weekly_retention_rate,
2378            weekly_activity_count,
2379        }
2380    }
2381
2382    /// Create a new seal proof for verification purposes only.
2383    ///
2384    /// This constructor is suitable for:
2385    /// - Proof verification (merkle path validation)
2386    /// - Testing and development
2387    /// - Backward compatibility with existing proofs
2388    /// - Client-side utility functions
2389    ///
2390    /// ⚠️  WARNING: This proof will have zero epoch data.
2391    /// For historical reward calculations, use oracle-generated proofs instead.
2392    ///
2393    /// ## Parameters
2394    /// - `path`: Seal merkle path nodes (sibling hashes)
2395    /// - `indices`: Seal direction indices (false = left, true = right)
2396    /// - `payment_root`: Payment root being proven (embedded in seal proof)
2397    ///
2398    /// ## Usage
2399    /// ```rust
2400    /// use miracle_api::prelude::SealProof;
2401    /// let path = vec![[1u8; 32], [2u8; 32]];
2402    /// let indices = vec![true, false];
2403    /// let payment_root = [3u8; 32];
2404    /// let proof = SealProof::new_for_verification(path, indices, payment_root);
2405    /// // Use for proof verification only
2406    /// ```
2407    pub fn new_for_verification(
2408        path: Vec<[u8; 32]>,
2409        indices: Vec<bool>,
2410        payment_root: [u8; 32],
2411    ) -> Self {
2412        Self {
2413            path,
2414            indices,
2415            payment_root,
2416            customer_reward_pool: 0,
2417            merchant_reward_pool: 0,
2418            total_customer_activity: 0,
2419            total_merchant_activity: 0,
2420            community_score: 0,
2421            customer_reward_share: 0,
2422            merchant_reward_share: 0,
2423            weekly_active_users: 0,
2424            weekly_retention_rate: 0,
2425            weekly_activity_count: 0,
2426        }
2427    }
2428}
2429
2430/// Verify a seal merkle proof.
2431///
2432/// This function verifies that a payment merkle root is included in the seal merkle tree.
2433/// The seal proof demonstrates that the payment root is authentic and was provided by the oracle.
2434///
2435/// ## Parameters
2436/// - `seal_path`: Seal merkle path nodes (Vec<[u8; 32]>)
2437/// - `seal_indices`: Seal direction indices (Vec<bool>)
2438/// - `payment_root`: Payment merkle root to verify
2439/// - `seal_root`: Root of the seal merkle tree
2440///
2441/// ## Returns
2442/// - `true` if seal proof is valid
2443/// - `false` if seal proof is invalid
2444///
2445/// ## Security
2446/// - Verifies payment root is authentic (from oracle)
2447/// - Prevents forged payment roots
2448/// - Ensures payment root is in the historical seal tree
2449fn verify_seal_proof(
2450    seal_path: &Vec<[u8; 32]>,
2451    seal_indices: &Vec<bool>,
2452    payment_root: [u8; 32],
2453    seal_root: [u8; 32],
2454) -> bool {
2455    // REJECT empty seal proof paths - they can't be verified
2456    if seal_path.is_empty() {
2457        return false;
2458    }
2459
2460    if seal_path.len() != seal_indices.len() {
2461        return false;
2462    }
2463
2464    let mut current_hash = payment_root;
2465
2466    // Traverse the seal proof path
2467    for (i, &path_node) in seal_path.iter().enumerate() {
2468        let is_right = seal_indices[i];
2469
2470        let mut combined_data = Vec::new();
2471        if is_right {
2472            // When is_right is true, the sibling is on the right
2473            // So current_hash should be on the left, path_node on the right
2474            combined_data.extend_from_slice(&current_hash);
2475            combined_data.extend_from_slice(&path_node);
2476        } else {
2477            // When is_right is false, the sibling is on the left
2478            // So path_node should be on the left, current_hash on the right
2479            combined_data.extend_from_slice(&path_node);
2480            combined_data.extend_from_slice(&current_hash);
2481        }
2482
2483        current_hash = hashv(&[&combined_data]).to_bytes();
2484    }
2485
2486    current_hash == seal_root
2487}
2488
2489/// Seal Merkle Tree for incremental construction of historical payment roots.
2490///
2491/// This tree grows incrementally, adding each new payment merkle root as a leaf.
2492/// It enables unlimited claim history while maintaining perfect security.
2493///
2494/// ## Growth Pattern
2495///
2496/// The seal tree grows incrementally:
2497/// - Epoch 0: [Payment Root 0]
2498/// - Epoch 1: [Payment Root 0] + [Payment Root 1] -> Seal Root 1
2499/// - Epoch 2: [Payment Root 0] + [Payment Root 1] + [Payment Root 2] -> Seal Root 2
2500/// - ...
2501/// - Epoch N: [Payment Root 0] + ... + [Payment Root N] -> Seal Root N
2502///
2503/// ## Security
2504/// - Each payment root is embedded in the seal tree
2505/// - Seal proofs demonstrate payment root authenticity
2506/// - Dual verification prevents all attack vectors
2507///
2508/// ## 🚨 CRITICAL: Chronological Ordering Requirement
2509///
2510/// **PAYMENT ROOTS MUST BE PROVIDED IN CHRONOLOGICAL ORDER (EPOCH 0, 1, 2, 3...)**
2511///
2512/// This is essential for:
2513/// - Deterministic seal merkle root generation
2514/// - Consistent verification between Oracle and on-chain program
2515/// - Preventing `0x10` (InvalidPaymentProof) errors
2516///
2517/// The Oracle team MUST ensure:
2518/// 1. Database queries return epochs in chronological order
2519/// 2. Payment roots are collected in epoch sequence
2520/// 3. `SealMerkleTree::from_payment_roots()` receives ordered data
2521///
2522/// Failure to maintain chronological ordering will result in:
2523/// - Different seal merkle roots between Oracle and on-chain
2524/// - Proof verification failures
2525/// - Inability to process claims
2526#[repr(C)]
2527#[derive(Clone, Debug, Serialize, Deserialize)]
2528pub struct SealMerkleTree {
2529    /// Current seal merkle root (32 bytes)
2530    pub root: [u8; 32],
2531    /// Internal tree structure for proof generation
2532    tree: Vec<Vec<[u8; 32]>>,
2533    /// Historical payment roots (leaves of the seal tree)
2534    payment_roots: Vec<[u8; 32]>,
2535}
2536
2537impl SealMerkleTree {
2538    /// Create a new empty seal merkle tree.
2539    ///
2540    /// ## Returns
2541    /// - Empty seal merkle tree with zero root
2542    pub fn new() -> Self {
2543        Self {
2544            root: [0u8; 32],
2545            tree: vec![vec![]],
2546            payment_roots: vec![],
2547        }
2548    }
2549
2550    /// Create a seal merkle tree from existing payment roots.
2551    ///
2552    /// ## Parameters
2553    /// - `payment_roots`: Vector of historical payment merkle roots
2554    ///
2555    /// ## Returns
2556    /// - Seal merkle tree with all payment roots included
2557    ///
2558    /// ## Important: Chronological Ordering
2559    /// The payment roots MUST be provided in chronological order (epoch 0, 1, 2, 3...)
2560    /// to ensure deterministic and verifiable seal merkle roots across all systems.
2561    /// This ordering rule is enforced to maintain consistency between Oracle and on-chain verification.
2562    pub fn from_payment_roots(payment_roots: Vec<[u8; 32]>) -> Self {
2563        if payment_roots.is_empty() {
2564            return Self::new();
2565        }
2566
2567        // CRITICAL: Enforce chronological ordering for deterministic seal merkle roots
2568        // The Oracle team must provide payment roots in epoch order (0, 1, 2, 3...)
2569        // This ensures the on-chain program can verify proofs against the same tree structure
2570        if payment_roots.len() > 1 {
2571            // Note: We cannot directly sort by epoch number from payment root hashes
2572            // The Oracle team MUST provide payment roots in chronological order (0, 1, 2, 3...)
2573            // This is a critical architectural requirement for deterministic tree construction
2574
2575            // Log warning about chronological ordering requirement
2576            // This helps debug ordering issues during development
2577            println!(
2578                "⚠️  WARNING: SealMerkleTree::from_payment_roots called with {} payment roots",
2579                payment_roots.len()
2580            );
2581            println!("   CRITICAL: Oracle team MUST provide payment roots in chronological order (epoch 0, 1, 2, 3...)");
2582            println!("   This ensures deterministic seal merkle roots for on-chain verification");
2583            println!("   Current implementation assumes correct ordering from Oracle team");
2584        }
2585
2586        // Build merkle tree from payment roots
2587        let mut tree = Vec::new();
2588        tree.push(payment_roots.clone());
2589
2590        while tree.last().unwrap().len() > 1 {
2591            let current_level = tree.last().unwrap();
2592            let mut next_level = Vec::new();
2593
2594            for chunk in current_level.chunks(2) {
2595                if chunk.len() == 2 {
2596                    // Correct hash ordering: left child first, then right child
2597                    let combined = hashv(&[&chunk[0], &chunk[1]]).to_bytes();
2598                    next_level.push(combined);
2599                } else {
2600                    // Single element case
2601                    let combined = hashv(&[&chunk[0], &chunk[0]]).to_bytes();
2602                    next_level.push(combined);
2603                }
2604            }
2605
2606            tree.push(next_level);
2607        }
2608
2609        let root = tree.last().unwrap()[0];
2610
2611        Self {
2612            root,
2613            tree,
2614            payment_roots,
2615        }
2616    }
2617
2618    /// Add a new payment root to the seal merkle tree.
2619    ///
2620    /// This function incrementally grows the seal tree by adding the new payment root
2621    /// and rebuilding the tree structure.
2622    ///
2623    /// ## Parameters
2624    /// - `payment_root`: New payment merkle root to add
2625    ///
2626    /// ## Returns
2627    /// - Updated seal merkle tree with new payment root included
2628    ///
2629    /// ## Important: Chronological Ordering
2630    /// Payment roots MUST be added in chronological order (epoch 0, 1, 2, 3...)
2631    /// to maintain deterministic tree structure. This method assumes the new payment root
2632    /// is for the next sequential epoch.
2633    pub fn add_payment_root(&mut self, payment_root: [u8; 32]) {
2634        // CRITICAL: Ensure payment roots are added in chronological order
2635        // This method assumes the new payment root is for the next sequential epoch
2636        // The Oracle team must call this method in epoch order to maintain consistency
2637        println!(
2638            "🔒 Adding payment root to seal merkle tree (epoch {})",
2639            self.payment_roots.len()
2640        );
2641        println!("   CRITICAL: Payment roots must be added in chronological order for deterministic verification");
2642
2643        self.payment_roots.push(payment_root);
2644
2645        // Rebuild the entire tree with the new payment root
2646        let mut tree = Vec::new();
2647        tree.push(self.payment_roots.clone());
2648
2649        while tree.last().unwrap().len() > 1 {
2650            let current_level = tree.last().unwrap();
2651            let mut next_level = Vec::new();
2652
2653            for chunk in current_level.chunks(2) {
2654                if chunk.len() == 2 {
2655                    // Correct hash ordering: left child first, then right child
2656                    let combined = hashv(&[&chunk[0], &chunk[1]]).to_bytes();
2657                    next_level.push(combined);
2658                } else {
2659                    // Single element case
2660                    let combined = hashv(&[&chunk[0], &chunk[0]]).to_bytes();
2661                    next_level.push(combined);
2662                }
2663            }
2664
2665            tree.push(next_level);
2666        }
2667
2668        self.tree = tree;
2669        self.root = self.tree.last().unwrap()[0];
2670    }
2671
2672    /// Get the current seal merkle root.
2673    ///
2674    /// ## Returns
2675    /// - Current seal merkle root (32 bytes)
2676    pub fn root(&self) -> [u8; 32] {
2677        self.root
2678    }
2679
2680    /// Get the number of payment roots in the seal tree.
2681    ///
2682    /// ## Returns
2683    /// - Number of historical payment roots
2684    pub fn payment_root_count(&self) -> usize {
2685        self.payment_roots.len()
2686    }
2687
2688    /// Get all payment roots in the seal tree.
2689    ///
2690    /// ## Returns
2691    /// - Vector of all historical payment roots
2692    pub fn payment_roots(&self) -> &[[u8; 32]] {
2693        &self.payment_roots
2694    }
2695
2696    /// Validate that payment roots are in chronological order.
2697    ///
2698    /// This method helps ensure the Oracle team is providing payment roots
2699    /// in the correct epoch sequence for deterministic tree construction.
2700    ///
2701    /// ## Returns
2702    /// - `true` if payment roots appear to be in chronological order
2703    /// - `false` if there are potential ordering issues
2704    ///
2705    /// ## Note
2706    /// This is a best-effort validation since we can't directly determine
2707    /// epoch numbers from payment root hashes. The Oracle team must ensure
2708    /// chronological ordering when calling this method.
2709    pub fn validate_chronological_order(&self) -> bool {
2710        if self.payment_roots.len() <= 1 {
2711            return true; // Single or no payment roots are always "ordered"
2712        }
2713
2714        // Log the current payment root count for debugging
2715        println!(
2716            "🔍 Validating chronological order of {} payment roots",
2717            self.payment_roots.len()
2718        );
2719        println!("   CRITICAL: Oracle team must ensure payment roots are provided in epoch order (0, 1, 2, 3...)");
2720
2721        // Since we can't directly validate epoch numbers from hashes,
2722        // we rely on the Oracle team's commitment to chronological ordering
2723        // This method serves as a reminder and debugging aid
2724        true
2725    }
2726
2727    /// Generate a seal proof for a specific payment root.
2728    ///
2729    /// This function creates a proof that demonstrates a payment root is included
2730    /// in the current seal merkle tree.
2731    ///
2732    /// ## Parameters
2733    /// - `payment_root`: Payment root to generate proof for
2734    ///
2735    /// ## Returns
2736    /// - `Some(SealProof)` if payment root is found
2737    /// - `None` if payment root is not in the tree
2738    pub fn generate_seal_proof(&self, payment_root: [u8; 32]) -> Option<SealProof> {
2739        // Find the index of the payment root
2740        let payment_root_index = self
2741            .payment_roots
2742            .iter()
2743            .position(|&root| root == payment_root)?;
2744
2745        let mut path = Vec::new();
2746        let mut indices = Vec::new();
2747
2748        let mut current_index = payment_root_index;
2749        for level in &self.tree[..self.tree.len() - 1] {
2750            let sibling_index = if current_index % 2 == 0 {
2751                current_index + 1
2752            } else {
2753                current_index - 1
2754            };
2755
2756            if sibling_index < level.len() {
2757                path.push(level[sibling_index]);
2758                indices.push(current_index % 2 == 1); // true if right sibling
2759            }
2760
2761            current_index /= 2;
2762        }
2763
2764        Some(SealProof::new_for_verification(path, indices, payment_root))
2765    }
2766
2767    /// Find and generate a seal proof for a payment root by epoch.
2768    ///
2769    /// This is a convenience function that finds a payment root by its epoch index
2770    /// and generates a seal proof for it.
2771    ///
2772    /// ## Parameters
2773    /// - `epoch_index`: Index of the epoch (0-based)
2774    ///
2775    /// ## Returns
2776    /// - `Some(SealProof)` if epoch exists
2777    /// - `None` if epoch index is out of bounds
2778    pub fn generate_seal_proof_for_epoch(&self, epoch_index: usize) -> Option<SealProof> {
2779        let payment_root = self.payment_roots.get(epoch_index)?;
2780        self.generate_seal_proof(*payment_root)
2781    }
2782
2783    /// Verify that a payment root is included in the seal tree.
2784    ///
2785    /// ## Parameters
2786    /// - `payment_root`: Payment root to verify
2787    ///
2788    /// ## Returns
2789    /// - `true` if payment root is in the seal tree
2790    /// - `false` if payment root is not in the seal tree
2791    pub fn contains_payment_root(&self, payment_root: [u8; 32]) -> bool {
2792        self.payment_roots.contains(&payment_root)
2793    }
2794}
2795
2796/// Create a new seal merkle tree.
2797///
2798/// This is a convenience function that creates an empty seal merkle tree.
2799/// Use `SealMerkleTree::from_payment_roots()` to create a tree with existing payment roots.
2800///
2801/// ## Returns
2802/// - Empty seal merkle tree
2803///
2804/// ## Example
2805/// ```rust
2806/// use miracle_api::sdk::create_seal_merkle_tree;
2807///
2808/// let mut seal_tree = create_seal_merkle_tree();
2809/// seal_tree.add_payment_root([1u8; 32]);
2810/// println!("Seal root: {:?}", seal_tree.root());
2811/// ```
2812pub fn create_seal_merkle_tree() -> SealMerkleTree {
2813    SealMerkleTree::new()
2814}
2815
2816/// Create a seal merkle tree from payment roots.
2817///
2818/// This is a convenience function that creates a seal merkle tree from existing payment roots.
2819///
2820/// ## Parameters
2821/// - `payment_roots`: Vector of historical payment merkle roots
2822///
2823/// ## Returns
2824/// - Seal merkle tree with all payment roots included
2825///
2826/// ## 🚨 CRITICAL: Chronological Ordering Requirement
2827///
2828/// **PAYMENT ROOTS MUST BE PROVIDED IN CHRONOLOGICAL ORDER (EPOCH 0, 1, 2, 3...)**
2829///
2830/// This is essential for deterministic seal merkle root generation and consistent
2831/// verification between Oracle and on-chain program. Failure to maintain chronological
2832/// ordering will result in proof verification failures.
2833///
2834/// ## Example
2835/// ```rust
2836/// use miracle_api::sdk::create_seal_merkle_tree_from_roots;
2837///
2838/// let payment_roots = vec![[1u8; 32], [2u8; 32], [3u8; 32]];
2839/// let seal_tree = create_seal_merkle_tree_from_roots(payment_roots);
2840/// println!("Seal root: {:?}", seal_tree.root());
2841/// ```
2842pub fn create_seal_merkle_tree_from_roots(payment_roots: Vec<[u8; 32]>) -> SealMerkleTree {
2843    SealMerkleTree::from_payment_roots(payment_roots)
2844}
2845
2846/// Generate a seal proof for a payment root.
2847///
2848/// This is a convenience function that generates a seal proof for a payment root
2849/// from a seal merkle tree.
2850///
2851/// ## Parameters
2852/// - `seal_tree`: Seal merkle tree
2853/// - `payment_root`: Payment root to generate proof for
2854///
2855/// ## Returns
2856/// - `Some(SealProof)` if payment root is found
2857/// - `None` if payment root is not in the tree
2858///
2859/// ## Example
2860/// ```rust
2861/// use miracle_api::sdk::{create_seal_merkle_tree_from_roots, generate_seal_proof};
2862///
2863/// let payment_roots = vec![[1u8; 32], [2u8; 32]];
2864/// let seal_tree = create_seal_merkle_tree_from_roots(payment_roots);
2865/// let seal_proof = generate_seal_proof(&seal_tree, [1u8; 32]);
2866/// assert!(seal_proof.is_some());
2867/// ```
2868pub fn generate_seal_proof(
2869    seal_tree: &SealMerkleTree,
2870    payment_root: [u8; 32],
2871) -> Option<SealProof> {
2872    seal_tree.generate_seal_proof(payment_root)
2873}
2874
2875/// Computes the epoch hash for the Epoch Hash Chain security solution.
2876///
2877/// This function implements the same logic as the on-chain program:
2878/// - Epoch 0: Uses the predefined genesis hash constant
2879/// - Subsequent epochs: Hash(previous_epoch_hash + epoch_number + current_payment_merkle_root)
2880///
2881/// ## Parameters
2882/// - `epoch`: The epoch number
2883/// - `previous_epoch_hash`: The hash from the previous epoch (use genesis hash for epoch 0)
2884/// - `payment_merkle_root`: The current epoch's payment merkle root
2885///
2886/// ## Returns
2887/// The computed epoch hash for tamper detection
2888///
2889/// ## Security
2890/// This function must be used consistently between off-chain oracle processing
2891/// and on-chain verification to maintain the hash chain integrity.
2892/// The epoch number inclusion ensures uniqueness even with identical payment roots.
2893///
2894/// ## Implementation Details
2895/// - **Epoch 0**: Uses the predefined genesis hash constant (same as on-chain program)
2896/// - **Subsequent epochs**: Build upon the previous epoch hash + epoch number + payment root
2897/// - **No fallback logic**: Since program now initializes with genesis hash, fallback is not needed
2898/// - **Uniqueness guarantee**: Epoch number ensures uniqueness even with identical payment roots
2899pub fn compute_epoch_hash(
2900    epoch: u64,
2901    previous_epoch_hash: [u8; 32],
2902    payment_merkle_root: [u8; 32],
2903) -> [u8; 32] {
2904    if epoch == 0 {
2905        // For epoch 0, use the predefined genesis hash constant
2906        // This maintains security while allowing epoch 0 to be properly initialized
2907        crate::consts::miracle_epoch_0_genesis()
2908    } else {
2909        // For subsequent epochs: Previous hash + EPOCH_NUMBER + current payment merkle root
2910        // This creates an unbreakable chain: each epoch depends on all previous epochs
2911        // The epoch number ensures uniqueness even when payment roots are identical (e.g., empty epochs)
2912
2913        // Handle fallback for backward compatibility (external callers might still pass [0; 32])
2914        let previous_epoch_hash = if previous_epoch_hash == [0u8; 32] {
2915            // If previous epoch hash is zero, use genesis as fallback (same as program)
2916            // This handles cases where the previous epoch hash hasn't been set yet
2917            crate::consts::miracle_epoch_0_genesis()
2918        } else {
2919            previous_epoch_hash
2920        };
2921
2922        // Include epoch number to ensure uniqueness even with empty payment roots
2923        let epoch_bytes = epoch.to_le_bytes();
2924        hashv(&[&previous_epoch_hash, &epoch_bytes, &payment_merkle_root]).to_bytes()
2925    }
2926}