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