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