@leda-mint-io/candymachine-client-sdk
Version:
Metaplex Candy Machine Client SDK
302 lines (301 loc) • 14.8 kB
JavaScript
;
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;
});
}