SHA256
Отключить репосты и добавить Solana-модуль
This commit is contained in:
@@ -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); });
|
||||
+56
@@ -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);});
|
||||
@@ -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,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 };
|
||||
Reference in New Issue
Block a user