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. Pool-split: fixed emission per slot, your share = your_SP / total_SP.
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 game_address = game_pda(&program_id()).0;
420    let treasury_address = treasury_pda(&program_id()).0;
421    let dojo_ata = get_associated_token_address(&signer, &DOJO_MINT);
422
423    Instruction {
424        program_id: program_id(),
425        accounts: vec![
426            AccountMeta::new(signer, true),
427            AccountMeta::new_readonly(config_address, false),
428            AccountMeta::new(dojo_address, false),
429            AccountMeta::new(forge_address, false),
430            AccountMeta::new(barracks_address, false),
431            AccountMeta::new_readonly(game_address, false),
432            AccountMeta::new(dojo_ata, false),
433            AccountMeta::new(DOJO_MINT, false),
434            AccountMeta::new(treasury_address, false),
435            AccountMeta::new_readonly(spl_token::ID, false),
436        ],
437        data: ClaimShards {}.to_bytes(),
438    }
439}
440
441/// Claim referral reward (SOL).
442pub fn claim_referral_reward(signer: Pubkey, referrer_dojo: Pubkey) -> Instruction {
443    let (referral_address, _) = referral_pda(&program_id(), &referrer_dojo);
444    let treasury_address = treasury_pda(&program_id()).0;
445
446    Instruction {
447        program_id: program_id(),
448        accounts: vec![
449            AccountMeta::new(signer, true),
450            AccountMeta::new(referrer_dojo, false),
451            AccountMeta::new(referral_address, false),
452            AccountMeta::new(treasury_address, false),
453            AccountMeta::new_readonly(system_program::ID, false),
454        ],
455        data: ClaimReferralReward {}.to_bytes(),
456    }
457}
458
459/// Claim next recruit-tier reward.
460pub fn claim_recruit_reward(signer: Pubkey) -> Instruction {
461    let config_address = config_pda(&program_id()).0;
462    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
463    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
464
465    Instruction {
466        program_id: program_id(),
467        accounts: vec![
468            AccountMeta::new(signer, true),
469            AccountMeta::new_readonly(config_address, false),
470            AccountMeta::new(dojo_address, false),
471            AccountMeta::new(tasks_address, false),
472        ],
473        data: ClaimRecruitReward {}.to_bytes(),
474    }
475}
476
477/// Claim next forge-tier reward.
478pub fn claim_forge_reward(signer: Pubkey) -> Instruction {
479    let config_address = config_pda(&program_id()).0;
480    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
481    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
482
483    Instruction {
484        program_id: program_id(),
485        accounts: vec![
486            AccountMeta::new(signer, true),
487            AccountMeta::new_readonly(config_address, false),
488            AccountMeta::new(dojo_address, false),
489            AccountMeta::new(tasks_address, false),
490        ],
491        data: ClaimForgeReward {}.to_bytes(),
492    }
493}
494
495/// Claim next dine-tier reward.
496pub fn claim_dine_reward(signer: Pubkey) -> Instruction {
497    let config_address = config_pda(&program_id()).0;
498    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
499    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
500
501    Instruction {
502        program_id: program_id(),
503        accounts: vec![
504            AccountMeta::new(signer, true),
505            AccountMeta::new_readonly(config_address, false),
506            AccountMeta::new(dojo_address, false),
507            AccountMeta::new(tasks_address, false),
508        ],
509        data: ClaimDineReward {}.to_bytes(),
510    }
511}
512
513/// Ed25519 verify instruction for daily claim. Must be prepended before claim_daily_reward.
514/// Client builds the same message as the server signs: prefix + dojo_pda + task_id.
515pub fn ed25519_verify_instruction_for_daily_claim(
516    dojo_pda: Pubkey,
517    signature: [u8; 64],
518) -> Instruction {
519    use crate::consts::{CLAIM_TASK_PREFIX, DAILY_TASK_START, TASK_VERIFIER};
520    let mut message = Vec::with_capacity(CLAIM_TASK_PREFIX.len() + 32 + 8);
521    message.extend_from_slice(CLAIM_TASK_PREFIX);
522    message.extend_from_slice(dojo_pda.as_ref());
523    message.extend_from_slice(&DAILY_TASK_START.to_le_bytes());
524    let verifier_bytes: [u8; 32] = TASK_VERIFIER.to_bytes();
525    crate::utils::new_ed25519_instruction_with_signature(&message, &signature, &verifier_bytes)
526}
527
528/// Claim daily reward (1 ticket per day, stacks if not claimed). Backend signature required.
529/// Transaction must include ed25519_verify_instruction_for_daily_claim as the preceding instruction.
530pub fn claim_daily_reward(signer: Pubkey, signature: [u8; 64]) -> Instruction {
531    let config_address = config_pda(&program_id()).0;
532    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
533    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
534    let instructions_sysvar = solana_program::sysvar::instructions::ID;
535
536    Instruction {
537        program_id: program_id(),
538        accounts: vec![
539            AccountMeta::new(signer, true),
540            AccountMeta::new_readonly(config_address, false),
541            AccountMeta::new(dojo_address, false),
542            AccountMeta::new(tasks_address, false),
543            AccountMeta::new_readonly(instructions_sysvar, false),
544        ],
545        data: ClaimDailyReward { signature }.to_bytes(),
546    }
547}
548
549/// Claim collection reward (3 ninjas same element+rarity). Pass collection_index (element×5 + rarity, 0–24).
550/// Program finds 3 matching shoguns in pool.
551pub fn claim_collection_reward(signer: Pubkey, collection_index: u8) -> Instruction {
552    let config_address = config_pda(&program_id()).0;
553    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
554    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
555
556    Instruction {
557        program_id: program_id(),
558        accounts: vec![
559            AccountMeta::new(signer, true),
560            AccountMeta::new_readonly(config_address, false),
561            AccountMeta::new(dojo_address, false),
562            AccountMeta::new(tasks_address, false),
563        ],
564        data: ClaimCollectionReward { collection_index }.to_bytes(),
565    }
566}
567
568/// Flash sale: 50 tickets for 5000 shards, max 5 per day.
569pub fn buy_flash_sale(signer: Pubkey) -> Instruction {
570    let config_address = config_pda(&program_id()).0;
571    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
572    let treasury_address = treasury_pda(&program_id()).0;
573    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
574    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
575
576    Instruction {
577        program_id: program_id(),
578        accounts: vec![
579            AccountMeta::new(signer, true),
580            AccountMeta::new_readonly(config_address, false),
581            AccountMeta::new(dojo_address, false),
582            AccountMeta::new(user_ata, false),
583            AccountMeta::new_readonly(treasury_address, false),
584            AccountMeta::new(treasury_ata, false),
585            AccountMeta::new_readonly(spl_token::ID, false),
586        ],
587        data: BuyFlashSale {}.to_bytes(),
588    }
589}
590
591/// Daily deal: 5 tickets for 300 shards. Burns shards.
592pub fn buy_tickets_with_shards(signer: Pubkey) -> Instruction {
593    let config_address = config_pda(&program_id()).0;
594    let game_address = game_pda(&program_id()).0;
595    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
596    let treasury_address = treasury_pda(&program_id()).0;
597    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
598    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
599
600    Instruction {
601        program_id: program_id(),
602        accounts: vec![
603            AccountMeta::new(signer, true),
604            AccountMeta::new_readonly(config_address, false),
605            AccountMeta::new(game_address, false),
606            AccountMeta::new(dojo_address, false),
607            AccountMeta::new(user_ata, false),
608            AccountMeta::new(treasury_ata, false),
609            AccountMeta::new(DOJO_MINT, false),
610            AccountMeta::new(treasury_address, false),
611            AccountMeta::new_readonly(spl_token::ID, false),
612        ],
613        data: BuyTicketsWithShards {}.to_bytes(),
614    }
615}
616
617/// Buy bundle: 150 recruitment tickets for 5 SOL (event deal).
618pub fn buy_bundle(signer: Pubkey) -> Instruction {
619    let config_address = config_pda(&program_id()).0;
620    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
621
622    Instruction {
623        program_id: program_id(),
624        accounts: vec![
625            AccountMeta::new(signer, true),
626            AccountMeta::new_readonly(config_address, false),
627            AccountMeta::new(dojo_address, false),
628            AccountMeta::new(FEE_COLLECTOR, false),
629            AccountMeta::new_readonly(system_program::ID, false),
630        ],
631        data: BuyBundle {}.to_bytes(),
632    }
633}
634
635/// Clear forge upgrade cooldown. Cost = remaining minutes (shards). One tx clears all.
636pub fn clear_forge_cooldown(signer: Pubkey) -> Instruction {
637    let config_address = config_pda(&program_id()).0;
638    let game_address = game_pda(&program_id()).0;
639    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
640    let (forge_address, _) = forge_pda(&program_id(), &dojo_address);
641    let treasury_address = treasury_pda(&program_id()).0;
642    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
643    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
644
645    Instruction {
646        program_id: program_id(),
647        accounts: vec![
648            AccountMeta::new(signer, true),
649            AccountMeta::new_readonly(config_address, false),
650            AccountMeta::new(game_address, false),
651            AccountMeta::new(dojo_address, false),
652            AccountMeta::new(forge_address, false),
653            AccountMeta::new(user_ata, false),
654            AccountMeta::new(treasury_ata, false),
655            AccountMeta::new(DOJO_MINT, false),
656            AccountMeta::new(treasury_address, false),
657            AccountMeta::new_readonly(spl_token::ID, false),
658        ],
659        data: ClearForgeCooldown {}.to_bytes(),
660    }
661}
662
663/// Set genesis slot and game.last_emission_slot (admin). halving_period_slots: 0 = use default (~58 days, matches Hyper Ninja).
664pub fn set_genesis_slot(authority: Pubkey, genesis_slot: u64, halving_period_slots: u64) -> Instruction {
665    let config_address = config_pda(&program_id()).0;
666    let game_address = game_pda(&program_id()).0;
667
668    Instruction {
669        program_id: program_id(),
670        accounts: vec![
671            AccountMeta::new(authority, true),
672            AccountMeta::new(config_address, false),
673            AccountMeta::new(game_address, false),
674        ],
675        data: SetGenesisSlot {
676            genesis_slot: genesis_slot.to_le_bytes(),
677            halving_period_slots: halving_period_slots.to_le_bytes(),
678        }
679        .to_bytes(),
680    }
681}
682
683/// Roll scene sections (1 or 10) — pay with Amethyst.
684/// seed: from BSM POST /roll/instruction (Option 7 centralized oracle).
685pub fn roll_scene_section_amethyst(signer: Pubkey, count: u64, seed: [u8; 32]) -> Instruction {
686    let config_address = config_pda(&program_id()).0;
687    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
688    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
689
690    Instruction {
691        program_id: program_id(),
692        accounts: vec![
693            AccountMeta::new(signer, true),
694            AccountMeta::new_readonly(config_address, false),
695            AccountMeta::new(dojo_address, false),
696            AccountMeta::new(scenes_address, false),
697            AccountMeta::new_readonly(system_program::ID, false),
698        ],
699        data: RollSceneSectionAmethyst {
700            count: count.to_le_bytes(),
701            seed,
702        }
703        .to_bytes(),
704    }
705}
706
707/// Roll scene sections (1 or 10) — pay with Shards (SPL $DOJO).
708/// seed: from BSM POST /roll/instruction (Option 7 centralized oracle).
709pub fn roll_scene_section_shards(signer: Pubkey, count: u64, seed: [u8; 32]) -> Instruction {
710    let config_address = config_pda(&program_id()).0;
711    let game_address = game_pda(&program_id()).0;
712    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
713    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
714    let treasury_address = treasury_pda(&program_id()).0;
715    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
716    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
717
718    Instruction {
719        program_id: program_id(),
720        accounts: vec![
721            AccountMeta::new(signer, true),
722            AccountMeta::new_readonly(config_address, false),
723            AccountMeta::new(game_address, false),
724            AccountMeta::new(dojo_address, false),
725            AccountMeta::new(scenes_address, false),
726            AccountMeta::new(user_ata, false),
727            AccountMeta::new(treasury_ata, false),
728            AccountMeta::new_readonly(DOJO_MINT, false),
729            AccountMeta::new(treasury_address, false),
730            AccountMeta::new_readonly(spl_token::ID, false),
731            AccountMeta::new_readonly(system_program::ID, false),
732        ],
733        data: RollSceneSectionShards {
734            count: count.to_le_bytes(),
735            seed,
736        }
737        .to_bytes(),
738    }
739}
740
741/// Salvage all duplicate scene sections for Amethyst refund. Program derives from on-chain state.
742pub fn salvage_scene_section(signer: Pubkey) -> Instruction {
743    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
744    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
745
746    Instruction {
747        program_id: program_id(),
748        accounts: vec![
749            AccountMeta::new(signer, true),
750            AccountMeta::new(dojo_address, false),
751            AccountMeta::new(scenes_address, false),
752        ],
753        data: SalvageSceneSection {}.to_bytes(),
754    }
755}
756
757/// Buy scene 6, 7, or 8 with Amethyst. Unlocks entire scene (all 12 sections).
758pub fn buy_scene(signer: Pubkey, scene_id: u64) -> Instruction {
759    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
760    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
761
762    Instruction {
763        program_id: program_id(),
764        accounts: vec![
765            AccountMeta::new(signer, true),
766            AccountMeta::new(dojo_address, false),
767            AccountMeta::new(scenes_address, false),
768            AccountMeta::new_readonly(system_program::ID, false),
769        ],
770        data: BuyScene {
771            scene_id: scene_id.to_le_bytes(),
772        }
773        .to_bytes(),
774    }
775}
776
777/// Buy scene (6/7/8) with mixed payment: spend all amethyst, cover shortfall with DOJO.
778pub fn buy_scene_dojo(signer: Pubkey, scene_id: u64) -> Instruction {
779    let config_address = config_pda(&program_id()).0;
780    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
781    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
782    let treasury_address = treasury_pda(&program_id()).0;
783    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
784    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
785
786    Instruction {
787        program_id: program_id(),
788        accounts: vec![
789            AccountMeta::new(signer, true),
790            AccountMeta::new_readonly(config_address, false),
791            AccountMeta::new(dojo_address, false),
792            AccountMeta::new(scenes_address, false),
793            AccountMeta::new(user_ata, false),
794            AccountMeta::new(treasury_ata, false),
795            AccountMeta::new(DOJO_MINT, false),
796            AccountMeta::new(treasury_address, false),
797            AccountMeta::new_readonly(spl_token::ID, false),
798            AccountMeta::new_readonly(system_program::ID, false),
799        ],
800        data: BuySceneDojo {
801            scene_id: scene_id.to_le_bytes(),
802        }
803        .to_bytes(),
804    }
805}
806
807/// Log (CPI from program; config signs). Variable-length message.
808pub fn log(config: Pubkey, msg: &[u8]) -> Instruction {
809    let mut data = Log {
810        _reserved: [0u8; 8],
811    }
812    .to_bytes();
813    data.extend_from_slice(msg);
814    Instruction {
815        program_id: program_id(),
816        accounts: vec![AccountMeta::new(config, true)],
817        data,
818    }
819}