#!/usr/bin/env node "use strict"; const fs = require("fs"); const path = require("path"); const { Connection, PublicKey, Transaction, sendAndConfirmTransaction } = require("@solana/web3.js"); const { TOKEN_2022_PROGRAM_ID, getAssociatedTokenAddressSync, createAssociatedTokenAccountIdempotentInstruction, createMintToInstruction } = require("@solana/spl-token"); const { PROGRAM_VERSION_V3, Vote, YesNoVote, VoteType, withCreateProposal, withInsertTransaction, withSignOffProposal, withCastVote, getTokenOwnerRecordAddress, getProposalTransactionAddress, } = require("@solana/spl-governance"); const { clusterUrl, ensureDir, loadKeypair, nowStamp, parseEnvConfig, resolveConfigPath, toInstructionData } = require("./js_common"); async function main() { const targetWallet = process.argv[3]; const nftMintStr = process.argv[4]; if (!targetWallet || !nftMintStr) throw new Error("Usage: node 05_propose_vote_mint_nft.js "); const cfg = parseEnvConfig(resolveConfigPath(process.argv[2], "nft-flow.config.env")); const conn = new Connection(clusterUrl(cfg.CLUSTER), "confirmed"); const main = loadKeypair(path.resolve(__dirname, "..", cfg.MAIN_KEYPAIR)); const realm = new PublicKey(cfg.REALM); const governance = new PublicKey(cfg.GOVERNANCE); const governingMint = new PublicKey(cfg.GOVERNING_MINT); const govPid = new PublicKey(cfg.SPL_GOVERNANCE_PROGRAM_ID); const nftMint = new PublicKey(nftMintStr); const target = new PublicKey(targetWallet); const mainTor = await getTokenOwnerRecordAddress(govPid, realm, governingMint, main.publicKey); const targetAta = getAssociatedTokenAddressSync(nftMint, target, false, TOKEN_2022_PROGRAM_ID); const ixCreate = []; const proposal = await withCreateProposal( ixCreate, govPid, PROGRAM_VERSION_V3, realm, governance, mainTor, `Mint NFT to ${target.toBase58().slice(0, 8)}`, cfg.NFT_METADATA_URI || "https://arweave.net/", governingMint, main.publicKey, undefined, VoteType.SINGLE_CHOICE, ["Approve"], true, main.publicKey ); const txCreate = await sendAndConfirmTransaction(conn, new Transaction().add(...ixCreate), [main], { commitment: "confirmed" }); const mintIx = [ createAssociatedTokenAccountIdempotentInstruction(main.publicKey, targetAta, target, nftMint, TOKEN_2022_PROGRAM_ID), createMintToInstruction(nftMint, targetAta, governance, 1n, [], TOKEN_2022_PROGRAM_ID), ].map(toInstructionData); const ixInsert = []; const proposalTx = await withInsertTransaction(ixInsert, govPid, PROGRAM_VERSION_V3, governance, proposal, mainTor, main.publicKey, 0, 0, 0, mintIx, main.publicKey); const txInsert = await sendAndConfirmTransaction(conn, new Transaction().add(...ixInsert), [main], { commitment: "confirmed" }); const ixSign = []; withSignOffProposal(ixSign, govPid, PROGRAM_VERSION_V3, realm, governance, proposal, main.publicKey, undefined, mainTor); const txSign = await sendAndConfirmTransaction(conn, new Transaction().add(...ixSign), [main], { commitment: "confirmed" }); const vote = Vote.fromYesNoVote(YesNoVote.Yes); const ixVote1 = []; await withCastVote(ixVote1, govPid, PROGRAM_VERSION_V3, realm, governance, proposal, mainTor, mainTor, main.publicKey, governingMint, vote, main.publicKey); const txVote1 = await sendAndConfirmTransaction(conn, new Transaction().add(...ixVote1), [main], { commitment: "confirmed" }); const computedTx = await getProposalTransactionAddress(govPid, PROGRAM_VERSION_V3, proposal, 0, 0); if (!computedTx.equals(proposalTx)) throw new Error("proposal tx mismatch"); const runs = path.resolve(__dirname, "..", cfg.RUNS_DIR || "./local/runs"); ensureDir(runs); const report = { type: "mint_nft", realm: realm.toBase58(), governance: governance.toBase58(), proposal: proposal.toBase58(), proposalTransaction: proposalTx.toBase58(), nftMint: nftMint.toBase58(), targetWallet: target.toBase58(), targetAta: targetAta.toBase58(), txCreate, txInsert, txSign, txVote1, }; const reportPath = path.join(runs, `${nowStamp()}_proposal_mint_${target.toBase58().slice(0, 8)}.json`); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2)); console.log("Mint proposal создан и проголосован."); console.log("proposal:", report.proposal); console.log("proposalTransaction:", report.proposalTransaction); console.log("report:", reportPath); } main().catch((e) => { console.error(e?.message || e); process.exit(1); });