shine-solana/shine/scripts/CreateGovernmentTokenAndDAO/js/05_create_dao_exec.js

192 lines
6.9 KiB
JavaScript

#!/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);
});