miracle_api/consts.rs
1use const_crypto::ed25519;
2use solana_program::{pubkey, pubkey::Pubkey};
3
4use crate::calculations::{
5 calculate_customer_reward, calculate_merchant_reward, calculate_smooth_community_decay,
6 calculate_time_decay,
7};
8
9// Project Start Time -- Devnet
10// Date and time (UTC): Monday, August 15, 2025 12:00:00 AM UTC
11// This will be finalized once go-live date is confirmed
12pub const START_AT: i64 = 1755216000;
13
14// // Project Start Time -- Mainnet
15// // Date and time (UTC): Monday, September 1, 2025 12:00:00 AM UTC
16// // This will be finalized once go-live date is confirmed
17// pub const START_AT: i64 = 1756684800;
18
19/// Project End Time - The unix timestamp after which mining will stop.
20// Date and time (GMT): Wednesday, September 1, 2075 12:00:00 AM UTC
21// 50 years from start date for long-term sustainability
22pub const END_AT: i64 = 3334521600;
23
24// Test initializer (mac::id.json)
25// pub const INITIALIZER_ADDRESS: Pubkey = pubkey!("Ac2ev4ofDx61tuSJCgq9ToSBfVwHD2a1FVJf6p7TAqiB");
26// Test initializer (mac&hp::test-id.json)
27pub const INITIALIZER_ADDRESS: Pubkey = pubkey!("4ALL9EAVHpv7ioJF95ktDLtrHUEJezPuFxTFFMy3fpSy");
28
29/// The authority allowed to initialize the program.
30// pub const INITIALIZER_ADDRESS: Pubkey = pubkey!("staryJacbXodPh4WfwVtgA5jkJhvsMHERtkdttnLEHc");
31
32/// The decimal precision of the Miracle token.
33/// There are 10^6 indivisible units per Miracle (called "glows").
34pub const TOKEN_DECIMALS: u8 = 6;
35
36/// One Miracle token, denominated in indivisible units.
37pub const ONE_MIRACLE: u64 = 10u64.pow(TOKEN_DECIMALS as u32);
38
39/// Ten MIRACLE token
40pub const TEN_MIRACLE: u64 = ONE_MIRACLE * 10;
41
42/// The maximum token supply (300 million).
43pub const MAX_SUPPLY: u64 = ONE_MIRACLE * 300_000_000;
44
45/// Pool limits for reward distribution (based on MAX_SUPPLY).
46/// These ensure proper allocation and prevent over-minting.
47pub const PAYMENT_POOL_LIMIT: u64 = MAX_SUPPLY.saturating_mul(67).saturating_div(100); // 201M (67%)
48pub const SOCIAL_POOL_LIMIT: u64 = MAX_SUPPLY.saturating_mul(3).saturating_div(100); // 9M (3%)
49
50/// The base daily rewards amount for the payment-based system (before community decay).
51/// This is the maximum daily rewards when community health is struggling.
52pub const BASE_DAILY_REWARDS: u64 = ONE_MIRACLE * 120_000; // 120k MIRACLE per day
53
54/// The seed of the config account PDA.
55pub const CONFIG: &[u8] = b"config";
56
57/// The seed of the metadata account PDA.
58pub const METADATA: &[u8] = b"metadata";
59
60/// The seed of the mint account PDA.
61pub const MINT: &[u8] = b"mint";
62
63/// The seed of proof account PDAs.
64pub const PROOF: &[u8] = b"proof";
65
66/// The seed of the treasury account PDA.
67pub const TREASURY: &[u8] = b"treasury";
68
69/// Noise for deriving the mint pda.
70// mint noise: Signer-Payer, big-endian: [83, 105, 103, 110, 101, 114, 45, 80, 97, 121, 101, 114]
71// #1 MINT ADDRESS: MirakQE19pNhjP71wR7Lr4JoKhbCrqcy5nz9eZpSdqX
72// nonce: 2932254387, big-endian: [174, 198, 166, 179]
73// #2 MINT ADDRESS: miraBBC187B9GKzL56W1P9v5MiuSnprPwBpFQLQ4WF2
74// Nonce: 308643398 (big-endian: [18, 101, 134, 70])
75// `Signer-Payer` concat(+) nonce
76/// Noise for deriving the mint pda.
77pub const MINT_NOISE: [u8; 16] = [
78 83, 105, 103, 110, 101, 114, 45, 80, 97, 121, 101, 114, 174, 198, 166, 179,
79];
80
81/// The name for token metadata.
82pub const METADATA_NAME: &str = "Miracle";
83
84/// The ticker symbol for token metadata.
85pub const METADATA_SYMBOL: &str = "MIRACLE";
86
87/// The uri for token metdata.
88pub const METADATA_URI: &str =
89 "https://github.com/miraland-labs/resources/blob/main/metadata/miracle.json";
90
91/// Genesis hash for epoch 0 (hash of "MIRACLE_EPOCH_0_EXTREME_WAYS")
92/// This is the foundation of the entire epoch hash chain
93pub const fn miracle_epoch_0_genesis() -> [u8; 32] {
94 // Use const_crypto for compile-time hash computation
95 // This makes the genesis hash self-documenting and verifiable
96 const_crypto::sha2::Sha256::new()
97 .update(b"MIRACLE_EPOCH_0_EXTREME_WAYS")
98 .finalize()
99}
100
101/// Program id for const pda derivations
102const PROGRAM_ID: [u8; 32] = unsafe { *(&crate::id() as *const Pubkey as *const [u8; 32]) };
103
104/// The address of the config account.
105pub const CONFIG_ADDRESS: Pubkey =
106 Pubkey::new_from_array(ed25519::derive_program_address(&[CONFIG], &PROGRAM_ID).0);
107
108/// The address of the mint metadata account.
109pub const METADATA_ADDRESS: Pubkey = Pubkey::new_from_array(
110 ed25519::derive_program_address(
111 &[
112 METADATA,
113 unsafe { &*(&mpl_token_metadata::ID as *const Pubkey as *const [u8; 32]) },
114 unsafe { &*(&MINT_ADDRESS as *const Pubkey as *const [u8; 32]) },
115 ],
116 unsafe { &*(&mpl_token_metadata::ID as *const Pubkey as *const [u8; 32]) },
117 )
118 .0,
119);
120
121// MI
122// MIRACLE MINT ADDRESS: MirakQE19pNhjP71wR7Lr4JoKhbCrqcy5nz9eZpSdqX
123/// The address of the mint account.
124pub const MINT_ADDRESS: Pubkey =
125 Pubkey::new_from_array(ed25519::derive_program_address(&[MINT, &MINT_NOISE], &PROGRAM_ID).0);
126
127/// The bump of the mint account.
128pub const MINT_BUMP: u8 = ed25519::derive_program_address(&[MINT, &MINT_NOISE], &PROGRAM_ID).1;
129
130/// The address of the treasury account.
131pub const TREASURY_ADDRESS: Pubkey =
132 Pubkey::new_from_array(ed25519::derive_program_address(&[TREASURY], &PROGRAM_ID).0);
133
134/// The bump of the treasury account, for cpis.
135pub const TREASURY_BUMP: u8 = ed25519::derive_program_address(&[TREASURY], &PROGRAM_ID).1;
136
137/// The address of the treasury token account.
138pub const TREASURY_TOKENS_ADDRESS: Pubkey = Pubkey::new_from_array(
139 ed25519::derive_program_address(
140 &[
141 unsafe { &*(&TREASURY_ADDRESS as *const Pubkey as *const [u8; 32]) },
142 unsafe { &*(&spl_token::id() as *const Pubkey as *const [u8; 32]) },
143 unsafe { &*(&MINT_ADDRESS as *const Pubkey as *const [u8; 32]) },
144 ],
145 unsafe { &*(&spl_associated_token_account::id() as *const Pubkey as *const [u8; 32]) },
146 )
147 .0,
148);
149
150// ===== PAYMENT-BASED REWARDS SYSTEM CONSTANTS =====
151
152/// Epoch 0 start date: Same as START_AT for consistency
153pub const EPOCH_0_START: i64 = START_AT;
154
155/// The duration of a daily epoch, in seconds (24 hours).
156pub const EPOCH_DURATION: i64 = 24 * 60 * 60;
157
158/// Maximum merkle proof length for payment proofs (supports ~33M participants).
159/// This prevents DoS attacks through extremely long proofs.
160pub const MAX_PAYMENT_PROOF_LENGTH: u8 = 25;
161
162/// Maximum merkle proof length for seal proofs (supports ~1M epochs).
163/// This prevents DoS attacks through extremely long proofs.
164/// Each epoch = 1 day, so this supports ~2,700 years of history.
165pub const MAX_SEAL_PROOF_LENGTH: u8 = 20;
166
167/// The seed of the snapshot account PDA.
168pub const SNAPSHOT: &[u8] = b"snapshot";
169
170/// The seed of the community metrics account PDA.
171pub const METRICS: &[u8] = b"metrics";
172
173/// Basis points for percentage calculations (1 basis point = 0.01%).
174pub const BASIS_POINTS: u64 = 10_000;
175
176/// The address of the snapshot account.
177pub const SNAPSHOT_ADDRESS: Pubkey =
178 Pubkey::new_from_array(ed25519::derive_program_address(&[SNAPSHOT], &PROGRAM_ID).0);
179
180/// The bump of the snapshot account.
181pub const SNAPSHOT_BUMP: u8 = ed25519::derive_program_address(&[SNAPSHOT], &PROGRAM_ID).1;
182
183/// The address of the community metrics account.
184pub const METRICS_ADDRESS: Pubkey =
185 Pubkey::new_from_array(ed25519::derive_program_address(&[METRICS], &PROGRAM_ID).0);
186
187/// The bump of the metrics account.
188pub const METRICS_BUMP: u8 = ed25519::derive_program_address(&[METRICS], &PROGRAM_ID).1;
189
190/// Common stablecoin mint addresses.
191pub const USDT_MINT: Pubkey = pubkey!("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB");
192pub const USDC_MINT: Pubkey = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
193pub const WSOL_MINT: Pubkey = pubkey!("So11111111111111111111111111111111111111112");
194
195// /// The address of the CU-optimized Solana noop program.
196// pub const NOOP_PROGRAM_ID: Pubkey = pubkey!("noop8ytexvkpCuqbf6FB89BSuNemHtPRqaNC31GWivW");
197
198// ===== COMMUNITY-DRIVEN DECAY SYSTEM CONSTANTS =====
199
200/// Community Health Targets (now configurable via UpdateTargets instruction)
201/// These constants have been moved to Config.community_targets
202/// Launch defaults: target_weekly_users = 500, target_weekly_activity = 5_000 (2,500 payments × 2), target_retention_rate = 5000
203
204/// Calculate daily rewards with smooth community decay and time decay.
205///
206/// ## Formula
207/// daily_rewards = BASE_DAILY_REWARDS * community_decay * time_decay / 100_000_000
208///
209/// ## Parameters
210/// - `community_score`: Community health score (0-10000 basis points)
211/// - `years_since_launch`: Number of years since project launch (for time decay)
212/// - Used here for time decay (reduces rewards over time)
213///
214/// ## Returns
215/// - Daily rewards amount in smallest units
216/// - Smooth transition from 100% to 60% of base rewards (community decay)
217/// - Time decay reduces rewards over 5 years to 10% minimum
218///
219/// ## Benefits
220/// - Replaces binary threshold with smooth function
221/// - Eliminates gaming incentives at 80% threshold
222/// - Provides fair rewards for all community health levels
223/// - Implements time-based decay for sustainable tokenomics
224pub fn calculate_daily_rewards(community_score: u16, years_since_launch: u32) -> u64 {
225 let community_decay = calculate_smooth_community_decay(community_score) as u128;
226 let time_decay = calculate_time_decay(years_since_launch) as u128;
227 let base = BASE_DAILY_REWARDS as u128;
228
229 let result = base
230 .saturating_mul(community_decay)
231 .saturating_mul(time_decay)
232 .saturating_div(100_000_000u128);
233 result as u64
234}
235
236/// Calculate daily rewards split between customers and merchants.
237/// This function extends the base daily rewards calculation with customer/merchant allocation.
238///
239/// ## Formula
240/// total_rewards = calculate_daily_rewards(community_score, years_since_launch)
241/// customer_pool = total_rewards * customer_reward_share / 10000
242/// merchant_pool = total_rewards * merchant_reward_share / 10000
243///
244/// ## Parameters
245/// - `community_score`: Community health score (0-10000 basis points)
246/// - `years_since_launch`: Number of years since project launch (for time decay)
247/// - `customer_reward_share`: Customer reward percentage in basis points (0-10000)
248/// - `merchant_reward_share`: Merchant reward percentage in basis points (0-10000)
249///
250/// - `years_since_launch`: Used here for time decay (reduces rewards over time)
251///
252/// ## Returns
253/// - Tuple of (customer_pool, merchant_pool) in smallest token units
254///
255/// ## Benefits
256/// - Fair allocation between customers and merchants
257/// - Oracle-configurable split ratios
258/// - Maintains total reward pool integrity
259/// - Supports platform evolution with dynamic splits
260pub fn calculate_daily_rewards_split(
261 community_score: u16,
262 years_since_launch: u32,
263 customer_reward_share: u16,
264 merchant_reward_share: u16,
265) -> (u64, u64) {
266 // Calculate total daily rewards
267 let total_rewards = calculate_daily_rewards(community_score, years_since_launch);
268
269 // Calculate customer pool
270 let customer_pool = (total_rewards as u128)
271 .saturating_mul(customer_reward_share as u128)
272 .saturating_div(BASIS_POINTS as u128) as u64;
273
274 // Calculate merchant pool
275 let merchant_pool = (total_rewards as u128)
276 .saturating_mul(merchant_reward_share as u128)
277 .saturating_div(BASIS_POINTS as u128) as u64;
278
279 (customer_pool, merchant_pool)
280}
281
282/// Calculate time decay factor based on years since launch.
283/// This is used for time decay calculations.
284///
285/// ## Formula
286/// time_decay = max(1000, 10000 - years_since_launch * 1800)
287///
288/// ## Purpose
289/// Reduces total rewards over time to ensure sustainable tokenomics.
290/// - 0 years: 100% rewards
291/// - 5 years: 10% rewards (minimum)
292///
293/// - `years_since_launch`: Used here for time decay (reduces rewards over time)
294
295/// Complete transparent calculation chain for customer rewards.
296/// This function combines all steps of the reward calculation for transparency.
297///
298/// ## Formula Chain
299/// 1. daily_rewards = calculate_daily_rewards(community_score, years_since_launch)
300/// 2. customer_pool = daily_rewards * customer_reward_share / 10000
301/// 3. customer_reward = (activity_count * customer_pool) / total_customer_activity
302///
303/// ## Parameters
304/// - `activity_count`: Number of activities performed by the customer
305/// - `community_score`: Community health score (0-10000 basis points)
306/// - `years_since_launch`: Number of years since project launch (for time decay)
307/// - `customer_reward_share`: Customer reward percentage in basis points (0-10000)
308/// - `total_customer_activity`: Total activity count across all customers
309///
310/// ## Returns
311/// - Individual customer reward amount in smallest token units
312///
313/// ## Benefits
314/// - Complete transparency from community metrics to individual rewards
315/// - Can be used off-chain for reward estimation
316/// - Deterministic calculation for verification
317/// - Public formula for user understanding
318///
319/// ## Note
320/// This function does NOT apply activity capping. Use `calculate_customer_reward_transparent_with_cap`
321/// for calculations that match on-chain behavior.
322pub fn calculate_customer_reward_transparent(
323 activity_count: u32,
324 community_score: u16,
325 years_since_launch: u32,
326 customer_reward_share: u16,
327 total_customer_activity: u32,
328) -> u64 {
329 // Step 1: Calculate daily rewards
330 let daily_rewards = calculate_daily_rewards(community_score, years_since_launch);
331
332 // Step 2: Calculate customer pool
333 let customer_pool = (daily_rewards as u128)
334 .saturating_mul(customer_reward_share as u128)
335 .saturating_div(BASIS_POINTS as u128) as u64;
336
337 // Step 3: Calculate individual reward
338 calculate_customer_reward(activity_count, customer_pool, total_customer_activity)
339}
340
341/// Complete transparent calculation chain for customer rewards WITH activity capping.
342/// This function matches the on-chain calculation behavior exactly.
343///
344/// ## Formula Chain
345/// 1. daily_rewards = calculate_daily_rewards(community_score, years_since_launch)
346/// 2. customer_pool = daily_rewards * customer_reward_share / 10000
347/// 3. capped_activity = min(activity_count, max_customer_activity_per_epoch)
348/// 4. customer_reward = (capped_activity * customer_pool) / total_customer_activity
349///
350/// ## Parameters
351/// - `activity_count`: Number of activities performed by the customer
352/// - `community_score`: Community health score (0-10000 basis points)
353/// - `years_since_launch`: Number of years since project launch (for time decay)
354/// - `customer_reward_share`: Customer reward percentage in basis points (0-10000)
355/// - `total_customer_activity`: Total activity count across all customers
356/// - `max_customer_activity_per_epoch`: Maximum allowed customer activity per epoch
357///
358/// ## Returns
359/// - Individual customer reward amount in smallest token units (with activity capping)
360///
361/// ## Benefits
362/// - Matches on-chain calculation exactly
363/// - Includes activity capping to prevent gaming
364/// - Complete transparency for off-chain estimation
365/// - Deterministic calculation for verification
366pub fn calculate_customer_reward_transparent_with_cap(
367 activity_count: u32,
368 community_score: u16,
369 years_since_launch: u32,
370 customer_reward_share: u16,
371 total_customer_activity: u32,
372 max_customer_activity_per_epoch: u32,
373) -> u64 {
374 // Step 1: Calculate daily rewards
375 let daily_rewards = calculate_daily_rewards(community_score, years_since_launch);
376
377 // Step 2: Calculate customer pool
378 let customer_pool = (daily_rewards as u128)
379 .saturating_mul(customer_reward_share as u128)
380 .saturating_div(BASIS_POINTS as u128) as u64;
381
382 // Step 3: Apply activity capping
383 let capped_activity = activity_count.min(max_customer_activity_per_epoch);
384
385 // Step 4: Calculate individual reward with capped activity
386 calculate_customer_reward(capped_activity, customer_pool, total_customer_activity)
387}
388
389/// Complete transparent calculation chain for merchant rewards.
390/// This function combines all steps of the reward calculation for transparency.
391///
392/// ## Formula Chain
393/// 1. daily_rewards = calculate_daily_rewards(community_score, years_since_launch)
394/// 2. merchant_pool = daily_rewards * merchant_reward_share / 10000
395/// 3. merchant_reward = (activity_count * merchant_pool) / total_merchant_activity
396///
397/// ## Parameters
398/// - `activity_count`: Number of activities performed by the merchant
399/// - `community_score`: Community health score (0-10000 basis points)
400/// - `years_since_launch`: Number of years since project launch (for time decay)
401/// - `merchant_reward_share`: Merchant reward percentage in basis points (0-10000)
402/// - `total_merchant_activity`: Total activity count across all merchants
403///
404/// ## Returns
405/// - Individual merchant reward amount in smallest token units
406///
407/// ## Benefits
408/// - Complete transparency from community metrics to individual rewards
409/// - Can be used off-chain for reward estimation
410/// - Deterministic calculation for verification
411/// - Public formula for user understanding
412///
413/// ## Note
414/// This function does NOT apply activity capping. Use `calculate_merchant_reward_transparent_with_cap`
415/// for calculations that match on-chain behavior.
416pub fn calculate_merchant_reward_transparent(
417 activity_count: u32,
418 community_score: u16,
419 years_since_launch: u32,
420 merchant_reward_share: u16,
421 total_merchant_activity: u32,
422) -> u64 {
423 // Step 1: Calculate daily rewards
424 let daily_rewards = calculate_daily_rewards(community_score, years_since_launch);
425
426 // Step 2: Calculate merchant pool
427 let merchant_pool = (daily_rewards as u128)
428 .saturating_mul(merchant_reward_share as u128)
429 .saturating_div(BASIS_POINTS as u128) as u64;
430
431 // Step 3: Calculate individual reward
432 calculate_merchant_reward(activity_count, merchant_pool, total_merchant_activity)
433}
434
435/// Complete transparent calculation chain for merchant rewards WITH activity capping.
436/// This function matches the on-chain calculation behavior exactly.
437///
438/// ## Formula Chain
439/// 1. daily_rewards = calculate_daily_rewards(community_score, years_since_launch)
440/// 2. merchant_pool = daily_rewards * merchant_reward_share / 10000
441/// 3. capped_activity = min(activity_count, max_merchant_activity_per_epoch)
442/// 4. merchant_reward = (capped_activity * merchant_pool) / total_merchant_activity
443///
444/// ## Parameters
445/// - `activity_count`: Number of activities performed by the merchant
446/// - `community_score`: Community health score (0-10000 basis points)
447/// - `years_since_launch`: Number of years since project launch (for time decay)
448/// - `merchant_reward_share`: Merchant reward percentage in basis points (0-10000)
449/// - `total_merchant_activity`: Total activity count across all merchants
450/// - `max_merchant_activity_per_epoch`: Maximum allowed merchant activity per epoch
451///
452/// ## Returns
453/// - Individual merchant reward amount in smallest token units (with activity capping)
454///
455/// ## Benefits
456/// - Matches on-chain calculation exactly
457/// - Includes activity capping to prevent gaming
458/// - Complete transparency for off-chain estimation
459/// - Deterministic calculation for verification
460pub fn calculate_merchant_reward_transparent_with_cap(
461 activity_count: u32,
462 community_score: u16,
463 years_since_launch: u32,
464 merchant_reward_share: u16,
465 total_merchant_activity: u32,
466 max_merchant_activity_per_epoch: u32,
467) -> u64 {
468 // Step 1: Calculate daily rewards
469 let daily_rewards = calculate_daily_rewards(community_score, years_since_launch);
470
471 // Step 2: Calculate merchant pool
472 let merchant_pool = (daily_rewards as u128)
473 .saturating_mul(merchant_reward_share as u128)
474 .saturating_div(BASIS_POINTS as u128) as u64;
475
476 // Step 3: Apply activity capping
477 let capped_activity = activity_count.min(max_merchant_activity_per_epoch);
478
479 // Step 4: Calculate individual reward with capped activity
480 calculate_merchant_reward(capped_activity, merchant_pool, total_merchant_activity)
481}