Skip to main content

tengu_api/
sdk.rs

1//! Instruction builders for client SDK. Builds `Instruction` with correct accounts and data.
2
3use solana_program::{
4    instruction::{AccountMeta, Instruction},
5    pubkey::Pubkey,
6};
7use solana_sdk_ids::system_program;
8use spl_associated_token_account::get_associated_token_address;
9
10use crate::{
11    consts::{DOJO_MINT, FEE_COLLECTOR},
12    instruction::*,
13    state::*,
14};
15
16fn program_id() -> Pubkey {
17    crate::ID
18}
19
20/// Initialize Config, Game, and Treasury. Admin only.
21pub fn initialize(authority: Pubkey) -> Instruction {
22    let config_address = config_pda(&program_id()).0;
23    let game_address = game_pda(&program_id()).0;
24    let treasury_address = treasury_pda(&program_id()).0;
25    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
26
27    Instruction {
28        program_id: program_id(),
29        accounts: vec![
30            AccountMeta::new(authority, true),
31            AccountMeta::new(config_address, false),
32            AccountMeta::new(game_address, false),
33            AccountMeta::new(treasury_address, false),
34            AccountMeta::new(treasury_ata, false),
35            AccountMeta::new_readonly(DOJO_MINT, false),
36            AccountMeta::new_readonly(spl_token::ID, false),
37            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
38            AccountMeta::new_readonly(system_program::ID, false),
39        ],
40        data: Initialize {}.to_bytes(),
41    }
42}
43
44/// Buy Starter Pack (initialize player Dojo). Optional referrer.
45/// Creates 1 starter shogun (assigned to barracks slot 0) + 1 recruitment ticket.
46pub fn buy_starter_pack(
47    signer: Pubkey,
48    referrer: Option<Pubkey>,
49) -> Instruction {
50    let config_address = config_pda(&program_id()).0;
51    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
52    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
53    let (forge_address, _) = forge_pda(&program_id(), &dojo_address);
54    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
55    let treasury_address = treasury_pda(&program_id()).0;
56
57    let referrer_bytes = referrer.map(|p| p.to_bytes()).unwrap_or([0u8; 32]);
58
59    let game_address = game_pda(&program_id()).0;
60    let mut accounts = vec![
61        AccountMeta::new(signer, true),
62        AccountMeta::new(config_address, false),
63        AccountMeta::new(game_address, false),
64        AccountMeta::new(dojo_address, false),
65        AccountMeta::new(barracks_address, false),
66        AccountMeta::new(forge_address, false),
67        AccountMeta::new(tasks_address, false),
68        AccountMeta::new(treasury_address, false),
69        AccountMeta::new(FEE_COLLECTOR, false),
70        AccountMeta::new_readonly(system_program::ID, false),
71    ];
72    if let Some(ref_dojo) = referrer {
73        let (referral_address, _) = referral_pda(&program_id(), &ref_dojo);
74        accounts.push(AccountMeta::new_readonly(ref_dojo, false));
75        accounts.push(AccountMeta::new(referral_address, false));
76    }
77
78    Instruction {
79        program_id: program_id(),
80        accounts,
81        data: BuyStarterPack { referrer: referrer_bytes }.to_bytes(),
82    }
83}
84
85/// Recruit shogun(s) — pay with recruitment tickets. Adds to fodder_counts.
86/// seed: from BSM POST /seed.
87pub fn recruit_shogun_tickets(signer: Pubkey, count: u64, seed: [u8; 32]) -> Instruction {
88    let config_address = config_pda(&program_id()).0;
89    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
90    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
91
92    Instruction {
93        program_id: program_id(),
94        accounts: vec![
95            AccountMeta::new(signer, true),
96            AccountMeta::new_readonly(config_address, false),
97            AccountMeta::new(dojo_address, false),
98            AccountMeta::new(tasks_address, false),
99        ],
100        data: RecruitShogunTickets {
101            count: count.to_le_bytes(),
102            seed,
103        }
104        .to_bytes(),
105    }
106}
107
108/// Recruit shogun(s) — pay with SOL. Adds to fodder_counts.
109/// seed: from BSM POST /seed.
110pub fn recruit_shogun_sol(signer: Pubkey, count: u64, seed: [u8; 32]) -> Instruction {
111    let config_address = config_pda(&program_id()).0;
112    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
113    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
114
115    Instruction {
116        program_id: program_id(),
117        accounts: vec![
118            AccountMeta::new(signer, true),
119            AccountMeta::new_readonly(config_address, false),
120            AccountMeta::new(dojo_address, false),
121            AccountMeta::new(tasks_address, false),
122            AccountMeta::new(FEE_COLLECTOR, false),
123        ],
124        data: RecruitShogunSol {
125            count: count.to_le_bytes(),
126            seed,
127        }
128        .to_bytes(),
129    }
130}
131
132/// Seat: promote one from fodder to barracks slot. rarity 0-4, element 0-4.
133pub fn seat_shogun(signer: Pubkey, slot: u64, rarity: u64, element: u64) -> Instruction {
134    let config_address = config_pda(&program_id()).0;
135    let game_address = game_pda(&program_id()).0;
136    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
137    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
138
139    Instruction {
140        program_id: program_id(),
141        accounts: vec![
142            AccountMeta::new(signer, true),
143            AccountMeta::new_readonly(config_address, false),
144            AccountMeta::new(game_address, false),
145            AccountMeta::new(dojo_address, false),
146            AccountMeta::new(barracks_address, false),
147        ],
148        data: SeatShogun {
149            slot: slot.to_le_bytes(),
150            rarity: rarity.to_le_bytes(),
151            element: element.to_le_bytes(),
152        }
153        .to_bytes(),
154    }
155}
156
157/// Replace: return old to fodder, promote new from fodder. Same slot.
158pub fn replace_shogun(signer: Pubkey, slot: u64, new_rarity: u64, new_element: u64) -> Instruction {
159    let config_address = config_pda(&program_id()).0;
160    let game_address = game_pda(&program_id()).0;
161    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
162    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
163
164    Instruction {
165        program_id: program_id(),
166        accounts: vec![
167            AccountMeta::new(signer, true),
168            AccountMeta::new_readonly(config_address, false),
169            AccountMeta::new(game_address, false),
170            AccountMeta::new(dojo_address, false),
171            AccountMeta::new(barracks_address, false),
172        ],
173        data: ReplaceShogun {
174            slot: slot.to_le_bytes(),
175            new_rarity: new_rarity.to_le_bytes(),
176            new_element: new_element.to_le_bytes(),
177        }
178        .to_bytes(),
179    }
180}
181
182/// Seat multiple shoguns from fodder into empty slots. Slots inferred.
183pub fn seat_shogun_fill_all(
184    signer: Pubkey,
185    entries: impl IntoIterator<Item = (u64, u64)>,
186) -> Instruction {
187    let config_address = config_pda(&program_id()).0;
188    let game_address = game_pda(&program_id()).0;
189    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
190    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
191
192    let mut arr: [SeatShogunFillAllEntry; 12] = [SeatShogunFillAllEntry {
193        rarity: [0; 8],
194        element: [0; 8],
195    }; 12];
196    let mut count = 0u8;
197    for (i, (rarity, element)) in entries.into_iter().take(12).enumerate() {
198        arr[i] = SeatShogunFillAllEntry {
199            rarity: rarity.to_le_bytes(),
200            element: element.to_le_bytes(),
201        };
202        count += 1;
203    }
204
205    Instruction {
206        program_id: program_id(),
207        accounts: vec![
208            AccountMeta::new(signer, true),
209            AccountMeta::new_readonly(config_address, false),
210            AccountMeta::new(game_address, false),
211            AccountMeta::new(dojo_address, false),
212            AccountMeta::new(barracks_address, false),
213        ],
214        data: SeatShogunFillAll {
215            count,
216            _pad: [0; 7],
217            entries: arr,
218        }
219        .to_bytes(),
220    }
221}
222
223/// Dine. Tier: 0=24h, 1=48h, 2=72h. Burns shards. Restores chakra for seated shogun.
224pub fn dine(signer: Pubkey, slot: u64, tier: u64) -> Instruction {
225    let config_address = config_pda(&program_id()).0;
226    let game_address = game_pda(&program_id()).0;
227    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
228    let treasury_address = treasury_pda(&program_id()).0;
229    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
230    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
231    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
232
233    Instruction {
234        program_id: program_id(),
235        accounts: vec![
236            AccountMeta::new(signer, true),
237            AccountMeta::new_readonly(config_address, false),
238            AccountMeta::new(game_address, false),
239            AccountMeta::new(dojo_address, false),
240            AccountMeta::new(barracks_address, false),
241            AccountMeta::new(user_ata, false),
242            AccountMeta::new(treasury_ata, false),
243            AccountMeta::new(DOJO_MINT, false),
244            AccountMeta::new(treasury_address, false),
245            AccountMeta::new_readonly(spl_token::ID, false),
246        ],
247        data: Dine {
248            tier: tier.to_le_bytes(),
249            slot: slot.to_le_bytes(),
250        }
251        .to_bytes(),
252    }
253}
254
255/// Upgrade barracks (Ninja Hut) level. Pay with shards. 1→2, 2→3, 3→4. Burns shards.
256pub fn upgrade_barracks_shards(signer: Pubkey) -> Instruction {
257    let config_address = config_pda(&program_id()).0;
258    let game_address = game_pda(&program_id()).0;
259    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
260    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
261    let treasury_address = treasury_pda(&program_id()).0;
262    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
263    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
264
265    Instruction {
266        program_id: program_id(),
267        accounts: vec![
268            AccountMeta::new(signer, true),
269            AccountMeta::new_readonly(config_address, false),
270            AccountMeta::new(game_address, false),
271            AccountMeta::new(dojo_address, false),
272            AccountMeta::new(barracks_address, false),
273            AccountMeta::new(user_ata, false),
274            AccountMeta::new(treasury_ata, false),
275            AccountMeta::new(DOJO_MINT, false),
276            AccountMeta::new(treasury_address, false),
277            AccountMeta::new_readonly(spl_token::ID, false),
278        ],
279        data: UpgradeBarracksShards {}.to_bytes(),
280    }
281}
282
283/// Upgrade barracks (Ninja Hut) level. Pay with SOL. 1→2, 2→3 only (3→4 shards only).
284pub fn upgrade_barracks_sol(signer: Pubkey) -> Instruction {
285    let config_address = config_pda(&program_id()).0;
286    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
287    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
288
289    Instruction {
290        program_id: program_id(),
291        accounts: vec![
292            AccountMeta::new(signer, true),
293            AccountMeta::new_readonly(config_address, false),
294            AccountMeta::new(dojo_address, false),
295            AccountMeta::new(barracks_address, false),
296            AccountMeta::new(FEE_COLLECTOR, false),
297            AccountMeta::new_readonly(system_program::ID, false),
298        ],
299        data: UpgradeBarracksSol {}.to_bytes(),
300    }
301}
302
303/// Upgrade forge level. Pay SOL (1–7, max level 7).
304pub fn upgrade_forge(signer: Pubkey) -> Instruction {
305    let config_address = config_pda(&program_id()).0;
306    let game_address = game_pda(&program_id()).0;
307    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
308    let (forge_address, _) = forge_pda(&program_id(), &dojo_address);
309    let treasury_address = treasury_pda(&program_id()).0;
310    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
311    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
312
313    Instruction {
314        program_id: program_id(),
315        accounts: vec![
316            AccountMeta::new(signer, true),
317            AccountMeta::new_readonly(config_address, false),
318            AccountMeta::new(game_address, false),
319            AccountMeta::new(dojo_address, false),
320            AccountMeta::new(forge_address, false),
321            AccountMeta::new(FEE_COLLECTOR, false),
322            AccountMeta::new_readonly(system_program::ID, false),
323            AccountMeta::new(user_ata, false),
324            AccountMeta::new(treasury_ata, false),
325            AccountMeta::new(DOJO_MINT, false),
326            AccountMeta::new(treasury_address, false),
327            AccountMeta::new_readonly(spl_token::ID, false),
328        ],
329        data: UpgradeForge {}.to_bytes(),
330    }
331}
332
333/// Merge: consume from fodder_counts. merge_type: 0=10×N→1R, 1=5×R→1SR, 2=3×SR→1SSR.
334/// seed: from BSM POST /seed.
335pub fn merge_shogun(signer: Pubkey, merge_type: u64, seed: [u8; 32]) -> Instruction {
336    let config_address = config_pda(&program_id()).0;
337    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
338    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
339
340    Instruction {
341        program_id: program_id(),
342        accounts: vec![
343            AccountMeta::new(signer, true),
344            AccountMeta::new_readonly(config_address, false),
345            AccountMeta::new(dojo_address, false),
346            AccountMeta::new(tasks_address, false),
347        ],
348        data: MergeShogun {
349            merge_type: merge_type.to_le_bytes(),
350            seed,
351        }
352        .to_bytes(),
353    }
354}
355
356/// Prestige: consume 2 from fodder_counts[rarity], upgrade seated shogun in slot. SSR/UR only.
357pub fn prestige_upgrade(signer: Pubkey, slot: u64) -> Instruction {
358    let config_address = config_pda(&program_id()).0;
359    let game_address = game_pda(&program_id()).0;
360    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
361    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
362    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
363
364    Instruction {
365        program_id: program_id(),
366        accounts: vec![
367            AccountMeta::new(signer, true),
368            AccountMeta::new_readonly(config_address, false),
369            AccountMeta::new(game_address, false),
370            AccountMeta::new(dojo_address, false),
371            AccountMeta::new(barracks_address, false),
372            AccountMeta::new(tasks_address, false),
373        ],
374        data: PrestigeUpgrade {
375            slot: slot.to_le_bytes(),
376        }
377        .to_bytes(),
378    }
379}
380
381/// Level up: spend shards, +10% SP per level. Burns shards.
382pub fn level_up_shogun(signer: Pubkey, slot: u64) -> Instruction {
383    let config_address = config_pda(&program_id()).0;
384    let game_address = game_pda(&program_id()).0;
385    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
386    let treasury_address = treasury_pda(&program_id()).0;
387    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
388    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
389    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
390
391    Instruction {
392        program_id: program_id(),
393        accounts: vec![
394            AccountMeta::new(signer, true),
395            AccountMeta::new_readonly(config_address, false),
396            AccountMeta::new(game_address, false),
397            AccountMeta::new(dojo_address, false),
398            AccountMeta::new(barracks_address, false),
399            AccountMeta::new(user_ata, false),
400            AccountMeta::new(treasury_ata, false),
401            AccountMeta::new(DOJO_MINT, false),
402            AccountMeta::new(treasury_address, false),
403            AccountMeta::new_readonly(spl_token::ID, false),
404        ],
405        data: LevelUpShogun {
406            slot: slot.to_le_bytes(),
407        }
408        .to_bytes(),
409    }
410}
411
412/// Claim shards as $DOJO token. Hyper Ninja–aligned: computes ore from shoguns, adds to shards, mints.
413/// Amount computed entirely on-chain; no client input (security).
414pub fn claim_shards(signer: Pubkey) -> Instruction {
415    let config_address = config_pda(&program_id()).0;
416    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
417    let (forge_address, _) = forge_pda(&program_id(), &dojo_address);
418    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
419    let treasury_address = treasury_pda(&program_id()).0;
420    let dojo_ata = get_associated_token_address(&signer, &DOJO_MINT);
421
422    Instruction {
423        program_id: program_id(),
424        accounts: vec![
425            AccountMeta::new(signer, true),
426            AccountMeta::new_readonly(config_address, false),
427            AccountMeta::new(dojo_address, false),
428            AccountMeta::new(forge_address, false),
429            AccountMeta::new(barracks_address, false),
430            AccountMeta::new(dojo_ata, false),
431            AccountMeta::new(DOJO_MINT, false),
432            AccountMeta::new(treasury_address, false),
433            AccountMeta::new_readonly(spl_token::ID, false),
434        ],
435        data: ClaimShards {}.to_bytes(),
436    }
437}
438
439/// Claim referral reward (SOL).
440pub fn claim_referral_reward(signer: Pubkey, referrer_dojo: Pubkey) -> Instruction {
441    let (referral_address, _) = referral_pda(&program_id(), &referrer_dojo);
442    let treasury_address = treasury_pda(&program_id()).0;
443
444    Instruction {
445        program_id: program_id(),
446        accounts: vec![
447            AccountMeta::new(signer, true),
448            AccountMeta::new(referrer_dojo, false),
449            AccountMeta::new(referral_address, false),
450            AccountMeta::new(treasury_address, false),
451            AccountMeta::new_readonly(system_program::ID, false),
452        ],
453        data: ClaimReferralReward {}.to_bytes(),
454    }
455}
456
457/// Claim next recruit-tier reward.
458pub fn claim_recruit_reward(signer: Pubkey) -> Instruction {
459    let config_address = config_pda(&program_id()).0;
460    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
461    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
462
463    Instruction {
464        program_id: program_id(),
465        accounts: vec![
466            AccountMeta::new(signer, true),
467            AccountMeta::new_readonly(config_address, false),
468            AccountMeta::new(dojo_address, false),
469            AccountMeta::new(tasks_address, false),
470        ],
471        data: ClaimRecruitReward {}.to_bytes(),
472    }
473}
474
475/// Claim next forge-tier reward.
476pub fn claim_forge_reward(signer: Pubkey) -> Instruction {
477    let config_address = config_pda(&program_id()).0;
478    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
479    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
480
481    Instruction {
482        program_id: program_id(),
483        accounts: vec![
484            AccountMeta::new(signer, true),
485            AccountMeta::new_readonly(config_address, false),
486            AccountMeta::new(dojo_address, false),
487            AccountMeta::new(tasks_address, false),
488        ],
489        data: ClaimForgeReward {}.to_bytes(),
490    }
491}
492
493/// Claim next dine-tier reward.
494pub fn claim_dine_reward(signer: Pubkey) -> Instruction {
495    let config_address = config_pda(&program_id()).0;
496    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
497    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
498
499    Instruction {
500        program_id: program_id(),
501        accounts: vec![
502            AccountMeta::new(signer, true),
503            AccountMeta::new_readonly(config_address, false),
504            AccountMeta::new(dojo_address, false),
505            AccountMeta::new(tasks_address, false),
506        ],
507        data: ClaimDineReward {}.to_bytes(),
508    }
509}
510
511/// Ed25519 verify instruction for daily claim. Must be prepended before claim_daily_reward.
512/// Client builds the same message as the server signs: prefix + dojo_pda + task_id.
513pub fn ed25519_verify_instruction_for_daily_claim(
514    dojo_pda: Pubkey,
515    signature: [u8; 64],
516) -> Instruction {
517    use crate::consts::{CLAIM_TASK_PREFIX, DAILY_TASK_START, TASK_VERIFIER};
518    let mut message = Vec::with_capacity(CLAIM_TASK_PREFIX.len() + 32 + 8);
519    message.extend_from_slice(CLAIM_TASK_PREFIX);
520    message.extend_from_slice(dojo_pda.as_ref());
521    message.extend_from_slice(&DAILY_TASK_START.to_le_bytes());
522    let verifier_bytes: [u8; 32] = TASK_VERIFIER.to_bytes();
523    crate::utils::new_ed25519_instruction_with_signature(&message, &signature, &verifier_bytes)
524}
525
526/// Claim daily reward (1 ticket per day, stacks if not claimed). Backend signature required.
527/// Transaction must include ed25519_verify_instruction_for_daily_claim as the preceding instruction.
528pub fn claim_daily_reward(signer: Pubkey, signature: [u8; 64]) -> Instruction {
529    let config_address = config_pda(&program_id()).0;
530    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
531    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
532    let instructions_sysvar = solana_program::sysvar::instructions::ID;
533
534    Instruction {
535        program_id: program_id(),
536        accounts: vec![
537            AccountMeta::new(signer, true),
538            AccountMeta::new_readonly(config_address, false),
539            AccountMeta::new(dojo_address, false),
540            AccountMeta::new(tasks_address, false),
541            AccountMeta::new_readonly(instructions_sysvar, false),
542        ],
543        data: ClaimDailyReward { signature }.to_bytes(),
544    }
545}
546
547/// Claim collection reward (3 ninjas same element+rarity). Pass collection_index (element×5 + rarity, 0–24).
548/// Program finds 3 matching shoguns in pool.
549pub fn claim_collection_reward(signer: Pubkey, collection_index: u8) -> Instruction {
550    let config_address = config_pda(&program_id()).0;
551    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
552    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
553
554    Instruction {
555        program_id: program_id(),
556        accounts: vec![
557            AccountMeta::new(signer, true),
558            AccountMeta::new_readonly(config_address, false),
559            AccountMeta::new(dojo_address, false),
560            AccountMeta::new(tasks_address, false),
561        ],
562        data: ClaimCollectionReward { collection_index }.to_bytes(),
563    }
564}
565
566/// Flash sale: 50 tickets for 5000 shards, max 5 per day.
567pub fn buy_flash_sale(signer: Pubkey) -> Instruction {
568    let config_address = config_pda(&program_id()).0;
569    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
570    let treasury_address = treasury_pda(&program_id()).0;
571    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
572    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
573
574    Instruction {
575        program_id: program_id(),
576        accounts: vec![
577            AccountMeta::new(signer, true),
578            AccountMeta::new_readonly(config_address, false),
579            AccountMeta::new(dojo_address, false),
580            AccountMeta::new(user_ata, false),
581            AccountMeta::new_readonly(treasury_address, false),
582            AccountMeta::new(treasury_ata, false),
583            AccountMeta::new_readonly(spl_token::ID, false),
584        ],
585        data: BuyFlashSale {}.to_bytes(),
586    }
587}
588
589/// Daily deal: 5 tickets for 300 shards. Burns shards.
590pub fn buy_tickets_with_shards(signer: Pubkey) -> Instruction {
591    let config_address = config_pda(&program_id()).0;
592    let game_address = game_pda(&program_id()).0;
593    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
594    let treasury_address = treasury_pda(&program_id()).0;
595    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
596    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
597
598    Instruction {
599        program_id: program_id(),
600        accounts: vec![
601            AccountMeta::new(signer, true),
602            AccountMeta::new_readonly(config_address, false),
603            AccountMeta::new(game_address, false),
604            AccountMeta::new(dojo_address, false),
605            AccountMeta::new(user_ata, false),
606            AccountMeta::new(treasury_ata, false),
607            AccountMeta::new(DOJO_MINT, false),
608            AccountMeta::new(treasury_address, false),
609            AccountMeta::new_readonly(spl_token::ID, false),
610        ],
611        data: BuyTicketsWithShards {}.to_bytes(),
612    }
613}
614
615/// Buy bundle: 150 recruitment tickets for 5 SOL (event deal).
616pub fn buy_bundle(signer: Pubkey) -> Instruction {
617    let config_address = config_pda(&program_id()).0;
618    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
619
620    Instruction {
621        program_id: program_id(),
622        accounts: vec![
623            AccountMeta::new(signer, true),
624            AccountMeta::new_readonly(config_address, false),
625            AccountMeta::new(dojo_address, false),
626            AccountMeta::new(FEE_COLLECTOR, false),
627            AccountMeta::new_readonly(system_program::ID, false),
628        ],
629        data: BuyBundle {}.to_bytes(),
630    }
631}
632
633/// Clear forge upgrade cooldown. Cost = remaining minutes (shards). One tx clears all.
634pub fn clear_forge_cooldown(signer: Pubkey) -> Instruction {
635    let config_address = config_pda(&program_id()).0;
636    let game_address = game_pda(&program_id()).0;
637    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
638    let (forge_address, _) = forge_pda(&program_id(), &dojo_address);
639    let treasury_address = treasury_pda(&program_id()).0;
640    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
641    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
642
643    Instruction {
644        program_id: program_id(),
645        accounts: vec![
646            AccountMeta::new(signer, true),
647            AccountMeta::new_readonly(config_address, false),
648            AccountMeta::new(game_address, false),
649            AccountMeta::new(dojo_address, false),
650            AccountMeta::new(forge_address, false),
651            AccountMeta::new(user_ata, false),
652            AccountMeta::new(treasury_ata, false),
653            AccountMeta::new(DOJO_MINT, false),
654            AccountMeta::new(treasury_address, false),
655            AccountMeta::new_readonly(spl_token::ID, false),
656        ],
657        data: ClearForgeCooldown {}.to_bytes(),
658    }
659}
660
661/// Set genesis slot and game.last_emission_slot (admin). halving_period_slots: 0 = use default (~58 days, matches Hyper Ninja).
662pub fn set_genesis_slot(authority: Pubkey, genesis_slot: u64, halving_period_slots: u64) -> Instruction {
663    let config_address = config_pda(&program_id()).0;
664    let game_address = game_pda(&program_id()).0;
665
666    Instruction {
667        program_id: program_id(),
668        accounts: vec![
669            AccountMeta::new(authority, true),
670            AccountMeta::new(config_address, false),
671            AccountMeta::new(game_address, false),
672        ],
673        data: SetGenesisSlot {
674            genesis_slot: genesis_slot.to_le_bytes(),
675            halving_period_slots: halving_period_slots.to_le_bytes(),
676        }
677        .to_bytes(),
678    }
679}
680
681/// Roll scene sections (1 or 10) — pay with Amethyst.
682/// seed: from BSM POST /roll/instruction (Option 7 centralized oracle).
683pub fn roll_scene_section_amethyst(signer: Pubkey, count: u64, seed: [u8; 32]) -> Instruction {
684    let config_address = config_pda(&program_id()).0;
685    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
686    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
687
688    Instruction {
689        program_id: program_id(),
690        accounts: vec![
691            AccountMeta::new(signer, true),
692            AccountMeta::new_readonly(config_address, false),
693            AccountMeta::new(dojo_address, false),
694            AccountMeta::new(scenes_address, false),
695            AccountMeta::new_readonly(system_program::ID, false),
696        ],
697        data: RollSceneSectionAmethyst {
698            count: count.to_le_bytes(),
699            seed,
700        }
701        .to_bytes(),
702    }
703}
704
705/// Roll scene sections (1 or 10) — pay with Shards (SPL $DOJO).
706/// seed: from BSM POST /roll/instruction (Option 7 centralized oracle).
707pub fn roll_scene_section_shards(signer: Pubkey, count: u64, seed: [u8; 32]) -> Instruction {
708    let config_address = config_pda(&program_id()).0;
709    let game_address = game_pda(&program_id()).0;
710    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
711    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
712    let treasury_address = treasury_pda(&program_id()).0;
713    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
714    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
715
716    Instruction {
717        program_id: program_id(),
718        accounts: vec![
719            AccountMeta::new(signer, true),
720            AccountMeta::new_readonly(config_address, false),
721            AccountMeta::new(game_address, false),
722            AccountMeta::new(dojo_address, false),
723            AccountMeta::new(scenes_address, false),
724            AccountMeta::new(user_ata, false),
725            AccountMeta::new(treasury_ata, false),
726            AccountMeta::new_readonly(DOJO_MINT, false),
727            AccountMeta::new(treasury_address, false),
728            AccountMeta::new_readonly(spl_token::ID, false),
729            AccountMeta::new_readonly(system_program::ID, false),
730        ],
731        data: RollSceneSectionShards {
732            count: count.to_le_bytes(),
733            seed,
734        }
735        .to_bytes(),
736    }
737}
738
739/// Salvage all duplicate scene sections for Amethyst refund. Program derives from on-chain state.
740pub fn salvage_scene_section(signer: Pubkey) -> Instruction {
741    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
742    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
743
744    Instruction {
745        program_id: program_id(),
746        accounts: vec![
747            AccountMeta::new(signer, true),
748            AccountMeta::new(dojo_address, false),
749            AccountMeta::new(scenes_address, false),
750        ],
751        data: SalvageSceneSection {}.to_bytes(),
752    }
753}
754
755/// Buy scene 6, 7, or 8 with Amethyst. Unlocks entire scene (all 12 sections).
756pub fn buy_scene(signer: Pubkey, scene_id: u64) -> Instruction {
757    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
758    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
759
760    Instruction {
761        program_id: program_id(),
762        accounts: vec![
763            AccountMeta::new(signer, true),
764            AccountMeta::new(dojo_address, false),
765            AccountMeta::new(scenes_address, false),
766            AccountMeta::new_readonly(system_program::ID, false),
767        ],
768        data: BuyScene {
769            scene_id: scene_id.to_le_bytes(),
770        }
771        .to_bytes(),
772    }
773}
774
775/// Buy scene (6/7/8) with mixed payment: spend all amethyst, cover shortfall with DOJO.
776pub fn buy_scene_dojo(signer: Pubkey, scene_id: u64) -> Instruction {
777    let config_address = config_pda(&program_id()).0;
778    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
779    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
780    let treasury_address = treasury_pda(&program_id()).0;
781    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
782    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
783
784    Instruction {
785        program_id: program_id(),
786        accounts: vec![
787            AccountMeta::new(signer, true),
788            AccountMeta::new_readonly(config_address, false),
789            AccountMeta::new(dojo_address, false),
790            AccountMeta::new(scenes_address, false),
791            AccountMeta::new(user_ata, false),
792            AccountMeta::new(treasury_ata, false),
793            AccountMeta::new(DOJO_MINT, false),
794            AccountMeta::new(treasury_address, false),
795            AccountMeta::new_readonly(spl_token::ID, false),
796            AccountMeta::new_readonly(system_program::ID, false),
797        ],
798        data: BuySceneDojo {
799            scene_id: scene_id.to_le_bytes(),
800        }
801        .to_bytes(),
802    }
803}
804
805/// Log (CPI from program; config signs). Variable-length message.
806pub fn log(config: Pubkey, msg: &[u8]) -> Instruction {
807    let mut data = Log {
808        _reserved: [0u8; 8],
809    }
810    .to_bytes();
811    data.extend_from_slice(msg);
812    Instruction {
813        program_id: program_id(),
814        accounts: vec![AccountMeta::new(config, true)],
815        data,
816    }
817}