#!/usr/bin/env node "use strict"; const fs = require("fs"); const path = require("path"); const readline = require("readline"); const { Keypair, PublicKey, clusterApiUrl } = require("@solana/web3.js"); function parseEnvConfig(configPath) { const raw = fs.readFileSync(configPath, "utf8"); const out = {}; for (const line of raw.split("\n")) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) continue; const eq = trimmed.indexOf("="); if (eq === -1) continue; const key = trimmed.slice(0, eq).trim(); let val = trimmed.slice(eq + 1).trim(); if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) val = val.slice(1, -1); val = val.replace(/\$HOME/g, process.env.HOME || ""); out[key] = val; } return out; } function assertRequired(cfg, key) { if (!cfg[key]) throw new Error(`В конфиге отсутствует обязательный параметр / Missing config key: ${key}`); } function resolveConfigPath(argvPath) { return argvPath ? path.resolve(argvPath) : path.resolve(__dirname, "..", "governance_token.config.env"); } function loadKeypair(filePath) { const arr = JSON.parse(fs.readFileSync(filePath, "utf8")); return Keypair.fromSecretKey(Uint8Array.from(arr)); } function findSingleJsonFile(dirPath) { const abs = path.resolve(dirPath); if (!fs.existsSync(abs)) throw new Error(`Папка не найдена / Directory not found: ${abs}`); const files = fs.readdirSync(abs).filter((f) => { const p = path.join(abs, f); return fs.statSync(p).isFile() && f.endsWith(".json"); }); if (files.length !== 1) { throw new Error(`В папке должен быть ровно 1 json-файл / Directory must contain exactly 1 json file: ${abs}. Сейчас: ${files.length}`); } return path.join(abs, files[0]); } function saveKeypair(filePath, keypair) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, JSON.stringify(Array.from(keypair.secretKey))); } function parseCluster(cluster) { if (cluster === "devnet" || cluster === "mainnet-beta" || cluster === "testnet") return clusterApiUrl(cluster); return cluster; } function nowStamp() { const d = new Date(); const p = (n) => String(n).padStart(2, "0"); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}_${p(d.getHours())}-${p(d.getMinutes())}-${p(d.getSeconds())}`; } async function askYes(prompt) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const answer = await new Promise((resolve) => rl.question(prompt, resolve)); rl.close(); return answer.trim() === "yes"; } function colors(s, code) { return `\x1b[${code}m${s}\x1b[0m`; } const ui = { info: (s) => console.log(colors(s, "36")), ok: (s) => console.log(colors(s, "32")), warn: (s) => console.log(colors(s, "33")), err: (s) => console.log(colors(s, "31")), title: (s) => console.log(colors(s, "1;35")), }; function getMintPublicKeyFromConfig(cfg) { if (cfg.GT_MINT_ADDRESS && cfg.GT_MINT_ADDRESS.trim()) return new PublicKey(cfg.GT_MINT_ADDRESS.trim()); if (cfg.GT_GOVERNMENT_TOKEN_KEYPAIR_DIR && cfg.GT_GOVERNMENT_TOKEN_KEYPAIR_DIR.trim()) { const kpPath = findSingleJsonFile(path.resolve(cfg.GT_GOVERNMENT_TOKEN_KEYPAIR_DIR)); return loadKeypair(kpPath).publicKey; } if (cfg.GT_MINT_KEYPAIR_PATH && cfg.GT_MINT_KEYPAIR_PATH.trim()) return loadKeypair(path.resolve(cfg.GT_MINT_KEYPAIR_PATH)).publicKey; throw new Error("Не задан mint: укажите GT_MINT_ADDRESS или положите 1 keypair в GT_GOVERNMENT_TOKEN_KEYPAIR_DIR"); } function getOperatorKeypairFromConfig(cfg) { if (cfg.GT_DAO_CREATOR_KEYPAIR_DIR && cfg.GT_DAO_CREATOR_KEYPAIR_DIR.trim()) { const kpPath = findSingleJsonFile(path.resolve(cfg.GT_DAO_CREATOR_KEYPAIR_DIR)); return loadKeypair(kpPath); } if (cfg.GT_OPERATOR_KEYPAIR_PATH && cfg.GT_OPERATOR_KEYPAIR_PATH.trim()) { return loadKeypair(path.resolve(cfg.GT_OPERATOR_KEYPAIR_PATH)); } throw new Error("Не задан ключ оператора: укажите GT_DAO_CREATOR_KEYPAIR_DIR или GT_OPERATOR_KEYPAIR_PATH"); } module.exports = { parseEnvConfig, assertRequired, resolveConfigPath, loadKeypair, findSingleJsonFile, saveKeypair, parseCluster, nowStamp, askYes, ui, getMintPublicKeyFromConfig, getOperatorKeypairFromConfig, };