SHA256
269 lines
9.6 KiB
JavaScript
269 lines
9.6 KiB
JavaScript
#!/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);
|
||
});
|