Skip to content

Instantly share code, notes, and snippets.

@Yuripetusko
Last active June 3, 2024 21:27
Show Gist options
  • Save Yuripetusko/e995b199aee8d12f000dd9ff22d8ae5d to your computer and use it in GitHub Desktop.
Save Yuripetusko/e995b199aee8d12f000dd9ff22d8ae5d to your computer and use it in GitHub Desktop.
import fs from 'fs';
const fsPromises = fs.promises;
export const getOrCreateNdjosnLogFile = async <T>(path: string) => {
let response: T[] = [];
if (fs.existsSync(path)) {
const rawResponse = fs.readFileSync(path, 'utf-8');
if (rawResponse.length !== 0) {
response = rawResponse
.toString()
.trim()
.split('\n')
.map((s) => JSON.parse(s));
}
} else {
// Init empty log
await fsPromises.writeFile(path, '');
}
return response;
};
import fs from 'fs';
const fsPromises = fs.promises;
export const mintinResultFilePaths = [
`${process.cwd()}/scripts/data/minting-results/minting-status.ndjson`,
`${process.cwd()}/scripts/data/minting-results/minted-gems-kusama-to-evm-mapping.ndjson`,
`${process.cwd()}/scripts/data/minting-results/minted-items-other-kusama-to-evm-mapping.ndjson`,
`${process.cwd()}/scripts/data/minting-results/minted-items-banners-kusama-to-evm-mapping.ndjson`,
`${process.cwd()}/scripts/data/minting-results/minted-items-khala-kusama-to-evm-mapping.ndjson`,
`${process.cwd()}/scripts/data/minting-results/minted-birds-kusama-to-evm-mapping.ndjson`,
`${process.cwd()}/scripts/data/minting-results/existing-gem-assets.ndjson`,
`${process.cwd()}/scripts/data/minting-results/existing-item-assets.ndjson`,
`${process.cwd()}/scripts/data/minting-results/minted-gems-kusama-to-evm-mapping.ndjson`,
`${process.cwd()}/scripts/data/minting-results/debugging-gems.ndjson`,
];
export const initMintingResultsFiles = async () => {
try {
for (const filePath of mintinResultFilePaths) {
const fileExists = fs.existsSync(filePath);
if (!fileExists) {
await fsPromises.writeFile(filePath, '');
}
}
} catch (error) {
console.log('Error', error);
process.exit(1);
}
};
const fsPromises = fs.promises;
type AddedAssetEntryMapping = Record<
string,
Record<
string,
{
slotId: string | null;
assetMetadata: string;
nftIds: string[];
evmAssetId: BigNumber;
}
>
>;
export const mintKanariaItemsByType = async ({
deployedCollections,
mintedResultsLogPath,
addedAssetsResultsLogPath,
kusamaItemsDataPath,
}: {
deployedCollections: DeployedCollectionsReturnType;
mintedResultsLogPath: string;
addedAssetsResultsLogPath: string;
kusamaItemsDataPath: string;
}) => {
try {
const accounts: SignerWithAddress[] = await ethers.getSigners();
const signer = accounts[0];
const deployerAddress = signer.address;
const accumulatorAddress =
process.env.ACCUMULATOR_ADDRESS !== undefined
? process.env.ACCUMULATOR_ADDRESS
: deployerAddress;
// Get minted items data, in case this script was already run and we are resuming
const alreadyMintedItems = await getOrCreateNdjosnLogFile<PremintedToken>(mintedResultsLogPath);
// Get added items assets entries data from previous step
const addedItemsAssetsEntriesResponse = await fsPromises.readFile(addedAssetsResultsLogPath);
const addedItemsAssetsEntries: AddedAssetEntryMapping = JSON.parse(
addedItemsAssetsEntriesResponse.toString(),
);
// Get original Kusama Items Data
const kusamaItemsDataResponse = await fsPromises.readFile(kusamaItemsDataPath);
const kusamaItemsDataArray: NftDataWithMetadata[] = JSON.parse(
kusamaItemsDataResponse.toString(),
);
const itemsCollections = deployedCollections.kanariaItems;
// Get kusama ids of nfts that were already minted on EVM
const alreadyMintedNftIds = alreadyMintedItems.map((n) => n.kusamaId);
// Filter Kusama gems, by those that were not yet minted on evm, also validate that its owner field which is a Kanaria id, was minted on EVM
const unmintedNfts = kusamaItemsDataArray.filter(
(n) =>
!alreadyMintedNftIds.includes(n.id) &&
!!n.collectionId &&
KANARIA_ITEMS_COLLECTIONS.includes(n.collectionId),
);
// Group all unminted nfts by collection id, because when we use batchMint function, we need to do it per collection
const unmintedNftsGroupedByCollection = groupBy((nft) => nft.collectionId, unmintedNfts);
// Get collection ids of yet to be minted grouped nfts, for easier iteration
const collectionIds: (keyof typeof unmintedNftsGroupedByCollection)[] = Object.keys(
unmintedNftsGroupedByCollection,
);
// We need to do batch mints in a context of a single collection
for (const collectionId of collectionIds) {
const collectionNfts = unmintedNftsGroupedByCollection[collectionId] || [];
// Batch collection nfts to be minted into chunks of 30, as contract might throw if we try to mint more than 20 at once
const batches = chunkArray(collectionNfts, 30);
// Find EVM address of deployed contract from previous step, by kusama collection id
const contractAddress = itemsCollections.find(
(g) => g.kusamaCollectionId === collectionId,
)?.address;
if (!contractAddress) {
console.error(
`Something went wrong! Contract address for collection ${collectionId} not found in deployed array`,
itemsCollections,
);
throw new Error(
`Contract address for collection ${collectionId} not found in deployed array`,
);
}
// Initialise contract instance for the collection in scope
const nftsContract = KanariaItems__factory.connect(
contractAddress,
(await ethers.getSigners())[0],
);
for (const batch of batches) {
const batchAssetIds = batch.map((k) => {
// Go through all pre-added asset entries mapping, and return asset ids that are meant for each nft in a batch
return Object.values(addedItemsAssetsEntries[contractAddress])
.map((a) => (a.nftIds.includes(k.id) ? a.evmAssetId : null))
.filter((a): a is BigNumber => a !== null);
});
if (batchAssetIds.length !== batch.length) {
throw new Error(`Something went wrong. batchAssetIds and batch length mismatch`);
}
console.log(
`Minting Items batch from collection ${collectionId}. ${batch.length} Items into accumulator account ${accumulatorAddress}`,
);
const tx = await nftsContract.batchMintWithAssets(accumulatorAddress, batchAssetIds);
const transactionReceipt = await tx.wait();
// Extract token ids of minted nfts from transaction receipt
const mintedTokenIds: BigNumber[] = uniq(
transactionReceipt.events
?.filter((event) => event.event === 'NestTransfer')
?.map((event) => event.args?.tokenId || null)
.flat()
.filter((r) => r !== null) || [],
);
if (mintedTokenIds.length !== batch.length) {
throw new Error(`Something went wrong. mintedTokenIds and batch length mismatch`);
}
console.log(
`Minted batch. found ${mintedTokenIds.length} minted token ids in tx receipt: `,
mintedTokenIds.map((t) => t.toNumber()),
);
// Save minted tokens to a log file, so we can resume from this point if something goes wrong
for (const [i, nft] of batch.entries()) {
await fsPromises.appendFile(
mintedResultsLogPath,
`${JSON.stringify({
address: contractAddress,
id: mintedTokenIds[i].toNumber(),
kusamaId: nft.id,
})}\n`,
);
}
}
}
console.log('Finished minting Kanaria items');
} catch (e: any) {
console.log('Error minting Kanaria birds', e);
throw e;
}
};
export const mintAllKanariaItems = async (deployedCollections: DeployedCollectionsReturnType) => {
console.log('Minting Kanaria items');
// Mint all Kanaria items except for banners and khala glasses
console.log('Minting Kanaria items: ITEMS OTHER THAN BANNERS AND KHALA GLASSES');
await mintKanariaItemsByType({
deployedCollections: deployedCollections,
mintedResultsLogPath: `${process.cwd()}/scripts/data/minting-results/minted-items-other-kusama-to-evm-mapping.ndjson`,
addedAssetsResultsLogPath: `${process.cwd()}/scripts/data/minting-results/added-assets-mapping-items.json`,
kusamaItemsDataPath: `${process.cwd()}/scripts/data/kanaria-items-other.json`,
});
// // Mint all Kanaria Khala glasses
// console.log('Minting Kanaria items: ITEMS KHALA GLASSES');
// await mintKanariaItemsByType({
// deployedCollections: deployedCollections,
// mintedResultsLogPath: `${process.cwd()}/scripts/data/minting-results/minted-items-khala-kusama-to-evm-mapping.ndjson`,
// addedAssetsResultsLogPath: `${process.cwd()}/scripts/data/minting-results/added-assets-mapping-items-khala.json`,
// kusamaItemsDataPath: `${process.cwd()}/scripts/data/kanaria-items-khala.json`,
// });
//
// // Mint all Kanaria banners
// console.log('Minting Kanaria items: ITEMS BANNERS');
// await mintKanariaItemsByType({
// deployedCollections: deployedCollections,
// mintedResultsLogPath: `${process.cwd()}/scripts/data/minting-results/minted-items-banners-kusama-to-evm-mapping.ndjson`,
// addedAssetsResultsLogPath: `${process.cwd()}/scripts/data/minting-results/added-assets-mapping-banners.json`,
// kusamaItemsDataPath: `${process.cwd()}/scripts/data/kanaria-banners.json`,
// });
};
async function main() {
try {
if (!isProd) {
console.warn(
'Warning: Not running in prod mode, check isProd value as well as any incorrect addresses in constants.ts',
);
}
await initMintingResultsFiles();
const network = hre.network.name as EVM_NETWORKS;
console.log(`START Kanaria minting scripts on network: ${network}`);
const feeData = await hre.ethers.provider.getFeeData();
console.log('feeData', feeData);
// Deploy all contracts or get them from the log file if they were already deployed
const collections = await performActionIfNotComplete<DeployedCollectionsReturnType>({
statusStepKey: 'deployAllContracts',
statusLogPath: `${process.cwd()}/scripts/data/minting-results/minting-status.ndjson`,
actionCallback: () => deployAllContracts(),
resultOutputPath: `${process.cwd()}/scripts/data/minting-results/collections.json`,
});
invariant(collections);
// Set all items and gems to be auto accepted into Kanaria
await performActionIfNotComplete<void>({
statusStepKey: 'setAutoAccept',
statusLogPath: `${process.cwd()}/scripts/data/minting-results/minting-status.ndjson`,
actionCallback: () => setAutoAccept(collections),
});
// Set valid equippable group ids on collections
await performActionIfNotComplete<void>({
statusStepKey: 'setAllValidParentForEquippableGroup',
statusLogPath: `${process.cwd()}/scripts/data/minting-results/minting-status.ndjson`,
actionCallback: () => setAllValidParentForEquippableGroup(collections),
});
// Deploy Kanaria catalog contract or get it from the log file if it was already deployed
const catalogAddress = await performActionIfNotComplete<string>({
statusStepKey: 'deployCatalog',
statusLogPath: `${process.cwd()}/scripts/data/minting-results/minting-status.ndjson`,
actionCallback: () => deployCatalog(),
resultOutputPath: `${process.cwd()}/scripts/data/minting-results/catalog-address.json`,
resultOutputKey: 'catalogAddress',
});
invariant(catalogAddress);
// Configure Catalog parts
await performActionIfNotComplete<void>({
statusStepKey: 'addPartsToCatalog',
statusLogPath: `${process.cwd()}/scripts/data/minting-results/minting-status.ndjson`,
actionCallback: () => addPartsToCatalog(catalogAddress, collections),
});
// Add all asset entries in advance to all contracts
await performActionIfNotComplete<void>({
statusStepKey: 'addedAssetEntries',
statusLogPath: `${process.cwd()}/scripts/data/minting-results/minting-status.ndjson`,
actionCallback: () => addAllItemsAssetEntries(collections, catalogAddress),
});
// Mint all Kanaria birds
await performActionIfNotComplete<void>({
statusStepKey: 'mintAllKanariaBirds',
statusLogPath: `${process.cwd()}/scripts/data/minting-results/minting-status.ndjson`,
actionCallback: () => mintAllKanariaBirds(catalogAddress, collections),
});
// Mint all gems
await performActionIfNotComplete<void>({
statusStepKey: 'mintAllKanariaGems',
statusLogPath: `${process.cwd()}/scripts/data/minting-results/minting-status.ndjson`,
actionCallback: () => mintAllKanariaGems(collections),
});
// Mint all items
await performActionIfNotComplete<void>({
statusStepKey: 'mintAllKanariaItems',
statusLogPath: `${process.cwd()}/scripts/data/minting-results/minting-status.ndjson`,
actionCallback: () => mintAllKanariaItems(collections),
});
console.log('ALL DONE!');
process.exit(0);
} catch (error) {
console.log('KANARIA MINTING MAIN SCRIPT ERROR', error);
process.exit(1);
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
import { Signer } from 'ethers';
import { ethers, run } from 'hardhat';
import { delay } from '@nomiclabs/hardhat-etherscan/dist/src/etherscan/EtherscanService';
import { KanariaCatalog__factory } from '../../typechain-types';
import { isVerificationPossible } from '../constants';
import { catalogMetadata } from '../utils/constants';
import fs from 'fs';
const fsPromises = fs.promises;
export const deployCatalog = async () => {
try {
console.log("Deploying Kanaria's Catalog contract");
const accounts: Signer[] = await ethers.getSigners();
const deployer = accounts[0];
console.log('Catalog deployer address: ' + (await deployer.getAddress()));
const catalogFactory = (await ethers.getContractFactory(
'KanariaCatalog',
)) as KanariaCatalog__factory;
const kanariaCatalog = await catalogFactory.deploy(catalogMetadata, 'svg');
await kanariaCatalog.deployed();
console.log('KanariaCatalog deployed to:', kanariaCatalog.address);
console.log('isVerificationPossible', isVerificationPossible);
if (isVerificationPossible) {
await delay(6000);
console.log('Etherscan contract verification starting now.');
try {
await run('verify:verify', {
address: kanariaCatalog.address,
contract: 'contracts/KanariaCatalog.sol:KanariaCatalog',
constructorArguments: [catalogMetadata, 'svg'],
});
} catch (e) {
console.log('Verification error', e);
}
}
await fsPromises.writeFile(
`${process.cwd()}/scripts/data/minting-results/catalog-address.json`,
JSON.stringify({ catalogAddress: kanariaCatalog.address }),
);
console.log('Done!');
return kanariaCatalog.address;
} catch (e: any) {
console.log('Error deploying Kanaria birds', e);
throw e;
}
};
import { getOrCreateNdjosnLogFile } from './get-or-create-ndjosn-log-file';
import fs from 'fs';
const fsPromises = fs.promises;
interface ScriptStep {
step: string;
status: boolean;
}
export const performActionIfNotComplete = async <T>({
statusStepKey,
statusLogPath,
resultOutputPath,
actionCallback,
resultOutputKey,
}: {
statusLogPath: string;
statusStepKey: string;
resultOutputPath?: string;
resultOutputKey?: string;
actionCallback: () => Promise<T>;
}) => {
const status = await getOrCreateNdjosnLogFile<ScriptStep>(statusLogPath);
const shouldPerformAction = status.find((r) => r.step === statusStepKey)?.status !== true;
if (shouldPerformAction) {
const result = await actionCallback();
await fsPromises.appendFile(
statusLogPath,
`${JSON.stringify({ step: statusStepKey, status: true })}\n`,
);
return result;
} else {
console.log(`Skipping ${statusStepKey} step`);
if (resultOutputPath) {
const response = await fsPromises.readFile(resultOutputPath, 'utf-8');
const responseJson = JSON.parse(response);
const result: T = resultOutputKey ? responseJson?.[resultOutputKey] : responseJson;
return result;
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment