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::{DGT_GROUP, 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 (mission_address, _) = mission_pda(&program_id(), &dojo_address);
56    let treasury_address = treasury_pda(&program_id()).0;
57
58    let referrer_bytes = referrer.map(|p| p.to_bytes()).unwrap_or([0u8; 32]);
59
60    let game_address = game_pda(&program_id()).0;
61    let mut accounts = vec![
62        AccountMeta::new(signer, true),
63        AccountMeta::new(config_address, false),
64        AccountMeta::new(game_address, false),
65        AccountMeta::new(dojo_address, false),
66        AccountMeta::new(barracks_address, false),
67        AccountMeta::new(forge_address, false),
68        AccountMeta::new(tasks_address, false),
69        AccountMeta::new(mission_address, false),
70        AccountMeta::new(treasury_address, false),
71        AccountMeta::new(FEE_COLLECTOR, false),
72        AccountMeta::new_readonly(system_program::ID, false),
73    ];
74    if let Some(ref_dojo) = referrer {
75        let (referral_address, _) = referral_pda(&program_id(), &ref_dojo);
76        accounts.push(AccountMeta::new_readonly(ref_dojo, false));
77        accounts.push(AccountMeta::new(referral_address, false));
78    }
79
80    Instruction {
81        program_id: program_id(),
82        accounts,
83        data: BuyStarterPack { referrer: referrer_bytes }.to_bytes(),
84    }
85}
86
87/// Recruit shogun(s) — pay with recruitment tickets. Adds to fodder_counts.
88/// seed: from BSM POST /seed.
89/// prestige: if Some, include Prestige account (must exist) to track per-prestige fodder.
90pub fn recruit_shogun_tickets(
91    signer: Pubkey,
92    count: u64,
93    seed: [u8; 32],
94    prestige: Option<Pubkey>,
95) -> Instruction {
96    let config_address = config_pda(&program_id()).0;
97    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
98    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
99
100    let mut accounts = vec![
101        AccountMeta::new(signer, true),
102        AccountMeta::new_readonly(config_address, false),
103        AccountMeta::new(dojo_address, false),
104        AccountMeta::new(tasks_address, false),
105    ];
106    if let Some(addr) = prestige {
107        accounts.push(AccountMeta::new(addr, false));
108    }
109
110    Instruction {
111        program_id: program_id(),
112        accounts,
113        data: RecruitShogunTickets {
114            count: count.to_le_bytes(),
115            seed,
116        }
117        .to_bytes(),
118    }
119}
120
121/// Recruit shogun(s) — pay with SOL. Adds to fodder_counts.
122/// seed: from BSM POST /seed.
123/// prestige: if Some, include Prestige account (must exist).
124pub fn recruit_shogun_sol(
125    signer: Pubkey,
126    count: u64,
127    seed: [u8; 32],
128    prestige: Option<Pubkey>,
129) -> Instruction {
130    let config_address = config_pda(&program_id()).0;
131    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
132    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
133
134    let treasury_address = treasury_pda(&program_id()).0;
135    let mut accounts = vec![
136        AccountMeta::new(signer, true),
137        AccountMeta::new_readonly(config_address, false),
138        AccountMeta::new(dojo_address, false),
139        AccountMeta::new(tasks_address, false),
140        AccountMeta::new(treasury_address, false),
141        AccountMeta::new(FEE_COLLECTOR, false),
142    ];
143    if let Some(addr) = prestige {
144        accounts.push(AccountMeta::new(addr, false));
145    }
146
147    Instruction {
148        program_id: program_id(),
149        accounts,
150        data: RecruitShogunSol {
151            count: count.to_le_bytes(),
152            seed,
153        }
154        .to_bytes(),
155    }
156}
157
158/// Seat: promote one from fodder to barracks slot. rarity 0-4, element 0-4.
159/// prestige: if Some, include Prestige account (must exist).
160pub fn seat_shogun(
161    signer: Pubkey,
162    slot: u64,
163    rarity: u64,
164    element: u64,
165    prestige: Option<Pubkey>,
166) -> Instruction {
167    let config_address = config_pda(&program_id()).0;
168    let game_address = game_pda(&program_id()).0;
169    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
170    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
171
172    let mut accounts = vec![
173        AccountMeta::new(signer, true),
174        AccountMeta::new_readonly(config_address, false),
175        AccountMeta::new(game_address, false),
176        AccountMeta::new(dojo_address, false),
177        AccountMeta::new(barracks_address, false),
178    ];
179    if let Some(addr) = prestige {
180        accounts.push(AccountMeta::new(addr, false));
181    }
182
183    Instruction {
184        program_id: program_id(),
185        accounts,
186        data: SeatShogun {
187            slot: slot.to_le_bytes(),
188            rarity: rarity.to_le_bytes(),
189            element: element.to_le_bytes(),
190        }
191        .to_bytes(),
192    }
193}
194
195/// Replace: return old to fodder, promote new from fodder. Same slot.
196/// prestige: if Some, include Prestige account (must exist).
197pub fn replace_shogun(
198    signer: Pubkey,
199    slot: u64,
200    new_rarity: u64,
201    new_element: u64,
202    prestige: Option<Pubkey>,
203) -> Instruction {
204    let config_address = config_pda(&program_id()).0;
205    let game_address = game_pda(&program_id()).0;
206    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
207    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
208    let (mission_address, _) = mission_pda(&program_id(), &dojo_address);
209
210    let mut 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        AccountMeta::new_readonly(mission_address, false),
217    ];
218    if let Some(addr) = prestige {
219        accounts.push(AccountMeta::new(addr, false));
220    }
221
222    Instruction {
223        program_id: program_id(),
224        accounts,
225        data: ReplaceShogun {
226            slot: slot.to_le_bytes(),
227            new_rarity: new_rarity.to_le_bytes(),
228            new_element: new_element.to_le_bytes(),
229        }
230        .to_bytes(),
231    }
232}
233
234/// Seat multiple shoguns from fodder into empty slots. Slots inferred.
235/// prestige: if Some, include Prestige account (must exist).
236pub fn seat_shogun_fill_all(
237    signer: Pubkey,
238    entries: impl IntoIterator<Item = (u64, u64)>,
239    prestige: Option<Pubkey>,
240) -> Instruction {
241    let config_address = config_pda(&program_id()).0;
242    let game_address = game_pda(&program_id()).0;
243    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
244    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
245
246    let mut arr: [SeatShogunFillAllEntry; 12] = [SeatShogunFillAllEntry {
247        rarity: [0; 8],
248        element: [0; 8],
249    }; 12];
250    let mut count = 0u8;
251    for (i, (rarity, element)) in entries.into_iter().take(12).enumerate() {
252        arr[i] = SeatShogunFillAllEntry {
253            rarity: rarity.to_le_bytes(),
254            element: element.to_le_bytes(),
255        };
256        count += 1;
257    }
258
259    let mut accounts = vec![
260        AccountMeta::new(signer, true),
261        AccountMeta::new_readonly(config_address, false),
262        AccountMeta::new(game_address, false),
263        AccountMeta::new(dojo_address, false),
264        AccountMeta::new(barracks_address, false),
265    ];
266    if let Some(addr) = prestige {
267        accounts.push(AccountMeta::new(addr, false));
268    }
269
270    Instruction {
271        program_id: program_id(),
272        accounts,
273        data: SeatShogunFillAll {
274            count,
275            _pad: [0; 7],
276            entries: arr,
277        }
278        .to_bytes(),
279    }
280}
281
282/// Dine. Tier: 0=24h, 1=48h, 2=72h. Burns shards. Restores chakra for seated shogun.
283pub fn dine(signer: Pubkey, slot: u64, tier: u64) -> Instruction {
284    let config_address = config_pda(&program_id()).0;
285    let game_address = game_pda(&program_id()).0;
286    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
287    let treasury_address = treasury_pda(&program_id()).0;
288    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
289    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
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(game_address, false),
298            AccountMeta::new(dojo_address, false),
299            AccountMeta::new(barracks_address, false),
300            AccountMeta::new(user_ata, false),
301            AccountMeta::new(treasury_ata, false),
302            AccountMeta::new(DOJO_MINT, false),
303            AccountMeta::new(treasury_address, false),
304            AccountMeta::new_readonly(spl_token::ID, false),
305        ],
306        data: Dine {
307            tier: tier.to_le_bytes(),
308            slot: slot.to_le_bytes(),
309        }
310        .to_bytes(),
311    }
312}
313
314/// Upgrade barracks (Ninja Hut) level. Pay with shards. 1→2, 2→3, 3→4. Burns shards.
315pub fn upgrade_barracks_shards(signer: Pubkey) -> Instruction {
316    let config_address = config_pda(&program_id()).0;
317    let game_address = game_pda(&program_id()).0;
318    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
319    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
320    let treasury_address = treasury_pda(&program_id()).0;
321    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
322    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
323
324    Instruction {
325        program_id: program_id(),
326        accounts: vec![
327            AccountMeta::new(signer, true),
328            AccountMeta::new_readonly(config_address, false),
329            AccountMeta::new(game_address, false),
330            AccountMeta::new(dojo_address, false),
331            AccountMeta::new(barracks_address, false),
332            AccountMeta::new(user_ata, false),
333            AccountMeta::new(treasury_ata, false),
334            AccountMeta::new(DOJO_MINT, false),
335            AccountMeta::new(treasury_address, false),
336            AccountMeta::new_readonly(spl_token::ID, false),
337        ],
338        data: UpgradeBarracksShards {}.to_bytes(),
339    }
340}
341
342/// Upgrade barracks (Ninja Hut) level. Pay with SOL. 1→2, 2→3 only (3→4 shards only).
343pub fn upgrade_barracks_sol(signer: Pubkey) -> Instruction {
344    let config_address = config_pda(&program_id()).0;
345    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
346    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
347    let treasury_address = treasury_pda(&program_id()).0;
348
349    Instruction {
350        program_id: program_id(),
351        accounts: vec![
352            AccountMeta::new(signer, true),
353            AccountMeta::new_readonly(config_address, false),
354            AccountMeta::new(dojo_address, false),
355            AccountMeta::new(barracks_address, false),
356            AccountMeta::new(treasury_address, false),
357            AccountMeta::new(FEE_COLLECTOR, false),
358            AccountMeta::new_readonly(system_program::ID, false),
359        ],
360        data: UpgradeBarracksSol {}.to_bytes(),
361    }
362}
363
364/// Upgrade forge level. Pay SOL (1–7, max level 7).
365pub fn upgrade_forge(signer: Pubkey) -> Instruction {
366    let config_address = config_pda(&program_id()).0;
367    let game_address = game_pda(&program_id()).0;
368    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
369    let (forge_address, _) = forge_pda(&program_id(), &dojo_address);
370    let treasury_address = treasury_pda(&program_id()).0;
371    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
372    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
373
374    Instruction {
375        program_id: program_id(),
376        accounts: vec![
377            AccountMeta::new(signer, true),
378            AccountMeta::new_readonly(config_address, false),
379            AccountMeta::new(game_address, false),
380            AccountMeta::new(dojo_address, false),
381            AccountMeta::new(forge_address, false),
382            AccountMeta::new(FEE_COLLECTOR, false),
383            AccountMeta::new_readonly(system_program::ID, false),
384            AccountMeta::new(user_ata, false),
385            AccountMeta::new(treasury_ata, false),
386            AccountMeta::new(DOJO_MINT, false),
387            AccountMeta::new(treasury_address, false),
388            AccountMeta::new_readonly(spl_token::ID, false),
389        ],
390        data: UpgradeForge {}.to_bytes(),
391    }
392}
393
394/// Merge: consume from fodder_counts. merge_type: 0=10×N, 1=5×R, 2=3×SR. Output rarity uses same chances as recruit (rarity_from_hash).
395/// seed: from BSM POST /seed.
396/// prestige: if Some, include Prestige account (must exist).
397pub fn merge_shogun(
398    signer: Pubkey,
399    merge_type: u64,
400    seed: [u8; 32],
401    prestige: Option<Pubkey>,
402) -> Instruction {
403    let config_address = config_pda(&program_id()).0;
404    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
405    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
406
407    let mut accounts = vec![
408        AccountMeta::new(signer, true),
409        AccountMeta::new_readonly(config_address, false),
410        AccountMeta::new(dojo_address, false),
411        AccountMeta::new(tasks_address, false),
412    ];
413    if let Some(addr) = prestige {
414        accounts.push(AccountMeta::new(addr, false));
415    }
416
417    Instruction {
418        program_id: program_id(),
419        accounts,
420        data: MergeShogun {
421            merge_type: merge_type.to_le_bytes(),
422            seed,
423        }
424        .to_bytes(),
425    }
426}
427
428/// Prestige: consume dupes from fodder, upgrade seated shogun in slot. SSR/UR only.
429/// prestige: if Some, include Prestige account (must exist).
430pub fn prestige_upgrade(signer: Pubkey, slot: u64, prestige: Option<Pubkey>) -> Instruction {
431    let config_address = config_pda(&program_id()).0;
432    let game_address = game_pda(&program_id()).0;
433    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
434    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
435    let (mission_address, _) = mission_pda(&program_id(), &dojo_address);
436    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
437
438    let mut accounts = vec![
439        AccountMeta::new(signer, true),
440        AccountMeta::new_readonly(config_address, false),
441        AccountMeta::new(game_address, false),
442        AccountMeta::new(dojo_address, false),
443        AccountMeta::new(barracks_address, false),
444        AccountMeta::new_readonly(mission_address, false),
445        AccountMeta::new(tasks_address, false),
446    ];
447    if let Some(addr) = prestige {
448        accounts.push(AccountMeta::new(addr, false));
449    }
450
451    Instruction {
452        program_id: program_id(),
453        accounts,
454        data: PrestigeUpgrade {
455            slot: slot.to_le_bytes(),
456        }
457        .to_bytes(),
458    }
459}
460
461/// Level up: spend shards, +10% SP per level. Burns shards.
462pub fn level_up_shogun(signer: Pubkey, slot: u64) -> Instruction {
463    let config_address = config_pda(&program_id()).0;
464    let game_address = game_pda(&program_id()).0;
465    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
466    let treasury_address = treasury_pda(&program_id()).0;
467    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
468    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
469    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
470    let (mission_address, _) = mission_pda(&program_id(), &dojo_address);
471
472    Instruction {
473        program_id: program_id(),
474        accounts: vec![
475            AccountMeta::new(signer, true),
476            AccountMeta::new_readonly(config_address, false),
477            AccountMeta::new(game_address, false),
478            AccountMeta::new(dojo_address, false),
479            AccountMeta::new(barracks_address, false),
480            AccountMeta::new_readonly(mission_address, false),
481            AccountMeta::new(user_ata, false),
482            AccountMeta::new(treasury_ata, false),
483            AccountMeta::new(DOJO_MINT, false),
484            AccountMeta::new(treasury_address, false),
485            AccountMeta::new_readonly(spl_token::ID, false),
486        ],
487        data: LevelUpShogun {
488            slot: slot.to_le_bytes(),
489        }
490        .to_bytes(),
491    }
492}
493
494/// Claim shards as $DOJO token. Pool-split: fixed emission per slot, your share = your_SP / total_SP.
495/// Amount computed entirely on-chain; no client input (security).
496pub fn claim_shards(signer: Pubkey) -> Instruction {
497    let config_address = config_pda(&program_id()).0;
498    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
499    let (forge_address, _) = forge_pda(&program_id(), &dojo_address);
500    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
501    let (mission_address, _) = mission_pda(&program_id(), &dojo_address);
502    let game_address = game_pda(&program_id()).0;
503    let treasury_address = treasury_pda(&program_id()).0;
504    let dojo_ata = get_associated_token_address(&signer, &DOJO_MINT);
505
506    Instruction {
507        program_id: program_id(),
508        accounts: vec![
509            AccountMeta::new(signer, true),
510            AccountMeta::new_readonly(config_address, false),
511            AccountMeta::new(dojo_address, false),
512            AccountMeta::new(forge_address, false),
513            AccountMeta::new(barracks_address, false),
514            AccountMeta::new_readonly(mission_address, false),
515            AccountMeta::new_readonly(game_address, false),
516            AccountMeta::new(dojo_ata, false),
517            AccountMeta::new(DOJO_MINT, false),
518            AccountMeta::new(treasury_address, false),
519            AccountMeta::new_readonly(spl_token::ID, false),
520        ],
521        data: ClaimShards {}.to_bytes(),
522    }
523}
524
525/// Claim referral reward (SOL).
526pub fn claim_referral_reward(signer: Pubkey, referrer_dojo: Pubkey) -> Instruction {
527    let (referral_address, _) = referral_pda(&program_id(), &referrer_dojo);
528    let treasury_address = treasury_pda(&program_id()).0;
529
530    Instruction {
531        program_id: program_id(),
532        accounts: vec![
533            AccountMeta::new(signer, true),
534            AccountMeta::new(referrer_dojo, false),
535            AccountMeta::new(referral_address, false),
536            AccountMeta::new(treasury_address, false),
537            AccountMeta::new_readonly(system_program::ID, false),
538        ],
539        data: ClaimReferralReward {}.to_bytes(),
540    }
541}
542
543/// Claim next recruit-tier reward.
544pub fn claim_recruit_reward(signer: Pubkey) -> Instruction {
545    let config_address = config_pda(&program_id()).0;
546    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
547    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
548
549    Instruction {
550        program_id: program_id(),
551        accounts: vec![
552            AccountMeta::new(signer, true),
553            AccountMeta::new_readonly(config_address, false),
554            AccountMeta::new(dojo_address, false),
555            AccountMeta::new(tasks_address, false),
556        ],
557        data: ClaimRecruitReward {}.to_bytes(),
558    }
559}
560
561/// Claim next forge-tier reward.
562pub fn claim_forge_reward(signer: Pubkey) -> Instruction {
563    let config_address = config_pda(&program_id()).0;
564    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
565    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
566
567    Instruction {
568        program_id: program_id(),
569        accounts: vec![
570            AccountMeta::new(signer, true),
571            AccountMeta::new_readonly(config_address, false),
572            AccountMeta::new(dojo_address, false),
573            AccountMeta::new(tasks_address, false),
574        ],
575        data: ClaimForgeReward {}.to_bytes(),
576    }
577}
578
579/// Claim next mission-tier reward (1→1, 2→2, 5→5, 20→10, 50→25 tickets).
580pub fn claim_mission_task_reward(signer: Pubkey) -> Instruction {
581    let config_address = config_pda(&program_id()).0;
582    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
583    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
584
585    Instruction {
586        program_id: program_id(),
587        accounts: vec![
588            AccountMeta::new(signer, true),
589            AccountMeta::new_readonly(config_address, false),
590            AccountMeta::new(dojo_address, false),
591            AccountMeta::new(tasks_address, false),
592        ],
593        data: ClaimMissionTaskReward {}.to_bytes(),
594    }
595}
596
597/// Claim next dine-tier reward.
598pub fn claim_dine_reward(signer: Pubkey) -> Instruction {
599    let config_address = config_pda(&program_id()).0;
600    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
601    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
602
603    Instruction {
604        program_id: program_id(),
605        accounts: vec![
606            AccountMeta::new(signer, true),
607            AccountMeta::new_readonly(config_address, false),
608            AccountMeta::new(dojo_address, false),
609            AccountMeta::new(tasks_address, false),
610        ],
611        data: ClaimDineReward {}.to_bytes(),
612    }
613}
614
615/// Ed25519 verify instruction for daily claim. Must be prepended before claim_daily_reward.
616/// Client builds the same message as the server signs: prefix + dojo_pda + task_id.
617pub fn ed25519_verify_instruction_for_daily_claim(
618    dojo_pda: Pubkey,
619    signature: [u8; 64],
620) -> Instruction {
621    use crate::consts::{CLAIM_TASK_PREFIX, DAILY_TASK_START, TASK_VERIFIER};
622    let mut message = Vec::with_capacity(CLAIM_TASK_PREFIX.len() + 32 + 8);
623    message.extend_from_slice(CLAIM_TASK_PREFIX);
624    message.extend_from_slice(dojo_pda.as_ref());
625    message.extend_from_slice(&DAILY_TASK_START.to_le_bytes());
626    let verifier_bytes: [u8; 32] = TASK_VERIFIER.to_bytes();
627    crate::utils::new_ed25519_instruction_with_signature(&message, &signature, &verifier_bytes)
628}
629
630/// Claim daily reward (1 ticket per day, no stacking). Backend signature required.
631/// Transaction must include ed25519_verify_instruction_for_daily_claim as the preceding instruction.
632pub fn claim_daily_reward(signer: Pubkey, signature: [u8; 64]) -> Instruction {
633    let config_address = config_pda(&program_id()).0;
634    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
635    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
636    let instructions_sysvar = solana_program::sysvar::instructions::ID;
637
638    Instruction {
639        program_id: program_id(),
640        accounts: vec![
641            AccountMeta::new(signer, true),
642            AccountMeta::new_readonly(config_address, false),
643            AccountMeta::new(dojo_address, false),
644            AccountMeta::new(tasks_address, false),
645            AccountMeta::new_readonly(instructions_sysvar, false),
646        ],
647        data: ClaimDailyReward { signature }.to_bytes(),
648    }
649}
650
651/// Ed25519 verify instruction for off-chain task (9–16). Must be prepended before claim_off_chain_task_reward.
652pub fn ed25519_verify_instruction_for_off_chain_task(
653    dojo_pda: Pubkey,
654    task_id: u64,
655    signature: [u8; 64],
656) -> Instruction {
657    use crate::consts::{CLAIM_TASK_PREFIX, TASK_VERIFIER};
658    let mut message = Vec::with_capacity(CLAIM_TASK_PREFIX.len() + 32 + 8);
659    message.extend_from_slice(CLAIM_TASK_PREFIX);
660    message.extend_from_slice(dojo_pda.as_ref());
661    message.extend_from_slice(&task_id.to_le_bytes());
662    let verifier_bytes: [u8; 32] = TASK_VERIFIER.to_bytes();
663    crate::utils::new_ed25519_instruction_with_signature(&message, &signature, &verifier_bytes)
664}
665
666/// Claim off-chain task reward (task_id 9–16). Backend signature required.
667/// Transaction must include ed25519_verify_instruction_for_off_chain_task as the preceding instruction.
668pub fn claim_off_chain_task_reward(
669    signer: Pubkey,
670    task_id: u64,
671    signature: [u8; 64],
672) -> Instruction {
673    let config_address = config_pda(&program_id()).0;
674    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
675    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
676    let instructions_sysvar = solana_program::sysvar::instructions::ID;
677
678    Instruction {
679        program_id: program_id(),
680        accounts: vec![
681            AccountMeta::new(signer, true),
682            AccountMeta::new_readonly(config_address, false),
683            AccountMeta::new(dojo_address, false),
684            AccountMeta::new(tasks_address, false),
685            AccountMeta::new_readonly(instructions_sysvar, false),
686        ],
687        data: ClaimOffChainTaskReward {
688            task_id: task_id.to_le_bytes(),
689            signature,
690        }
691        .to_bytes(),
692    }
693}
694
695/// Claim Seeker task reward. Verifies user owns a Seeker Genesis Token (SGT) on-chain; one-time claim.
696/// Anti-Sybil: Seeker PDA (per SGT mint) prevents reusing same SGT across wallets.
697/// Pass the signer's SGT token account (Token-2022 ATA) and the SGT mint.
698/// Client should use getTokenAccountsByOwner or similar to find the user's SGT.
699pub fn claim_seeker_task_reward(
700    signer: Pubkey,
701    signer_sgt_token_account: Pubkey,
702    sgt_mint: Pubkey,
703) -> Instruction {
704    let config_address = config_pda(&program_id()).0;
705    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
706    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
707    let (seeker_address, _) = seeker_pda(&program_id(), &sgt_mint);
708
709    Instruction {
710        program_id: program_id(),
711        accounts: vec![
712            AccountMeta::new(signer, true),
713            AccountMeta::new_readonly(config_address, false),
714            AccountMeta::new(dojo_address, false),
715            AccountMeta::new(tasks_address, false),
716            AccountMeta::new_readonly(signer_sgt_token_account, false),
717            AccountMeta::new_readonly(sgt_mint, false),
718            AccountMeta::new_readonly(spl_token_2022::ID, false),
719            AccountMeta::new(seeker_address, false),
720            AccountMeta::new_readonly(system_program::ID, false),
721        ],
722        data: ClaimSeekerTaskReward {}.to_bytes(),
723    }
724}
725
726/// Mint soulbound NFT for Seeker users. User can either claim Seeker task first or mint directly
727/// (creates Seeker if needed). One soulbound per SGT mint.
728/// Pass soulbound_mint as a new keypair pubkey (client generates keypair for the mint account).
729pub fn mint_soulbound(
730    signer: Pubkey,
731    signer_sgt_token_account: Pubkey,
732    sgt_mint: Pubkey,
733    soulbound_mint: Pubkey,
734) -> Instruction {
735    let config_address = config_pda(&program_id()).0;
736    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
737    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
738    let (seeker_address, _) = seeker_pda(&program_id(), &sgt_mint);
739    let (treasury_address, _) = treasury_pda(&program_id());
740    let soulbound_token = get_associated_token_address(&signer, &soulbound_mint);
741
742    Instruction {
743        program_id: program_id(),
744        accounts: vec![
745            AccountMeta::new(signer, true),
746            AccountMeta::new_readonly(config_address, false),
747            AccountMeta::new(dojo_address, false),
748            AccountMeta::new(tasks_address, false),
749            AccountMeta::new(seeker_address, false),
750            AccountMeta::new_readonly(signer_sgt_token_account, false),
751            AccountMeta::new_readonly(sgt_mint, false),
752            AccountMeta::new(soulbound_mint, true), // client keypair must sign for create_account
753            AccountMeta::new_readonly(treasury_address, false),
754            AccountMeta::new(DGT_GROUP, false), // DGT group mint (writable for initialize_member)
755            AccountMeta::new(soulbound_token, false),
756            AccountMeta::new_readonly(spl_token_2022::ID, false),
757            AccountMeta::new_readonly(system_program::ID, false),
758            AccountMeta::new_readonly(spl_associated_token_account::ID, false),
759        ],
760        data: MintSoulbound {}.to_bytes(),
761    }
762}
763
764/// Start mission (expedition). Pay DOJO, burn. Slots on mission excluded from mining.
765/// mission_type: 0=Short, 1=Medium, 2=Long. slot_mask: bits for participating slots.
766/// Mission account created if needed (existing dojos). New dojos get Mission in BuyStarterPack.
767pub fn start_mission(signer: Pubkey, mission_type: u64, slot_mask: u64) -> Instruction {
768    let config_address = config_pda(&program_id()).0;
769    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
770    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
771    let (mission_address, _) = mission_pda(&program_id(), &dojo_address);
772    let game_address = game_pda(&program_id()).0;
773    let treasury_address = treasury_pda(&program_id()).0;
774    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
775    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
776
777    Instruction {
778        program_id: program_id(),
779        accounts: vec![
780            AccountMeta::new(signer, true),
781            AccountMeta::new_readonly(config_address, false),
782            AccountMeta::new(dojo_address, false),
783            AccountMeta::new(barracks_address, false),
784            AccountMeta::new(mission_address, false),
785            AccountMeta::new(game_address, false),
786            AccountMeta::new(user_ata, false),
787            AccountMeta::new(treasury_ata, false),
788            AccountMeta::new(DOJO_MINT, false),
789            AccountMeta::new(treasury_address, false),
790            AccountMeta::new_readonly(spl_token::ID, false),
791            AccountMeta::new_readonly(system_program::ID, false),
792        ],
793        data: StartMission {
794            mission_type: mission_type.to_le_bytes(),
795            slot_mask: slot_mask.to_le_bytes(),
796        }
797        .to_bytes(),
798    }
799}
800
801/// Claim mission reward. Requires BSM seed from POST /mission/seed. prestige: optional for free-roll tracking.
802pub fn claim_mission_reward(
803    signer: Pubkey,
804    mission_index: u64,
805    seed: [u8; 32],
806    prestige: Option<Pubkey>,
807) -> Instruction {
808    let config_address = config_pda(&program_id()).0;
809    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
810    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
811    let (mission_address, _) = mission_pda(&program_id(), &dojo_address);
812    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
813    let game_address = game_pda(&program_id()).0;
814    let treasury_address = treasury_pda(&program_id()).0;
815    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
816    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
817
818    let mut accounts = vec![
819        AccountMeta::new(signer, true),
820        AccountMeta::new_readonly(config_address, false),
821        AccountMeta::new(dojo_address, false),
822        AccountMeta::new_readonly(barracks_address, false),
823        AccountMeta::new(mission_address, false),
824        AccountMeta::new(game_address, false),
825        AccountMeta::new(tasks_address, false),
826        AccountMeta::new(user_ata, false),
827        AccountMeta::new(DOJO_MINT, false),
828        AccountMeta::new(treasury_address, false),
829        AccountMeta::new(treasury_ata, false),
830        AccountMeta::new_readonly(spl_token::ID, false),
831    ];
832    if let Some(addr) = prestige {
833        accounts.push(AccountMeta::new(addr, false));
834    }
835
836    Instruction {
837        program_id: program_id(),
838        accounts,
839        data: ClaimMissionReward {
840            mission_index: mission_index.to_le_bytes(),
841            seed,
842        }
843        .to_bytes(),
844    }
845}
846
847/// Claim collection reward (3 ninjas same element+rarity). Pass collection_index (element×5 + rarity, 0–24).
848/// Program finds 3 matching shoguns in pool.
849pub fn claim_collection_reward(signer: Pubkey, collection_index: u8) -> Instruction {
850    let config_address = config_pda(&program_id()).0;
851    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
852    let (tasks_address, _) = tasks_pda(&program_id(), &dojo_address);
853
854    Instruction {
855        program_id: program_id(),
856        accounts: vec![
857            AccountMeta::new(signer, true),
858            AccountMeta::new_readonly(config_address, false),
859            AccountMeta::new(dojo_address, false),
860            AccountMeta::new(tasks_address, false),
861        ],
862        data: ClaimCollectionReward { collection_index }.to_bytes(),
863    }
864}
865
866/// Flash sale: 50 tickets for 5000 shards, max 5 per day.
867pub fn buy_flash_sale(signer: Pubkey) -> Instruction {
868    let config_address = config_pda(&program_id()).0;
869    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
870    let treasury_address = treasury_pda(&program_id()).0;
871    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
872    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
873
874    Instruction {
875        program_id: program_id(),
876        accounts: vec![
877            AccountMeta::new(signer, true),
878            AccountMeta::new_readonly(config_address, false),
879            AccountMeta::new(dojo_address, false),
880            AccountMeta::new(user_ata, false),
881            AccountMeta::new_readonly(treasury_address, false),
882            AccountMeta::new(treasury_ata, false),
883            AccountMeta::new_readonly(spl_token::ID, false),
884        ],
885        data: BuyFlashSale {}.to_bytes(),
886    }
887}
888
889/// Daily deal: 5 tickets for 300 shards. Burns shards.
890pub fn buy_tickets_with_shards(signer: Pubkey) -> Instruction {
891    let config_address = config_pda(&program_id()).0;
892    let game_address = game_pda(&program_id()).0;
893    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
894    let treasury_address = treasury_pda(&program_id()).0;
895    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
896    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
897
898    Instruction {
899        program_id: program_id(),
900        accounts: vec![
901            AccountMeta::new(signer, true),
902            AccountMeta::new_readonly(config_address, false),
903            AccountMeta::new(game_address, false),
904            AccountMeta::new(dojo_address, false),
905            AccountMeta::new(user_ata, false),
906            AccountMeta::new(treasury_ata, false),
907            AccountMeta::new(DOJO_MINT, false),
908            AccountMeta::new(treasury_address, false),
909            AccountMeta::new_readonly(spl_token::ID, false),
910        ],
911        data: BuyTicketsWithShards {}.to_bytes(),
912    }
913}
914
915/// Buy bundle: 150 recruitment tickets for 5 SOL (event deal).
916pub fn buy_bundle(signer: Pubkey) -> Instruction {
917    let config_address = config_pda(&program_id()).0;
918    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
919    let treasury_address = treasury_pda(&program_id()).0;
920
921    Instruction {
922        program_id: program_id(),
923        accounts: vec![
924            AccountMeta::new(signer, true),
925            AccountMeta::new_readonly(config_address, false),
926            AccountMeta::new(dojo_address, false),
927            AccountMeta::new(treasury_address, false),
928            AccountMeta::new(FEE_COLLECTOR, false),
929            AccountMeta::new_readonly(system_program::ID, false),
930        ],
931        data: BuyBundle {}.to_bytes(),
932    }
933}
934
935/// Clear forge upgrade cooldown. Cost = remaining minutes (shards). One tx clears all.
936pub fn clear_forge_cooldown(signer: Pubkey) -> Instruction {
937    let config_address = config_pda(&program_id()).0;
938    let game_address = game_pda(&program_id()).0;
939    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
940    let (forge_address, _) = forge_pda(&program_id(), &dojo_address);
941    let treasury_address = treasury_pda(&program_id()).0;
942    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
943    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
944
945    Instruction {
946        program_id: program_id(),
947        accounts: vec![
948            AccountMeta::new(signer, true),
949            AccountMeta::new_readonly(config_address, false),
950            AccountMeta::new(game_address, false),
951            AccountMeta::new(dojo_address, false),
952            AccountMeta::new(forge_address, false),
953            AccountMeta::new(user_ata, false),
954            AccountMeta::new(treasury_ata, false),
955            AccountMeta::new(DOJO_MINT, false),
956            AccountMeta::new(treasury_address, false),
957            AccountMeta::new_readonly(spl_token::ID, false),
958        ],
959        data: ClearForgeCooldown {}.to_bytes(),
960    }
961}
962
963/// Set genesis slot and game.last_emission_slot (admin). halving_period_slots: 0 = use default (~58 days, matches Hyper Ninja).
964pub fn set_genesis_slot(authority: Pubkey, genesis_slot: u64, halving_period_slots: u64) -> Instruction {
965    let config_address = config_pda(&program_id()).0;
966    let game_address = game_pda(&program_id()).0;
967
968    Instruction {
969        program_id: program_id(),
970        accounts: vec![
971            AccountMeta::new(authority, true),
972            AccountMeta::new(config_address, false),
973            AccountMeta::new(game_address, false),
974        ],
975        data: SetGenesisSlot {
976            genesis_slot: genesis_slot.to_le_bytes(),
977            halving_period_slots: halving_period_slots.to_le_bytes(),
978        }
979        .to_bytes(),
980    }
981}
982
983/// Roll scene sections (1 or 10) — pay with Amethyst.
984/// seed: from BSM POST /roll/instruction (Option 7 centralized oracle).
985pub fn roll_scene_section_amethyst(signer: Pubkey, count: u64, seed: [u8; 32]) -> Instruction {
986    let config_address = config_pda(&program_id()).0;
987    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
988    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
989
990    Instruction {
991        program_id: program_id(),
992        accounts: vec![
993            AccountMeta::new(signer, true),
994            AccountMeta::new_readonly(config_address, false),
995            AccountMeta::new(dojo_address, false),
996            AccountMeta::new(scenes_address, false),
997            AccountMeta::new_readonly(system_program::ID, false),
998        ],
999        data: RollSceneSectionAmethyst {
1000            count: count.to_le_bytes(),
1001            seed,
1002        }
1003        .to_bytes(),
1004    }
1005}
1006
1007/// Roll scene sections (1 or 10) — pay with Shards (SPL $DOJO).
1008/// seed: from BSM POST /roll/instruction (Option 7 centralized oracle).
1009pub fn roll_scene_section_shards(signer: Pubkey, count: u64, seed: [u8; 32]) -> Instruction {
1010    let config_address = config_pda(&program_id()).0;
1011    let game_address = game_pda(&program_id()).0;
1012    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
1013    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
1014    let treasury_address = treasury_pda(&program_id()).0;
1015    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
1016    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
1017
1018    Instruction {
1019        program_id: program_id(),
1020        accounts: vec![
1021            AccountMeta::new(signer, true),
1022            AccountMeta::new_readonly(config_address, false),
1023            AccountMeta::new(game_address, false),
1024            AccountMeta::new(dojo_address, false),
1025            AccountMeta::new(scenes_address, false),
1026            AccountMeta::new(user_ata, false),
1027            AccountMeta::new(treasury_ata, false),
1028            AccountMeta::new(DOJO_MINT, false),
1029            AccountMeta::new(treasury_address, false),
1030            AccountMeta::new_readonly(spl_token::ID, false),
1031            AccountMeta::new_readonly(system_program::ID, false),
1032        ],
1033        data: RollSceneSectionShards {
1034            count: count.to_le_bytes(),
1035            seed,
1036        }
1037        .to_bytes(),
1038    }
1039}
1040
1041/// Salvage all duplicate scene sections for Amethyst refund. Program derives from on-chain state.
1042pub fn salvage_scene_section(signer: Pubkey) -> Instruction {
1043    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
1044    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
1045
1046    Instruction {
1047        program_id: program_id(),
1048        accounts: vec![
1049            AccountMeta::new(signer, true),
1050            AccountMeta::new(dojo_address, false),
1051            AccountMeta::new(scenes_address, false),
1052        ],
1053        data: SalvageSceneSection {}.to_bytes(),
1054    }
1055}
1056
1057/// Set active scene (background). Requires scene unlocked.
1058/// Updates Game.total_effective_spirit_power for pool-split.
1059pub fn update_active_scene(signer: Pubkey, scene_id: u64) -> Instruction {
1060    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
1061    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
1062    let game_address = game_pda(&program_id()).0;
1063    let (barracks_address, _) = barracks_pda(&program_id(), &dojo_address);
1064
1065    Instruction {
1066        program_id: program_id(),
1067        accounts: vec![
1068            AccountMeta::new(signer, true),
1069            AccountMeta::new(dojo_address, false),
1070            AccountMeta::new(scenes_address, false),
1071            AccountMeta::new(game_address, false),
1072            AccountMeta::new(barracks_address, false),
1073        ],
1074        data: UpdateActiveScene {
1075            scene_id: scene_id.to_le_bytes(),
1076        }
1077        .to_bytes(),
1078    }
1079}
1080
1081/// Buy chest: 1 SOL → 5000 Amethyst. 90% to treasury, 10% to fee collector.
1082pub fn buy_chest(signer: Pubkey) -> Instruction {
1083    let config_address = config_pda(&program_id()).0;
1084    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
1085    let treasury_address = treasury_pda(&program_id()).0;
1086
1087    Instruction {
1088        program_id: program_id(),
1089        accounts: vec![
1090            AccountMeta::new(signer, true),
1091            AccountMeta::new_readonly(config_address, false),
1092            AccountMeta::new(dojo_address, false),
1093            AccountMeta::new(treasury_address, false),
1094            AccountMeta::new(FEE_COLLECTOR, false),
1095            AccountMeta::new_readonly(system_program::ID, false),
1096        ],
1097        data: BuyChest {}.to_bytes(),
1098    }
1099}
1100
1101/// Buy scene 6, 7, or 8 with Amethyst. Unlocks entire scene (all 12 sections).
1102pub fn buy_scene(signer: Pubkey, scene_id: u64) -> Instruction {
1103    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
1104    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
1105
1106    Instruction {
1107        program_id: program_id(),
1108        accounts: vec![
1109            AccountMeta::new(signer, true),
1110            AccountMeta::new(dojo_address, false),
1111            AccountMeta::new(scenes_address, false),
1112            AccountMeta::new_readonly(system_program::ID, false),
1113        ],
1114        data: BuyScene {
1115            scene_id: scene_id.to_le_bytes(),
1116        }
1117        .to_bytes(),
1118    }
1119}
1120
1121/// Buy scene (6/7/8) with mixed payment: spend all amethyst, cover shortfall with DOJO.
1122pub fn buy_scene_dojo(signer: Pubkey, scene_id: u64) -> Instruction {
1123    let config_address = config_pda(&program_id()).0;
1124    let (game_address, _) = game_pda(&program_id());
1125    let (dojo_address, _) = dojo_pda(&program_id(), &signer);
1126    let (scenes_address, _) = scenes_pda(&program_id(), &dojo_address);
1127    let treasury_address = treasury_pda(&program_id()).0;
1128    let user_ata = get_associated_token_address(&signer, &DOJO_MINT);
1129    let treasury_ata = get_associated_token_address(&treasury_address, &DOJO_MINT);
1130
1131    Instruction {
1132        program_id: program_id(),
1133        accounts: vec![
1134            AccountMeta::new(signer, true),
1135            AccountMeta::new_readonly(config_address, false),
1136            AccountMeta::new(game_address, false),
1137            AccountMeta::new(dojo_address, false),
1138            AccountMeta::new(scenes_address, false),
1139            AccountMeta::new(user_ata, false),
1140            AccountMeta::new(treasury_ata, false),
1141            AccountMeta::new(DOJO_MINT, false),
1142            AccountMeta::new(treasury_address, false),
1143            AccountMeta::new_readonly(spl_token::ID, false),
1144            AccountMeta::new_readonly(system_program::ID, false),
1145        ],
1146        data: BuySceneDojo {
1147            scene_id: scene_id.to_le_bytes(),
1148        }
1149        .to_bytes(),
1150    }
1151}
1152
1153/// Log (CPI from program; config signs). Variable-length message.
1154pub fn log(config: Pubkey, msg: &[u8]) -> Instruction {
1155    let mut data = Log {
1156        _reserved: [0u8; 8],
1157    }
1158    .to_bytes();
1159    data.extend_from_slice(msg);
1160    Instruction {
1161        program_id: program_id(),
1162        accounts: vec![AccountMeta::new(config, true)],
1163        data,
1164    }
1165}