192 lines
6.9 KiB
JavaScript
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);
|
|
});
|