df_sol/
rust_template.rs

1use crate::{create_files, Files};
2use anyhow::Result;
3use clap::{Parser, ValueEnum};
4use heck::{ToPascalCase, ToSnakeCase};
5use solana_sdk::{
6    pubkey::Pubkey,
7    signature::{read_keypair_file, write_keypair_file, Keypair},
8    signer::Signer,
9};
10use std::fs::File;
11use std::io::Write;
12use std::{fs, path::Path};
13
14const ANCHOR_VERSION: &str = "0.30.0";
15
16/// Program initialization template
17#[derive(Clone, Debug, Default, Eq, PartialEq, Parser, ValueEnum, Copy)]
18pub enum ProgramTemplate {
19    /// Program with a basic template
20    #[default]
21    Basic,
22    /// Program with a counter template
23    Counter,
24    /// Program with a mint token template
25    MintToken,
26}
27
28/// Create a program from the given name and template.
29pub fn create_program(name: &str, template: ProgramTemplate) -> Result<()> {
30    let program_path = Path::new("programs").join(name);
31    let common_files = vec![
32        ("Cargo.toml".into(), workspace_manifest().into()),
33        (program_path.join("Cargo.toml"), cargo_toml(name, template)),
34        (program_path.join("Xargo.toml"), xargo_toml().into()),
35    ];
36
37    let template_files = match template {
38        ProgramTemplate::Basic => create_program_template_basic(name, &program_path),
39        ProgramTemplate::Counter => create_program_template_counter(name, &program_path),
40        ProgramTemplate::MintToken => create_program_template_mint_token(name, &program_path),
41    };
42
43    create_files(&[common_files, template_files].concat())
44}
45
46/// Create a program with a basic template
47fn create_program_template_basic(name: &str, program_path: &Path) -> Files {
48    vec![(
49        program_path.join("src").join("lib.rs"),
50        format!(
51            r#"use anchor_lang::prelude::*;
52
53declare_id!("{}");
54
55#[program]
56pub mod {} {{
57    use super::*;
58
59    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {{
60        Ok(())
61    }}
62}}
63
64#[derive(Accounts)]
65pub struct Initialize {{}}
66"#,
67            get_or_create_program_id(name),
68            name.to_snake_case(),
69        ),
70    )]
71}
72
73/// Create a program with counter template
74fn create_program_template_counter(name: &str, program_path: &Path) -> Files {
75    vec![(
76        program_path.join("src").join("lib.rs"),
77        format!(
78            r#"use anchor_lang::prelude::*;
79
80declare_id!("{}");
81
82#[program]
83pub mod {} {{
84    use super::*;
85
86    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {{
87        let counter_account = &mut ctx.accounts.counter;
88        counter_account.count = 0;
89        Ok(())
90    }}
91
92    pub fn increment(ctx: Context<Increment>) -> Result<()> {{
93        let counter_account = &mut ctx.accounts.counter;
94        counter_account.count += 1;
95        Ok(())
96    }}
97}}
98
99#[derive(Accounts)]
100pub struct Initialize<'info> {{
101    #[account(
102        init,
103        seeds = [b"counter"],
104        bump,
105        payer=user,
106        space = Counter::space()
107    )]
108    pub counter: Account<'info, Counter>,
109
110    #[account(mut)]
111    pub user: Signer<'info>,
112
113    pub system_program: Program<'info, System>
114}}
115
116#[derive(Accounts)]
117pub struct Increment<'info> {{
118    #[account(mut)]
119    pub counter: Account<'info, Counter>,
120
121    #[account(mut)]  // Remove leading space
122    pub user: Signer<'info>,
123
124    pub system_program: Program<'info, System>
125}}
126
127#[account]
128pub struct Counter {{
129    count: u64
130}}
131
132impl Counter {{
133    pub fn space() -> usize {{
134        8 +  // discriminator
135        8 // counter
136    }}
137}}
138"#,
139            get_or_create_program_id(name),
140            name.to_snake_case(),
141        ),
142    )]
143}
144
145/// Create a program with mint token template
146fn create_program_template_mint_token(name: &str, program_path: &Path) -> Files {
147    vec![(
148        program_path.join("src").join("lib.rs"),
149        format!(
150            r#"use anchor_lang::prelude::*;
151use anchor_spl::{{
152    associated_token::AssociatedToken,
153    metadata::{{
154        create_metadata_accounts_v3, mpl_token_metadata::types::DataV2, CreateMetadataAccountsV3,
155    }},
156    token::{{mint_to, Mint, MintTo, Token, TokenAccount}},
157}};
158
159declare_id!("{}");
160
161#[program]
162pub mod {} {{
163    use super::*;
164    pub fn init_token(ctx: Context<InitToken>, metadata: InitTokenParams) -> Result<()> {{
165        // Define seeds and signer for creating a token account
166        let seeds = &["mint".as_bytes(), &[ctx.bumps.mint]];
167        let signer = [&seeds[..]];
168
169        // Define the token data with provided metadata
170        let token_data: DataV2 = DataV2 {{
171            name: metadata.name,
172            symbol: metadata.symbol,
173            uri: metadata.uri,
174            seller_fee_basis_points: 0,
175            creators: None,
176            collection: None,
177            uses: None,
178        }};
179
180        // Create context for the Metadata Accounts creation with the signer
181        let metadata_ctx = CpiContext::new_with_signer(
182            ctx.accounts.token_metadata_program.to_account_info(),
183            CreateMetadataAccountsV3 {{
184                payer: ctx.accounts.payer.to_account_info(),
185                update_authority: ctx.accounts.mint.to_account_info(),
186                mint: ctx.accounts.mint.to_account_info(),
187                metadata: ctx.accounts.metadata.to_account_info(),
188                mint_authority: ctx.accounts.mint.to_account_info(),
189                system_program: ctx.accounts.system_program.to_account_info(),
190                rent: ctx.accounts.rent.to_account_info(),
191            }},
192            &signer,
193        );
194
195        // Call to create metadata accounts with the given token data
196        create_metadata_accounts_v3(metadata_ctx, token_data, false, true, None)?;
197
198        msg!("Token mint created successfully.");
199
200        Ok(())
201    }}
202
203    pub fn mint_tokens(ctx: Context<MintTokens>, quantity: u64) -> Result<()> {{
204        // Define seeds and signer for minting tokens
205        let seeds = &["mint".as_bytes(), &[ctx.bumps.mint]];
206        let signer = [&seeds[..]];
207
208        // Mint tokens to the destination account with the given quantity
209        mint_to(
210            CpiContext::new_with_signer(
211                ctx.accounts.token_program.to_account_info(),
212                MintTo {{
213                    authority: ctx.accounts.mint.to_account_info(),
214                    to: ctx.accounts.destination.to_account_info(),
215                    mint: ctx.accounts.mint.to_account_info(),
216                }},
217                &signer,
218            ),
219            quantity,
220        )?;
221
222        Ok(())
223    }}
224}}
225
226// Struct defining the context for initializing a token
227#[derive(Accounts)]
228#[instruction(
229    params: InitTokenParams
230)]
231pub struct InitToken<'info> {{
232    /// CHECK: New Metaplex Account being created
233    #[account(mut)]
234    pub metadata: UncheckedAccount<'info>,
235    #[account(
236        init,
237        seeds = [b"mint"],
238        bump,
239        payer = payer,
240        mint::decimals = params.decimals,
241        mint::authority = mint,
242    )]
243    pub mint: Account<'info, Mint>,
244    #[account(mut)]
245    pub payer: Signer<'info>,
246    pub rent: Sysvar<'info, Rent>,
247    pub system_program: Program<'info, System>,
248    pub token_program: Program<'info, Token>,
249    /// CHECK: Metaplex program ID
250    pub token_metadata_program: UncheckedAccount<'info>,
251}}
252
253// Struct defining the parameters for initializing a token
254#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)]
255pub struct InitTokenParams {{
256    pub name: String,
257    pub symbol: String,
258    pub uri: String,
259    pub decimals: u8,
260}}
261
262// Struct defining the context for minting tokens
263#[derive(Accounts)]
264pub struct MintTokens<'info> {{
265    #[account(
266        mut,
267        seeds = [b"mint"],
268        bump,
269        mint::authority = mint,
270    )]
271    pub mint: Account<'info, Mint>,
272    #[account(
273        init_if_needed, //Initializes the destination account if it does not exist
274        payer = payer,
275        associated_token::mint = mint,
276        associated_token::authority = payer,
277    )]
278    pub destination: Account<'info, TokenAccount>,
279    #[account(mut)]
280    pub payer: Signer<'info>,
281    pub rent: Sysvar<'info, Rent>,
282    pub system_program: Program<'info, System>,
283    pub token_program: Program<'info, Token>,
284    pub associated_token_program: Program<'info, AssociatedToken>,
285}}
286"#,
287            get_or_create_program_id(name),
288            name.to_snake_case(),
289        ),
290    )]
291}
292
293const fn workspace_manifest() -> &'static str {
294    r#"[workspace]
295members = [
296    "programs/*"
297]
298resolver = "2"
299
300[profile.release]
301overflow-checks = true
302lto = "fat"
303codegen-units = 1
304[profile.release.build-override]
305opt-level = 3
306incremental = false
307codegen-units = 1
308"#
309}
310
311fn cargo_toml(name: &str, template: ProgramTemplate) -> String {
312    let template_files = match template {
313        ProgramTemplate::Basic => cargo_toml_basic(name),
314        ProgramTemplate::Counter => cargo_toml_counter(name),
315        ProgramTemplate::MintToken => cargo_toml_mint_token(name),
316    };
317
318    template_files
319}
320
321fn cargo_toml_basic(name: &str) -> String {
322    format!(
323        r#"[package]
324name = "{0}"
325version = "0.1.0"
326description = "Created with Anchor"
327edition = "2021"
328
329[lib]
330crate-type = ["cdylib", "lib"]
331name = "{1}"
332
333[features]
334default = []
335cpi = ["no-entrypoint"]
336no-entrypoint = []
337no-idl = []
338no-log-ix-name = []
339idl-build = ["anchor-lang/idl-build"]
340
341[dependencies]
342anchor-lang = "{2}"
343"#,
344        name,
345        name.to_snake_case(),
346        ANCHOR_VERSION,
347    )
348}
349
350fn cargo_toml_counter(name: &str) -> String {
351    format!(
352        r#"[package]
353name = "{0}"
354version = "0.1.0"
355description = "Created with Anchor"
356edition = "2021"
357
358[lib]
359crate-type = ["cdylib", "lib"]
360name = "{1}"
361
362[features]
363default = []
364cpi = ["no-entrypoint"]
365no-entrypoint = []
366no-idl = []
367no-log-ix-name = []
368idl-build = ["anchor-lang/idl-build"]
369
370[dependencies]
371anchor-lang = "{2}"
372"#,
373        name,
374        name.to_snake_case(),
375        ANCHOR_VERSION,
376    )
377}
378
379fn cargo_toml_mint_token(name: &str) -> String {
380    format!(
381        r#"[package]
382name = "{0}"
383version = "0.1.0"
384description = "Created with Anchor"
385edition = "2021"
386
387[lib]
388crate-type = ["cdylib", "lib"]
389name = "{1}"
390
391[features]
392default = []
393cpi = ["no-entrypoint"]
394no-entrypoint = []
395no-idl = []
396no-log-ix-name = []
397idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
398
399[dependencies]
400anchor-lang = {{ version = "{2}", features = ["init-if-needed"] }}
401anchor-spl = {{ version = "{3}", features = ["metadata"] }}
402"#,
403        name,
404        name.to_snake_case(),
405        ANCHOR_VERSION,
406        ANCHOR_VERSION
407    )
408}
409
410fn xargo_toml() -> &'static str {
411    r#"[target.bpfel-unknown-unknown.dependencies.std]
412features = []
413"#
414}
415
416/// Read the program keypair file or create a new one if it doesn't exist.
417pub fn get_or_create_program_id(name: &str) -> Pubkey {
418    let keypair_path = Path::new("target")
419        .join("deploy")
420        .join(format!("{}-keypair.json", name.to_snake_case()));
421
422    read_keypair_file(&keypair_path)
423        .unwrap_or_else(|_| {
424            let keypair = Keypair::new();
425            write_keypair_file(&keypair, keypair_path).expect("Unable to create program keypair");
426            keypair
427        })
428        .pubkey()
429}
430
431pub fn create_anchor_toml(
432    program_id: String,
433    test_script: String,
434    template: ProgramTemplate,
435) -> String {
436    let template_files = match template {
437        ProgramTemplate::Basic => create_anchor_toml_basic(program_id, test_script),
438        ProgramTemplate::Counter => create_anchor_toml_counter(program_id, test_script),
439        ProgramTemplate::MintToken => create_anchor_toml_mint_token(program_id, test_script),
440    };
441
442    template_files
443}
444
445pub fn create_anchor_toml_basic(program_id: String, test_script: String) -> String {
446    format!(
447        r#"[toolchain]
448
449[features]
450seeds = false
451skip-lint = false
452
453[programs.localnet]
454counter = "{program_id}"
455
456[registry]
457url = "https://api.apr.dev"
458
459[provider]
460cluster = "Localnet"
461wallet = "wallet.json"
462
463[scripts]
464test = "{test_script}"
465"#,
466    )
467}
468
469pub fn create_anchor_toml_counter(program_id: String, test_script: String) -> String {
470    format!(
471        r#"[toolchain]
472
473[features]
474seeds = false
475skip-lint = false
476
477[programs.localnet]
478counter = "{program_id}"
479
480[registry]
481url = "https://api.apr.dev"
482
483[provider]
484cluster = "Localnet"
485wallet = "wallet.json"
486
487[scripts]
488test = "{test_script}"
489"#,
490    )
491}
492
493pub fn create_anchor_toml_mint_token(program_id: String, test_script: String) -> String {
494    format!(
495        r#"[toolchain]
496
497[features]
498seeds = false
499skip-lint = false
500
501[programs.localnet]
502counter = "{program_id}"
503[programs.devnet]
504counter = "{program_id}"
505
506[registry]
507url = "https://api.apr.dev"
508
509[provider]
510cluster = "devnet"
511wallet = "wallet.json"
512
513[scripts]
514test = "{test_script}"
515"#,
516    )
517}
518
519pub fn ts_deploy_script() -> &'static str {
520    r#"// Migrations are an early feature. Currently, they're nothing more than this
521// single deploy script that's invoked from the CLI, injecting a provider
522// configured from the workspace's Anchor.toml.
523
524const anchor = require("@coral-xyz/anchor");
525
526module.exports = async function (provider) {
527  // Configure client to use the provider.
528  anchor.setProvider(provider);
529
530  // Add your deploy script here.
531};
532"#
533}
534
535pub fn ts_package_json(license: String, template: ProgramTemplate) -> String {
536    let template_files = match template {
537        ProgramTemplate::Basic => ts_package_json_basic(license),
538        ProgramTemplate::Counter => ts_package_json_counter(license),
539        ProgramTemplate::MintToken => ts_package_json_mint_token(license),
540    };
541
542    template_files
543}
544
545pub fn ts_package_json_basic(license: String) -> String {
546    format!(
547        r#"{{
548  "license": "{license}",
549  "scripts": {{
550    "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
551    "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
552  }},
553  "dependencies": {{
554    "@coral-xyz/anchor": "^{ANCHOR_VERSION}"
555  }},
556  "devDependencies": {{
557    "chai": "^4.3.4",
558    "mocha": "^9.0.3",
559    "ts-mocha": "^10.0.0",
560    "@types/bn.js": "^5.1.0",
561    "@types/chai": "^4.3.0",
562    "@types/mocha": "^9.0.0",
563    "typescript": "^4.3.5",
564    "prettier": "^2.6.2"
565  }}
566}}
567"#
568    )
569}
570
571pub fn ts_package_json_counter(license: String) -> String {
572    format!(
573        r#"{{
574  "license": "{license}",
575  "scripts": {{
576    "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
577    "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
578  }},
579  "dependencies": {{
580    "@coral-xyz/anchor": "^{ANCHOR_VERSION}",
581    "@solana/web3.js": "^1.92.3"
582  }},
583  "devDependencies": {{
584    "chai": "^4.3.4",
585    "mocha": "^9.0.3",
586    "ts-mocha": "^10.0.0",
587    "@types/bn.js": "^5.1.0",
588    "@types/chai": "^4.3.0",
589    "@types/mocha": "^9.0.0",
590    "typescript": "^4.3.5",
591    "prettier": "^2.6.2"
592  }}
593}}
594"#
595    )
596}
597
598pub fn ts_package_json_mint_token(license: String) -> String {
599    format!(
600        r#"{{
601  "license": "{license}",
602  "scripts": {{
603    "lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
604    "lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
605  }},
606  "dependencies": {{
607    "@coral-xyz/anchor": "^{ANCHOR_VERSION}",
608    "@solana/web3.js": "^1.92.3"
609  }},
610  "devDependencies": {{
611    "chai": "^4.3.4",
612    "mocha": "^9.0.3",
613    "ts-mocha": "^10.0.0",
614    "@types/bn.js": "^5.1.0",
615    "@types/chai": "^4.3.0",
616    "@types/mocha": "^9.0.0",
617    "typescript": "^4.3.5",
618    "prettier": "^2.6.2"
619  }}
620}}
621"#
622    )
623}
624
625pub fn ts_mocha(name: &str, template: ProgramTemplate) -> String {
626    let template_files = match template {
627        ProgramTemplate::Basic => ts_mocha_basic(name),
628        ProgramTemplate::Counter => ts_mocha_counter(name),
629        ProgramTemplate::MintToken => ts_mocha_mint_token(name),
630    };
631
632    template_files
633}
634
635pub fn ts_mocha_basic(name: &str) -> String {
636    format!(
637        r#"import * as anchor from "@coral-xyz/anchor";
638import {{ Program }} from "@coral-xyz/anchor";
639import {{ {} }} from "../target/types/{}";
640
641describe("{}", () => {{
642  // Configure the client to use the local cluster.
643  anchor.setProvider(anchor.AnchorProvider.env());
644
645  const program = anchor.workspace.{} as Program<{}>;
646
647  it("Is initialized!", async () => {{
648    // Add your test here.
649    const tx = await program.methods.initialize().rpc();
650    console.log("Your transaction signature", tx);
651  }});
652}});
653"#,
654        name.to_pascal_case(),
655        name.to_snake_case(),
656        name,
657        name.to_pascal_case(),
658        name.to_pascal_case(),
659    )
660}
661
662pub fn ts_mocha_counter(name: &str) -> String {
663    format!(
664        r#"import * as anchor from "@coral-xyz/anchor";
665import {{ Program }} from "@coral-xyz/anchor";
666import {{  PublicKey }} from "@solana/web3.js";
667import {{ expect }} from "chai";
668import {{ {} }} from "../target/types/{}";
669
670
671describe("{}", () => {{
672  // Configure the client to use the local cluster.
673  const provider = anchor.AnchorProvider.env();
674  anchor.setProvider(provider);
675
676  const program = anchor.workspace.{} as Program<{}>;
677  let counterAccount: PublicKey;
678  let counterBump: number;
679
680  before("Boilerplates", async () => {{
681    [counterAccount, counterBump] = await PublicKey.findProgramAddress(
682      [Buffer.from("counter")],
683      program.programId
684    );
685  }});
686
687  it("Initialize counter!", async () => {{
688    await program.methods
689      .initialize()
690      .accounts({{
691        counter: counterAccount,
692        user: provider.wallet.publicKey,
693      }})
694      .rpc();
695
696    const counter = await program.account.counter.fetch(counterAccount);
697    expect(counter.count.toString()).eq("0")
698  }});
699  it("Increment counter", async () => {{
700    await program.methods
701      .increment()
702      .accounts({{
703        counter: counterAccount,
704        user: provider.wallet.publicKey,
705      }})
706      .rpc();
707
708    const counter = await program.account.counter.fetch(counterAccount);
709    expect(counter.count.toString()).eq("1")
710  }});
711}});
712"#,
713        name.to_pascal_case(),
714        name.to_snake_case(),
715        name,
716        name.to_pascal_case(),
717        name.to_pascal_case(),
718    )
719}
720
721pub fn ts_mocha_mint_token(name: &str) -> String {
722    format!(
723        r#"import * as anchor from "@coral-xyz/anchor";
724import {{ Program }} from "@coral-xyz/anchor";
725import {{ PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY }} from "@solana/web3.js";
726import {{ assert }} from "chai";
727import BN from "bn.js";
728import {{ {} }} from "../target/types/{}";
729
730describe("{}", () => {{
731  // Configure the client to use the local cluster.
732  const provider = anchor.AnchorProvider.env();
733  anchor.setProvider(provider);
734
735  const program = anchor.workspace.{} as Program<{}>;
736
737  // Metaplex Constants
738  const METADATA_SEED = "metadata";
739  const TOKEN_METADATA_PROGRAM_ID = new PublicKey(
740    "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
741  );
742
743  // Constants from our program
744  const MINT_SEED = "mint";
745
746  // Data for our tests
747  const payer = provider.wallet.publicKey;
748  const metadata = {{
749    name: "Icy",
750    symbol: "ICY",
751    uri: "https://cdn.discordapp.com/emojis/1192768878183465062.png?size=240&quality=lossless",
752    decimals: 9,
753  }};
754  const mintAmount = 10;
755
756  // Derive the public key for our mint account
757  const [mint] = PublicKey.findProgramAddressSync(
758    [Buffer.from(MINT_SEED)],
759    program.programId
760  );
761
762  // Derive the public key for our metadata account using the Metaplex program
763  const [metadataAddress] = PublicKey.findProgramAddressSync(
764    [
765      Buffer.from(METADATA_SEED),
766      TOKEN_METADATA_PROGRAM_ID.toBuffer(),
767      mint.toBuffer(),
768    ],
769    TOKEN_METADATA_PROGRAM_ID
770  );
771
772  it("initialize", async () => {{
773    // Check if the mint account already exists
774    const info = await provider.connection.getAccountInfo(mint);
775    if (info) {{
776      return; // Do not attempt to initialize if already initialized
777    }}
778    console.log("  Mint not found. Attempting to initialize.");
779
780    // Define the accounts and arguments for the `initToken` function call
781    const context = {{
782      metadata: metadataAddress,
783      mint,
784      payer,
785      rent: SYSVAR_RENT_PUBKEY,
786      systemProgram: SystemProgram.programId,
787      tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
788      tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
789    }};
790
791    // Call the `initToken` function to initialize the mint account
792    const txHash = await program.methods
793      .initToken(metadata)
794      .accounts(context)
795      .rpc();
796
797    // Wait for confirmation and log transaction details
798    await provider.connection.confirmTransaction(txHash, "finalized");
799    console.log(`  https://explorer.solana.com/tx/${{txHash}}?cluster=devnet`);
800
801    // Verify that the mint account was initialized
802    const newInfo = await provider.connection.getAccountInfo(mint);
803    assert(newInfo, "  Mint should be initialized.");
804  }});
805
806  it("mint tokens", async () => {{
807    // Derive the associated token account address for the payer
808    const destination = anchor.utils.token.associatedAddress({{
809      mint: mint,
810      owner: payer,
811    }});
812
813    // Get initial token balance (0 if account not yet created)
814    let initialBalance: number;
815    try {{
816      const balance = await provider.connection.getTokenAccountBalance(
817        destination
818      );
819      initialBalance = balance.value.uiAmount;
820    }} catch {{
821      // Token account not yet initiated has 0 balance
822      initialBalance = 0;
823    }}
824
825    // Define the accounts and arguments for the `mintTokens` function call
826    const context = {{
827      mint,
828      destination,
829      payer,
830      rent: SYSVAR_RENT_PUBKEY,
831      systemProgram: SystemProgram.programId,
832      tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
833      associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID,
834    }};
835
836    // Call the `mintTokens` function to mint tokens
837    const txHash = await program.methods
838      .mintTokens(new BN(mintAmount * 10 ** metadata.decimals))
839      .accounts(context)
840      .rpc();
841    await provider.connection.confirmTransaction(txHash);
842    console.log(`  https://explorer.solana.com/tx/${{txHash}}?cluster=devnet`);
843
844    // check icy balance of payer
845    const postBalance = (
846      await provider.connection.getTokenAccountBalance(destination)
847    ).value.uiAmount;
848    assert.equal(
849      initialBalance + mintAmount,
850      postBalance,
851      "Post balance should equal initial plus mint amount"
852    );
853  }});
854}});
855"#,
856        name.to_pascal_case(),
857        name.to_snake_case(),
858        name,
859        name.to_pascal_case(),
860        name.to_pascal_case(),
861    )
862}
863
864pub fn ts_config() -> &'static str {
865    r#"{
866  "compilerOptions": {
867    "types": ["mocha", "chai"],
868    "typeRoots": ["./node_modules/@types"],
869    "lib": ["es2015"],
870    "module": "commonjs",
871    "target": "es6",
872    "esModuleInterop": true
873  }
874}
875"#
876}
877
878pub fn git_ignore() -> &'static str {
879    r#".anchor
880.DS_Store
881**/*.rs.bk
882node_modules
883test-ledger
884.yarn
885"#
886}
887
888pub fn prettier_ignore() -> &'static str {
889    r#".anchor
890.DS_Store
891target
892node_modules
893dist
894build
895test-ledger
896"#
897}
898
899pub fn get_test_script() -> &'static str {
900    "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
901}
902
903pub fn readme(template: ProgramTemplate) -> String {
904    let template_files = match template {
905        ProgramTemplate::Basic => readme_basic(),
906        ProgramTemplate::Counter => readme_counter(),
907        ProgramTemplate::MintToken => readme_mint_token(),
908    };
909
910    template_files
911}
912
913pub fn readme_basic() -> String {
914    r#"**Build Program**
915```sh
916anchor build
917```
918
919**Test Program**
920```sh
921anchor test 
922```
923"#
924    .to_string()
925}
926
927pub fn readme_counter() -> String {
928    r#"**Build Program**
929```sh
930anchor build
931```
932
933**Test Program**
934```sh
935anchor test 
936```
937"#
938    .to_string()
939}
940
941pub fn readme_mint_token() -> String {
942    r#"### How to Test for Creating Token and Minting Token to Other Wallet
943
944Since the program utilizes the Metaplex program, deployment to the Devnet network is required.
945
9461. **Configure Solana URL to Devnet**
947    ```sh
948    solana config set --url https://api.devnet.solana.com
949    ```
950
9512. **Build Program**
952    ```sh
953    anchor build
954    ```
955
9563. **Airdrop SOL to Address**
957    - To deploy the `mint_token` program, ensure you have 2-3 SOL in the wallet which stores the `wallet.json` file.
958    - Get the address of the wallet:
959        ```shell
960        solana address --keypair wallet.json
961        ```
962    - Airdrop to another address:
963        ```shell
964        solana airdrop 2 <address>
965        ```
966    - Check the balance of the address:
967        ```shell
968        solana balance <address>
969        ```
970
9714. **Deploy Program**
972    ```sh
973    anchor deploy
974    ```
975
9765. **Test Program**
977    ```sh
978    anchor test --skip-deploy
979    ```
980"#.to_string()
981}
982
983pub fn create_test_files(project_name: &str, template: ProgramTemplate) -> Result<()> {
984    fs::create_dir_all("tests")?;
985
986    let mut mocha = File::create(format!("tests/{}.ts", &project_name))?;
987    mocha.write_all(ts_mocha(project_name, template).as_bytes())?;
988
989    Ok(())
990}
991
992pub fn devbox_json() -> String {
993    format!(
994        r#"{{
995  "packages": {{
996    "curl": {{
997      "version": "latest"
998    }},
999    "nodejs": {{
1000      "version": "18"
1001    }},
1002    "yarn": {{
1003      "version": "latest"
1004    }},
1005    "libiconv": {{
1006      "version": "latest"
1007    }},
1008    "darwin.apple_sdk.frameworks.Security": {{
1009      "platforms": [
1010        "aarch64-darwin",
1011        "x86_64-darwin"
1012      ]
1013    }},
1014    "darwin.apple_sdk.frameworks.SystemConfiguration": {{
1015      "platforms": [
1016        "aarch64-darwin",
1017        "x86_64-darwin"
1018      ]
1019    }}
1020  }},
1021  "shell": {{
1022    "init_hook": [
1023      "curl \"https://sh.rustup.rs\" -sfo rustup.sh && sh rustup.sh -y && rustup component add rustfmt clippy",
1024      "export PATH=\"${{HOME}}/.cargo/bin:${{PATH}}\"",
1025      "sh -c \"$(curl -sSfL https://release.solana.com/v1.18.16/install)\"",
1026      "export PATH=\"$HOME/.local/share/solana/install/active_release/bin:$PATH\"",
1027      "cargo install --git https://github.com/coral-xyz/anchor avm --locked --force",
1028      "avm install {ANCHOR_VERSION}",
1029      "avm use latest",
1030      "cargo install df-sol"
1031    ]
1032  }}
1033}}"#
1034    )
1035}