#!/usr/bin/env node "use strict"; const fs = require("fs"); const path = require("path"); const BN = require("bn.js"); const { Connection, Keypair, SystemProgram, Transaction, sendAndConfirmTransaction, } = require("@solana/web3.js"); const { TOKEN_PROGRAM_ID, AuthorityType, getMintLen, createInitializeMintInstruction, createSetAuthorityInstruction, } = require("@solana/spl-token"); const { createUmi } = require("@metaplex-foundation/umi-bundle-defaults"); const { createSignerFromKeypair, signerIdentity, percentAmount, none, some } = require("@metaplex-foundation/umi"); const { fromWeb3JsKeypair, fromWeb3JsPublicKey } = require("@metaplex-foundation/umi-web3js-adapters"); const { mplTokenMetadata, createV1, TokenStandard } = require("@metaplex-foundation/mpl-token-metadata"); const { PublicKey, assertRequired, clusterUrl, ensureArweaveUri, ensureDir, loadKeypair, nowStamp, parseEnvConfig, resolveConfigPath, } = require("./js_common"); function lamportsToSol(lamports) { return Number(lamports) / 1_000_000_000; } async function attachGovernanceMetadata(cfg, cluster, issuer, mintPubkey, mintKeypair) { ensureArweaveUri("DAO_GOV_TOKEN_METADATA_URI", cfg.DAO_GOV_TOKEN_METADATA_URI); ensureArweaveUri("DAO_GOV_TOKEN_IMAGE_URL", cfg.DAO_GOV_TOKEN_IMAGE_URL); const umi = createUmi(clusterUrl(cluster)); const issuerSigner = createSignerFromKeypair(umi, fromWeb3JsKeypair(issuer)); const mintSigner = createSignerFromKeypair(umi, fromWeb3JsKeypair(mintKeypair)); umi.use(signerIdentity(issuerSigner)); umi.use(mplTokenMetadata()); const builder = createV1(umi, { mint: mintSigner, authority: issuerSigner, payer: issuerSigner, updateAuthority: issuerSigner, name: cfg.DAO_GOV_NFT_NAME, symbol: cfg.DAO_GOV_NFT_SYMBOL, uri: cfg.DAO_GOV_TOKEN_METADATA_URI, sellerFeeBasisPoints: percentAmount(0), tokenStandard: TokenStandard.Fungible, decimals: some(0), creators: none(), collection: none(), uses: none(), collectionDetails: none(), ruleSet: none(), printSupply: none(), primarySaleHappened: false, isMutable: true, isCollection: false, splTokenProgram: fromWeb3JsPublicKey(TOKEN_PROGRAM_ID), }); const result = await builder.sendAndConfirm(umi); return Buffer.from(result.signature).toString("base64"); } async function main() { const configPath = resolveConfigPath(process.argv[2], "dao.config.env"); if (!fs.existsSync(configPath)) throw new Error(`Конфиг не найден: ${configPath}`); const cfg = parseEnvConfig(configPath); [ "DAO_CLUSTER", "DAO_GOV_NFT_NAME", "DAO_GOV_NFT_SYMBOL", "DAO_GOV_TOKEN_DESCRIPTION", "DAO_ISSUER_KEYPAIR", "SPL_GOVERNANCE_PROGRAM_ID", "DAO_GOV_TOKEN_METADATA_URI", "DAO_GOV_TOKEN_IMAGE_URL", ].forEach((k) => assertRequired(cfg, k)); const cluster = cfg.DAO_CLUSTER; const connection = new Connection(clusterUrl(cluster), "confirmed"); const issuer = loadKeypair(path.resolve(cfg.DAO_ISSUER_KEYPAIR)); const mintKeypair = cfg.DAO_GOV_MINT_KEYPAIR ? loadKeypair(path.resolve(cfg.DAO_GOV_MINT_KEYPAIR)) : Keypair.generate(); const governanceProgramId = new PublicKey(cfg.SPL_GOVERNANCE_PROGRAM_ID); const startBalance = await connection.getBalance(issuer.publicKey, "confirmed"); console.log("============================================================"); console.log("CREATE SHiNE GOVERNANCE MINT"); console.log("------------------------------------------------------------"); console.log("Сеть: ", cluster); console.log("Mint: ", mintKeypair.publicKey.toBase58()); console.log("Issuer: ", issuer.publicKey.toBase58()); console.log("Governance program:", governanceProgramId.toBase58()); console.log("Баланс до: ", `${lamportsToSol(startBalance)} SOL`); console.log("Name: ", cfg.DAO_GOV_NFT_NAME); console.log("Symbol: ", cfg.DAO_GOV_NFT_SYMBOL); console.log("Metadata URI: ", cfg.DAO_GOV_TOKEN_METADATA_URI); console.log("Image URL: ", cfg.DAO_GOV_TOKEN_IMAGE_URL); console.log("============================================================"); const mintLen = getMintLen([]); const mintRent = await connection.getMinimumBalanceForRentExemption(mintLen); const txMint = new Transaction().add( SystemProgram.createAccount({ fromPubkey: issuer.publicKey, newAccountPubkey: mintKeypair.publicKey, space: mintLen, lamports: mintRent, programId: TOKEN_PROGRAM_ID, }), createInitializeMintInstruction(mintKeypair.publicKey, 0, issuer.publicKey, issuer.publicKey, TOKEN_PROGRAM_ID) ); const sigMint = await sendAndConfirmTransaction(connection, txMint, [issuer, mintKeypair], { commitment: "confirmed", }); const sigMetadata = await attachGovernanceMetadata(cfg, cluster, issuer, mintKeypair.publicKey, mintKeypair); const report = { createdAt: new Date().toISOString(), cluster, mint: mintKeypair.publicKey.toBase58(), issuer: issuer.publicKey.toBase58(), mintAuthority: issuer.publicKey.toBase58(), freezeAuthority: issuer.publicKey.toBase58(), metadataUpdateAuthority: issuer.publicKey.toBase58(), governanceProgramId: governanceProgramId.toBase58(), metadataUri: cfg.DAO_GOV_TOKEN_METADATA_URI, imageUrl: cfg.DAO_GOV_TOKEN_IMAGE_URL, txMint: sigMint, txMetadata: sigMetadata, }; const runsDir = path.resolve(__dirname, "..", "local", "runs"); ensureDir(runsDir); const reportPath = path.join(runsDir, `${nowStamp()}_governance_mint.json`); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); console.log("Готово. Governance mint создан."); console.log("mint:", mintKeypair.publicKey.toBase58()); console.log("report:", reportPath); } main().catch((e) => { console.error("CreateMint error:", e?.message || e); process.exit(1); });