Подготовка перед деплоем в mainnet

This commit is contained in:
AidarKC
2026-07-01 12:23:30 +04:00
parent ab4dab34aa
commit 0240db59ee
52 changed files with 1018 additions and 473 deletions
@@ -0,0 +1,268 @@
#!/usr/bin/env node
"use strict";
const fs = require("fs");
const path = require("path");
const readline = require("readline");
const {
Connection,
PublicKey,
Transaction,
sendAndConfirmTransaction,
} = require("@solana/web3.js");
const {
TOKEN_PROGRAM_ID,
AuthorityType,
createSetAuthorityInstruction,
} = require("@solana/spl-token");
const {
PROGRAM_VERSION_V3,
getRealm,
withSetRealmAuthority,
SetRealmAuthorityAction,
} = require("@solana/spl-governance");
const { createUmi } = require("@metaplex-foundation/umi-bundle-defaults");
const {
createSignerFromKeypair,
signerIdentity,
none,
some,
} = require("@metaplex-foundation/umi");
const { fromWeb3JsKeypair, fromWeb3JsPublicKey } = require("@metaplex-foundation/umi-web3js-adapters");
const {
mplTokenMetadata,
fetchMetadataFromSeeds,
updateV1,
} = require("@metaplex-foundation/mpl-token-metadata");
const {
clusterUrl,
ensureDir,
loadKeypair,
nowStamp,
parseEnvConfig,
resolveConfigPath,
} = require("./js_common");
function lamportsToSol(lamports) {
return Number(lamports) / 1_000_000_000;
}
async function askYes() {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const answer = await new Promise((resolve) =>
rl.question("Введите YES для передачи authority на DAO: ", resolve)
);
rl.close();
return answer.trim() === "YES";
}
async function fetchMetadataState(cluster, issuer, mint) {
const umi = createUmi(clusterUrl(cluster));
const issuerSigner = createSignerFromKeypair(umi, fromWeb3JsKeypair(issuer));
umi.use(signerIdentity(issuerSigner));
umi.use(mplTokenMetadata());
const metadata = await fetchMetadataFromSeeds(umi, { mint: fromWeb3JsPublicKey(mint) });
return {
umi,
issuerSigner,
metadata,
};
}
async function fetchMetadataUpdateAuthority(cluster, mint) {
const umi = createUmi(clusterUrl(cluster));
umi.use(mplTokenMetadata());
const metadata = await fetchMetadataFromSeeds(umi, { mint: fromWeb3JsPublicKey(mint) });
return metadata.updateAuthority.toString();
}
async function main() {
const configPath = resolveConfigPath(process.argv[2], "dao.config.env");
if (!fs.existsSync(configPath)) throw new Error(`Конфиг не найден: ${configPath}`);
const cfg = parseEnvConfig(configPath);
[
"DAO_CLUSTER",
"DAO_REALM_NAME",
"DAO_ISSUER_KEYPAIR",
"SPL_GOVERNANCE_PROGRAM_ID",
"DAO_GOV_TOKEN_MINT",
].forEach((k) => {
if (!cfg[k]) throw new Error(`В конфиге отсутствует обязательный параметр: ${k}`);
});
const cluster = cfg.DAO_CLUSTER;
const connection = new Connection(clusterUrl(cluster), "confirmed");
const issuer = loadKeypair(path.resolve(cfg.DAO_ISSUER_KEYPAIR));
const governanceProgramId = new PublicKey(cfg.SPL_GOVERNANCE_PROGRAM_ID);
const mint = new PublicKey(cfg.DAO_GOV_TOKEN_MINT);
const [realmPk] = PublicKey.findProgramAddressSync(
[Buffer.from("governance"), Buffer.from(cfg.DAO_REALM_NAME, "utf8")],
governanceProgramId
);
const [governancePk] = PublicKey.findProgramAddressSync(
[Buffer.from("account-governance"), realmPk.toBuffer(), realmPk.toBuffer()],
governanceProgramId
);
const realmInfo = await connection.getAccountInfo(realmPk, "confirmed");
if (!realmInfo) throw new Error(`Realm не найден: ${realmPk.toBase58()}. Сначала выполните шаг 2.`);
const governanceInfo = await connection.getAccountInfo(governancePk, "confirmed");
if (!governanceInfo) {
throw new Error(`Governance PDA не найден: ${governancePk.toBase58()}. Сначала выполните шаг 2.`);
}
const realmBefore = await getRealm(connection, realmPk);
const realmAuthorityBefore = realmBefore.account.authority?.toBase58?.() || null;
if (realmAuthorityBefore !== issuer.publicKey.toBase58()) {
throw new Error(`Realm authority уже не у issuer: ${realmAuthorityBefore}`);
}
const mintInfoBefore = await connection.getParsedAccountInfo(mint, "confirmed");
if (!mintInfoBefore.value) throw new Error(`Mint не найден: ${mint.toBase58()}`);
const mintParsedBefore = mintInfoBefore.value.data.parsed.info;
const mintAuthorityBefore = mintParsedBefore.mintAuthority || null;
const freezeAuthorityBefore = mintParsedBefore.freezeAuthority || null;
if (mintAuthorityBefore && mintAuthorityBefore !== issuer.publicKey.toBase58()) {
throw new Error(`Mint authority уже не у issuer: ${mintAuthorityBefore}`);
}
if (freezeAuthorityBefore && freezeAuthorityBefore !== issuer.publicKey.toBase58()) {
throw new Error(`Freeze authority уже не у issuer: ${freezeAuthorityBefore}`);
}
const { umi, metadata, issuerSigner } = await fetchMetadataState(cluster, issuer, mint);
const metadataUpdateAuthorityBefore = metadata.updateAuthority.toString();
if (metadataUpdateAuthorityBefore !== issuer.publicKey.toBase58()) {
throw new Error(`Metadata update authority уже не у issuer: ${metadataUpdateAuthorityBefore}`);
}
const startBalance = await connection.getBalance(issuer.publicKey, "confirmed");
console.log("============================================================");
console.log("TRANSFER DAO AUTHORITIES");
console.log("------------------------------------------------------------");
console.log("Сеть: ", cluster);
console.log("Realm: ", realmPk.toBase58());
console.log("Governance PDA: ", governancePk.toBase58());
console.log("Issuer: ", issuer.publicKey.toBase58());
console.log("Governance mint: ", mint.toBase58());
console.log("Баланс до: ", `${lamportsToSol(startBalance)} SOL`);
console.log("Mint authority до: ", mintAuthorityBefore || "null");
console.log("Freeze authority до: ", freezeAuthorityBefore || "null");
console.log("Realm authority до: ", realmAuthorityBefore || "null");
console.log("Metadata authority до: ", metadataUpdateAuthorityBefore);
console.log("============================================================");
if (!(await askYes())) {
console.log("Отменено.");
return;
}
const ixTransferAuthorities = [
createSetAuthorityInstruction(
mint,
issuer.publicKey,
AuthorityType.MintTokens,
governancePk,
[],
TOKEN_PROGRAM_ID
),
createSetAuthorityInstruction(
mint,
issuer.publicKey,
AuthorityType.FreezeAccount,
governancePk,
[],
TOKEN_PROGRAM_ID
),
];
const sigTransferAuthorities = await sendAndConfirmTransaction(
connection,
new Transaction().add(...ixTransferAuthorities),
[issuer],
{ commitment: "confirmed" }
);
const ixSetRealmAuthority = [];
withSetRealmAuthority(
ixSetRealmAuthority,
governanceProgramId,
PROGRAM_VERSION_V3,
realmPk,
issuer.publicKey,
governancePk,
SetRealmAuthorityAction.SetChecked
);
const sigSetRealmAuthority = await sendAndConfirmTransaction(
connection,
new Transaction().add(...ixSetRealmAuthority),
[issuer],
{ commitment: "confirmed" }
);
const metadataTx = await updateV1(umi, {
authority: issuerSigner,
mint: fromWeb3JsPublicKey(mint),
newUpdateAuthority: some(fromWeb3JsPublicKey(governancePk)),
data: none(),
primarySaleHappened: none(),
isMutable: none(),
collection: { __kind: "None" },
collectionDetails: { __kind: "None" },
uses: { __kind: "None" },
ruleSet: { __kind: "None" },
authorizationData: none(),
}).sendAndConfirm(umi);
const sigMetadataAuthority = Buffer.from(metadataTx.signature).toString("base64");
const mintInfoAfter = await connection.getParsedAccountInfo(mint, "confirmed");
const mintParsedAfter = mintInfoAfter.value.data.parsed.info;
const mintAuthorityAfter = mintParsedAfter.mintAuthority || null;
const freezeAuthorityAfter = mintParsedAfter.freezeAuthority || null;
const realmAfter = await getRealm(connection, realmPk);
const realmAuthorityAfter = realmAfter.account.authority?.toBase58?.() || null;
const metadataUpdateAuthorityAfter = await fetchMetadataUpdateAuthority(cluster, mint);
const report = {
createdAt: new Date().toISOString(),
cluster,
realmName: cfg.DAO_REALM_NAME,
governanceProgramId: governanceProgramId.toBase58(),
issuer: issuer.publicKey.toBase58(),
governingMint: mint.toBase58(),
realm: realmPk.toBase58(),
governance: governancePk.toBase58(),
mintAuthorityBefore,
freezeAuthorityBefore,
metadataUpdateAuthorityBefore,
realmAuthorityBefore,
mintAuthorityAfter,
freezeAuthorityAfter,
realmAuthorityAfter,
metadataUpdateAuthorityAfter,
txTransferAuthorities: sigTransferAuthorities,
txSetRealmAuthority: sigSetRealmAuthority,
txSetMetadataUpdateAuthority: sigMetadataAuthority,
};
const runsDir = path.resolve(__dirname, "..", "local", "runs");
ensureDir(runsDir);
const reportPath = path.join(runsDir, `${nowStamp()}_transfer_dao_authorities.json`);
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
console.log("Все authority переданы на DAO.");
console.log("realm:", realmPk.toBase58());
console.log("governance:", governancePk.toBase58());
console.log("mintAuthority после:", mintAuthorityAfter || "null");
console.log("freezeAuthority после:", freezeAuthorityAfter || "null");
console.log("realmAuthority после:", realmAuthorityAfter || "null");
console.log("metadataAuthority после:", metadataUpdateAuthorityAfter);
console.log("report:", reportPath);
}
main().catch((e) => {
console.error("TransferAuthorities error:", e?.message || e);
process.exit(1);
});