1use {
2 crate::{
3 config::ProgramWorkspace, create_files, override_or_create_files, Files, PackageManager,
4 VERSION,
5 },
6 anyhow::Result,
7 clap::{Parser, ValueEnum},
8 heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase},
9 solana_keypair::{read_keypair_file, write_keypair_file, Keypair},
10 solana_pubkey::Pubkey,
11 solana_signer::Signer,
12 std::{
13 fmt::Write as _,
14 fs::{self, File},
15 io::Write as _,
16 path::Path,
17 process::Stdio,
18 },
19};
20
21const ANCHOR_MSRV: &str = "1.89.0";
22
23#[derive(Clone, Debug, Default, Eq, PartialEq, Parser, ValueEnum)]
25pub enum ProgramTemplate {
26 Single,
28 #[default]
30 Multiple,
31}
32
33pub fn create_program(
35 name: &str,
36 template: ProgramTemplate,
37 test_template: Option<&TestTemplate>,
38) -> Result<()> {
39 let program_path = Path::new("programs").join(name);
40 let common_files = vec![
41 ("Cargo.toml".into(), workspace_manifest().into()),
42 ("rust-toolchain.toml".into(), rust_toolchain_toml()),
43 (
44 program_path.join("Cargo.toml"),
45 cargo_toml(name, test_template),
46 ),
47 ];
49
50 let template_files = match template {
51 ProgramTemplate::Single => {
52 println!(
53 "Note: Using single-file template. For better code organization and \
54 maintainability, consider using --template multiple (default)."
55 );
56 create_program_template_single(name, &program_path)
57 }
58 ProgramTemplate::Multiple => create_program_template_multiple(name, &program_path),
59 };
60
61 create_files(&[common_files, template_files].concat())
62}
63
64fn rust_toolchain_toml() -> String {
66 format!(
67 r#"[toolchain]
68channel = "{ANCHOR_MSRV}"
69components = ["rustfmt","clippy"]
70profile = "minimal"
71"#
72 )
73}
74
75fn create_program_template_single(name: &str, program_path: &Path) -> Files {
77 vec![(
78 program_path.join("src").join("lib.rs"),
79 format!(
80 r#"use anchor_lang::prelude::*;
81
82declare_id!("{}");
83
84#[program]
85pub mod {} {{
86 use super::*;
87
88 pub fn initialize(ctx: Context<Initialize>) -> Result<()> {{
89 msg!("Greetings from: {{:?}}", ctx.program_id);
90 Ok(())
91 }}
92}}
93
94#[derive(Accounts)]
95pub struct Initialize {{}}
96"#,
97 get_or_create_program_id(name),
98 name.to_snake_case(),
99 ),
100 )]
101}
102
103fn create_program_template_multiple(name: &str, program_path: &Path) -> Files {
105 let src_path = program_path.join("src");
106 vec![
107 (
108 src_path.join("lib.rs"),
109 format!(
110 r#"pub mod constants;
111pub mod error;
112pub mod instructions;
113pub mod state;
114
115use anchor_lang::prelude::*;
116
117pub use constants::*;
118pub use instructions::*;
119pub use state::*;
120
121declare_id!("{}");
122
123#[program]
124pub mod {} {{
125 use super::*;
126
127 pub fn initialize(ctx: Context<Initialize>) -> Result<()> {{
128 initialize::handler(ctx)
129 }}
130}}
131"#,
132 get_or_create_program_id(name),
133 name.to_snake_case(),
134 ),
135 ),
136 (
137 src_path.join("constants.rs"),
138 r#"use anchor_lang::prelude::*;
139
140#[constant]
141pub const SEED: &str = "anchor";
142"#
143 .into(),
144 ),
145 (
146 src_path.join("error.rs"),
147 r#"use anchor_lang::prelude::*;
148
149#[error_code]
150pub enum ErrorCode {
151 #[msg("Custom error message")]
152 CustomError,
153}
154"#
155 .into(),
156 ),
157 (
158 src_path.join("instructions.rs"),
159 r#"pub mod initialize;
160
161pub use initialize::*;
162"#
163 .into(),
164 ),
165 (
166 src_path.join("instructions").join("initialize.rs"),
167 r#"use anchor_lang::prelude::*;
168
169#[derive(Accounts)]
170pub struct Initialize {}
171
172pub fn handler(ctx: Context<Initialize>) -> Result<()> {
173 msg!("Greetings from: {:?}", ctx.program_id);
174 Ok(())
175}
176"#
177 .into(),
178 ),
179 (src_path.join("state.rs"), r#""#.into()),
180 ]
181}
182
183const fn workspace_manifest() -> &'static str {
184 r#"[workspace]
185members = [
186 "programs/*"
187]
188resolver = "2"
189
190[profile.release]
191overflow-checks = true
192lto = "fat"
193codegen-units = 1
194[profile.release.build-override]
195opt-level = 3
196incremental = false
197codegen-units = 1
198"#
199}
200
201fn cargo_toml(name: &str, test_template: Option<&TestTemplate>) -> String {
202 let test_sbf_feature = match test_template {
203 Some(TestTemplate::Mollusk) => r#"test-sbf = []"#,
204 _ => "",
205 };
206 let dev_dependencies = match test_template {
207 Some(TestTemplate::Mollusk) => {
208 r#"
209[dev-dependencies]
210mollusk-svm = "~0.10"
211"#
212 }
213 Some(TestTemplate::Litesvm) => {
214 r#"
215[dev-dependencies]
216litesvm = "0.10.0"
217solana-message = "3.0.1"
218solana-transaction = "3.0.2"
219solana-signer = "3.0.0"
220solana-keypair = "3.0.1"
221"#
222 }
223 _ => "",
224 };
225
226 format!(
227 r#"[package]
228name = "{0}"
229version = "0.1.0"
230description = "Created with Anchor"
231edition = "2021"
232
233[lib]
234crate-type = ["cdylib", "lib"]
235name = "{1}"
236
237[features]
238default = []
239cpi = ["no-entrypoint"]
240no-entrypoint = []
241no-idl = []
242no-log-ix-name = []
243idl-build = ["anchor-lang/idl-build"]
244anchor-debug = []
245custom-heap = []
246custom-panic = []
247{2}
248
249[dependencies]
250anchor-lang = "{3}"
251{4}
252
253[lints.rust]
254unexpected_cfgs = {{ level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] }}
255"#,
256 name,
257 name.to_snake_case(),
258 test_sbf_feature,
259 VERSION,
260 dev_dependencies,
261 )
262}
263
264pub fn get_or_create_program_id(name: &str) -> Pubkey {
266 let keypair_path = Path::new("target")
267 .join("deploy")
268 .join(format!("{}-keypair.json", name.to_snake_case()));
269
270 read_keypair_file(&keypair_path)
271 .unwrap_or_else(|_| {
272 let keypair = Keypair::new();
273 write_keypair_file(&keypair, keypair_path).expect("Unable to create program keypair");
274 keypair
275 })
276 .pubkey()
277}
278
279pub fn deploy_js_script_host(cluster_url: &str, script_path: &str) -> String {
280 format!(
281 r#"
282const anchor = require('@anchor-lang/core');
283
284// Deploy script defined by the user.
285const userScript = require("{script_path}");
286
287async function main() {{
288 const connection = new anchor.web3.Connection(
289 "{cluster_url}",
290 anchor.AnchorProvider.defaultOptions().commitment
291 );
292 const wallet = anchor.Wallet.local();
293 const provider = new anchor.AnchorProvider(connection, wallet);
294
295 // Run the user's deploy script.
296 userScript(provider);
297}}
298main();
299"#,
300 )
301}
302
303pub fn deploy_ts_script_host(cluster_url: &str, script_path: &str) -> String {
304 format!(
305 r#"import * as anchor from '@anchor-lang/core';
306
307// Deploy script defined by the user.
308const userScript = require("{script_path}");
309
310async function main() {{
311 const connection = new anchor.web3.Connection(
312 "{cluster_url}",
313 anchor.AnchorProvider.defaultOptions().commitment
314 );
315 const wallet = anchor.Wallet.local();
316 const provider = new anchor.AnchorProvider(connection, wallet);
317
318 // Run the user's deploy script.
319 userScript(provider);
320}}
321main();
322"#,
323 )
324}
325
326pub fn deploy_script() -> &'static str {
327 r#"// Migrations are an early feature. Currently, they're nothing more than this
328// single deploy script that's invoked from the CLI, injecting a provider
329// configured from the workspace's Anchor.toml.
330
331const anchor = require("@anchor-lang/core");
332
333module.exports = async function (provider) {
334 // Configure client to use the provider.
335 anchor.setProvider(provider);
336
337 // Add your deploy script here.
338};
339"#
340}
341
342pub fn ts_deploy_script() -> &'static str {
343 r#"// Migrations are an early feature. Currently, they're nothing more than this
344// single deploy script that's invoked from the CLI, injecting a provider
345// configured from the workspace's Anchor.toml.
346
347import * as anchor from "@anchor-lang/core";
348
349module.exports = async function (provider: anchor.AnchorProvider) {
350 // Configure client to use the provider.
351 anchor.setProvider(provider);
352
353 // Add your deploy script here.
354};
355"#
356}
357
358pub fn mocha(name: &str) -> String {
359 format!(
360 r#"const anchor = require("@anchor-lang/core");
361
362describe("{}", () => {{
363 // Configure the client to use the local cluster.
364 anchor.setProvider(anchor.AnchorProvider.env());
365
366 it("Is initialized!", async () => {{
367 // Add your test here.
368 const program = anchor.workspace.{};
369 const tx = await program.methods.initialize().rpc();
370 console.log("Your transaction signature", tx);
371 }});
372}});
373"#,
374 name,
375 name.to_lower_camel_case(),
376 )
377}
378
379pub fn jest(name: &str) -> String {
380 format!(
381 r#"const anchor = require("@anchor-lang/core");
382
383describe("{}", () => {{
384 // Configure the client to use the local cluster.
385 anchor.setProvider(anchor.AnchorProvider.env());
386
387 it("Is initialized!", async () => {{
388 // Add your test here.
389 const program = anchor.workspace.{};
390 const tx = await program.methods.initialize().rpc();
391 console.log("Your transaction signature", tx);
392 }});
393}});
394"#,
395 name,
396 name.to_lower_camel_case(),
397 )
398}
399
400pub fn package_json(jest: bool, license: String) -> String {
401 if jest {
402 format!(
403 r#"{{
404 "license": "{license}",
405 "scripts": {{
406 "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
407 "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
408 }},
409 "dependencies": {{
410 "@anchor-lang/core": "^{VERSION}"
411 }},
412 "devDependencies": {{
413 "jest": "^29.0.3",
414 "prettier": "^2.6.2"
415 }}
416}}
417 "#
418 )
419 } else {
420 format!(
421 r#"{{
422 "license": "{license}",
423 "scripts": {{
424 "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
425 "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
426 }},
427 "dependencies": {{
428 "@anchor-lang/core": "^{VERSION}"
429 }},
430 "devDependencies": {{
431 "chai": "^4.3.4",
432 "mocha": "^9.0.3",
433 "prettier": "^2.6.2"
434 }}
435}}
436"#
437 )
438 }
439}
440
441pub fn ts_package_json(jest: bool, license: String) -> String {
442 if jest {
443 format!(
444 r#"{{
445 "license": "{license}",
446 "scripts": {{
447 "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
448 "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
449 }},
450 "dependencies": {{
451 "@anchor-lang/core": "^{VERSION}"
452 }},
453 "devDependencies": {{
454 "@types/bn.js": "^5.1.0",
455 "@types/jest": "^29.0.3",
456 "jest": "^29.0.3",
457 "prettier": "^2.6.2",
458 "ts-jest": "^29.0.2",
459 "typescript": "^5.7.3"
460 }}
461}}
462"#
463 )
464 } else {
465 format!(
466 r#"{{
467 "license": "{license}",
468 "scripts": {{
469 "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
470 "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
471 }},
472 "dependencies": {{
473 "@anchor-lang/core": "^{VERSION}"
474 }},
475 "devDependencies": {{
476 "chai": "^4.3.4",
477 "mocha": "^9.0.3",
478 "ts-mocha": "^10.0.0",
479 "@types/bn.js": "^5.1.0",
480 "@types/chai": "^4.3.0",
481 "@types/mocha": "^9.0.0",
482 "typescript": "^5.7.3",
483 "prettier": "^2.6.2"
484 }}
485}}
486"#
487 )
488 }
489}
490
491pub fn ts_mocha(name: &str) -> String {
492 format!(
493 r#"import * as anchor from "@anchor-lang/core";
494import {{ Program }} from "@anchor-lang/core";
495import {{ {} }} from "../target/types/{}";
496
497describe("{}", () => {{
498 // Configure the client to use the local cluster.
499 anchor.setProvider(anchor.AnchorProvider.env());
500
501 const program = anchor.workspace.{} as Program<{}>;
502
503 it("Is initialized!", async () => {{
504 // Add your test here.
505 const tx = await program.methods.initialize().rpc();
506 console.log("Your transaction signature", tx);
507 }});
508}});
509"#,
510 name.to_pascal_case(),
511 name.to_snake_case(),
512 name,
513 name.to_lower_camel_case(),
514 name.to_pascal_case(),
515 )
516}
517
518pub fn ts_jest(name: &str) -> String {
519 format!(
520 r#"import * as anchor from "@anchor-lang/core";
521import {{ Program }} from "@anchor-lang/core";
522import {{ {} }} from "../target/types/{}";
523
524describe("{}", () => {{
525 // Configure the client to use the local cluster.
526 anchor.setProvider(anchor.AnchorProvider.env());
527
528 const program = anchor.workspace.{} as Program<{}>;
529
530 it("Is initialized!", async () => {{
531 // Add your test here.
532 const tx = await program.methods.initialize().rpc();
533 console.log("Your transaction signature", tx);
534 }});
535}});
536"#,
537 name.to_pascal_case(),
538 name.to_snake_case(),
539 name,
540 name.to_lower_camel_case(),
541 name.to_pascal_case(),
542 )
543}
544
545pub fn ts_config(jest: bool) -> &'static str {
546 if jest {
547 r#"{
548 "compilerOptions": {
549 "types": ["jest"],
550 "typeRoots": ["./node_modules/@types"],
551 "lib": ["es2015"],
552 "module": "commonjs",
553 "target": "es6",
554 "esModuleInterop": true
555 }
556}
557"#
558 } else {
559 r#"{
560 "compilerOptions": {
561 "types": ["mocha", "chai"],
562 "typeRoots": ["./node_modules/@types"],
563 "lib": ["es2015"],
564 "module": "commonjs",
565 "target": "es6",
566 "esModuleInterop": true
567 }
568}
569"#
570 }
571}
572
573pub fn git_ignore() -> &'static str {
574 r#".anchor
575.DS_Store
576target
577**/*.rs.bk
578node_modules
579test-ledger
580.yarn
581.surfpool
582"#
583}
584
585pub fn prettier_ignore() -> &'static str {
586 r#".anchor
587.DS_Store
588target
589node_modules
590dist
591build
592test-ledger
593"#
594}
595
596pub fn node_shell(
597 cluster_url: &str,
598 wallet_path: &str,
599 programs: Vec<ProgramWorkspace>,
600) -> Result<String> {
601 let mut eval_string = format!(
602 r#"
603const anchor = require('@anchor-lang/core');
604const web3 = anchor.web3;
605const PublicKey = anchor.web3.PublicKey;
606const Keypair = anchor.web3.Keypair;
607
608const __wallet = new anchor.Wallet(
609 Keypair.fromSecretKey(
610 Buffer.from(
611 JSON.parse(
612 require('fs').readFileSync(
613 "{wallet_path}",
614 {{
615 encoding: "utf-8",
616 }},
617 ),
618 ),
619 ),
620 ),
621);
622const __connection = new web3.Connection("{cluster_url}", "processed");
623const provider = new anchor.AnchorProvider(__connection, __wallet, {{
624 commitment: "processed",
625 preflightcommitment: "processed",
626}});
627anchor.setProvider(provider);
628"#,
629 );
630
631 for program in programs {
632 write!(
633 &mut eval_string,
634 r#"
635anchor.workspace.{} = new anchor.Program({}, provider);
636"#,
637 program.name.to_lower_camel_case(),
638 serde_json::to_string(&program.idl)?,
639 )?;
640 }
641
642 Ok(eval_string)
643}
644
645#[derive(Clone, Debug, Default, Eq, PartialEq, Parser, ValueEnum)]
647pub enum TestTemplate {
648 Mocha,
650 Jest,
652 Rust,
654 Mollusk,
656 #[default]
658 Litesvm,
659}
660
661impl TestTemplate {
662 pub fn get_test_script(&self, js: bool, pkg_manager: &PackageManager) -> String {
663 let pkg_manager_exec_cmd = match pkg_manager {
664 PackageManager::Yarn => "yarn run",
665 PackageManager::NPM => "npx",
666 PackageManager::PNPM => "pnpm exec",
667 PackageManager::Bun => "bunx",
668 };
669
670 match &self {
671 Self::Mocha => {
672 if js {
673 format!("{pkg_manager_exec_cmd} mocha -t 1000000 tests/")
674 } else {
675 format!(
676 r#"{pkg_manager_exec_cmd} ts-mocha -p ./tsconfig.json -t 1000000 "tests/**/*.ts""#
677 )
678 }
679 }
680 Self::Jest => {
681 if js {
682 format!("{pkg_manager_exec_cmd} jest")
683 } else {
684 format!("{pkg_manager_exec_cmd} jest --preset ts-jest")
685 }
686 }
687 Self::Rust | Self::Litesvm => "cargo test".to_owned(),
688 Self::Mollusk => "cargo test-sbf".to_owned(),
689 }
690 }
691
692 pub fn create_test_files(&self, project_name: &str, js: bool, program_id: &str) -> Result<()> {
693 match self {
694 Self::Mocha => {
695 fs::create_dir_all("tests")?;
697
698 if js {
699 let mut test = File::create(format!("tests/{}.js", &project_name))?;
700 test.write_all(mocha(project_name).as_bytes())?;
701 } else {
702 let mut mocha = File::create(format!("tests/{}.ts", &project_name))?;
703 mocha.write_all(ts_mocha(project_name).as_bytes())?;
704 }
705 }
706 Self::Jest => {
707 fs::create_dir_all("tests")?;
709
710 let mut test = File::create(format!("tests/{}.test.js", &project_name))?;
711 test.write_all(jest(project_name).as_bytes())?;
712 }
713 Self::Rust => {
714 let exit = std::process::Command::new("cargo")
716 .arg("new")
717 .arg("--vcs")
718 .arg("none")
719 .arg("--lib")
720 .arg("tests")
721 .stderr(Stdio::inherit())
722 .output()
723 .map_err(|e| anyhow::format_err!("{}", e))?;
724 if !exit.status.success() {
725 eprintln!("'cargo new --lib tests' failed");
726 std::process::exit(exit.status.code().unwrap_or(1));
727 }
728
729 let mut files = Vec::new();
730 let tests_path = Path::new("tests");
731 files.extend(vec![(
732 tests_path.join("Cargo.toml"),
733 tests_cargo_toml(project_name),
734 )]);
735 files.extend(create_program_template_rust_test(
736 project_name,
737 tests_path,
738 program_id,
739 ));
740 override_or_create_files(&files)?;
741 }
742 Self::Mollusk => {
743 let tests_path_str = format!("programs/{}/tests", &project_name);
745 let tests_path = Path::new(&tests_path_str);
746 fs::create_dir_all(tests_path)?;
747
748 let mut files = Vec::new();
749 files.extend(create_program_template_mollusk_test(
750 project_name,
751 tests_path,
752 ));
753 override_or_create_files(&files)?;
754 }
755
756 Self::Litesvm => {
757 let tests_path_str = format!("programs/{}/tests", &project_name);
758 let tests_path = Path::new(&tests_path_str);
759 fs::create_dir_all(tests_path)?;
760 let mut files = Vec::new();
761 files.extend(create_program_template_litesvm_test(
762 project_name,
763 tests_path,
764 ));
765 override_or_create_files(&files)?;
766 }
767 }
768
769 Ok(())
770 }
771}
772
773pub fn tests_cargo_toml(name: &str) -> String {
774 format!(
775 r#"[package]
776name = "tests"
777version = "0.1.0"
778description = "Created with Anchor"
779edition = "2021"
780rust-version = "{ANCHOR_MSRV}"
781
782[dependencies]
783anchor-client = "{VERSION}"
784{name} = {{ version = "0.1.0", path = "../programs/{name}" }}
785solana-keypair = "3.0.0"
786solana-pubkey = "3.0.0"
787"#
788 )
789}
790
791fn create_program_template_rust_test(name: &str, tests_path: &Path, program_id: &str) -> Files {
793 let src_path = tests_path.join("src");
794 vec![
795 (
796 src_path.join("lib.rs"),
797 r#"#[cfg(test)]
798mod test_initialize;
799"#
800 .into(),
801 ),
802 (
803 src_path.join("test_initialize.rs"),
804 format!(
805 r#"use anchor_client::{{
806 CommitmentConfig,
807 Client, Cluster,
808}};
809use solana_keypair::{{read_keypair_file}};
810use solana_pubkey::Pubkey;
811
812#[test]
813fn test_initialize() {{
814 let program_id = "{0}";
815 let anchor_wallet = std::env::var("ANCHOR_WALLET").unwrap();
816 let payer = read_keypair_file(&anchor_wallet).unwrap();
817
818 let client = Client::new_with_options(Cluster::Localnet, &payer, CommitmentConfig::confirmed());
819 let program_id = Pubkey::try_from(program_id).unwrap();
820 let program = client.program(program_id).unwrap();
821
822 let tx = program
823 .request()
824 .accounts({1}::accounts::Initialize {{}})
825 .args({1}::instruction::Initialize {{}})
826 .send()
827 .expect("");
828
829 println!("Your transaction signature {{}}", tx);
830}}
831"#,
832 program_id,
833 name.to_snake_case(),
834 ),
835 ),
836 ]
837}
838
839fn create_program_template_mollusk_test(name: &str, tests_path: &Path) -> Files {
841 vec![(
842 tests_path.join("test_initialize.rs"),
843 format!(
844 r#"#![cfg(feature = "test-sbf")]
845
846use {{
847 anchor_lang::{{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}},
848 mollusk_svm::{{result::Check, Mollusk}},
849}};
850
851#[test]
852fn test_initialize() {{
853 let program_id = {0}::id();
854
855 let mollusk = Mollusk::new(&program_id, "{0}");
856
857 let instruction = Instruction::new_with_bytes(
858 program_id,
859 &{0}::instruction::Initialize {{}}.data(),
860 {0}::accounts::Initialize {{}}.to_account_metas(None),
861 );
862
863 mollusk.process_and_validate_instruction(&instruction, &[], &[Check::success()]);
864}}
865"#,
866 name.to_snake_case(),
867 ),
868 )]
869}
870
871fn create_program_template_litesvm_test(name: &str, tests_path: &Path) -> Files {
873 vec![(
874 tests_path.join("test_initialize.rs"),
875 format!(
876 r#"
877use {{
878 anchor_lang::{{solana_program::instruction::Instruction, InstructionData, ToAccountMetas}},
879 litesvm::LiteSVM,
880 solana_message::{{Message, VersionedMessage}},
881 solana_signer::Signer,
882 solana_keypair::Keypair,
883 solana_transaction::versioned::VersionedTransaction,
884}};
885
886#[test]
887fn test_initialize() {{
888 let program_id = {0}::id();
889 let payer = Keypair::new();
890 let mut svm = LiteSVM::new();
891 let bytes = include_bytes!("../../../target/deploy/{0}.so");
892 svm.add_program(program_id, bytes).unwrap();
893 svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
894
895 let instruction = Instruction::new_with_bytes(
896 program_id,
897 &{0}::instruction::Initialize {{}}.data(),
898 {0}::accounts::Initialize {{}}.to_account_metas(None),
899 );
900
901 let blockhash = svm.latest_blockhash();
902 let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);
903 let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[payer]).unwrap();
904
905 let res = svm.send_transaction(tx);
906 assert!(res.is_ok());
907}}
908"#,
909 name.to_snake_case(),
910 ),
911 )]
912}