SHA256
Отключить репосты и добавить Solana-модуль
This commit is contained in:
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { Connection, SystemProgram, Transaction, sendAndConfirmTransaction } = require("@solana/web3.js");
|
||||
const { TOKEN_PROGRAM_ID, getMintLen, createInitializeMintInstruction } = require("@solana/spl-token");
|
||||
const { parseEnvConfig, assertRequired, resolveConfigPath, loadKeypair, findSingleJsonFile, saveKeypair, parseCluster, nowStamp, ui, getOperatorKeypairFromConfig } = require("./_common");
|
||||
|
||||
async function main() {
|
||||
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||
assertRequired(cfg, "GT_CLUSTER"); assertRequired(cfg, "GT_RUNS_DIR");
|
||||
const operator = getOperatorKeypairFromConfig(cfg);
|
||||
const connection = new Connection(parseCluster(cfg.GT_CLUSTER), "confirmed");
|
||||
const gtDir = path.resolve(cfg.GT_GOVERNMENT_TOKEN_KEYPAIR_DIR || path.join(__dirname, "..", "keypairs", "government_token"));
|
||||
fs.mkdirSync(gtDir, { recursive: true });
|
||||
const mintKeypairPath = findSingleJsonFile(gtDir);
|
||||
const mint = loadKeypair(mintKeypairPath);
|
||||
const mintLen = getMintLen([]);
|
||||
const rent = await connection.getMinimumBalanceForRentExemption(mintLen, "confirmed");
|
||||
ui.title("=== Создание governance token (SPL classic) / Create governance token (SPL classic) ===");
|
||||
const tx = new Transaction().add(
|
||||
SystemProgram.createAccount({ fromPubkey: operator.publicKey, newAccountPubkey: mint.publicKey, space: mintLen, lamports: rent, programId: TOKEN_PROGRAM_ID }),
|
||||
createInitializeMintInstruction(mint.publicKey, 0, operator.publicKey, operator.publicKey, TOKEN_PROGRAM_ID)
|
||||
);
|
||||
const sig = await sendAndConfirmTransaction(connection, tx, [operator, mint], { commitment: "confirmed" });
|
||||
const runsDir = path.resolve(cfg.GT_RUNS_DIR); fs.mkdirSync(runsDir, { recursive: true });
|
||||
const outMintPath = mintKeypairPath;
|
||||
saveKeypair(outMintPath, mint);
|
||||
fs.writeFileSync(path.join(runsDir, `${nowStamp()}_create_token.json`), JSON.stringify({ mint: mint.publicKey.toBase58(), txCreateMint: sig }, null, 2));
|
||||
ui.ok(`OK: Mint ${mint.publicKey.toBase58()}`);
|
||||
ui.info(`RU: Использован keypair: ${mintKeypairPath}`);
|
||||
ui.info(`EN: Used keypair: ${mintKeypairPath}`);
|
||||
ui.info(`RU: Вставьте этот mint в файл: ${path.resolve(__dirname, "..", "governance_token.config.env")}`);
|
||||
ui.info(`RU: Строка: GT_MINT_ADDRESS="${mint.publicKey.toBase58()}"`);
|
||||
ui.info(`EN: Put this mint into file: ${path.resolve(__dirname, "..", "governance_token.config.env")}`);
|
||||
ui.info(`EN: Line: GT_MINT_ADDRESS="${mint.publicKey.toBase58()}"`);
|
||||
ui.info(`Mint keypair: ${outMintPath}`);
|
||||
ui.info(`Tx: ${sig}`);
|
||||
}
|
||||
main().catch((e) => { ui.err(`Ошибка / Error: ${e?.message || e}`); process.exit(1); });
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
const { Connection, PublicKey, Transaction, sendAndConfirmTransaction } = require("@solana/web3.js");
|
||||
const { TOKEN_2022_PROGRAM_ID, getAssociatedTokenAddressSync, createAssociatedTokenAccountIdempotentInstruction, createMintToInstruction, getAccount } = require("@solana/spl-token");
|
||||
const { parseEnvConfig, assertRequired, resolveConfigPath, parseCluster, ui, getMintPublicKeyFromConfig, getOperatorKeypairFromConfig } = require("./_common");
|
||||
|
||||
async function main() {
|
||||
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||
const receiver = new PublicKey(process.argv[3]);
|
||||
if (!process.argv[3]) throw new Error("Использование / Usage: node .../02...js <config.env> <receiver_wallet>");
|
||||
assertRequired(cfg, "GT_CLUSTER");
|
||||
const mint = getMintPublicKeyFromConfig(cfg);
|
||||
const operator = getOperatorKeypairFromConfig(cfg);
|
||||
const connection = new Connection(parseCluster(cfg.GT_CLUSTER), "confirmed");
|
||||
const ata = getAssociatedTokenAddressSync(mint, receiver, false, TOKEN_2022_PROGRAM_ID);
|
||||
const ataInfo = await connection.getAccountInfo(ata, "confirmed");
|
||||
if (ataInfo) {
|
||||
const tokenAcc = await getAccount(connection, ata, "confirmed", TOKEN_2022_PROGRAM_ID);
|
||||
if (tokenAcc.amount >= 1n) {
|
||||
throw new Error(
|
||||
`На кошельке уже есть membership token / Wallet already has membership token. wallet=${receiver.toBase58()} amount=${tokenAcc.amount.toString()}`
|
||||
);
|
||||
}
|
||||
}
|
||||
const ix = [
|
||||
createAssociatedTokenAccountIdempotentInstruction(operator.publicKey, ata, receiver, mint, TOKEN_2022_PROGRAM_ID),
|
||||
createMintToInstruction(mint, ata, operator.publicKey, 1n, [], TOKEN_2022_PROGRAM_ID),
|
||||
];
|
||||
ui.title("=== Выпуск 1 membership токена / Mint 1 membership token ===");
|
||||
const sig = await sendAndConfirmTransaction(connection, new Transaction().add(...ix), [operator], { commitment: "confirmed" });
|
||||
ui.ok("Успешно / Success");
|
||||
ui.info(`Mint: ${mint.toBase58()}`); ui.info(`Wallet: ${receiver.toBase58()}`); ui.info(`Tx: ${sig}`);
|
||||
}
|
||||
main().catch((e) => { ui.err(`Ошибка / Error: ${e?.message || e}`); process.exit(1); });
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
const { Connection, PublicKey, Transaction, sendAndConfirmTransaction } = require("@solana/web3.js");
|
||||
const { TOKEN_2022_PROGRAM_ID, getAssociatedTokenAddressSync, createBurnCheckedInstruction } = require("@solana/spl-token");
|
||||
const { parseEnvConfig, assertRequired, resolveConfigPath, parseCluster, ui, getMintPublicKeyFromConfig, getOperatorKeypairFromConfig } = require("./_common");
|
||||
|
||||
async function main() {
|
||||
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||
const targetOwner = new PublicKey(process.argv[3]);
|
||||
if (!process.argv[3]) throw new Error("Использование / Usage: node .../03...js <config.env> <target_owner_wallet>");
|
||||
assertRequired(cfg, "GT_CLUSTER");
|
||||
const mint = getMintPublicKeyFromConfig(cfg);
|
||||
const operator = getOperatorKeypairFromConfig(cfg);
|
||||
const connection = new Connection(parseCluster(cfg.GT_CLUSTER), "confirmed");
|
||||
const targetAta = getAssociatedTokenAddressSync(mint, targetOwner, false, TOKEN_2022_PROGRAM_ID);
|
||||
const ix = createBurnCheckedInstruction(targetAta, mint, operator.publicKey, 1n, 0, [], TOKEN_2022_PROGRAM_ID);
|
||||
ui.title("=== Принудительное сжигание 1 токена / Force burn 1 token ===");
|
||||
const sig = await sendAndConfirmTransaction(connection, new Transaction().add(ix), [operator], { commitment: "confirmed" });
|
||||
ui.ok("Успешно / Success");
|
||||
ui.info(`Mint: ${mint.toBase58()}`); ui.info(`Wallet: ${targetOwner.toBase58()}`); ui.info(`Tx: ${sig}`);
|
||||
}
|
||||
main().catch((e) => { ui.err(`Ошибка / Error: ${e?.message || e}`); process.exit(1); });
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
const { Connection, PublicKey, Transaction, sendAndConfirmTransaction } = require("@solana/web3.js");
|
||||
const { TOKEN_2022_PROGRAM_ID, AuthorityType, createSetAuthorityInstruction } = require("@solana/spl-token");
|
||||
const { parseEnvConfig, assertRequired, resolveConfigPath, parseCluster, askYes, ui, getMintPublicKeyFromConfig, getOperatorKeypairFromConfig } = require("./_common");
|
||||
|
||||
async function main() {
|
||||
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||
assertRequired(cfg, "GT_CLUSTER"); assertRequired(cfg, "GT_GOVERNANCE_PDA");
|
||||
const mint = getMintPublicKeyFromConfig(cfg);
|
||||
const operator = getOperatorKeypairFromConfig(cfg);
|
||||
const governancePda = new PublicKey(cfg.GT_GOVERNANCE_PDA);
|
||||
const connection = new Connection(parseCluster(cfg.GT_CLUSTER), "confirmed");
|
||||
ui.title("=== Передача прав DAO / Transfer rights to DAO ===");
|
||||
ui.warn(`RU: Будут переданы права Mint/Freeze/PermanentDelegate от ${operator.publicKey.toBase58()} на ${governancePda.toBase58()}`);
|
||||
ui.warn(`EN: Mint/Freeze/PermanentDelegate authorities will be transferred to governance PDA.`);
|
||||
const ok = await askYes("Введите yes / Type yes to continue: ");
|
||||
if (!ok) return ui.warn("Отменено / Cancelled");
|
||||
const ixs = [
|
||||
createSetAuthorityInstruction(mint, operator.publicKey, AuthorityType.MintTokens, governancePda, [], TOKEN_2022_PROGRAM_ID),
|
||||
createSetAuthorityInstruction(mint, operator.publicKey, AuthorityType.FreezeAccount, governancePda, [], TOKEN_2022_PROGRAM_ID),
|
||||
createSetAuthorityInstruction(mint, operator.publicKey, AuthorityType.PermanentDelegate, governancePda, [], TOKEN_2022_PROGRAM_ID),
|
||||
];
|
||||
const sig = await sendAndConfirmTransaction(connection, new Transaction().add(...ixs), [operator], { commitment: "confirmed" });
|
||||
ui.ok("Успешно / Success");
|
||||
ui.info(`Mint: ${mint.toBase58()}`); ui.info(`DAO PDA: ${governancePda.toBase58()}`); ui.info(`Tx: ${sig}`);
|
||||
}
|
||||
main().catch((e) => { ui.err(`Ошибка / Error: ${e?.message || e}`); process.exit(1); });
|
||||
@@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const BN = require("bn.js");
|
||||
const {
|
||||
Connection,
|
||||
PublicKey,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
} = require("@solana/web3.js");
|
||||
const {
|
||||
getAssociatedTokenAddressSync,
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} = require("@solana/spl-token");
|
||||
const {
|
||||
MintMaxVoteWeightSource,
|
||||
VoteThreshold,
|
||||
VoteThresholdType,
|
||||
VoteTipping,
|
||||
GovernanceConfig,
|
||||
PROGRAM_VERSION_V3,
|
||||
GoverningTokenConfigAccountArgs,
|
||||
GoverningTokenType,
|
||||
withCreateRealm,
|
||||
withDepositGoverningTokens,
|
||||
withCreateGovernance,
|
||||
withCreateNativeTreasury,
|
||||
withSetRealmAuthority,
|
||||
SetRealmAuthorityAction,
|
||||
} = require("@solana/spl-governance");
|
||||
const { parseEnvConfig, assertRequired, resolveConfigPath, parseCluster, nowStamp, getOperatorKeypairFromConfig, getMintPublicKeyFromConfig, ui } = require("./_common");
|
||||
|
||||
async function main() {
|
||||
const configPath = resolveConfigPath(process.argv[2]);
|
||||
const cfg = parseEnvConfig(configPath);
|
||||
[
|
||||
"GT_CLUSTER", "DAO_REALM_NAME", "SPL_GOVERNANCE_PROGRAM_ID", "DAO_VOTING_TIME_SEC", "DAO_APPROVAL_THRESHOLD_PERCENT"
|
||||
].forEach((k) => assertRequired(cfg, k));
|
||||
|
||||
const cluster = cfg.GT_CLUSTER;
|
||||
const connection = new Connection(parseCluster(cluster), "confirmed");
|
||||
const operator = getOperatorKeypairFromConfig(cfg);
|
||||
const governanceProgramId = new PublicKey(cfg.SPL_GOVERNANCE_PROGRAM_ID);
|
||||
const mint = getMintPublicKeyFromConfig(cfg);
|
||||
const votingTimeSec = Number(cfg.DAO_VOTING_TIME_SEC);
|
||||
const thresholdPct = Number(cfg.DAO_APPROVAL_THRESHOLD_PERCENT);
|
||||
const runsDir = path.resolve(cfg.DAO_RUNS_DIR || path.join(__dirname, "runs"));
|
||||
fs.mkdirSync(runsDir, { recursive: true });
|
||||
|
||||
const mintAi = await connection.getAccountInfo(mint, "confirmed");
|
||||
if (!mintAi) throw new Error(`Governing mint not found: ${mint.toBase58()}`);
|
||||
if (!mintAi.owner.equals(TOKEN_PROGRAM_ID)) {
|
||||
throw new Error(
|
||||
`Этот CreateDAO ожидает governing mint под классическим SPL Token (${TOKEN_PROGRAM_ID.toBase58()}). ` +
|
||||
`Текущий mint owner: ${mintAi.owner.toBase58()}`
|
||||
);
|
||||
}
|
||||
|
||||
const [realmPda] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("governance"), Buffer.from(cfg.DAO_REALM_NAME, "utf8")],
|
||||
governanceProgramId
|
||||
);
|
||||
const realmExists = (await connection.getAccountInfo(realmPda)) !== null;
|
||||
if (realmExists) throw new Error(`Realm already exists: ${realmPda.toBase58()}`);
|
||||
|
||||
const ownerAtaToken2022 = getAssociatedTokenAddressSync(mint, operator.publicKey, false, TOKEN_2022_PROGRAM_ID);
|
||||
const ownerAtaToken = getAssociatedTokenAddressSync(mint, operator.publicKey, false, TOKEN_PROGRAM_ID);
|
||||
let ownerAta = ownerAtaToken2022;
|
||||
let ownerAtaInfo = await connection.getAccountInfo(ownerAtaToken2022, "confirmed");
|
||||
let tokenProgramId = TOKEN_2022_PROGRAM_ID;
|
||||
if (!ownerAtaInfo) {
|
||||
ownerAta = ownerAtaToken;
|
||||
ownerAtaInfo = await connection.getAccountInfo(ownerAtaToken, "confirmed");
|
||||
tokenProgramId = TOKEN_PROGRAM_ID;
|
||||
}
|
||||
if (!ownerAtaInfo) throw new Error("Operator ATA for governing mint not found. Mint at least 1 token to operator first.");
|
||||
|
||||
const programVersion = PROGRAM_VERSION_V3;
|
||||
const ixRealm = [];
|
||||
const communityTokenConfig = new GoverningTokenConfigAccountArgs({
|
||||
voterWeightAddin: undefined,
|
||||
maxVoterWeightAddin: undefined,
|
||||
tokenType: GoverningTokenType.Membership,
|
||||
});
|
||||
const realmPk = await withCreateRealm(
|
||||
ixRealm,
|
||||
governanceProgramId,
|
||||
programVersion,
|
||||
cfg.DAO_REALM_NAME,
|
||||
operator.publicKey,
|
||||
mint,
|
||||
operator.publicKey,
|
||||
undefined,
|
||||
MintMaxVoteWeightSource.FULL_SUPPLY_FRACTION,
|
||||
new BN(1),
|
||||
communityTokenConfig,
|
||||
undefined
|
||||
);
|
||||
const sigRealm = await sendAndConfirmTransaction(connection, new Transaction().add(...ixRealm), [operator], { commitment: "confirmed" });
|
||||
|
||||
const ixDeposit = [];
|
||||
const tokenOwnerRecordPk = await withDepositGoverningTokens(
|
||||
ixDeposit,
|
||||
governanceProgramId,
|
||||
programVersion,
|
||||
realmPk,
|
||||
ownerAta,
|
||||
mint,
|
||||
operator.publicKey,
|
||||
operator.publicKey,
|
||||
operator.publicKey,
|
||||
new BN(1),
|
||||
true,
|
||||
tokenProgramId
|
||||
);
|
||||
const sigDeposit = await sendAndConfirmTransaction(connection, new Transaction().add(...ixDeposit), [operator], { commitment: "confirmed" });
|
||||
|
||||
const governanceConfig = new GovernanceConfig({
|
||||
communityVoteThreshold: new VoteThreshold({ type: VoteThresholdType.YesVotePercentage, value: thresholdPct }),
|
||||
minCommunityTokensToCreateProposal: new BN(1),
|
||||
minInstructionHoldUpTime: 0,
|
||||
baseVotingTime: votingTimeSec,
|
||||
communityVoteTipping: VoteTipping.Early,
|
||||
minCouncilTokensToCreateProposal: new BN(0),
|
||||
councilVoteThreshold: new VoteThreshold({ type: VoteThresholdType.Disabled }),
|
||||
councilVetoVoteThreshold: new VoteThreshold({ type: VoteThresholdType.Disabled }),
|
||||
communityVetoVoteThreshold: new VoteThreshold({ type: VoteThresholdType.Disabled }),
|
||||
councilVoteTipping: VoteTipping.Disabled,
|
||||
votingCoolOffTime: 0,
|
||||
depositExemptProposalCount: 0,
|
||||
});
|
||||
const ixGov = [];
|
||||
const governancePk = await withCreateGovernance(
|
||||
ixGov,
|
||||
governanceProgramId,
|
||||
programVersion,
|
||||
realmPk,
|
||||
realmPk,
|
||||
governanceConfig,
|
||||
tokenOwnerRecordPk,
|
||||
operator.publicKey,
|
||||
operator.publicKey
|
||||
);
|
||||
const treasuryPk = await withCreateNativeTreasury(ixGov, governanceProgramId, programVersion, governancePk, operator.publicKey);
|
||||
const sigGov = await sendAndConfirmTransaction(connection, new Transaction().add(...ixGov), [operator], { commitment: "confirmed" });
|
||||
|
||||
const ixRealmAuthority = [];
|
||||
withSetRealmAuthority(
|
||||
ixRealmAuthority,
|
||||
governanceProgramId,
|
||||
programVersion,
|
||||
realmPk,
|
||||
operator.publicKey,
|
||||
governancePk,
|
||||
SetRealmAuthorityAction.SetChecked
|
||||
);
|
||||
const sigSetRealmAuthority = await sendAndConfirmTransaction(connection, new Transaction().add(...ixRealmAuthority), [operator], { commitment: "confirmed" });
|
||||
|
||||
const report = {
|
||||
createdAt: new Date().toISOString(),
|
||||
cluster,
|
||||
realmName: cfg.DAO_REALM_NAME,
|
||||
governanceProgramId: governanceProgramId.toBase58(),
|
||||
governingMint: mint.toBase58(),
|
||||
operator: operator.publicKey.toBase58(),
|
||||
realm: realmPk.toBase58(),
|
||||
governance: governancePk.toBase58(),
|
||||
nativeTreasury: treasuryPk.toBase58(),
|
||||
tokenOwnerRecord: tokenOwnerRecordPk.toBase58(),
|
||||
txRealm: sigRealm,
|
||||
txDeposit: sigDeposit,
|
||||
txGovernanceTreasury: sigGov,
|
||||
txSetRealmAuthority: sigSetRealmAuthority,
|
||||
};
|
||||
const reportPath = path.join(runsDir, `${nowStamp()}_create_dao.json`);
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||
|
||||
ui.ok("DAO created successfully / DAO успешно создан");
|
||||
ui.info(`Realm: ${realmPk.toBase58()}`);
|
||||
ui.info(`Governance PDA: ${governancePk.toBase58()}`);
|
||||
ui.info(`Treasury: ${treasuryPk.toBase58()}`);
|
||||
ui.info(`Report: ${reportPath}`);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("CreateDAO error:", e?.message || e);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/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,
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { spawn } = require("child_process");
|
||||
const { parseEnvConfig, resolveConfigPath, nowStamp, ui } = require("./_common");
|
||||
const DEFAULT_PREFIX = "SHi";
|
||||
|
||||
async function main() {
|
||||
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||
const runsDir = path.resolve(cfg.GT_RUNS_DIR || path.join(__dirname, "..", "runs"));
|
||||
fs.mkdirSync(runsDir, { recursive: true });
|
||||
const prefix = process.argv[3] || cfg.GT_VANITY_PREFIX || DEFAULT_PREFIX;
|
||||
if (!/^[1-9A-HJ-NP-Za-km-z]+$/.test(prefix)) throw new Error("Префикс Base58 без 0/O/I/l");
|
||||
ui.title("=== Vanity подбор mint keypair / Vanity mint keypair grind ===");
|
||||
ui.info(`Prefix: ${prefix}`);
|
||||
const args = ["grind", "--starts-with", `${prefix}:1`];
|
||||
const p = spawn("solana-keygen", args, { cwd: runsDir, stdio: ["ignore", "pipe", "pipe"] });
|
||||
const lines = [];
|
||||
const on = (d) => {
|
||||
for (const l of String(d).split("\n")) {
|
||||
const line = l.trim(); if (!line) continue;
|
||||
lines.push(line); console.log(line);
|
||||
}
|
||||
};
|
||||
p.stdout.on("data", on); p.stderr.on("data", on);
|
||||
const code = await new Promise((resolve) => p.on("close", resolve));
|
||||
if (code !== 0) throw new Error(`solana-keygen grind exit code ${code}`);
|
||||
const rp = path.join(runsDir, `${nowStamp()}_vanity_grind_report.json`);
|
||||
fs.writeFileSync(rp, JSON.stringify({ createdAt: new Date().toISOString(), prefix, command: `solana-keygen ${args.join(" ")}`, outputLog: lines }, null, 2));
|
||||
ui.ok("Готово / Done");
|
||||
ui.info(`Report: ${rp}`);
|
||||
ui.info(`RU: Скопируйте выбранный keypair из runs в keypairs/government_token/ (один json-файл).`);
|
||||
ui.info(`EN: Copy selected keypair from runs to keypairs/government_token/ (single json file).`);
|
||||
}
|
||||
main().catch((e) => { ui.err(`Ошибка / Error: ${e?.message || e}`); process.exit(1); });
|
||||
Reference in New Issue
Block a user