Add governance token scripts with shell wrappers and vanity grind
This commit is contained in:
parent
c680b16e58
commit
34c1142173
3021
shine/package-lock.json
generated
Normal file
3021
shine/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,16 +5,23 @@
|
||||
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coral-xyz/anchor": "^0.31.1"
|
||||
"@coral-xyz/anchor": "^0.31.1",
|
||||
"@metaplex-foundation/mpl-token-metadata": "^3.4.0",
|
||||
"@metaplex-foundation/mpl-toolbox": "^0.10.0",
|
||||
"@metaplex-foundation/umi": "^1.5.1",
|
||||
"@metaplex-foundation/umi-bundle-defaults": "^1.5.1",
|
||||
"@metaplex-foundation/umi-web3js-adapters": "^1.5.1",
|
||||
"@solana/spl-token": "^0.4.14",
|
||||
"@solana/spl-governance": "^0.3.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.3.4",
|
||||
"mocha": "^9.0.3",
|
||||
"ts-mocha": "^10.0.0",
|
||||
"@types/bn.js": "^5.1.0",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"typescript": "^5.7.3",
|
||||
"prettier": "^2.6.2"
|
||||
"chai": "^4.3.4",
|
||||
"mocha": "^9.0.3",
|
||||
"prettier": "^2.6.2",
|
||||
"ts-mocha": "^10.0.0",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const {
|
||||
Connection,
|
||||
Keypair,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
} = require("@solana/web3.js");
|
||||
const {
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
ExtensionType,
|
||||
getMintLen,
|
||||
createInitializeMintInstruction,
|
||||
createInitializeNonTransferableMintInstruction,
|
||||
createInitializePermanentDelegateInstruction,
|
||||
} = require("@solana/spl-token");
|
||||
const {
|
||||
parseEnvConfig,
|
||||
assertRequired,
|
||||
resolveConfigPath,
|
||||
loadKeypair,
|
||||
saveKeypair,
|
||||
parseCluster,
|
||||
nowStamp,
|
||||
} = require("./_common");
|
||||
|
||||
async function main() {
|
||||
const configPath = resolveConfigPath(process.argv[2]);
|
||||
const cfg = parseEnvConfig(configPath);
|
||||
assertRequired(cfg, "GT_CLUSTER");
|
||||
assertRequired(cfg, "GT_OPERATOR_KEYPAIR_PATH");
|
||||
assertRequired(cfg, "GT_RUNS_DIR");
|
||||
|
||||
const operator = loadKeypair(path.resolve(cfg.GT_OPERATOR_KEYPAIR_PATH));
|
||||
const connection = new Connection(parseCluster(cfg.GT_CLUSTER), "confirmed");
|
||||
|
||||
let mint;
|
||||
if (cfg.GT_MINT_KEYPAIR_PATH) {
|
||||
mint = loadKeypair(path.resolve(cfg.GT_MINT_KEYPAIR_PATH));
|
||||
} else {
|
||||
mint = Keypair.generate();
|
||||
}
|
||||
|
||||
const extensions = [ExtensionType.NonTransferable, ExtensionType.PermanentDelegate];
|
||||
const mintLen = getMintLen(extensions);
|
||||
const rent = await connection.getMinimumBalanceForRentExemption(mintLen, "confirmed");
|
||||
|
||||
const tx = new Transaction().add(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: operator.publicKey,
|
||||
newAccountPubkey: mint.publicKey,
|
||||
space: mintLen,
|
||||
lamports: rent,
|
||||
programId: TOKEN_2022_PROGRAM_ID,
|
||||
}),
|
||||
createInitializeNonTransferableMintInstruction(mint.publicKey, TOKEN_2022_PROGRAM_ID),
|
||||
createInitializePermanentDelegateInstruction(
|
||||
mint.publicKey,
|
||||
operator.publicKey,
|
||||
TOKEN_2022_PROGRAM_ID
|
||||
),
|
||||
createInitializeMintInstruction(
|
||||
mint.publicKey,
|
||||
0,
|
||||
operator.publicKey,
|
||||
operator.publicKey,
|
||||
TOKEN_2022_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 =
|
||||
cfg.GT_MINT_KEYPAIR_PATH && cfg.GT_MINT_KEYPAIR_PATH.trim()
|
||||
? path.resolve(cfg.GT_MINT_KEYPAIR_PATH)
|
||||
: path.join(runsDir, `${nowStamp()}_mint-keypair.json`);
|
||||
saveKeypair(outMintPath, mint);
|
||||
|
||||
const report = {
|
||||
createdAt: new Date().toISOString(),
|
||||
cluster: cfg.GT_CLUSTER,
|
||||
operator: operator.publicKey.toBase58(),
|
||||
mint: mint.publicKey.toBase58(),
|
||||
tokenProgram: TOKEN_2022_PROGRAM_ID.toBase58(),
|
||||
nonTransferable: true,
|
||||
permanentDelegate: operator.publicKey.toBase58(),
|
||||
mintAuthority: operator.publicKey.toBase58(),
|
||||
freezeAuthority: operator.publicKey.toBase58(),
|
||||
mintKeypairPath: outMintPath,
|
||||
txCreateMint: sig,
|
||||
};
|
||||
const reportPath = path.join(runsDir, `${nowStamp()}_create_token.json`);
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||
|
||||
console.log("Governance token создан.");
|
||||
console.log("Mint:", mint.publicKey.toBase58());
|
||||
console.log("Mint keypair:", outMintPath);
|
||||
console.log("Tx:", sig);
|
||||
console.log("Report:", reportPath);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("Ошибка создания governance token:", e?.message || e);
|
||||
process.exit(1);
|
||||
});
|
||||
7
shine/scripts/governance_token/01_создать_governance_токен.sh
Executable file
7
shine/scripts/governance_token/01_создать_governance_токен.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_PATH="${1:-$SCRIPT_DIR/governance_token.config.env}"
|
||||
|
||||
node "$SCRIPT_DIR/01_create_governance_token_exec.js" "$CONFIG_PATH"
|
||||
@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
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 {
|
||||
parseEnvConfig,
|
||||
assertRequired,
|
||||
resolveConfigPath,
|
||||
loadKeypair,
|
||||
parseCluster,
|
||||
} = require("./_common");
|
||||
|
||||
async function main() {
|
||||
const configPath = resolveConfigPath(process.argv[2]);
|
||||
const mint = new PublicKey(process.argv[3]);
|
||||
const receiver = new PublicKey(process.argv[4]);
|
||||
const amount = BigInt(process.argv[5] || "1");
|
||||
if (!process.argv[4]) {
|
||||
throw new Error(
|
||||
"Использование: node 02_mint_membership_to_wallet_exec.js <config.env> <mint> <receiver_wallet> [amount]"
|
||||
);
|
||||
}
|
||||
if (amount <= 0n) throw new Error("amount должен быть > 0");
|
||||
|
||||
const cfg = parseEnvConfig(configPath);
|
||||
assertRequired(cfg, "GT_CLUSTER");
|
||||
assertRequired(cfg, "GT_OPERATOR_KEYPAIR_PATH");
|
||||
const operator = loadKeypair(path.resolve(cfg.GT_OPERATOR_KEYPAIR_PATH));
|
||||
const connection = new Connection(parseCluster(cfg.GT_CLUSTER), "confirmed");
|
||||
|
||||
const ata = getAssociatedTokenAddressSync(
|
||||
mint,
|
||||
receiver,
|
||||
false,
|
||||
TOKEN_2022_PROGRAM_ID
|
||||
);
|
||||
|
||||
const ix = [
|
||||
createAssociatedTokenAccountIdempotentInstruction(
|
||||
operator.publicKey,
|
||||
ata,
|
||||
receiver,
|
||||
mint,
|
||||
TOKEN_2022_PROGRAM_ID
|
||||
),
|
||||
createMintToInstruction(
|
||||
mint,
|
||||
ata,
|
||||
operator.publicKey,
|
||||
amount,
|
||||
[],
|
||||
TOKEN_2022_PROGRAM_ID
|
||||
),
|
||||
];
|
||||
|
||||
const sig = await sendAndConfirmTransaction(connection, new Transaction().add(...ix), [operator], {
|
||||
commitment: "confirmed",
|
||||
});
|
||||
|
||||
console.log("Mint выполнен.");
|
||||
console.log("Receiver:", receiver.toBase58());
|
||||
console.log("ATA:", ata.toBase58());
|
||||
console.log("Amount:", amount.toString());
|
||||
console.log("Tx:", sig);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("Ошибка mint membership:", e?.message || e);
|
||||
process.exit(1);
|
||||
});
|
||||
16
shine/scripts/governance_token/02_выпустить_токен_на_кошелек.sh
Executable file
16
shine/scripts/governance_token/02_выпустить_токен_на_кошелек.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_PATH="${1:-$SCRIPT_DIR/governance_token.config.env}"
|
||||
MINT="${2:-}"
|
||||
WALLET="${3:-}"
|
||||
AMOUNT="${4:-1}"
|
||||
|
||||
if [[ -z "$MINT" || -z "$WALLET" ]]; then
|
||||
echo "Использование:"
|
||||
echo " $0 [config.env] <mint> <wallet> [amount]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
node "$SCRIPT_DIR/02_mint_membership_to_wallet_exec.js" "$CONFIG_PATH" "$MINT" "$WALLET" "$AMOUNT"
|
||||
@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const {
|
||||
Connection,
|
||||
PublicKey,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
} = require("@solana/web3.js");
|
||||
const {
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
getAssociatedTokenAddressSync,
|
||||
createBurnCheckedInstruction,
|
||||
} = require("@solana/spl-token");
|
||||
const {
|
||||
parseEnvConfig,
|
||||
assertRequired,
|
||||
resolveConfigPath,
|
||||
loadKeypair,
|
||||
parseCluster,
|
||||
} = require("./_common");
|
||||
|
||||
async function main() {
|
||||
const configPath = resolveConfigPath(process.argv[2]);
|
||||
const mint = new PublicKey(process.argv[3]);
|
||||
const targetOwner = new PublicKey(process.argv[4]);
|
||||
const amount = BigInt(process.argv[5] || "1");
|
||||
if (!process.argv[4]) {
|
||||
throw new Error(
|
||||
"Использование: node 03_force_burn_from_wallet_exec.js <config.env> <mint> <target_owner_wallet> [amount]"
|
||||
);
|
||||
}
|
||||
if (amount <= 0n) throw new Error("amount должен быть > 0");
|
||||
|
||||
const cfg = parseEnvConfig(configPath);
|
||||
assertRequired(cfg, "GT_CLUSTER");
|
||||
assertRequired(cfg, "GT_OPERATOR_KEYPAIR_PATH");
|
||||
const operator = loadKeypair(path.resolve(cfg.GT_OPERATOR_KEYPAIR_PATH));
|
||||
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,
|
||||
amount,
|
||||
0,
|
||||
[],
|
||||
TOKEN_2022_PROGRAM_ID
|
||||
);
|
||||
|
||||
const sig = await sendAndConfirmTransaction(connection, new Transaction().add(ix), [operator], {
|
||||
commitment: "confirmed",
|
||||
});
|
||||
|
||||
console.log("Force burn выполнен.");
|
||||
console.log("Target owner:", targetOwner.toBase58());
|
||||
console.log("Target ATA:", targetAta.toBase58());
|
||||
console.log("Amount:", amount.toString());
|
||||
console.log("Tx:", sig);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("Ошибка force burn:", e?.message || e);
|
||||
process.exit(1);
|
||||
});
|
||||
16
shine/scripts/governance_token/03_принудительно_сжечь_токен.sh
Executable file
16
shine/scripts/governance_token/03_принудительно_сжечь_токен.sh
Executable file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_PATH="${1:-$SCRIPT_DIR/governance_token.config.env}"
|
||||
MINT="${2:-}"
|
||||
WALLET="${3:-}"
|
||||
AMOUNT="${4:-1}"
|
||||
|
||||
if [[ -z "$MINT" || -z "$WALLET" ]]; then
|
||||
echo "Использование:"
|
||||
echo " $0 [config.env] <mint> <wallet> [amount]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
node "$SCRIPT_DIR/03_force_burn_from_wallet_exec.js" "$CONFIG_PATH" "$MINT" "$WALLET" "$AMOUNT"
|
||||
@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const {
|
||||
Connection,
|
||||
PublicKey,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
} = require("@solana/web3.js");
|
||||
const {
|
||||
TOKEN_2022_PROGRAM_ID,
|
||||
AuthorityType,
|
||||
createSetAuthorityInstruction,
|
||||
} = require("@solana/spl-token");
|
||||
const {
|
||||
parseEnvConfig,
|
||||
assertRequired,
|
||||
resolveConfigPath,
|
||||
loadKeypair,
|
||||
parseCluster,
|
||||
askYes,
|
||||
} = require("./_common");
|
||||
|
||||
async function main() {
|
||||
const configPath = resolveConfigPath(process.argv[2]);
|
||||
const mint = new PublicKey(process.argv[3]);
|
||||
if (!process.argv[3]) {
|
||||
throw new Error(
|
||||
"Использование: node 04_transfer_rights_to_governance_pda_exec.js <config.env> <mint>"
|
||||
);
|
||||
}
|
||||
|
||||
const cfg = parseEnvConfig(configPath);
|
||||
assertRequired(cfg, "GT_CLUSTER");
|
||||
assertRequired(cfg, "GT_OPERATOR_KEYPAIR_PATH");
|
||||
assertRequired(cfg, "GT_GOVERNANCE_PDA");
|
||||
|
||||
const operator = loadKeypair(path.resolve(cfg.GT_OPERATOR_KEYPAIR_PATH));
|
||||
const governancePda = new PublicKey(cfg.GT_GOVERNANCE_PDA);
|
||||
const connection = new Connection(parseCluster(cfg.GT_CLUSTER), "confirmed");
|
||||
|
||||
console.log("============================================================");
|
||||
console.log("ПЕРЕДАЧА ПРАВ УПРАВЛЕНИЯ ТОКЕНОМ НА GOVERNANCE PDA");
|
||||
console.log("------------------------------------------------------------");
|
||||
console.log("Сеть: ", cfg.GT_CLUSTER);
|
||||
console.log("Mint: ", mint.toBase58());
|
||||
console.log("Текущий оператор: ", operator.publicKey.toBase58());
|
||||
console.log("Новый authority (DAO PDA): ", governancePda.toBase58());
|
||||
console.log("Будет передано:");
|
||||
console.log(" 1) MintTokens authority");
|
||||
console.log(" 2) FreezeAccount authority");
|
||||
console.log(" 3) PermanentDelegate authority");
|
||||
console.log("После этого текущий оператор утратит эти права.");
|
||||
console.log("============================================================");
|
||||
|
||||
const ok = await askYes("Введите yes для подтверждения: ");
|
||||
if (!ok) {
|
||||
console.log("Отменено пользователем.");
|
||||
return;
|
||||
}
|
||||
|
||||
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",
|
||||
});
|
||||
console.log("Готово. Tx:", sig);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("Ошибка передачи прав:", e?.message || e);
|
||||
process.exit(1);
|
||||
});
|
||||
14
shine/scripts/governance_token/04_передать_права_governance_pda.sh
Executable file
14
shine/scripts/governance_token/04_передать_права_governance_pda.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_PATH="${1:-$SCRIPT_DIR/governance_token.config.env}"
|
||||
MINT="${2:-}"
|
||||
|
||||
if [[ -z "$MINT" ]]; then
|
||||
echo "Использование:"
|
||||
echo " $0 [config.env] <mint>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
node "$SCRIPT_DIR/04_transfer_rights_to_governance_pda_exec.js" "$CONFIG_PATH" "$MINT"
|
||||
@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { spawn } = require("child_process");
|
||||
const {
|
||||
parseEnvConfig,
|
||||
resolveConfigPath,
|
||||
nowStamp,
|
||||
} = require("./_common");
|
||||
|
||||
// Можно править прямо в начале файла
|
||||
const DEFAULT_PREFIX = "SH";
|
||||
const DEFAULT_MATCH_COUNT = 1;
|
||||
const DEFAULT_IGNORE_CASE = false;
|
||||
|
||||
function expectedTries(prefixLen) {
|
||||
return Math.pow(58, prefixLen);
|
||||
}
|
||||
|
||||
function ensureSolanaKeygen() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const p = spawn("solana-keygen", ["--version"], { stdio: ["ignore", "pipe", "pipe"] });
|
||||
p.on("error", reject);
|
||||
p.on("close", (code) => {
|
||||
if (code === 0) resolve();
|
||||
else reject(new Error("solana-keygen не найден или недоступен"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const configPath = resolveConfigPath(process.argv[2]);
|
||||
const cfg = parseEnvConfig(configPath);
|
||||
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;
|
||||
const count = Number(process.argv[4] || DEFAULT_MATCH_COUNT);
|
||||
const ignoreCase = (process.argv[5] || "").toLowerCase() === "ignore-case" || DEFAULT_IGNORE_CASE;
|
||||
if (!/^[1-9A-HJ-NP-Za-km-z]+$/.test(prefix)) {
|
||||
throw new Error("Префикс должен быть в Base58 (без 0 O I l)");
|
||||
}
|
||||
if (!Number.isInteger(count) || count <= 0) throw new Error("count должен быть целым > 0");
|
||||
|
||||
await ensureSolanaKeygen();
|
||||
|
||||
const expected = expectedTries(prefix.length);
|
||||
console.log("============================================================");
|
||||
console.log("VANITY MINT KEYPAIR GRIND");
|
||||
console.log("------------------------------------------------------------");
|
||||
console.log("Prefix:", prefix);
|
||||
console.log("Count:", count);
|
||||
console.log("Runs dir:", runsDir);
|
||||
console.log("Среднее ожидание на 1 совпадение:", expected.toLocaleString("en-US"), "попыток");
|
||||
console.log("============================================================");
|
||||
|
||||
const args = ["grind", "--starts-with", `${prefix}:${count}`];
|
||||
if (ignoreCase) args.push("--ignore-case");
|
||||
|
||||
const child = spawn("solana-keygen", args, {
|
||||
cwd: runsDir,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
const lines = [];
|
||||
let attempts = 0;
|
||||
let found = 0;
|
||||
|
||||
const onLine = (raw) => {
|
||||
const line = String(raw).trim();
|
||||
if (!line) return;
|
||||
lines.push(line);
|
||||
console.log(line);
|
||||
|
||||
const mAttempts = line.match(/Searched\s+([0-9,]+)\s+keypairs/i);
|
||||
if (mAttempts) {
|
||||
attempts = Number(mAttempts[1].replace(/,/g, "")) || attempts;
|
||||
const pct = Math.min(100, (attempts / expected) * 100);
|
||||
console.log(
|
||||
`[progress] attempts=${attempts.toLocaleString("en-US")} ~${pct.toFixed(2)}% от среднего ожидания`
|
||||
);
|
||||
}
|
||||
if (/Wrote keypair to/i.test(line) || /Found matching key/i.test(line)) found += 1;
|
||||
};
|
||||
|
||||
child.stdout.on("data", (d) => String(d).split("\n").forEach(onLine));
|
||||
child.stderr.on("data", (d) => String(d).split("\n").forEach(onLine));
|
||||
|
||||
const code = await new Promise((resolve) => child.on("close", resolve));
|
||||
if (code !== 0) {
|
||||
throw new Error(`solana-keygen grind завершился с кодом ${code}`);
|
||||
}
|
||||
|
||||
const report = {
|
||||
createdAt: new Date().toISOString(),
|
||||
prefix,
|
||||
count,
|
||||
ignoreCase,
|
||||
runsDir,
|
||||
avgExpectedTriesPerMatch: expected,
|
||||
attemptsObserved: attempts,
|
||||
foundHintsInOutput: found,
|
||||
command: `solana-keygen ${args.join(" ")}`,
|
||||
outputLog: lines,
|
||||
};
|
||||
|
||||
const reportPath = path.join(runsDir, `${nowStamp()}_vanity_grind_report.json`);
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||
console.log("Report:", reportPath);
|
||||
console.log("Готово. Keypair-файлы сохранены в:", runsDir);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("Ошибка vanity grind:", e?.message || e);
|
||||
process.exit(1);
|
||||
});
|
||||
14
shine/scripts/governance_token/05_подобрать_vanity_mint.sh
Executable file
14
shine/scripts/governance_token/05_подобрать_vanity_mint.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_PATH="${1:-$SCRIPT_DIR/governance_token.config.env}"
|
||||
PREFIX="${2:-}"
|
||||
COUNT="${3:-1}"
|
||||
IGNORE_CASE="${4:-}"
|
||||
|
||||
if [[ -n "$PREFIX" ]]; then
|
||||
node "$SCRIPT_DIR/05_vanity_mint_keypair_grind_exec.js" "$CONFIG_PATH" "$PREFIX" "$COUNT" "$IGNORE_CASE"
|
||||
else
|
||||
node "$SCRIPT_DIR/05_vanity_mint_keypair_grind_exec.js" "$CONFIG_PATH"
|
||||
fi
|
||||
51
shine/scripts/governance_token/README.md
Normal file
51
shine/scripts/governance_token/README.md
Normal file
@ -0,0 +1,51 @@
|
||||
# Governance Token Scripts
|
||||
|
||||
Скрипты для управления governance token на Token-2022:
|
||||
- `NonTransferable`
|
||||
- `PermanentDelegate`
|
||||
|
||||
## Конфиг
|
||||
|
||||
Файл: `scripts/governance_token/governance_token.config.env`
|
||||
|
||||
Ключи:
|
||||
- `GT_CLUSTER` (`devnet` / `mainnet-beta`)
|
||||
- `GT_OPERATOR_KEYPAIR_PATH`
|
||||
- `GT_GOVERNANCE_PDA`
|
||||
- `GT_MINT_KEYPAIR_PATH` (опционально)
|
||||
- `GT_RUNS_DIR`
|
||||
- `GT_VANITY_PREFIX`
|
||||
|
||||
## Скрипты
|
||||
|
||||
1. Создать новый governance token:
|
||||
|
||||
```bash
|
||||
node scripts/governance_token/01_create_governance_token_exec.js scripts/governance_token/governance_token.config.env
|
||||
```
|
||||
|
||||
2. Выпустить токен участнику:
|
||||
|
||||
```bash
|
||||
node scripts/governance_token/02_mint_membership_to_wallet_exec.js scripts/governance_token/governance_token.config.env <MINT> <WALLET> [AMOUNT]
|
||||
```
|
||||
|
||||
3. Принудительно сжечь токен у участника:
|
||||
|
||||
```bash
|
||||
node scripts/governance_token/03_force_burn_from_wallet_exec.js scripts/governance_token/governance_token.config.env <MINT> <WALLET> [AMOUNT]
|
||||
```
|
||||
|
||||
4. Передать права на Governance PDA (с подтверждением `yes`):
|
||||
|
||||
```bash
|
||||
node scripts/governance_token/04_transfer_rights_to_governance_pda_exec.js scripts/governance_token/governance_token.config.env <MINT>
|
||||
```
|
||||
|
||||
5. Vanity-подбор mint keypair через `solana-keygen grind`:
|
||||
|
||||
```bash
|
||||
node scripts/governance_token/05_vanity_mint_keypair_grind_exec.js scripts/governance_token/governance_token.config.env [PREFIX] [COUNT] [ignore-case]
|
||||
```
|
||||
|
||||
Результаты сохраняются в `GT_RUNS_DIR`.
|
||||
88
shine/scripts/governance_token/_common.js
Normal file
88
shine/scripts/governance_token/_common.js
Normal file
@ -0,0 +1,88 @@
|
||||
#!/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(`В конфиге отсутствует обязательный параметр: ${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 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 toPublicKey(v, fieldName) {
|
||||
try {
|
||||
return new PublicKey(v);
|
||||
} catch (_) {
|
||||
throw new Error(`Некорректный pubkey в ${fieldName}: ${v}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseEnvConfig,
|
||||
assertRequired,
|
||||
resolveConfigPath,
|
||||
loadKeypair,
|
||||
saveKeypair,
|
||||
parseCluster,
|
||||
nowStamp,
|
||||
askYes,
|
||||
toPublicKey,
|
||||
};
|
||||
19
shine/scripts/governance_token/governance_token.config.env
Normal file
19
shine/scripts/governance_token/governance_token.config.env
Normal file
@ -0,0 +1,19 @@
|
||||
# Конфиг governance token (Token-2022, non-transferable)
|
||||
|
||||
# devnet | mainnet-beta
|
||||
GT_CLUSTER="devnet"
|
||||
|
||||
# Путь к keypair кошелька-оператора (issuer/admin)
|
||||
GT_OPERATOR_KEYPAIR_PATH="$HOME/.config/solana/phantomWallet.json"
|
||||
|
||||
# Governance PDA (сюда передаем управляющие права после создания DAO)
|
||||
GT_GOVERNANCE_PDA="REPLACE_WITH_GOVERNANCE_PDA"
|
||||
|
||||
# Если нужен vanity mint, этот файл можно заполнить автоматически 05-скриптом
|
||||
GT_MINT_KEYPAIR_PATH=""
|
||||
|
||||
# Папка для результатов/логов
|
||||
GT_RUNS_DIR="./scripts/governance_token/runs"
|
||||
|
||||
# Дефолт для vanity-подбора (05)
|
||||
GT_VANITY_PREFIX="DAO"
|
||||
Loading…
Reference in New Issue
Block a user