1use super::transaction::{AccountMeta, Instruction};
4
5pub const ATA_PROGRAM_ID: [u8; 32] = [
11 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, 13, 131, 11, 90, 19, 153, 218,
12 255, 16, 132, 4, 142, 123, 216, 219, 233, 248, 89,
13];
14
15pub const SPL_TOKEN_PROGRAM_ID: [u8; 32] = [
17 6, 221, 246, 225, 215, 101, 161, 147, 217, 203, 225, 70, 206, 235, 121, 172, 28, 180, 133, 237,
18 95, 91, 55, 145, 58, 140, 245, 133, 126, 255, 0, 169,
19];
20
21const SYSTEM_PROGRAM_ID: [u8; 32] = [0; 32];
23
24pub fn derive_ata_address(wallet: &[u8; 32], mint: &[u8; 32]) -> [u8; 32] {
30 use sha2::{Digest, Sha256};
32 let mut hasher = Sha256::new();
33 hasher.update(wallet);
34 hasher.update(SPL_TOKEN_PROGRAM_ID);
35 hasher.update(mint);
36 hasher.update(ATA_PROGRAM_ID);
37 hasher.update(b"ProgramDerivedAddress");
38 let result = hasher.finalize();
39 let mut out = [0u8; 32];
40 out.copy_from_slice(&result);
41 out
42}
43
44pub fn create_ata(payer: [u8; 32], wallet: [u8; 32], mint: [u8; 32]) -> Instruction {
49 let ata = derive_ata_address(&wallet, &mint);
50
51 Instruction {
52 program_id: ATA_PROGRAM_ID,
53 accounts: vec![
54 AccountMeta::new(payer, true), AccountMeta::new(ata, false), AccountMeta::new_readonly(wallet, false), AccountMeta::new_readonly(mint, false), AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false), AccountMeta::new_readonly(SPL_TOKEN_PROGRAM_ID, false), ],
61 data: vec![0], }
63}
64
65pub fn create_ata_idempotent(payer: [u8; 32], wallet: [u8; 32], mint: [u8; 32]) -> Instruction {
69 let ata = derive_ata_address(&wallet, &mint);
70
71 Instruction {
72 program_id: ATA_PROGRAM_ID,
73 accounts: vec![
74 AccountMeta::new(payer, true),
75 AccountMeta::new(ata, false),
76 AccountMeta::new_readonly(wallet, false),
77 AccountMeta::new_readonly(mint, false),
78 AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
79 AccountMeta::new_readonly(SPL_TOKEN_PROGRAM_ID, false),
80 ],
81 data: vec![1], }
83}
84
85pub const MEMO_PROGRAM_ID: [u8; 32] = [
91 5, 74, 83, 80, 248, 93, 200, 130, 214, 20, 165, 86, 114, 120, 138, 41, 109, 223, 30, 171, 171,
92 208, 166, 6, 120, 136, 73, 50, 244, 238, 246, 160,
93];
94
95pub fn memo(memo_text: &str, signers: &[[u8; 32]]) -> Instruction {
101 let accounts: Vec<AccountMeta> = signers
102 .iter()
103 .map(|pk| AccountMeta::new_readonly(*pk, true))
104 .collect();
105
106 Instruction {
107 program_id: MEMO_PROGRAM_ID,
108 accounts,
109 data: memo_text.as_bytes().to_vec(),
110 }
111}
112
113pub fn memo_unsigned(memo_text: &str) -> Instruction {
115 Instruction {
116 program_id: MEMO_PROGRAM_ID,
117 accounts: vec![],
118 data: memo_text.as_bytes().to_vec(),
119 }
120}
121
122pub const STAKE_PROGRAM_ID: [u8; 32] = [
128 6, 161, 216, 23, 145, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
129 0, 0, 0,
130];
131
132pub const STAKE_CONFIG_ID: [u8; 32] = [
134 6, 161, 216, 23, 165, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
135 0, 0, 0,
136];
137
138pub const CLOCK_SYSVAR: [u8; 32] = [
140 6, 167, 213, 23, 24, 199, 116, 201, 40, 86, 99, 152, 105, 29, 94, 182, 139, 94, 184, 163, 155,
141 75, 109, 92, 115, 85, 91, 33, 0, 0, 0, 0,
142];
143
144pub const STAKE_HISTORY_SYSVAR: [u8; 32] = [
146 6, 167, 213, 23, 25, 47, 10, 175, 198, 242, 101, 227, 251, 119, 204, 122, 218, 130, 197, 41,
147 208, 190, 59, 19, 110, 45, 0, 85, 32, 0, 0, 0,
148];
149
150pub fn stake_delegate(
154 stake_account: [u8; 32],
155 vote_account: [u8; 32],
156 stake_authority: [u8; 32],
157) -> Instruction {
158 let mut data = vec![0u8; 4]; data[0] = 2;
160
161 Instruction {
162 program_id: STAKE_PROGRAM_ID,
163 accounts: vec![
164 AccountMeta::new(stake_account, false), AccountMeta::new_readonly(vote_account, false), AccountMeta::new_readonly(CLOCK_SYSVAR, false),
167 AccountMeta::new_readonly(STAKE_HISTORY_SYSVAR, false),
168 AccountMeta::new_readonly(STAKE_CONFIG_ID, false),
169 AccountMeta::new_readonly(stake_authority, true), ],
171 data,
172 }
173}
174
175pub fn stake_deactivate(stake_account: [u8; 32], stake_authority: [u8; 32]) -> Instruction {
179 let mut data = vec![0u8; 4];
180 data[0] = 5; Instruction {
183 program_id: STAKE_PROGRAM_ID,
184 accounts: vec![
185 AccountMeta::new(stake_account, false),
186 AccountMeta::new_readonly(CLOCK_SYSVAR, false),
187 AccountMeta::new_readonly(stake_authority, true),
188 ],
189 data,
190 }
191}
192
193pub fn stake_withdraw(
197 stake_account: [u8; 32],
198 withdraw_authority: [u8; 32],
199 recipient: [u8; 32],
200 lamports: u64,
201) -> Instruction {
202 let mut data = vec![0u8; 12];
203 data[0] = 4; data[4..12].copy_from_slice(&lamports.to_le_bytes());
205
206 Instruction {
207 program_id: STAKE_PROGRAM_ID,
208 accounts: vec![
209 AccountMeta::new(stake_account, false),
210 AccountMeta::new(recipient, false),
211 AccountMeta::new_readonly(CLOCK_SYSVAR, false),
212 AccountMeta::new_readonly(STAKE_HISTORY_SYSVAR, false),
213 AccountMeta::new_readonly(withdraw_authority, true),
214 ],
215 data,
216 }
217}
218
219pub fn advance_nonce(nonce_account: [u8; 32], nonce_authority: [u8; 32]) -> Instruction {
228 let recent_blockhashes_sysvar: [u8; 32] = [
230 6, 167, 213, 23, 24, 199, 116, 201, 40, 86, 99, 152, 105, 29, 94, 182, 139, 94, 184, 163,
231 155, 75, 109, 92, 115, 85, 91, 32, 0, 0, 0, 0,
232 ];
233
234 let mut data = vec![0u8; 4];
235 data[0] = 4; Instruction {
238 program_id: SYSTEM_PROGRAM_ID,
239 accounts: vec![
240 AccountMeta::new(nonce_account, false),
241 AccountMeta::new_readonly(recent_blockhashes_sysvar, false),
242 AccountMeta::new_readonly(nonce_authority, true),
243 ],
244 data,
245 }
246}
247
248pub fn initialize_nonce(nonce_account: [u8; 32], nonce_authority: [u8; 32]) -> Instruction {
252 let recent_blockhashes_sysvar: [u8; 32] = [
253 6, 167, 213, 23, 24, 199, 116, 201, 40, 86, 99, 152, 105, 29, 94, 182, 139, 94, 184, 163,
254 155, 75, 109, 92, 115, 85, 91, 32, 0, 0, 0, 0,
255 ];
256 let rent_sysvar: [u8; 32] = [
258 6, 167, 213, 23, 25, 47, 10, 175, 198, 242, 101, 227, 251, 119, 204, 122, 218, 130, 197,
259 41, 208, 190, 59, 19, 110, 45, 0, 85, 31, 0, 0, 0,
260 ];
261
262 let mut data = vec![0u8; 36];
263 data[0] = 6; data[4..36].copy_from_slice(&nonce_authority);
265
266 Instruction {
267 program_id: SYSTEM_PROGRAM_ID,
268 accounts: vec![
269 AccountMeta::new(nonce_account, false),
270 AccountMeta::new_readonly(recent_blockhashes_sysvar, false),
271 AccountMeta::new_readonly(rent_sysvar, false),
272 ],
273 data,
274 }
275}
276
277pub mod address_lookup_table {
283 use super::*;
284
285 pub const ID: [u8; 32] = [
287 0x06, 0xa1, 0xd8, 0x17, 0x91, 0x37, 0x68, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
288 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
289 0x00, 0x00,
290 ];
291
292 #[must_use]
300 pub fn create(
301 authority: [u8; 32],
302 payer: [u8; 32],
303 lookup_table: [u8; 32],
304 recent_slot: u64,
305 ) -> Instruction {
306 let mut data = vec![0u8; 4]; data.extend_from_slice(&recent_slot.to_le_bytes());
308
309 Instruction {
310 program_id: ID,
311 accounts: vec![
312 AccountMeta::new(lookup_table, false),
313 AccountMeta::new_readonly(authority, true),
314 AccountMeta::new(payer, true),
315 AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
316 ],
317 data,
318 }
319 }
320
321 #[must_use]
323 pub fn extend(
324 lookup_table: [u8; 32],
325 authority: [u8; 32],
326 payer: [u8; 32],
327 new_addresses: &[[u8; 32]],
328 ) -> Instruction {
329 let mut data = vec![0u8; 4];
330 data[0] = 2; data.extend_from_slice(&(new_addresses.len() as u32).to_le_bytes());
333 for addr in new_addresses {
334 data.extend_from_slice(addr);
335 }
336
337 Instruction {
338 program_id: ID,
339 accounts: vec![
340 AccountMeta::new(lookup_table, false),
341 AccountMeta::new_readonly(authority, true),
342 AccountMeta::new(payer, true),
343 AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
344 ],
345 data,
346 }
347 }
348
349 #[must_use]
353 pub fn deactivate(lookup_table: [u8; 32], authority: [u8; 32]) -> Instruction {
354 let mut data = vec![0u8; 4];
355 data[0] = 3; Instruction {
358 program_id: ID,
359 accounts: vec![
360 AccountMeta::new(lookup_table, false),
361 AccountMeta::new_readonly(authority, true),
362 ],
363 data,
364 }
365 }
366
367 #[must_use]
369 pub fn close(lookup_table: [u8; 32], authority: [u8; 32], recipient: [u8; 32]) -> Instruction {
370 let mut data = vec![0u8; 4];
371 data[0] = 4; Instruction {
374 program_id: ID,
375 accounts: vec![
376 AccountMeta::new(lookup_table, false),
377 AccountMeta::new_readonly(authority, true),
378 AccountMeta::new(recipient, false),
379 ],
380 data,
381 }
382 }
383}
384
385pub mod token_metadata {
391 use super::*;
392
393 pub const ID: [u8; 32] = [
395 0x0b, 0x74, 0x65, 0x78, 0x74, 0x50, 0x55, 0x73, 0x40, 0x6a, 0xc2, 0x14, 0x12, 0xf3, 0x26,
396 0xf7, 0x1b, 0x1e, 0xce, 0xf0, 0x77, 0x87, 0x28, 0x76, 0xf8, 0xba, 0x16, 0x1b, 0x70, 0x4c,
397 0x9f, 0x04,
398 ];
399
400 #[derive(Debug, Clone)]
402 pub struct DataV2 {
403 pub name: String,
405 pub symbol: String,
407 pub uri: String,
409 pub seller_fee_basis_points: u16,
411 pub creators: Option<Vec<Creator>>,
413 }
414
415 #[derive(Debug, Clone)]
417 pub struct Creator {
418 pub address: [u8; 32],
420 pub verified: bool,
422 pub share: u8,
424 }
425
426 pub fn derive_metadata_address(mint: &[u8; 32]) -> [u8; 32] {
430 use sha2::{Digest, Sha256};
431 let mut hasher = Sha256::new();
433 hasher.update(b"metadata");
434 hasher.update(ID);
435 hasher.update(mint);
436 hasher.update(ID);
437 hasher.update(b"ProgramDerivedAddress");
438 let result = hasher.finalize();
439 let mut out = [0u8; 32];
440 out.copy_from_slice(&result);
441 out
442 }
443
444 fn borsh_string(s: &str) -> Vec<u8> {
446 let bytes = s.as_bytes();
447 let mut out = Vec::with_capacity(4 + bytes.len());
448 out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
449 out.extend_from_slice(bytes);
450 out
451 }
452
453 fn serialize_data_v2(data: &DataV2) -> Vec<u8> {
455 let mut buf = Vec::new();
456 buf.extend(borsh_string(&data.name));
457 buf.extend(borsh_string(&data.symbol));
458 buf.extend(borsh_string(&data.uri));
459 buf.extend_from_slice(&data.seller_fee_basis_points.to_le_bytes());
460
461 match &data.creators {
463 None => buf.push(0),
464 Some(creators) => {
465 buf.push(1);
466 buf.extend_from_slice(&(creators.len() as u32).to_le_bytes());
467 for c in creators {
468 buf.extend_from_slice(&c.address);
469 buf.push(u8::from(c.verified));
470 buf.push(c.share);
471 }
472 }
473 }
474 buf
475 }
476
477 pub fn create_metadata_v3(
488 metadata: [u8; 32],
489 mint: [u8; 32],
490 mint_authority: [u8; 32],
491 payer: [u8; 32],
492 update_authority: [u8; 32],
493 data: &DataV2,
494 is_mutable: bool,
495 ) -> Instruction {
496 let mut ix_data = vec![33];
498 ix_data.extend(serialize_data_v2(data));
499 ix_data.push(u8::from(is_mutable));
500 ix_data.push(0);
502
503 Instruction {
504 program_id: ID,
505 accounts: vec![
506 AccountMeta::new(metadata, false),
507 AccountMeta::new_readonly(mint, false),
508 AccountMeta::new_readonly(mint_authority, true),
509 AccountMeta::new(payer, true),
510 AccountMeta::new_readonly(update_authority, false),
511 AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
512 ],
513 data: ix_data,
514 }
515 }
516
517 pub fn update_metadata_v2(
527 metadata: [u8; 32],
528 update_authority: [u8; 32],
529 new_data: Option<&DataV2>,
530 new_update_authority: Option<&[u8; 32]>,
531 primary_sale_happened: Option<bool>,
532 is_mutable: Option<bool>,
533 ) -> Instruction {
534 let mut ix_data = vec![15];
536
537 match new_data {
539 None => ix_data.push(0),
540 Some(d) => {
541 ix_data.push(1);
542 ix_data.extend(serialize_data_v2(d));
543 }
544 }
545
546 match new_update_authority {
548 None => ix_data.push(0),
549 Some(auth) => {
550 ix_data.push(1);
551 ix_data.extend_from_slice(auth);
552 }
553 }
554
555 match primary_sale_happened {
557 None => ix_data.push(0),
558 Some(val) => {
559 ix_data.push(1);
560 ix_data.push(u8::from(val));
561 }
562 }
563
564 match is_mutable {
566 None => ix_data.push(0),
567 Some(val) => {
568 ix_data.push(1);
569 ix_data.push(u8::from(val));
570 }
571 }
572
573 Instruction {
574 program_id: ID,
575 accounts: vec![
576 AccountMeta::new(metadata, false),
577 AccountMeta::new_readonly(update_authority, true),
578 ],
579 data: ix_data,
580 }
581 }
582}
583
584#[cfg(test)]
589#[allow(clippy::unwrap_used, clippy::expect_used)]
590mod tests {
591 use super::*;
592
593 const WALLET: [u8; 32] = [1; 32];
594 const MINT: [u8; 32] = [2; 32];
595 const PAYER: [u8; 32] = [3; 32];
596
597 #[test]
600 fn test_derive_ata_deterministic() {
601 let ata1 = derive_ata_address(&WALLET, &MINT);
602 let ata2 = derive_ata_address(&WALLET, &MINT);
603 assert_eq!(ata1, ata2);
604 }
605
606 #[test]
607 fn test_derive_ata_different_mints() {
608 let mint2 = [3; 32];
609 let ata1 = derive_ata_address(&WALLET, &MINT);
610 let ata2 = derive_ata_address(&WALLET, &mint2);
611 assert_ne!(ata1, ata2);
612 }
613
614 #[test]
615 fn test_create_ata_instruction() {
616 let ix = create_ata(PAYER, WALLET, MINT);
617 assert_eq!(ix.program_id, ATA_PROGRAM_ID);
618 assert_eq!(ix.accounts.len(), 6);
619 assert_eq!(ix.data, vec![0]); assert!(ix.accounts[0].is_signer); }
622
623 #[test]
624 fn test_create_ata_idempotent() {
625 let ix = create_ata_idempotent(PAYER, WALLET, MINT);
626 assert_eq!(ix.data, vec![1]); }
628
629 #[test]
632 fn test_memo_basic() {
633 let ix = memo("Hello, Solana!", &[WALLET]);
634 assert_eq!(ix.program_id, MEMO_PROGRAM_ID);
635 assert_eq!(ix.data, b"Hello, Solana!");
636 assert_eq!(ix.accounts.len(), 1);
637 assert!(ix.accounts[0].is_signer);
638 }
639
640 #[test]
641 fn test_memo_unsigned() {
642 let ix = memo_unsigned("test memo");
643 assert!(ix.accounts.is_empty());
644 assert_eq!(ix.data, b"test memo");
645 }
646
647 #[test]
648 fn test_memo_multiple_signers() {
649 let signer2 = [4; 32];
650 let ix = memo("multi-signer memo", &[WALLET, signer2]);
651 assert_eq!(ix.accounts.len(), 2);
652 }
653
654 #[test]
657 fn test_stake_delegate() {
658 let vote = [5; 32];
659 let ix = stake_delegate(WALLET, vote, PAYER);
660 assert_eq!(ix.program_id, STAKE_PROGRAM_ID);
661 assert_eq!(ix.accounts.len(), 6);
662 assert_eq!(ix.data[0], 2); }
664
665 #[test]
666 fn test_stake_deactivate() {
667 let ix = stake_deactivate(WALLET, PAYER);
668 assert_eq!(ix.accounts.len(), 3);
669 assert_eq!(ix.data[0], 5); }
671
672 #[test]
673 fn test_stake_withdraw() {
674 let recipient = [6; 32];
675 let ix = stake_withdraw(WALLET, PAYER, recipient, 1_000_000_000);
676 assert_eq!(ix.data[0], 4); let lamports = u64::from_le_bytes(ix.data[4..12].try_into().unwrap());
678 assert_eq!(lamports, 1_000_000_000);
679 }
680
681 #[test]
684 fn test_advance_nonce() {
685 let nonce_account = [7; 32];
686 let ix = advance_nonce(nonce_account, PAYER);
687 assert_eq!(ix.program_id, SYSTEM_PROGRAM_ID);
688 assert_eq!(ix.accounts.len(), 3);
689 assert_eq!(ix.data[0], 4); }
691
692 #[test]
693 fn test_initialize_nonce() {
694 let nonce_account = [8; 32];
695 let ix = initialize_nonce(nonce_account, PAYER);
696 assert_eq!(ix.data[0], 6); assert_eq!(&ix.data[4..36], &PAYER); }
699
700 #[test]
703 fn test_alt_create() {
704 let table = [9; 32];
705 let ix = address_lookup_table::create(PAYER, PAYER, table, 12345);
706 assert_eq!(ix.program_id, address_lookup_table::ID);
707 assert_eq!(ix.accounts.len(), 4);
708 assert_eq!(ix.data[0], 0); let slot = u64::from_le_bytes(ix.data[4..12].try_into().unwrap());
710 assert_eq!(slot, 12345);
711 }
712
713 #[test]
714 fn test_alt_extend() {
715 let table = [9; 32];
716 let addrs = [[10u8; 32], [11u8; 32]];
717 let ix = address_lookup_table::extend(table, PAYER, PAYER, &addrs);
718 assert_eq!(ix.data[0], 2); let count = u32::from_le_bytes(ix.data[4..8].try_into().unwrap());
720 assert_eq!(count, 2);
721 assert_eq!(&ix.data[8..40], &addrs[0]);
722 assert_eq!(&ix.data[40..72], &addrs[1]);
723 }
724
725 #[test]
726 fn test_alt_deactivate() {
727 let table = [9; 32];
728 let ix = address_lookup_table::deactivate(table, PAYER);
729 assert_eq!(ix.data[0], 3);
730 assert_eq!(ix.accounts.len(), 2);
731 }
732
733 #[test]
734 fn test_alt_close() {
735 let table = [9; 32];
736 let recipient = [10; 32];
737 let ix = address_lookup_table::close(table, PAYER, recipient);
738 assert_eq!(ix.data[0], 4);
739 assert_eq!(ix.accounts.len(), 3);
740 }
741
742 #[test]
745 fn test_derive_metadata_deterministic() {
746 let addr1 = token_metadata::derive_metadata_address(&MINT);
747 let addr2 = token_metadata::derive_metadata_address(&MINT);
748 assert_eq!(addr1, addr2);
749 }
750
751 #[test]
752 fn test_create_metadata_v3() {
753 let metadata = [12; 32];
754 let update_auth = [13; 32];
755 let data = token_metadata::DataV2 {
756 name: "My NFT".to_string(),
757 symbol: "MNFT".to_string(),
758 uri: "https://example.com/meta.json".to_string(),
759 seller_fee_basis_points: 500,
760 creators: Some(vec![token_metadata::Creator {
761 address: PAYER,
762 verified: true,
763 share: 100,
764 }]),
765 };
766 let ix = token_metadata::create_metadata_v3(
767 metadata,
768 MINT,
769 PAYER,
770 PAYER,
771 update_auth,
772 &data,
773 true,
774 );
775 assert_eq!(ix.program_id, token_metadata::ID);
776 assert_eq!(ix.accounts.len(), 6);
777 assert_eq!(ix.data[0], 33); }
779
780 #[test]
781 fn test_update_metadata_v2() {
782 let metadata = [12; 32];
783 let ix = token_metadata::update_metadata_v2(metadata, PAYER, None, None, Some(true), None);
784 assert_eq!(ix.data[0], 15); assert_eq!(ix.accounts.len(), 2);
786 }
787
788 #[test]
789 fn test_metadata_without_creators() {
790 let metadata = [12; 32];
791 let update_auth = [13; 32];
792 let data = token_metadata::DataV2 {
793 name: "Token".to_string(),
794 symbol: "TKN".to_string(),
795 uri: "https://example.com".to_string(),
796 seller_fee_basis_points: 0,
797 creators: None,
798 };
799 let ix = token_metadata::create_metadata_v3(
800 metadata,
801 MINT,
802 PAYER,
803 PAYER,
804 update_auth,
805 &data,
806 false,
807 );
808 assert_eq!(ix.data[0], 33);
809 let last_data = ix.data.last().unwrap();
811 assert_eq!(*last_data, 0);
813 }
814}