UNPKG

@leda-mint-io/candymachine-client-sdk

Version:

Metaplex Candy Machine Client SDK

302 lines (301 loc) 14.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAssetManifest = exports.uploadV2 = void 0; const anchor_1 = require("@j0nnyboi/anchor"); const web3_js_1 = require("@safecoin/web3.js"); const promise_pool_1 = require("@supercharge/promise-pool"); const cache_1 = require("./../cache"); const enums_1 = require("../enums"); const constants_1 = require("../constants"); const arweave_1 = require("./arweave"); const helpers_1 = require("./helpers"); /** * * Upload a new candy machine with the settings passed and cache. * At the end of the upload, the cache can be downloaded. */ function uploadV2({ files, cacheName, env, totalNFTs, storage, retainAuthority, mutable, // nftStorageKey, // ipfsCredentials, // pinataJwt, // pinataGateway, // awsS3Bucket, batchSize, price, treasuryWallet, // splToken, gatekeeper, goLiveDate, endSettings, whitelistMintSettings, hiddenSettings, // uuid, walletKeyPair, anchorProgram, rateLimit, }) { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* () { // const savedContent = loadCache(cacheName, env); let cacheContent = { program: { uuid: '', candyMachine: '', }, items: {}, }; const filesNames = files.map((file) => file.name); // if (!cacheContent.program) { // cacheContent.program = {}; // } // if (!cacheContent.items) { // cacheContent.items = {}; // } const dedupedAssetKeys = getAssetKeysNeedingUpload(cacheContent.items, filesNames); console.log('dedupedAssetKeys', dedupedAssetKeys); let candyMachine = // cacheContent.program.candyMachine // ? new PublicKey(cacheContent.program.candyMachine) // : undefined; if (!((_a = cacheContent === null || cacheContent === void 0 ? void 0 : cacheContent.program) === null || _a === void 0 ? void 0 : _a.uuid)) { const firstManifestFile = files.find((file) => file.name === '0.json'); if (!firstManifestFile) throw new Error('0.json must be present'); const firstAssetManifest = JSON.parse(yield firstManifestFile.text()); try { // TODO - SPL TOKEN PAYMENT if (!((_c = (_b = firstAssetManifest.properties) === null || _b === void 0 ? void 0 : _b.creators) === null || _c === void 0 ? void 0 : _c.every((creator) => creator.address !== undefined))) { throw new Error('Creator address is missing'); } // initialize candy console.info(`initializing candy machine`); const res = yield (0, helpers_1.createCandyMachineV2)(anchorProgram, walletKeyPair, treasuryWallet, // splToken, { itemsAvailable: new anchor_1.BN(totalNFTs), uuid: null, symbol: firstAssetManifest.symbol, sellerFeeBasisPoints: firstAssetManifest.seller_fee_basis_points, isMutable: mutable, maxSupply: new anchor_1.BN(0), retainAuthority: retainAuthority, gatekeeper, goLiveDate, price, endSettings, whitelistMintSettings, hiddenSettings, creators: firstAssetManifest.properties.creators.map((creator) => { return { address: new web3_js_1.PublicKey(creator.address), verified: true, share: creator.share, }; }), }); console.log('res', res); cacheContent.program.uuid = res.uuid; cacheContent.program.candyMachine = res.candyMachine.toBase58(); cacheContent.startDate = goLiveDate; candyMachine = res.candyMachine; // TODO - set collection mint console.info(`initialized config for a candy machine with publickey: ${res.candyMachine.toBase58()}`); // saveCache(cacheName, env, cacheContent); } catch (exx) { console.error('Error deploying config to Solana network.', exx); throw exx; } } else { console.info(`config for a candy machine with publickey: ${cacheContent === null || cacheContent === void 0 ? void 0 : cacheContent.program.candyMachine} has been already initialized`); } // TODO CHANGE ANY const uploadedItems = Object.values(cacheContent.items).filter((f) => !!f.link).length; console.info(`[${uploadedItems}] out of [${totalNFTs}] items have been uploaded`); if (dedupedAssetKeys.length) { console.info(`Starting upload for [${dedupedAssetKeys.length}] items, format ${JSON.stringify(dedupedAssetKeys[0])}`); } if (dedupedAssetKeys.length) { // TODO - progress bar // TODO make single promise not overlap each other yield promise_pool_1.PromisePool.withConcurrency(batchSize || 1) .for(dedupedAssetKeys) .handleError((err, asset) => __awaiter(this, void 0, void 0, function* () { console.error(`\nError uploading ${JSON.stringify(asset)} asset (skipping)`, err.message); yield (0, helpers_1.sleep)(5000); })) .process((asset) => __awaiter(this, void 0, void 0, function* () { console.log('processing asset: ', asset); const jsonFile = files.find((file) => (0, helpers_1.getFileName)(file.name) === asset.index && file.type === constants_1.JSON_EXTENSION); const imageFile = files.find((file) => (0, helpers_1.getFileName)(file.name) === asset.index && file.type.startsWith('image/')); if (!jsonFile) { throw new Error(`JSON file ${asset.index}.json is missing`); } const manifest = getAssetManifest(asset.index, JSON.parse(yield (jsonFile === null || jsonFile === void 0 ? void 0 : jsonFile.text()))); // TODO - ADD ANIMATIONS const manifestBuffer = Buffer.from(JSON.stringify(manifest)); if (!imageFile) throw new Error(`Image file ${asset.index} is missing`); let link, imageLink, animationLink; try { switch (storage) { case enums_1.StorageType.Arweave: default: ; [link, imageLink] = yield (0, arweave_1.arweaveUpload)(walletKeyPair, anchorProgram, env, imageFile, manifestBuffer, manifest, Number(asset.index)); if (link && imageLink) { cacheContent.items[asset.index] = { link, imageLink, name: manifest.name, onChain: false, }; } } } catch (err) { (0, cache_1.saveCache)(cacheName, env, cacheContent); console.error(err); } })); } let uploadSuccessful = true; try { if (!hiddenSettings && candyMachine) { uploadSuccessful = yield writeIndices({ anchorProgram, cacheContent, cacheName, env, candyMachine, walletKeyPair, rateLimit, }); console.log('cache content: ', cacheContent); const uploadedItems = Object.values(cacheContent.items).filter((f) => !!f.link).length; console.log('uploadedItems: ', uploadedItems); console.log('totalNFTs: ', totalNFTs); console.log('uploadSuccessful: ', uploadSuccessful); uploadSuccessful = uploadSuccessful && uploadedItems == totalNFTs; } else { console.log('Skipping upload to chain as this is a hidden Candy Machine'); } console.log(`Done. Successful = ${uploadSuccessful}.`); return cacheContent.program.candyMachine; } catch (err) { console.error(err); return false; } }); } exports.uploadV2 = uploadV2; /** * From the Cache object & a list of file paths, return a list of asset keys * (filenames without extension nor path) that should be uploaded, sorted numerically in ascending order. * Assets which should be uploaded either are not present in the Cache object, * or do not truthy value for the `link` property. */ function getAssetKeysNeedingUpload(items, files) { const all = [...new Set([...Object.keys(items), ...files])]; const keyMap = {}; const assets = all .filter((k) => !k.includes('.json')) .reduce((acc, assetKey) => { var _a; const key = (0, helpers_1.getFileName)(assetKey); const ext = (0, helpers_1.getFileExtension)(assetKey); if (!((_a = items[key]) === null || _a === void 0 ? void 0 : _a.link) && !keyMap[key]) { keyMap[key] = true; acc.push({ mediaExt: ext, index: key }); } return acc; }, []) .sort((a, b) => Number.parseInt(a.index, 10) - Number.parseInt(b.index, 10)); console.log(assets); return assets; } /** * Returns a Manifest from a path and an assetKey * Replaces image.ext => index.ext * Replaces animation_url.ext => index.ext */ function getAssetManifest(assetIndex, manifest) { manifest.image = manifest.image.replace('image', assetIndex); if ('animation_url' in manifest) { manifest.animation_url = manifest.animation_url.replace('animation_url', assetIndex); } return manifest; } exports.getAssetManifest = getAssetManifest; /** * For each asset present in the Cache object, write to the deployed * configuration an additional line with the name of the asset and the link * to its manifest, if the asset was not already written according to the * value of `onChain` property in the Cache object, for said asset. */ function writeIndices({ anchorProgram, cacheContent, cacheName, env, candyMachine, walletKeyPair, rateLimit, }) { return __awaiter(this, void 0, void 0, function* () { let uploadSuccessful = true; const keys = Object.keys(cacheContent.items); const poolArray = []; const allIndicesInSlice = Array.from(Array(keys.length).keys()); let offset = 0; while (offset < allIndicesInSlice.length) { let length = 0; let lineSize = 0; let configLines = allIndicesInSlice.slice(offset, offset + 16); while (length < 850 && lineSize < 16 && configLines[lineSize] !== undefined) { length += cacheContent.items[keys[configLines[lineSize]]].link.length + cacheContent.items[keys[configLines[lineSize]]].name.length; if (length < 850) lineSize++; } configLines = allIndicesInSlice.slice(offset, offset + lineSize); offset += lineSize; const onChain = configLines.filter((i) => { var _a; return ((_a = cacheContent.items[keys[i]]) === null || _a === void 0 ? void 0 : _a.onChain) || false; }); const index = keys[configLines[0]]; if (onChain.length != configLines.length) { poolArray.push({ index, configLines }); } } console.info(`Writing all indices in ${poolArray.length} transactions...`); // TODO - progress bar const addConfigLines = ({ index, configLines }) => __awaiter(this, void 0, void 0, function* () { const response = yield anchorProgram.methods .addConfigLines(index, configLines.map((i) => ({ uri: cacheContent.items[keys[i]].link, name: cacheContent.items[keys[i]].name, }))) .accounts({ candyMachine, authority: walletKeyPair.publicKey, }) .signers([]) .rpc(); console.log(response); configLines.forEach((i) => { cacheContent.items[keys[i]] = Object.assign(Object.assign({}, cacheContent.items[keys[i]]), { onChain: true, verifyRun: false }); }); // saveCache(cacheName, env, cacheContent); // progressBar.increment(); }); yield promise_pool_1.PromisePool.withConcurrency(rateLimit || 5) .for(poolArray) .handleError((err, { index, configLines }) => __awaiter(this, void 0, void 0, function* () { console.error(`\nFailed writing indices ${index}-${keys[configLines[configLines.length - 1]]}: ${err.message}`); yield (0, helpers_1.sleep)(5000); uploadSuccessful = false; })) .process(({ index, configLines }) => __awaiter(this, void 0, void 0, function* () { yield addConfigLines({ index, configLines }); })); // progressBar.stop(); (0, cache_1.saveCache)(cacheName, env, cacheContent); return uploadSuccessful; }); }