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"
|
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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": {
|
"devDependencies": {
|
||||||
"chai": "^4.3.4",
|
|
||||||
"mocha": "^9.0.3",
|
|
||||||
"ts-mocha": "^10.0.0",
|
|
||||||
"@types/bn.js": "^5.1.0",
|
"@types/bn.js": "^5.1.0",
|
||||||
"@types/chai": "^4.3.0",
|
"@types/chai": "^4.3.0",
|
||||||
"@types/mocha": "^9.0.0",
|
"@types/mocha": "^9.0.0",
|
||||||
"typescript": "^5.7.3",
|
"chai": "^4.3.4",
|
||||||
"prettier": "^2.6.2"
|
"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