chore: commit all pending workspace changes
This commit is contained in:
parent
a43a929250
commit
f6c57d5366
@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
"use strict";
|
||||||
|
const path = require("path");
|
||||||
|
const BN = require("bn.js");
|
||||||
|
const { Connection, Transaction, sendAndConfirmTransaction } = require("@solana/web3.js");
|
||||||
|
const { getAssociatedTokenAddressSync, createAssociatedTokenAccountIdempotentInstruction, createMintToInstruction, TOKEN_PROGRAM_ID } = require("@solana/spl-token");
|
||||||
|
const { withDepositGoverningTokens, PROGRAM_VERSION_V3, getTokenOwnerRecordAddress } = require("@solana/spl-governance");
|
||||||
|
const { parseEnvConfig, resolveConfigPath, loadKeypair, clusterUrl, PublicKey } = require("./js_common");
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||||
|
const conn = new Connection(clusterUrl(cfg.CLUSTER), "confirmed");
|
||||||
|
const main = loadKeypair(path.resolve(__dirname, cfg.MAIN_KEYPAIR));
|
||||||
|
const voter2 = loadKeypair(path.resolve(__dirname, cfg.VOTER2_KEYPAIR));
|
||||||
|
const realm = new PublicKey(cfg.REALM);
|
||||||
|
const mint = new PublicKey(cfg.GOVERNING_MINT);
|
||||||
|
const govPid = new PublicKey(cfg.SPL_GOVERNANCE_PROGRAM_ID);
|
||||||
|
|
||||||
|
const ata = getAssociatedTokenAddressSync(mint, voter2.publicKey, false, TOKEN_PROGRAM_ID);
|
||||||
|
const ix1 = [
|
||||||
|
createAssociatedTokenAccountIdempotentInstruction(main.publicKey, ata, voter2.publicKey, mint, TOKEN_PROGRAM_ID),
|
||||||
|
createMintToInstruction(mint, ata, main.publicKey, 1n, [], TOKEN_PROGRAM_ID),
|
||||||
|
];
|
||||||
|
const sigMint = await sendAndConfirmTransaction(conn, new Transaction().add(...ix1), [main], { commitment: "confirmed" });
|
||||||
|
|
||||||
|
const tor = await getTokenOwnerRecordAddress(govPid, realm, mint, voter2.publicKey);
|
||||||
|
const ai = await conn.getAccountInfo(tor, "confirmed");
|
||||||
|
let sigDeposit = null;
|
||||||
|
if (!ai) {
|
||||||
|
const ix2 = [];
|
||||||
|
await withDepositGoverningTokens(ix2, govPid, PROGRAM_VERSION_V3, realm, ata, mint, voter2.publicKey, main.publicKey, voter2.publicKey, new BN(1), true);
|
||||||
|
sigDeposit = await sendAndConfirmTransaction(conn, new Transaction().add(...ix2), [main, voter2], { commitment: "confirmed" });
|
||||||
|
}
|
||||||
|
console.log("prepare done");
|
||||||
|
console.log("mint tx:", sigMint);
|
||||||
|
console.log("deposit tx:", sigDeposit || "already exists");
|
||||||
|
}
|
||||||
|
main().catch((e) => { console.error(e?.message || e); process.exit(1); });
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
"use strict";
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const BN = require("bn.js");
|
||||||
|
const { Connection, Keypair, SystemProgram, Transaction, sendAndConfirmTransaction } = require("@solana/web3.js");
|
||||||
|
const {
|
||||||
|
TOKEN_2022_PROGRAM_ID,
|
||||||
|
ExtensionType,
|
||||||
|
getMintLen,
|
||||||
|
createInitializeMintInstruction,
|
||||||
|
createInitializePermanentDelegateInstruction,
|
||||||
|
createInitializeNonTransferableMintInstruction,
|
||||||
|
getAssociatedTokenAddressSync,
|
||||||
|
createAssociatedTokenAccountIdempotentInstruction,
|
||||||
|
createMintToInstruction,
|
||||||
|
createSetAuthorityInstruction,
|
||||||
|
AuthorityType,
|
||||||
|
} = require("@solana/spl-token");
|
||||||
|
const { parseEnvConfig, resolveConfigPath, loadKeypair, clusterUrl, nowStamp, PublicKey } = require("./js_common");
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const target = process.argv[3];
|
||||||
|
if (!target) throw new Error("Usage: node 01_create_nft_for_wallet_admin.js <config.env> <target_wallet>");
|
||||||
|
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||||
|
const conn = new Connection(clusterUrl(cfg.CLUSTER), "confirmed");
|
||||||
|
const main = loadKeypair(path.resolve(__dirname, cfg.MAIN_KEYPAIR));
|
||||||
|
const governance = new PublicKey(cfg.GOVERNANCE);
|
||||||
|
const targetPk = new PublicKey(target);
|
||||||
|
const mint = Keypair.generate();
|
||||||
|
const mintLen = getMintLen([ExtensionType.NonTransferable, ExtensionType.PermanentDelegate]);
|
||||||
|
const rentMint = await conn.getMinimumBalanceForRentExemption(mintLen, "confirmed");
|
||||||
|
const ata = getAssociatedTokenAddressSync(mint.publicKey, targetPk, false, TOKEN_2022_PROGRAM_ID);
|
||||||
|
const tx = new Transaction().add(
|
||||||
|
SystemProgram.createAccount({ fromPubkey: main.publicKey, newAccountPubkey: mint.publicKey, space: mintLen, lamports: rentMint, programId: TOKEN_2022_PROGRAM_ID }),
|
||||||
|
createInitializeNonTransferableMintInstruction(mint.publicKey, TOKEN_2022_PROGRAM_ID),
|
||||||
|
createInitializePermanentDelegateInstruction(mint.publicKey, main.publicKey, TOKEN_2022_PROGRAM_ID),
|
||||||
|
createInitializeMintInstruction(mint.publicKey, 0, main.publicKey, main.publicKey, TOKEN_2022_PROGRAM_ID),
|
||||||
|
createAssociatedTokenAccountIdempotentInstruction(main.publicKey, ata, targetPk, mint.publicKey, TOKEN_2022_PROGRAM_ID),
|
||||||
|
createMintToInstruction(mint.publicKey, ata, main.publicKey, 1n, [], TOKEN_2022_PROGRAM_ID),
|
||||||
|
createSetAuthorityInstruction(mint.publicKey, main.publicKey, AuthorityType.MintTokens, governance, [], TOKEN_2022_PROGRAM_ID),
|
||||||
|
createSetAuthorityInstruction(mint.publicKey, main.publicKey, AuthorityType.FreezeAccount, governance, [], TOKEN_2022_PROGRAM_ID),
|
||||||
|
createSetAuthorityInstruction(mint.publicKey, main.publicKey, AuthorityType.PermanentDelegate, governance, [], TOKEN_2022_PROGRAM_ID)
|
||||||
|
);
|
||||||
|
const sig = await sendAndConfirmTransaction(conn, tx, [main, mint], { commitment: "confirmed" });
|
||||||
|
const runs = path.resolve(__dirname, cfg.RUNS_DIR || "./runs");
|
||||||
|
fs.mkdirSync(runs, { recursive: true });
|
||||||
|
const report = { createdAt: new Date().toISOString(), mint: mint.publicKey.toBase58(), owner: targetPk.toBase58(), ata: ata.toBase58(), tx: sig };
|
||||||
|
const rp = path.join(runs, `${nowStamp()}_admin_create_nft_${targetPk.toBase58().slice(0,8)}.json`);
|
||||||
|
fs.writeFileSync(rp, JSON.stringify(report, null, 2));
|
||||||
|
console.log("NFT created and delegated to governance");
|
||||||
|
console.log("mint:", report.mint);
|
||||||
|
console.log("owner:", report.owner);
|
||||||
|
console.log("report:", rp);
|
||||||
|
}
|
||||||
|
main().catch((e)=>{console.error(e?.message||e);process.exit(1);});
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
#!/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,
|
||||||
|
createInitializePermanentDelegateInstruction,
|
||||||
|
createInitializeNonTransferableMintInstruction,
|
||||||
|
createSetAuthorityInstruction,
|
||||||
|
AuthorityType,
|
||||||
|
} = require("@solana/spl-token");
|
||||||
|
const { parseEnvConfig, resolveConfigPath, loadKeypair, clusterUrl, nowStamp, PublicKey } = require("./js_common");
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||||
|
const conn = new Connection(clusterUrl(cfg.CLUSTER), "confirmed");
|
||||||
|
const main = loadKeypair(path.resolve(__dirname, cfg.MAIN_KEYPAIR));
|
||||||
|
const governance = new PublicKey(cfg.GOVERNANCE);
|
||||||
|
const mint = Keypair.generate();
|
||||||
|
const mintLen = getMintLen([ExtensionType.NonTransferable, ExtensionType.PermanentDelegate]);
|
||||||
|
const rentMint = await conn.getMinimumBalanceForRentExemption(mintLen, "confirmed");
|
||||||
|
const tx = new Transaction().add(
|
||||||
|
SystemProgram.createAccount({ fromPubkey: main.publicKey, newAccountPubkey: mint.publicKey, space: mintLen, lamports: rentMint, programId: TOKEN_2022_PROGRAM_ID }),
|
||||||
|
createInitializeNonTransferableMintInstruction(mint.publicKey, TOKEN_2022_PROGRAM_ID),
|
||||||
|
createInitializePermanentDelegateInstruction(mint.publicKey, main.publicKey, TOKEN_2022_PROGRAM_ID),
|
||||||
|
createInitializeMintInstruction(mint.publicKey, 0, main.publicKey, main.publicKey, TOKEN_2022_PROGRAM_ID),
|
||||||
|
createSetAuthorityInstruction(mint.publicKey, main.publicKey, AuthorityType.MintTokens, governance, [], TOKEN_2022_PROGRAM_ID),
|
||||||
|
createSetAuthorityInstruction(mint.publicKey, main.publicKey, AuthorityType.FreezeAccount, governance, [], TOKEN_2022_PROGRAM_ID),
|
||||||
|
createSetAuthorityInstruction(mint.publicKey, main.publicKey, AuthorityType.PermanentDelegate, governance, [], TOKEN_2022_PROGRAM_ID)
|
||||||
|
);
|
||||||
|
const sig = await sendAndConfirmTransaction(conn, tx, [main, mint], { commitment: "confirmed" });
|
||||||
|
const runs = path.resolve(__dirname, cfg.RUNS_DIR || "./runs");
|
||||||
|
fs.mkdirSync(runs, { recursive: true });
|
||||||
|
const rp = path.join(runs, `${nowStamp()}_empty_nft_template.json`);
|
||||||
|
fs.writeFileSync(rp, JSON.stringify({ mint: mint.publicKey.toBase58(), tx: sig, createdAt: new Date().toISOString() }, null, 2));
|
||||||
|
console.log("EMPTY NFT template created");
|
||||||
|
console.log("mint:", mint.publicKey.toBase58());
|
||||||
|
console.log("report:", rp);
|
||||||
|
}
|
||||||
|
main().catch((e)=>{console.error(e?.message||e);process.exit(1);});
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
#!/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 { TOKEN_2022_PROGRAM_ID, getAssociatedTokenAddressSync, createMintToInstruction } = require("@solana/spl-token");
|
||||||
|
const {
|
||||||
|
PROGRAM_VERSION_V3, Vote, YesNoVote, VoteType,
|
||||||
|
withCreateProposal, withInsertTransaction, withSignOffProposal, withCastVote,
|
||||||
|
getTokenOwnerRecordAddress, getProposalTransactionAddress
|
||||||
|
} = require("@solana/spl-governance");
|
||||||
|
const { parseEnvConfig, resolveConfigPath, loadKeypair, clusterUrl, nowStamp, 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 02_propose_vote_mint_nft.js <config.env> <target_wallet> <nft_mint>");
|
||||||
|
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||||
|
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 ataExists = (await conn.getAccountInfo(targetAta, "confirmed")) !== null;
|
||||||
|
if (!ataExists) throw new Error(`Target ATA not found. Create it first: ${targetAta.toBase58()}`);
|
||||||
|
|
||||||
|
const ixCreate = [];
|
||||||
|
const proposal = await withCreateProposal(ixCreate, govPid, PROGRAM_VERSION_V3, realm, governance, mainTor, `Mint NFT to ${target.toBase58().slice(0,8)}`, "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 = [createMintToInstruction(nftMint, targetAta, governance, 1n, [], TOKEN_2022_PROGRAM_ID)];
|
||||||
|
const insertData = mintIx.map(toInstructionData);
|
||||||
|
const ixInsert = [];
|
||||||
|
const proposalTx = await withInsertTransaction(ixInsert, govPid, PROGRAM_VERSION_V3, governance, proposal, mainTor, main.publicKey, 0, 0, 0, insertData, 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 || "./runs"); fs.mkdirSync(runs, { recursive: true });
|
||||||
|
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 rp = path.join(runs, `${nowStamp()}_proposal_mint_${target.toBase58().slice(0,8)}.json`);
|
||||||
|
fs.writeFileSync(rp, JSON.stringify(report, null, 2));
|
||||||
|
console.log("proposal mint created and voted");
|
||||||
|
console.log("report:", rp);
|
||||||
|
console.log("execute command:");
|
||||||
|
console.log(`node 03_execute_mint_nft.js ${resolveConfigPath(process.argv[2])} ${proposal.toBase58()} ${proposalTx.toBase58()} ${nftMint.toBase58()} ${target.toBase58()}`);
|
||||||
|
}
|
||||||
|
main().catch((e)=>{console.error(e?.message||e);process.exit(1);});
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
"use strict";
|
||||||
|
const { Connection, PublicKey, Transaction, sendAndConfirmTransaction } = require("@solana/web3.js");
|
||||||
|
const { TOKEN_2022_PROGRAM_ID, getAssociatedTokenAddressSync, createMintToInstruction } = require("@solana/spl-token");
|
||||||
|
const { PROGRAM_VERSION_V3, withExecuteTransaction } = require("@solana/spl-governance");
|
||||||
|
const { parseEnvConfig, resolveConfigPath, loadKeypair, clusterUrl, toInstructionData } = require("./js_common");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||||
|
const proposal = new PublicKey(process.argv[3]);
|
||||||
|
const proposalTx = new PublicKey(process.argv[4]);
|
||||||
|
const nftMint = new PublicKey(process.argv[5]);
|
||||||
|
const target = new PublicKey(process.argv[6]);
|
||||||
|
if (!process.argv[6]) throw new Error("Usage: node 03_execute_mint_nft.js <config.env> <proposal> <proposalTx> <nftMint> <targetWallet>");
|
||||||
|
const conn = new Connection(clusterUrl(cfg.CLUSTER), "confirmed");
|
||||||
|
const main = loadKeypair(path.resolve(__dirname, cfg.MAIN_KEYPAIR));
|
||||||
|
const governance = new PublicKey(cfg.GOVERNANCE); const govPid = new PublicKey(cfg.SPL_GOVERNANCE_PROGRAM_ID);
|
||||||
|
const targetAta = getAssociatedTokenAddressSync(nftMint, target, false, TOKEN_2022_PROGRAM_ID);
|
||||||
|
const ataExists = (await conn.getAccountInfo(targetAta, "confirmed")) !== null;
|
||||||
|
if (!ataExists) throw new Error(`Target ATA not found. Create it first: ${targetAta.toBase58()}`);
|
||||||
|
const mintIx = [
|
||||||
|
createMintToInstruction(nftMint, targetAta, governance, 1n, [], TOKEN_2022_PROGRAM_ID),
|
||||||
|
].map(toInstructionData);
|
||||||
|
const ix = [];
|
||||||
|
await withExecuteTransaction(ix, govPid, PROGRAM_VERSION_V3, governance, proposal, proposalTx, mintIx);
|
||||||
|
const sig = await sendAndConfirmTransaction(conn, new Transaction().add(...ix), [main], { commitment: "confirmed" });
|
||||||
|
console.log("execute mint done:", sig);
|
||||||
|
}
|
||||||
|
main().catch((e)=>{console.error(e?.message||e);process.exit(1);});
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
#!/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, createBurnCheckedInstruction } = require("@solana/spl-token");
|
||||||
|
const {
|
||||||
|
PROGRAM_VERSION_V3, Vote, YesNoVote, VoteType,
|
||||||
|
withCreateProposal, withInsertTransaction, withSignOffProposal, withCastVote,
|
||||||
|
getTokenOwnerRecordAddress
|
||||||
|
} = require("@solana/spl-governance");
|
||||||
|
const { parseEnvConfig, resolveConfigPath, loadKeypair, clusterUrl, nowStamp, 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 04_propose_vote_burn_nft.js <config.env> <target_wallet> <nft_mint>");
|
||||||
|
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||||
|
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 sourceAta = getAssociatedTokenAddressSync(nftMint, target, false, TOKEN_2022_PROGRAM_ID);
|
||||||
|
|
||||||
|
const ixCreate = [];
|
||||||
|
const proposal = await withCreateProposal(ixCreate, govPid, PROGRAM_VERSION_V3, realm, governance, mainTor, `Burn NFT ${nftMint.toBase58().slice(0,8)}`, "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 burnIx = [toInstructionData(createBurnCheckedInstruction(sourceAta, nftMint, governance, 1n, 0, [], TOKEN_2022_PROGRAM_ID))];
|
||||||
|
const ixInsert = [];
|
||||||
|
const proposalTx = await withInsertTransaction(ixInsert, govPid, PROGRAM_VERSION_V3, governance, proposal, mainTor, main.publicKey, 0, 0, 0, burnIx, 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 runs = path.resolve(__dirname, cfg.RUNS_DIR || "./runs"); fs.mkdirSync(runs, { recursive: true });
|
||||||
|
const report = { type: "burn_nft", realm: realm.toBase58(), governance: governance.toBase58(), proposal: proposal.toBase58(), proposalTransaction: proposalTx.toBase58(), nftMint: nftMint.toBase58(), targetWallet: target.toBase58(), sourceAta: sourceAta.toBase58(), txCreate, txInsert, txSign, txVote1 };
|
||||||
|
const rp = path.join(runs, `${nowStamp()}_proposal_burn_${target.toBase58().slice(0,8)}.json`);
|
||||||
|
fs.writeFileSync(rp, JSON.stringify(report, null, 2));
|
||||||
|
console.log("proposal burn created and voted");
|
||||||
|
console.log("report:", rp);
|
||||||
|
console.log("execute command:");
|
||||||
|
console.log(`node 05_execute_burn_nft.js ${resolveConfigPath(process.argv[2])} ${proposal.toBase58()} ${proposalTx.toBase58()} ${nftMint.toBase58()} ${target.toBase58()}`);
|
||||||
|
}
|
||||||
|
main().catch((e)=>{console.error(e?.message||e);process.exit(1);});
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
#!/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 { PROGRAM_VERSION_V3, withExecuteTransaction } = require("@solana/spl-governance");
|
||||||
|
const { parseEnvConfig, resolveConfigPath, loadKeypair, clusterUrl, toInstructionData } = require("./js_common");
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2]));
|
||||||
|
const proposal = new PublicKey(process.argv[3]);
|
||||||
|
const proposalTx = new PublicKey(process.argv[4]);
|
||||||
|
const nftMint = new PublicKey(process.argv[5]);
|
||||||
|
const target = new PublicKey(process.argv[6]);
|
||||||
|
if (!process.argv[6]) throw new Error("Usage: node 05_execute_burn_nft.js <config.env> <proposal> <proposalTx> <nftMint> <targetWallet>");
|
||||||
|
const conn = new Connection(clusterUrl(cfg.CLUSTER), "confirmed");
|
||||||
|
const main = loadKeypair(path.resolve(__dirname, cfg.MAIN_KEYPAIR));
|
||||||
|
const governance = new PublicKey(cfg.GOVERNANCE); const govPid = new PublicKey(cfg.SPL_GOVERNANCE_PROGRAM_ID);
|
||||||
|
const sourceAta = getAssociatedTokenAddressSync(nftMint, target, false, TOKEN_2022_PROGRAM_ID);
|
||||||
|
const burnIx = [toInstructionData(createBurnCheckedInstruction(sourceAta, nftMint, governance, 1n, 0, [], TOKEN_2022_PROGRAM_ID))];
|
||||||
|
const ix = [];
|
||||||
|
await withExecuteTransaction(ix, govPid, PROGRAM_VERSION_V3, governance, proposal, proposalTx, burnIx);
|
||||||
|
const sig = await sendAndConfirmTransaction(conn, new Transaction().add(...ix), [main], { commitment: "confirmed" });
|
||||||
|
console.log("execute burn done:", sig);
|
||||||
|
}
|
||||||
|
main().catch((e)=>{console.error(e?.message||e);process.exit(1);});
|
||||||
66
shine/scripts/CreateGovernmentNFTAndDAO/README.md
Normal file
66
shine/scripts/CreateGovernmentNFTAndDAO/README.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# CreateGovernmentNFTAndDAO
|
||||||
|
|
||||||
|
## RU
|
||||||
|
|
||||||
|
Скрипты для Devnet, чтобы управлять NFT через DAO (Realms/SPL Governance):
|
||||||
|
1) создать предложение на выпуск NFT (`mint`) и выполнить его;
|
||||||
|
2) создать предложение на сжигание NFT (`burn`) и выполнить его.
|
||||||
|
|
||||||
|
### Что лежит в папке
|
||||||
|
|
||||||
|
- `config.env` — параметры кластера, DAO, ключей.
|
||||||
|
- `keypairs/` — ключи оператора и второго участника.
|
||||||
|
- `runs/` — отчёты запусков (proposal, tx и т.д.).
|
||||||
|
- `00_prepare_voter2_deposit.js` — депонирование governance-токена для второго голосующего.
|
||||||
|
- `01_create_nft_for_wallet_admin.js` — создать NFT на кошелёк и делегировать право governance PDA.
|
||||||
|
- `01b_create_empty_nft_template.js` — создать пустой NFT mint-шаблон (supply=0) для будущего DAO mint.
|
||||||
|
- `02_propose_vote_mint_nft.js` — создать+подписать+проголосовать за proposal на mint.
|
||||||
|
- `03_execute_mint_nft.js` — выполнить proposal mint.
|
||||||
|
- `04_propose_vote_burn_nft.js` — создать+подписать+проголосовать за proposal на burn.
|
||||||
|
- `05_execute_burn_nft.js` — выполнить proposal burn.
|
||||||
|
|
||||||
|
### Важно перед запуском
|
||||||
|
|
||||||
|
1. Нужен `node`, `@solana/web3.js`, `@solana/spl-token`, `@solana/spl-governance`.
|
||||||
|
2. В `config.env` должен быть корректный `REALM`, `GOVERNANCE`, `GOVERNING_MINT`, `MAIN_KEYPAIR`.
|
||||||
|
3. Для `mint via DAO` целевой ATA должен существовать заранее (скрипт `02` это проверяет).
|
||||||
|
|
||||||
|
### Быстрый полный тест (mint + burn)
|
||||||
|
|
||||||
|
1. Создать NFT-шаблон (куда DAO будет минтить):
|
||||||
|
- `node 01b_create_empty_nft_template.js ./config.env`
|
||||||
|
2. Создать ATA для целевого кошелька и этого mint (если ещё нет).
|
||||||
|
3. Поднять proposal на mint:
|
||||||
|
- `node 02_propose_vote_mint_nft.js ./config.env <target_wallet> <nft_mint>`
|
||||||
|
4. Выполнить proposal (команду берёшь из консоли шага 3):
|
||||||
|
- `node 03_execute_mint_nft.js ./config.env <proposal> <proposal_tx> <nft_mint> <target_wallet>`
|
||||||
|
5. Создать NFT для burn-теста:
|
||||||
|
- `node 01_create_nft_for_wallet_admin.js ./config.env <wallet_with_nft>`
|
||||||
|
6. Поднять proposal на burn:
|
||||||
|
- `node 04_propose_vote_burn_nft.js ./config.env <wallet_with_nft> <nft_mint>`
|
||||||
|
7. Выполнить proposal burn (команда из шага 6):
|
||||||
|
- `node 05_execute_burn_nft.js ./config.env <proposal> <proposal_tx> <nft_mint> <wallet_with_nft>`
|
||||||
|
|
||||||
|
### Как проверить результат
|
||||||
|
|
||||||
|
Смотри JSON-отчёты в `runs/`: там есть `proposal`, `proposalTransaction`, tx подписи и mint/кошельки.
|
||||||
|
|
||||||
|
Для проверки через час:
|
||||||
|
1) поднимаешь proposal (скрипт `02` или `04`);
|
||||||
|
2) ждёшь;
|
||||||
|
3) запускаешь соответствующий `execute` скрипт с параметрами из отчёта.
|
||||||
|
|
||||||
|
### Проверка DAO
|
||||||
|
|
||||||
|
В текущем `config.env`:
|
||||||
|
- Realm: `2DTh1ivaekAW8kRYzGPsL2taFLJFFkBjEwqPisebxsS7`
|
||||||
|
- Governance PDA: `EMZ8vmr1xB4HZBDCFL9rHB98m1C5cYrGnRA8ZHayyGwD`
|
||||||
|
- Governing mint: `F1KctLRvVzqwcBYNGsivnjR39gY8Uvq5U3uyaqEBNASg`
|
||||||
|
|
||||||
|
## EN
|
||||||
|
|
||||||
|
Devnet scripts for DAO-governed NFT flow (Realms/SPL Governance):
|
||||||
|
- propose/sign/vote/execute NFT mint to a wallet;
|
||||||
|
- propose/sign/vote/execute NFT burn from a wallet.
|
||||||
|
|
||||||
|
Main idea: first script in each pair creates proposal and vote, second script executes proposal later.
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
# TEMP Devnet Report (2026-05-15)
|
||||||
|
|
||||||
|
## Актуальный DAO из config.env
|
||||||
|
|
||||||
|
- Realm: `2DTh1ivaekAW8kRYzGPsL2taFLJFFkBjEwqPisebxsS7`
|
||||||
|
- Governance PDA: `EMZ8vmr1xB4HZBDCFL9rHB98m1C5cYrGnRA8ZHayyGwD`
|
||||||
|
- Governing mint: `F1KctLRvVzqwcBYNGsivnjR39gY8Uvq5U3uyaqEBNASg`
|
||||||
|
|
||||||
|
## Подтверждённый успешный цикл (mint + burn через DAO)
|
||||||
|
|
||||||
|
- NFT minted by DAO:
|
||||||
|
- mint: `4xU8omSH3RfTyDHxWVCEm1HVTcf97YWkT8H67GvVpssz`
|
||||||
|
- target ATA: `2fUHqDDdcCcxyG62dZwyNiT2H9xZc3Rgy9oFtWiTdCXr`
|
||||||
|
- result: `amount=1`, `supply=1`
|
||||||
|
- NFT burned by DAO:
|
||||||
|
- mint: `9Up5SURRoBybsrfZnR7nKZC5gHarrccdVuMzoxeU3Xia`
|
||||||
|
- source ATA: `3Q3cfBrNgyagEpMNPEZngYpCgw9U8Bd2KK4BbBfzvL5u`
|
||||||
|
- result: `amount=0`, `supply=0`
|
||||||
|
|
||||||
|
Стоимость этого полного цикла: **`0.22709372 SOL`**.
|
||||||
|
|
||||||
|
## Тест "запустить сейчас, проверить/выполнить через час"
|
||||||
|
|
||||||
|
Создан новый proposal на mint (уже signed + voted):
|
||||||
|
- report: `runs/2026-05-15_18-29-59_proposal_mint_HMww7YSV.json`
|
||||||
|
- proposal: `CKV2RSJ4HiUGQvdhGyizub89csTF52TD2Z2HciQFPkdW`
|
||||||
|
- proposalTx: `AAvEV3q9MeHedbJMsTk1C8nLg3LJtTLUBWt9fgi2wnqC`
|
||||||
|
- nft mint: `hssoT46Vp7KzisNAffBSQxGLmtxfzcswVeeEi4eq8gW`
|
||||||
|
- target wallet: `HMww7YSVfwVm4i8sugqj7wyH26dqzHykzv3wzWwzEvPA`
|
||||||
|
|
||||||
|
Команда для выполнения через час:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node 03_execute_mint_nft.js ./config.env CKV2RSJ4HiUGQvdhGyizub89csTF52TD2Z2HciQFPkdW AAvEV3q9MeHedbJMsTk1C8nLg3LJtTLUBWt9fgi2wnqC hssoT46Vp7KzisNAffBSQxGLmtxfzcswVeeEi4eq8gW HMww7YSVfwVm4i8sugqj7wyH26dqzHykzv3wzWwzEvPA
|
||||||
|
```
|
||||||
|
|
||||||
|
Если execute пройдёт успешно — DAO путь для mint подтверждён повторно.
|
||||||
13
shine/scripts/CreateGovernmentNFTAndDAO/config.env
Normal file
13
shine/scripts/CreateGovernmentNFTAndDAO/config.env
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
CLUSTER="devnet"
|
||||||
|
SPL_GOVERNANCE_PROGRAM_ID="GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw"
|
||||||
|
|
||||||
|
# DAO for NFT governance flow (created 2026-05-15, single-voter supply)
|
||||||
|
REALM="2DTh1ivaekAW8kRYzGPsL2taFLJFFkBjEwqPisebxsS7"
|
||||||
|
GOVERNANCE="EMZ8vmr1xB4HZBDCFL9rHB98m1C5cYrGnRA8ZHayyGwD"
|
||||||
|
GOVERNING_MINT="F1KctLRvVzqwcBYNGsivnjR39gY8Uvq5U3uyaqEBNASg"
|
||||||
|
|
||||||
|
# voters
|
||||||
|
MAIN_KEYPAIR="./keypairs/main_FUc28.json"
|
||||||
|
VOTER2_KEYPAIR="./keypairs/voter2_HMww7.json"
|
||||||
|
|
||||||
|
RUNS_DIR="./runs"
|
||||||
50
shine/scripts/CreateGovernmentNFTAndDAO/js_common.js
Normal file
50
shine/scripts/CreateGovernmentNFTAndDAO/js_common.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
"use strict";
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const { PublicKey, Keypair, clusterApiUrl } = require("@solana/web3.js");
|
||||||
|
const { InstructionData, AccountMetaData } = require("@solana/spl-governance");
|
||||||
|
|
||||||
|
function parseEnvConfig(configPath) {
|
||||||
|
const raw = fs.readFileSync(configPath, "utf8");
|
||||||
|
const out = {};
|
||||||
|
for (const line of raw.split("\n")) {
|
||||||
|
const t = line.trim();
|
||||||
|
if (!t || t.startsWith("#")) continue;
|
||||||
|
const i = t.indexOf("=");
|
||||||
|
if (i < 0) continue;
|
||||||
|
const k = t.slice(0, i).trim();
|
||||||
|
let v = t.slice(i + 1).trim();
|
||||||
|
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) v = v.slice(1, -1);
|
||||||
|
out[k] = v;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveConfigPath(argvPath) {
|
||||||
|
return argvPath ? path.resolve(argvPath) : path.resolve(__dirname, "config.env");
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadKeypair(fp) {
|
||||||
|
return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(fs.readFileSync(fp, "utf8"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clusterUrl(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())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toInstructionData(ix) {
|
||||||
|
return new InstructionData({
|
||||||
|
programId: ix.programId,
|
||||||
|
accounts: ix.keys.map((k) => new AccountMetaData({ pubkey: k.pubkey, isSigner: !!k.isSigner, isWritable: !!k.isWritable })),
|
||||||
|
data: Uint8Array.from(ix.data),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { parseEnvConfig, resolveConfigPath, loadKeypair, clusterUrl, nowStamp, toInstructionData, PublicKey };
|
||||||
@ -0,0 +1 @@
|
|||||||
|
[221,119,143,125,90,136,155,115,191,198,210,85,228,111,251,118,168,138,27,60,249,62,247,24,121,228,139,112,218,69,55,143,215,21,229,69,219,1,74,36,10,239,63,163,48,240,58,208,237,251,209,37,17,202,215,77,13,165,178,18,141,21,193,64]
|
||||||
@ -0,0 +1 @@
|
|||||||
|
[241,27,187,46,105,28,241,60,120,167,90,61,230,106,125,146,230,244,198,39,162,46,76,224,131,59,229,157,27,45,29,78,243,24,184,76,185,238,209,112,35,54,121,13,2,104,76,182,80,33,144,21,30,188,173,23,63,146,192,200,40,126,139,145]
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"createdAt": "2026-05-15T15:06:41.395Z",
|
||||||
|
"mint": "3HJQNtkpP4pv1uKe1XDWRkp1KjY5qmEMEZqzNwDBHuph",
|
||||||
|
"owner": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"ata": "CHr24KW5RY3hR1dnQATEMq9ZxFuqorDmoc95TZsmJLQG",
|
||||||
|
"tx": "5nSjS43xRUdVJH4V5xZygcdUjzz1dFUJBVQc4rQwyZZAm7juGeVtKdWHrhb8tXLLcnZmKasZWPAqfqQpXWYtqhs"
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"mint": "HgbScrAw5HN8dx5VG9w5JvGQqgXvinbLwL48VvVfBS6d",
|
||||||
|
"tx": "4Zk2FBjiEaP9C1kVdADkFX3gD12Lsw9vF7X3E3wYH58CN1fGceU2vnEYE8c3kcevAFeqw7LEr4JPxMbkU7ujREwU",
|
||||||
|
"createdAt": "2026-05-15T15:06:48.909Z"
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "mint_nft",
|
||||||
|
"realm": "H35UPU98sC2bo265Q4R6PWdvbaotbbCvjDcuvwjDvewf",
|
||||||
|
"governance": "9aD18P5nun1RPVpEeeCeG5ensyry9WKrwjdX4stVa7qP",
|
||||||
|
"proposal": "FE5Bmd1ojeSNL9mVYP9E5EgjqMKwpGeaj5fjXBuh1yUB",
|
||||||
|
"proposalTransaction": "AxCuhhgk1DjYMkijePe3fpr6t9LCb8bNaYq1ksEbKKa1",
|
||||||
|
"nftMint": "HgbScrAw5HN8dx5VG9w5JvGQqgXvinbLwL48VvVfBS6d",
|
||||||
|
"targetWallet": "HMww7YSVfwVm4i8sugqj7wyH26dqzHykzv3wzWwzEvPA",
|
||||||
|
"targetAta": "65FUT9jtrscCCTjsxd3z1DMwG4S1Yq2Sd9ZupcocpoK",
|
||||||
|
"txCreate": "4iu3DmStaP94J3rZ3mmCNpv4XycLa82PyXj8HEyijUiX3BDcrjaL6GQahjbUjLhXaREz1RiGNTsq8ChDGtJjFTMp",
|
||||||
|
"txInsert": "kiWnMQGvVwckLXrjPVNVStKQyXyHMhAn2pY9tSeyeW898zat84KUCYQrDDkhs6pLe5PrczUPViGVCDNzFiMe16w",
|
||||||
|
"txSign": "52dD55YaitkKLQmASeJDPiy64eeGTWiNktDTAcTEB4u7cVisxtoVGPyPbatEKDbcMZKkUBc7VMtBuDz7xefMyfn5",
|
||||||
|
"txVote1": "hApj3uocwAep4a58GSqsVW2vt7bwPZMawfPRs3hwNvYNEtQBWrcW32eGPWuPYjtcVunbxizy5kDXe8QwMiA4h8k"
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"createdAt": "2026-05-15T15:08:51.409Z",
|
||||||
|
"cluster": "devnet",
|
||||||
|
"realmName": "NFT-DAO-0515-E",
|
||||||
|
"governanceProgramId": "GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw",
|
||||||
|
"governingMint": "2yf1CwPzdBoDM7a376CGSfyEAf58CHtA4rZRGwNeUTNd",
|
||||||
|
"operator": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"realm": "8yRYrnMWTrhHieegyGniMzskwPFZTka8JNZeKppoD6Zw",
|
||||||
|
"governance": "8V676kt5LAY3CawCDsbpLheTds9RPJSn6CdwpHew6GD6",
|
||||||
|
"nativeTreasury": "642nn5B6nvsHrTnM75Jb8wW4JFye11n37Mzc3u4b7GNz",
|
||||||
|
"tokenOwnerRecord": "EfW4fSPKqXcWYEEYZWXY1Nk3FtmaE1VJij8uZNjnCUwR",
|
||||||
|
"txRealm": "A6Rmb6jKDKrx5mc3Lge8iPe2nJMNpoczw7mmpQBS9Z7131viztzzGtx1z3xwzNfh1nU5rDekyLrcCn4ZpkDF6kE",
|
||||||
|
"txDeposit": "4hk68TjzKVrnpjse2SUSXuCzQfYEHUr2jvKVuUGDFABkvrKFHopfuDvcrsWwKoKCS9crM9oXnUy1asnPTAsZya69",
|
||||||
|
"txGovernanceTreasury": "4gGjGwjrPzvWYG7unBbWQrrCRcfuRiD7E6FQBGv6WcJtnuerPgo9nCWo6dygNEAeWPpYQRDRA5sjmKwDBc2yWUDM",
|
||||||
|
"txSetRealmAuthority": "W6zWxPKAge7Nu4o9uUiL5AxKYBuhc1g6C8BtqQrhqdtTgYPNxFwZDpvFsm1WEEM1ewhxUqn5qHWvhJDDtrPXvPc"
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"createdAt": "2026-05-15T15:09:35.073Z",
|
||||||
|
"cluster": "devnet",
|
||||||
|
"realmName": "NFT-DAO-0515-F",
|
||||||
|
"governanceProgramId": "GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw",
|
||||||
|
"governingMint": "F1KctLRvVzqwcBYNGsivnjR39gY8Uvq5U3uyaqEBNASg",
|
||||||
|
"operator": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"realm": "2DTh1ivaekAW8kRYzGPsL2taFLJFFkBjEwqPisebxsS7",
|
||||||
|
"governance": "EMZ8vmr1xB4HZBDCFL9rHB98m1C5cYrGnRA8ZHayyGwD",
|
||||||
|
"nativeTreasury": "3L9neAHgGb158ieWs7LBbHUaH3qrSN6AnLGPmNNpF5Mq",
|
||||||
|
"tokenOwnerRecord": "94JCGgmCxoen3JfN75rV3DRHpJ5QA7TaisW8Y9CnwvcY",
|
||||||
|
"txRealm": "2j2cDJ6joMn6XuRv4sttm7fbAsSRTh8e17TCSTEpV7k1LSFYnRHTmccCVew2w86vWc7gQCJnZKXzJTrRxeopXqAR",
|
||||||
|
"txDeposit": "4ccx97DF3ezqpFaUhLLE1n9L7HwFibYyv8mwnPcE5EGQJnL4Tq61vUa79ES1mD7qJrak6kwGTeZa1ELVSJy3R6Wi",
|
||||||
|
"txGovernanceTreasury": "66drnSSkhB4ES6nFMSdQbAK9khMo469c6DwA71XJU2jzUKog1UgzYZ99d3SBCsRHpJhakT7J71xmP2MvgfTiuGun",
|
||||||
|
"txSetRealmAuthority": "2zsYuJd1nGTPoUenS4eDeVxwHrEtTLss4hBRBjcciZm8Xv3zsMqaJwGnHUETNquPijeUAcn4vn9EDHpAfGTzAA4b"
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"createdAt": "2026-05-15T15:09:52.941Z",
|
||||||
|
"mint": "744E11uXxhuy2AwVcrYwrC6yeF5GzEtmcL6Z9o6NzZV",
|
||||||
|
"owner": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"ata": "8xQnr4dh3aYpnePn39qsA3EC6uFEMBa5eNptCjMd2ufZ",
|
||||||
|
"tx": "2KiztENxtLJz61A9T8seFWJS5BcJQn3oQr5jL6WtRPWckG26vPrjv8TRurqVVusA8qkXBBW2Zk7ZydP1G5PFY5Zz"
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"mint": "G4UewUZ3M7eohHGYMMmASbsxyi62ovdb3PcvvWQLNWj",
|
||||||
|
"tx": "4pgepoc4mCxJxg73JWg9Dqxa96Y6owbSFXjbvAE9J4g2SXRBhPvphFCb5WT8MTTQYNzMQdweuTta9vr7VfBmT6DL",
|
||||||
|
"createdAt": "2026-05-15T15:09:52.455Z"
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "mint_nft",
|
||||||
|
"realm": "2DTh1ivaekAW8kRYzGPsL2taFLJFFkBjEwqPisebxsS7",
|
||||||
|
"governance": "EMZ8vmr1xB4HZBDCFL9rHB98m1C5cYrGnRA8ZHayyGwD",
|
||||||
|
"proposal": "HfUPpCUyN4f9fydw7zPQf8EmhLFxsarF3oLQ54hRkHmP",
|
||||||
|
"proposalTransaction": "QDoGm745Evej1B78qAyCjGEosskxQJC77hGBiDhKNnS",
|
||||||
|
"nftMint": "G4UewUZ3M7eohHGYMMmASbsxyi62ovdb3PcvvWQLNWj",
|
||||||
|
"targetWallet": "HMww7YSVfwVm4i8sugqj7wyH26dqzHykzv3wzWwzEvPA",
|
||||||
|
"targetAta": "q9Q74ajue4S5qr7xXX1fz3PbkQ9wugQfW4NBnjtjKKZ",
|
||||||
|
"txCreate": "7WaFb6kzibkUMXyTVrahxuu12ydCdCcDU6bYgWP6juA6uYhEoWtMXrYAR5J8pBDMbx89T5pqrTdHyC1pmiR8mmi",
|
||||||
|
"txInsert": "5EedBVP48q13RySrGMxdhwrMqXa59bwCYLcUL1EZykDJbecDyoFagDZsEjHosGUeTYepzRDXqSkb7S2ZykcsWAeP",
|
||||||
|
"txSign": "3VsA4wwyervDjaAaw1eU72mvuvZorDCEbyczUJnvSemcfWrthSYM8VPV3EzWLAd7hhT2wZKkrDXQRk3nrswg15ri",
|
||||||
|
"txVote1": "66CzrV93qqccquAJt3Px2AFDH16Qkk6rJkCTDZkuLWnqb5GHs6ff1g944g9xtjNMpSiBz9YLwXUULSykuMB52f9v"
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "mint_nft",
|
||||||
|
"realm": "2DTh1ivaekAW8kRYzGPsL2taFLJFFkBjEwqPisebxsS7",
|
||||||
|
"governance": "EMZ8vmr1xB4HZBDCFL9rHB98m1C5cYrGnRA8ZHayyGwD",
|
||||||
|
"proposal": "GcSrXHmxBNxGskXeE16e9EX7GgWtAP3X6x2DCuTcDdp8",
|
||||||
|
"proposalTransaction": "Hgjnsd8qJxrYbtemsHLyerBjgyHpK55RhoSftW4HvwXR",
|
||||||
|
"nftMint": "G4UewUZ3M7eohHGYMMmASbsxyi62ovdb3PcvvWQLNWj",
|
||||||
|
"targetWallet": "HMww7YSVfwVm4i8sugqj7wyH26dqzHykzv3wzWwzEvPA",
|
||||||
|
"targetAta": "q9Q74ajue4S5qr7xXX1fz3PbkQ9wugQfW4NBnjtjKKZ",
|
||||||
|
"txCreate": "4WmFfSry92sv5GnMXHQiL28NERQ8j7jcrc2sQSHANaznLePwwSfeMQFVnxvdK2TPwr2dQ4uAJt1ARNCyuVhR5AH2",
|
||||||
|
"txInsert": "2iKF1EbRKatnpz566WmShHyUcYHkvKhMtBvdFTtoqKoUrrKwSCfFGmv6UxD998hGCuCmJ5sgtzR7aQQNcPTQrJdf",
|
||||||
|
"txSign": "4iZHCAMxoJMPmzqNKccf48Qhg1kBV7eurdSQ3cGsALihGZK8nxE4vrCyyUQiTJffGKBc4KZNTSSxphQsGmFMSWen",
|
||||||
|
"txVote1": "2jxbmxUFSc9CkwiXhE3gnMXS6w83ZdXYSMmfzDUv3PYHSnWQUAttbFQMixmUiho1Gs2XgostMqvKJ2AVL8rAec51"
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "burn_nft",
|
||||||
|
"realm": "2DTh1ivaekAW8kRYzGPsL2taFLJFFkBjEwqPisebxsS7",
|
||||||
|
"governance": "EMZ8vmr1xB4HZBDCFL9rHB98m1C5cYrGnRA8ZHayyGwD",
|
||||||
|
"proposal": "BYuwup4JQUEXcigREwwMoHW1TKvDM29ohmjXDGwZfb47",
|
||||||
|
"proposalTransaction": "6TZdVa1dgN5hdWvW2rmZTMhYHLCP2GddeNttdhTvpu32",
|
||||||
|
"nftMint": "744E11uXxhuy2AwVcrYwrC6yeF5GzEtmcL6Z9o6NzZV",
|
||||||
|
"targetWallet": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"sourceAta": "8xQnr4dh3aYpnePn39qsA3EC6uFEMBa5eNptCjMd2ufZ",
|
||||||
|
"txCreate": "4pzzFSnd6F89VriF8HaUjoR2zvtLxxvKRU1UBrYy9capswmrsjx9DkmY5aHAaUgSrVM34H49G8UmV5CicPbfScem",
|
||||||
|
"txInsert": "4E5FggG9dSPcYXiaHZLiMuw54QMzAFFbom48NfxxR2SfsifGZ9SBbNb7sthohcG8nMALUCxoZiNGw3Nsvogf2163",
|
||||||
|
"txSign": "4CidBfaWqX3FWtABRgELhHdH99TcKHtiDiakwDx129ZZ8j9kSvsrnAQsFiZWeTabaWDFnzm3dB6EQ8HcUTS4XeL5",
|
||||||
|
"txVote1": "VkiFThKY1W41Uy8baxWoz4vBgJQZNMGDwewKxTbMsbFqQHAejztFreKmdryesCHnv4h3PPejBkV3EmV37qQLdA7"
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"createdAt": "2026-05-15T15:14:04.947Z",
|
||||||
|
"mint": "9Up5SURRoBybsrfZnR7nKZC5gHarrccdVuMzoxeU3Xia",
|
||||||
|
"owner": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"ata": "3Q3cfBrNgyagEpMNPEZngYpCgw9U8Bd2KK4BbBfzvL5u",
|
||||||
|
"tx": "NvaRG6LUNf4RqG5J4uZzKHkKSkyACWTPYC9WDGovdFedUjUR3JA2Wbfhx9H6X35iXkuMz6jfjqMq2jHHo3FHdfy"
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"mint": "4xU8omSH3RfTyDHxWVCEm1HVTcf97YWkT8H67GvVpssz",
|
||||||
|
"tx": "4KLKJaG6w67YX5RidkYvgKuA4UAb9v8N1AbxWY6cmHJYSbZA2nevM2v8qBtTRe7t6jxGqFATttGpXogNK2Teyse7",
|
||||||
|
"createdAt": "2026-05-15T15:14:04.949Z"
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "mint_nft",
|
||||||
|
"realm": "2DTh1ivaekAW8kRYzGPsL2taFLJFFkBjEwqPisebxsS7",
|
||||||
|
"governance": "EMZ8vmr1xB4HZBDCFL9rHB98m1C5cYrGnRA8ZHayyGwD",
|
||||||
|
"proposal": "EspTK14DG6WAbip3hdh27zWLYSCjEiMqj7ZbdoxZVrPH",
|
||||||
|
"proposalTransaction": "DYH4dFzVsxYFyLpcTSGL3ce7VN1xNFbrcc3AMpWqA2XT",
|
||||||
|
"nftMint": "4xU8omSH3RfTyDHxWVCEm1HVTcf97YWkT8H67GvVpssz",
|
||||||
|
"targetWallet": "HMww7YSVfwVm4i8sugqj7wyH26dqzHykzv3wzWwzEvPA",
|
||||||
|
"targetAta": "2fUHqDDdcCcxyG62dZwyNiT2H9xZc3Rgy9oFtWiTdCXr",
|
||||||
|
"txCreate": "xwWL7W2VTgc2zyswo7ZdkvV7exEJA6FGzpiZan4DtDVjjNhf5wL1VkhH1Cxng2z4ACmT8TQHXyct6tkSZvPuTrj",
|
||||||
|
"txInsert": "4fmyYG4AJ7pZLa6is5qrPY2znLtVe3YuXDEKhqrwVZZsLm64nUrMdoQ6WPQR17HsGqsxt2WqWgQkCSg9QzERLunn",
|
||||||
|
"txSign": "4ouuHY2kpp573y3YB2w5TuaneNvwy1hbCYz8n6z2SRxV52YGe5fVdTMgoiFwNPwj9XeCxpb1CVfKvvsSttFgSUqB",
|
||||||
|
"txVote1": "48gz4HjvaK8seAs6otZYnf1viiy8jtmBFvPZCsjsjZ6ig5tqVMfksMvxf1orZRtYe5BXMozMD6uW7NmNyzMasZgP"
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "burn_nft",
|
||||||
|
"realm": "2DTh1ivaekAW8kRYzGPsL2taFLJFFkBjEwqPisebxsS7",
|
||||||
|
"governance": "EMZ8vmr1xB4HZBDCFL9rHB98m1C5cYrGnRA8ZHayyGwD",
|
||||||
|
"proposal": "BbwBWxzB3Tkf7r8QftycZAsFAKNtKz6nWZGsabjhSSxc",
|
||||||
|
"proposalTransaction": "4SbdRwFyDN52CRETNpHqe79ABiTSkeSwuttHUAnq99om",
|
||||||
|
"nftMint": "9Up5SURRoBybsrfZnR7nKZC5gHarrccdVuMzoxeU3Xia",
|
||||||
|
"targetWallet": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"sourceAta": "3Q3cfBrNgyagEpMNPEZngYpCgw9U8Bd2KK4BbBfzvL5u",
|
||||||
|
"txCreate": "5D6P7tPnvVy2B3NjrYQCYRwVKczkwD2D8PQCeTNy1SuiNoMdP31DiHDTi4k9B5fQbiEo5x2FCp5CR28muFTTnrWp",
|
||||||
|
"txInsert": "vwmnX2w9uJxbNoVK8jn2pLSvjbmEYYN7QcGnEm3zGSfXaQsNZpS8FsyH6584XpB2831A78MZxdNX6Fdi4a97Zn6",
|
||||||
|
"txSign": "4ViTxSiumXdkFMaHsN6Hghdegc6hNrzH4LJPuvkPRXxThsLbevdFBs4q5u4bKRCDGKBkBF4Ftx24beq34AqzNDN3",
|
||||||
|
"txVote1": "2Z367p3DztZHkVo25V649HBRzsdxk69mAByTEPg7PfBRLHRtiZ4KJeb2Hm5vkAj9g7EGHxKKfY35jPyaaPfXhKfC"
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"mint": "hssoT46Vp7KzisNAffBSQxGLmtxfzcswVeeEi4eq8gW",
|
||||||
|
"tx": "5LwmJLfUskcJgz9ZhZH3ojnXDvMjeBe3TiLLtLVndYxnb5oVUfGkrpGsdvh5RYGgC1zahATpQUHc9bJMtvNe4UW9",
|
||||||
|
"createdAt": "2026-05-15T15:29:37.181Z"
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "mint_nft",
|
||||||
|
"realm": "2DTh1ivaekAW8kRYzGPsL2taFLJFFkBjEwqPisebxsS7",
|
||||||
|
"governance": "EMZ8vmr1xB4HZBDCFL9rHB98m1C5cYrGnRA8ZHayyGwD",
|
||||||
|
"proposal": "CKV2RSJ4HiUGQvdhGyizub89csTF52TD2Z2HciQFPkdW",
|
||||||
|
"proposalTransaction": "AAvEV3q9MeHedbJMsTk1C8nLg3LJtTLUBWt9fgi2wnqC",
|
||||||
|
"nftMint": "hssoT46Vp7KzisNAffBSQxGLmtxfzcswVeeEi4eq8gW",
|
||||||
|
"targetWallet": "HMww7YSVfwVm4i8sugqj7wyH26dqzHykzv3wzWwzEvPA",
|
||||||
|
"targetAta": "9wxC9Zb5FCbkrhjcHkyhft1Kn6FVdzamijk99zcQz5xZ",
|
||||||
|
"txCreate": "3z2otjQtptrDgDXPQUWKos6EhDXqK9zifAXaSdZjKEPyHTxaFS32TXoCHy2QKBEMv5NAMVXqDRF9mifFLSbbhUXK",
|
||||||
|
"txInsert": "Sys1JeebsUyACPb3TjUQ5NciBXwAjAhUVj5geRpabmxB23MhsQsw9USCGqCKqX9zpdUAg6rQ7qt8v4QFtMB3cDt",
|
||||||
|
"txSign": "5wdYVUgYtew7PPmtHCyNPWVZvruw58FmiQhwbVGHngPKbfL1s1LTtZtNiAAVfzBzpaavzA8y2CjUYrSizS7hQuh9",
|
||||||
|
"txVote1": "DtWj3CCq48JBVRmSfwWu2k2uQSHuArMAbJbz2KSXpMDBqtTsaBr9Duintn8gFz5Dnmm4VjT4Cf6L5yB2PxyW8Cc"
|
||||||
|
}
|
||||||
11
shine/scripts/CreateGovernmentTokenAndDAO/01_create_governance_token.sh
Executable file
11
shine/scripts/CreateGovernmentTokenAndDAO/01_create_governance_token.sh
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
#
|
||||||
|
# RU: Создает governance token (Token-2022, NonTransferable + PermanentDelegate)
|
||||||
|
# с настройками из governance_token.config.env.
|
||||||
|
# EN: Creates governance token (Token-2022, NonTransferable + PermanentDelegate)
|
||||||
|
# using settings from governance_token.config.env.
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_PATH="$SCRIPT_DIR/governance_token.config.env"
|
||||||
|
node "$SCRIPT_DIR/js/01_create_governance_token_exec.js" "$CONFIG_PATH"
|
||||||
21
shine/scripts/CreateGovernmentTokenAndDAO/02_mint_token_to_wallet.sh
Executable file
21
shine/scripts/CreateGovernmentTokenAndDAO/02_mint_token_to_wallet.sh
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
#
|
||||||
|
# RU: Выпускает ровно 1 membership-токен на указанный кошелек.
|
||||||
|
# Если у кошелька уже есть >=1 токен, скрипт завершится ошибкой.
|
||||||
|
# EN: Mints exactly 1 membership token to the given wallet.
|
||||||
|
# If wallet already has >=1 token, script exits with error.
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_PATH="$SCRIPT_DIR/governance_token.config.env"
|
||||||
|
WALLET="${1:-}"
|
||||||
|
|
||||||
|
if [[ -z "$WALLET" ]]; then
|
||||||
|
echo "Использование:"
|
||||||
|
echo " $0 <wallet>"
|
||||||
|
echo "Usage:"
|
||||||
|
echo " $0 <wallet>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
node "$SCRIPT_DIR/js/02_mint_membership_to_wallet_exec.js" "$CONFIG_PATH" "$WALLET"
|
||||||
19
shine/scripts/CreateGovernmentTokenAndDAO/03_force_burn_from_wallet.sh
Executable file
19
shine/scripts/CreateGovernmentTokenAndDAO/03_force_burn_from_wallet.sh
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
#
|
||||||
|
# RU: Принудительно сжигает 1 membership-токен на указанном кошельке.
|
||||||
|
# EN: Force-burns exactly 1 membership token from the given wallet.
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_PATH="$SCRIPT_DIR/governance_token.config.env"
|
||||||
|
WALLET="${1:-}"
|
||||||
|
|
||||||
|
if [[ -z "$WALLET" ]]; then
|
||||||
|
echo "Использование:"
|
||||||
|
echo " $0 <wallet>"
|
||||||
|
echo "Usage:"
|
||||||
|
echo " $0 <wallet>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
node "$SCRIPT_DIR/js/03_force_burn_from_wallet_exec.js" "$CONFIG_PATH" "$WALLET"
|
||||||
10
shine/scripts/CreateGovernmentTokenAndDAO/04_create_dao.sh
Executable file
10
shine/scripts/CreateGovernmentTokenAndDAO/04_create_dao.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
#
|
||||||
|
# RU: Создает DAO (Realm + Governance + Treasury) на уже существующем governance mint.
|
||||||
|
# EN: Creates DAO (Realm + Governance + Treasury) using existing governance mint.
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_PATH="$SCRIPT_DIR/governance_token.config.env"
|
||||||
|
|
||||||
|
node "$SCRIPT_DIR/js/05_create_dao_exec.js" "$CONFIG_PATH"
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
#
|
||||||
|
# RU: Передает права Mint/Freeze/PermanentDelegate на Governance PDA из конфига.
|
||||||
|
# Перед отправкой транзакции внутри JS будет подтверждение "yes".
|
||||||
|
# EN: Transfers Mint/Freeze/PermanentDelegate authorities to Governance PDA
|
||||||
|
# from config. JS script asks for "yes" confirmation before sending.
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_PATH="$SCRIPT_DIR/governance_token.config.env"
|
||||||
|
node "$SCRIPT_DIR/js/04_transfer_rights_to_governance_pda_exec.js" "$CONFIG_PATH"
|
||||||
74
shine/scripts/CreateGovernmentTokenAndDAO/README.md
Normal file
74
shine/scripts/CreateGovernmentTokenAndDAO/README.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# CreateGovernmentTokenAndDAO
|
||||||
|
|
||||||
|
## RU
|
||||||
|
|
||||||
|
Единый набор скриптов для:
|
||||||
|
1. создания governance token,
|
||||||
|
2. выдачи/сжигания membership токенов,
|
||||||
|
3. передачи прав на Governance PDA,
|
||||||
|
4. создания DAO (Realm/Governance/Treasury).
|
||||||
|
|
||||||
|
### Важная структура ключей
|
||||||
|
|
||||||
|
Используются две папки:
|
||||||
|
- `keypairs/dao_creator/` — ключ инициатора DAO и плательщика (ровно 1 `*.json`).
|
||||||
|
- `keypairs/government_token/` — ключ mint governance token (ровно 1 `*.json`).
|
||||||
|
|
||||||
|
Скрипты автоматически берут единственный файл из этих папок.
|
||||||
|
Если в папке `government_token` 0 файлов или больше 1 — скрипт завершится ошибкой.
|
||||||
|
|
||||||
|
### Скрипты
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./01_create_governance_token.sh
|
||||||
|
./02_mint_token_to_wallet.sh <WALLET>
|
||||||
|
./03_force_burn_from_wallet.sh <WALLET>
|
||||||
|
./04_create_dao.sh
|
||||||
|
./05_transfer_rights_to_governance_pda.sh
|
||||||
|
./grind_vanity_mint.sh [PREFIX] [COUNT] [ignore-case]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Базовый порядок
|
||||||
|
|
||||||
|
1. (Опционально) `grind_vanity_mint.sh`, затем ОБЯЗАТЕЛЬНО скопировать выбранный json в `keypairs/government_token/`.
|
||||||
|
Пример:
|
||||||
|
```bash
|
||||||
|
cp ./runs/<FOUND_KEYPAIR>.json ./keypairs/government_token/selected_mint.json
|
||||||
|
```
|
||||||
|
2. `01_create_governance_token.sh`
|
||||||
|
3. В `governance_token.config.env` указать `GT_MINT_ADDRESS`.
|
||||||
|
4. `02_mint_token_to_wallet.sh <WALLET>`
|
||||||
|
5. `03_force_burn_from_wallet.sh <WALLET>`
|
||||||
|
6. `04_create_dao.sh`
|
||||||
|
7. Внести полученный Governance PDA в `GT_GOVERNANCE_PDA`.
|
||||||
|
8. `05_transfer_rights_to_governance_pda.sh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## EN
|
||||||
|
|
||||||
|
Unified scripts for:
|
||||||
|
1. governance token creation,
|
||||||
|
2. membership mint/burn,
|
||||||
|
3. authority transfer to Governance PDA,
|
||||||
|
4. DAO creation (Realm/Governance/Treasury).
|
||||||
|
|
||||||
|
### Required keypair layout
|
||||||
|
|
||||||
|
Two folders are used:
|
||||||
|
- `keypairs/dao_creator/` — DAO creator/payer keypair (exactly 1 `*.json`).
|
||||||
|
- `keypairs/government_token/` — governance token mint keypair (exactly 1 `*.json`).
|
||||||
|
|
||||||
|
Scripts auto-detect the single file in each folder.
|
||||||
|
If `government_token` has 0 files or more than 1 file, script fails with error.
|
||||||
|
|
||||||
|
### Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./01_create_governance_token.sh
|
||||||
|
./02_mint_token_to_wallet.sh <WALLET>
|
||||||
|
./03_force_burn_from_wallet.sh <WALLET>
|
||||||
|
./04_create_dao.sh
|
||||||
|
./05_transfer_rights_to_governance_pda.sh
|
||||||
|
./grind_vanity_mint.sh [PREFIX] [COUNT] [ignore-case]
|
||||||
|
```
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
# Конфиг CreateGovernmentTokenAndDAO
|
||||||
|
|
||||||
|
# devnet | mainnet-beta
|
||||||
|
GT_CLUSTER="devnet"
|
||||||
|
|
||||||
|
# Папка с keypair инициатора DAO/плательщика.
|
||||||
|
# Внутри должен быть ровно 1 json-файл.
|
||||||
|
GT_DAO_CREATOR_KEYPAIR_DIR="./keypairs/dao_creator"
|
||||||
|
|
||||||
|
# Папка с keypair governance token mint.
|
||||||
|
# Внутри должен быть ровно 1 json-файл (или 0, тогда 01-скрипт создаст selected_mint.json).
|
||||||
|
GT_GOVERNMENT_TOKEN_KEYPAIR_DIR="./keypairs/government_token"
|
||||||
|
|
||||||
|
# Governance PDA (сюда передаем управляющие права после создания DAO)
|
||||||
|
GT_GOVERNANCE_PDA="REPLACE_WITH_GOVERNANCE_PDA"
|
||||||
|
|
||||||
|
# Явный mint-адрес (если указан, приоритетнее keypair-папки)
|
||||||
|
GT_MINT_ADDRESS=""
|
||||||
|
|
||||||
|
# Папка для результатов/логов
|
||||||
|
GT_RUNS_DIR="./runs"
|
||||||
|
|
||||||
|
# Дефолт для vanity-подбора (05)
|
||||||
|
GT_VANITY_PREFIX="SHiNE"
|
||||||
|
|
||||||
|
# ===== DAO create settings (05_create_dao.sh) =====
|
||||||
|
DAO_REALM_NAME="CreateDAO Test 2026-05-15"
|
||||||
|
DAO_VOTING_TIME_SEC="3600"
|
||||||
|
DAO_APPROVAL_THRESHOLD_PERCENT="51"
|
||||||
|
DAO_RUNS_DIR="./runs"
|
||||||
|
|
||||||
|
# SPL Governance program
|
||||||
|
SPL_GOVERNANCE_PROGRAM_ID="GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw"
|
||||||
19
shine/scripts/CreateGovernmentTokenAndDAO/grind_vanity_mint.sh
Executable file
19
shine/scripts/CreateGovernmentTokenAndDAO/grind_vanity_mint.sh
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
#
|
||||||
|
# RU: Подбирает vanity mint keypair через `solana-keygen grind`.
|
||||||
|
# Параметры: [PREFIX] [COUNT] [ignore-case]
|
||||||
|
# EN: Finds vanity mint keypair using `solana-keygen grind`.
|
||||||
|
# Args: [PREFIX] [COUNT] [ignore-case]
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
CONFIG_PATH="$SCRIPT_DIR/governance_token.config.env"
|
||||||
|
PREFIX="${1:-}"
|
||||||
|
COUNT="${2:-1}"
|
||||||
|
IGNORE_CASE="${3:-}"
|
||||||
|
|
||||||
|
if [[ -n "$PREFIX" ]]; then
|
||||||
|
node "$SCRIPT_DIR/js/grind_vanity_mint_exec.js" "$CONFIG_PATH" "$PREFIX" "$COUNT" "$IGNORE_CASE"
|
||||||
|
else
|
||||||
|
node "$SCRIPT_DIR/js/grind_vanity_mint_exec.js" "$CONFIG_PATH"
|
||||||
|
fi
|
||||||
@ -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); });
|
||||||
@ -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); });
|
||||||
@ -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); });
|
||||||
@ -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);
|
||||||
|
});
|
||||||
118
shine/scripts/CreateGovernmentTokenAndDAO/js/_common.js
Normal file
118
shine/scripts/CreateGovernmentTokenAndDAO/js/_common.js
Normal file
@ -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); });
|
||||||
@ -0,0 +1 @@
|
|||||||
|
[221,119,143,125,90,136,155,115,191,198,210,85,228,111,251,118,168,138,27,60,249,62,247,24,121,228,139,112,218,69,55,143,215,21,229,69,219,1,74,36,10,239,63,163,48,240,58,208,237,251,209,37,17,202,215,77,13,165,178,18,141,21,193,64]
|
||||||
@ -0,0 +1 @@
|
|||||||
|
[134,197,95,124,255,199,219,182,107,27,148,43,9,167,197,238,72,191,98,205,70,227,160,213,107,110,89,3,33,49,199,29,6,122,106,78,28,40,164,141,120,125,226,194,56,246,248,203,15,90,120,32,226,163,174,32,67,73,246,167,52,25,64,236]
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"createdAt": "2026-05-15T12:20:22.373Z",
|
"createdAt": "2026-05-15T12:33:36.911Z",
|
||||||
"prefix": "DAo",
|
"prefix": "DAo",
|
||||||
"count": 1,
|
"count": 1,
|
||||||
"ignoreCase": false,
|
"ignoreCase": false,
|
||||||
@ -11,6 +11,6 @@
|
|||||||
"outputLog": [
|
"outputLog": [
|
||||||
"Searching with 16 threads for:",
|
"Searching with 16 threads for:",
|
||||||
"1 pubkey that starts with 'DAo' and ends with ''",
|
"1 pubkey that starts with 'DAo' and ends with ''",
|
||||||
"Wrote keypair to DAou7SeaykoMooghA5SURLYhkkU8NEhV5Y2T6fsXD7rn.json"
|
"Wrote keypair to DAoHZ4bmVZU7Cx9xnSsc1xJJfTJnP2s4TeMgci7x6AsG.json"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"createdAt": "2026-05-15T12:34:25.973Z",
|
||||||
|
"prefix": "SHi",
|
||||||
|
"count": 1,
|
||||||
|
"ignoreCase": false,
|
||||||
|
"runsDir": "/home/ai/work/SOLANA/shine-solana/shine/scripts/governance_token/runs",
|
||||||
|
"avgExpectedTriesPerMatch": 195112,
|
||||||
|
"attemptsObserved": 1000000,
|
||||||
|
"foundHintsInOutput": 1,
|
||||||
|
"command": "solana-keygen grind --starts-with SHi:1",
|
||||||
|
"outputLog": [
|
||||||
|
"Searching with 16 threads for:",
|
||||||
|
"1 pubkey that starts with 'SHi' and ends with ''",
|
||||||
|
"Searched 1000000 keypairs in 4s. 0 matches found.",
|
||||||
|
"Wrote keypair to SHiwSvDUGjsye9ZE8YAttXdycuDcprWq95oqr69WP9f.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"createdAt": "2026-05-15T12:46:32.749Z",
|
||||||
|
"cluster": "devnet",
|
||||||
|
"operator": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"mint": "SHiwSvDUGjsye9ZE8YAttXdycuDcprWq95oqr69WP9f",
|
||||||
|
"tokenProgram": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
|
||||||
|
"nonTransferable": true,
|
||||||
|
"permanentDelegate": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"mintAuthority": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"freezeAuthority": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"mintKeypairPath": "/home/ai/work/SOLANA/shine-solana/shine/scripts/governance_token/mint_keypairs/selected_mint.json",
|
||||||
|
"txCreateMint": "4oBPaP4L6E1z4UZUgLKXAA3ZBJcxtPgDcTL6MCfQJkmG8dsX3sARp7dDKYrqYT9B4H326N4HZpTwAJytfjnDfYQb"
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"createdAt": "2026-05-15T13:57:13.110Z",
|
||||||
|
"prefix": "SHi",
|
||||||
|
"command": "solana-keygen grind --starts-with SHi:1",
|
||||||
|
"outputLog": [
|
||||||
|
"Searching with 16 threads for:",
|
||||||
|
"1 pubkey that starts with 'SHi' and ends with ''",
|
||||||
|
"Wrote keypair to SHiEKwoeggb2Nd6AvkqKBFgh3ubBmW5YtVES4xu5j7h.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"mint": "SHiEKwoeggb2Nd6AvkqKBFgh3ubBmW5YtVES4xu5j7h",
|
||||||
|
"txCreateMint": "64cHd8ez4EuPs5TbaVLEqyHjp6ufiuZUyoErg3X98XUPoysWnTgeq4FUafJUqS97Hq8C7AuwbiQZPBVwjdCfKF5W"
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"createdAt": "2026-05-15T14:07:22.593Z",
|
||||||
|
"cluster": "devnet",
|
||||||
|
"realmName": "DAO-Cost-0515-B",
|
||||||
|
"governanceProgramId": "GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw",
|
||||||
|
"governingMint": "DrKQHbtacwD2jqgxZCatLk2PCbNvgPrNQFswVP72hC98",
|
||||||
|
"operator": "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P",
|
||||||
|
"realm": "H35UPU98sC2bo265Q4R6PWdvbaotbbCvjDcuvwjDvewf",
|
||||||
|
"governance": "9aD18P5nun1RPVpEeeCeG5ensyry9WKrwjdX4stVa7qP",
|
||||||
|
"nativeTreasury": "FbupU7ivym2P2UFi4qwypNGu1eTPDyb7Uctha1XEALCy",
|
||||||
|
"tokenOwnerRecord": "GvVc8DDrDXbPtYWzsdvPD6SXzWp4cXVmvUQ9Y68rDKz6",
|
||||||
|
"txRealm": "Xz5GF29JtWHZV1eTuy1gZsRM5RuDGWoEjJtotwzJrJ6yrhrHD87miJjDcJrHfn95Dcu8ELtxaJScBKYpmnh8QjQ",
|
||||||
|
"txDeposit": "3CStA356srRnXREyEjSf6nbEKDqiwWj7tA3EvMqsHSAfj8d6rtm5MC1t7gNvtgAYH3GMVhdE7zHx1iR1pgE1TNMY",
|
||||||
|
"txGovernanceTreasury": "omxdAHQtrFgA6qaDzX7QWC51ofuFWbkDTUjweksXwBqeT7tJr84hRsR7FMKAr45mi6b7xoDR7B9chq1qhfygtVg",
|
||||||
|
"txSetRealmAuthority": "5E61sgqx6Ax3bThG6AVFjxfbqaoNCUXQuECSWDshgKVsnyxNT9NSmkpqD4Gu3tC9B8s5nmYLVTb6DtfepMZSj6y9"
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
[63,203,53,184,240,127,53,242,32,8,61,128,100,43,24,37,2,59,78,168,105,230,234,235,6,193,28,26,127,173,235,154,180,206,206,50,137,55,225,129,136,21,78,124,42,104,92,200,135,65,248,101,101,217,247,196,235,54,104,253,117,198,199,95]
|
||||||
@ -0,0 +1 @@
|
|||||||
|
[134,197,95,124,255,199,219,182,107,27,148,43,9,167,197,238,72,191,98,205,70,227,160,213,107,110,89,3,33,49,199,29,6,122,106,78,28,40,164,141,120,125,226,194,56,246,248,203,15,90,120,32,226,163,174,32,67,73,246,167,52,25,64,236]
|
||||||
@ -0,0 +1 @@
|
|||||||
|
[175,188,30,40,32,154,227,126,97,66,48,147,223,9,161,80,124,65,129,226,43,249,24,216,42,36,22,39,172,158,72,190,6,122,109,215,230,183,230,136,221,4,43,131,22,137,145,82,134,161,14,135,252,49,35,44,166,15,180,139,72,11,94,118]
|
||||||
@ -1,114 +0,0 @@
|
|||||||
#!/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);
|
|
||||||
});
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
@ -1,82 +0,0 @@
|
|||||||
#!/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);
|
|
||||||
});
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
#!/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);
|
|
||||||
});
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
#!/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);
|
|
||||||
});
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
#!/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);
|
|
||||||
});
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
# 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`.
|
|
||||||
@ -1,88 +0,0 @@
|
|||||||
#!/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,
|
|
||||||
};
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# Конфиг 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="./runs"
|
|
||||||
|
|
||||||
# Дефолт для vanity-подбора (05)
|
|
||||||
GT_VANITY_PREFIX="DAo"
|
|
||||||
@ -1 +0,0 @@
|
|||||||
[115,67,66,30,87,145,246,163,61,151,201,124,183,214,51,151,22,218,111,91,138,240,184,170,169,117,123,68,99,10,100,161,180,207,127,168,102,124,209,83,46,144,109,253,200,122,20,82,223,74,69,105,53,218,226,231,88,238,93,98,54,161,167,31]
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"createdAt": "2026-05-15T12:20:09.856Z",
|
|
||||||
"prefix": "DAo",
|
|
||||||
"count": 1,
|
|
||||||
"ignoreCase": false,
|
|
||||||
"runsDir": "/home/ai/work/SOLANA/shine-solana/shine/scripts/governance_token/scripts/governance_token/runs",
|
|
||||||
"avgExpectedTriesPerMatch": 195112,
|
|
||||||
"attemptsObserved": 0,
|
|
||||||
"foundHintsInOutput": 1,
|
|
||||||
"command": "solana-keygen grind --starts-with DAo:1",
|
|
||||||
"outputLog": [
|
|
||||||
"Searching with 16 threads for:",
|
|
||||||
"1 pubkey that starts with 'DAo' and ends with ''",
|
|
||||||
"Wrote keypair to DAoxa7YRxa8Se5KFQmkBbxBJ3Fo8FeY8N9td6NGz38zu.json"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
[127,127,232,76,177,128,31,8,169,68,116,117,202,252,44,203,63,250,250,210,30,186,201,66,110,211,193,137,111,186,165,157,180,207,144,236,191,129,155,243,98,167,69,154,219,76,79,141,228,101,60,132,255,6,252,105,99,29,143,220,127,120,236,26]
|
|
||||||
Loading…
Reference in New Issue
Block a user