diff --git a/README.md b/README.md index 06a6037d..d31fd3d5 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,6 @@ # Source Code from "How To Create An ENTIRE NFT Collection (10,000+) & MINT In Under 1 Hour Without Coding Knowledge" -## Multiple Active Branches - -- [main](https://github.com/codeSTACKr/video-source-code-create-nft-collection/tree/main) = Source code from the video -- [fix-uploads-mints](https://github.com/codeSTACKr/video-source-code-create-nft-collection/tree/fix-uploads-mints) = Updated to fix issues uploading files, metadata, and minting explained [below](#quota-limit-reached-or-too-many-requests-errors). - -[Video Link](https://youtu.be/AaCgydeMu64) +Video: [How To Create An ENTIRE NFT Collection (10,000+) & MINT In Under 1 Hour Without Coding Knowledge](https://youtu.be/AaCgydeMu64) Base code is from [hashlips_art_engine](https://github.com/HashLips/hashlips_art_engine) @@ -44,17 +39,16 @@ Ensure that your layer names in the `config.js` file match exactly to your layer ### "Quota Limit Reached" or "Too many requests" errors -There have been some changes made to the code in the [fix-uploads-mints](https://github.com/codeSTACKr/video-source-code-create-nft-collection/tree/fix-uploads-mints) branch resulting from some errors when uploading files, metadata, and minting using NFTPort. Depending on your plan, Free vs Community, there are rate limits. +There have been some changes made to the code from the original video resulting from some errors when uploading files, metadata, and minting using NFTPort. Depending on your plan, Free vs Community, there are rate limits. To fix these issues, I've updated the code to include a timeout that will allow the files to be uploaded at a slower rate, instead of all at once, eliminating these errors. **To use this code:** -- Clone this repo or download the zip file. +- Clone this repo or download the latest release zip file. - Unzip, if needed, and open the folder in VS Code. - From the terminal type: - `npm install` -- Ensure you are on the `fix-uploads-mints` branch if you cloned the repo. - Copy your image layers into the `layers` folder. - Use the `src/config.js` file to set up your layers and NFT information. diff --git a/banner.png b/banner.png deleted file mode 100644 index a3f46f37..00000000 Binary files a/banner.png and /dev/null differ diff --git a/layers/Background/Blue.png b/layers/Background/Blue.png new file mode 100644 index 00000000..20d3ee56 Binary files /dev/null and b/layers/Background/Blue.png differ diff --git a/layers/Background/Orange.png b/layers/Background/Orange.png new file mode 100644 index 00000000..0c5c4377 Binary files /dev/null and b/layers/Background/Orange.png differ diff --git a/layers/Background/Yellow.png b/layers/Background/Yellow.png new file mode 100644 index 00000000..58a71a30 Binary files /dev/null and b/layers/Background/Yellow.png differ diff --git a/logo.png b/logo.png deleted file mode 100644 index 14a4da31..00000000 Binary files a/logo.png and /dev/null differ diff --git a/package.json b/package.json index 7269f186..5817a8bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "10k-collection-video", - "version": "0.1.0", + "version": "1.0.0", "description": "Source code from \"How To Create An ENTIRE NFT Collection (10,000+) & MINT In Under 1 Hour Without Coding Knowledge\" video.", "main": "index.js", "bin": "index.js", diff --git a/src/config.js b/src/config.js index 3ffd8653..3ee6d734 100644 --- a/src/config.js +++ b/src/config.js @@ -79,7 +79,7 @@ const background = { }; const extraMetadata = { - external_url: "https://codecats.xyz", + external_url: "https://codecats.xyz", // Replace with your website or remove this line if you do not have one. }; const rarityDelimiter = "#"; diff --git a/utils/nftport/genericMetas.js b/utils/nftport/genericMetas.js index 2932c532..975c9853 100644 --- a/utils/nftport/genericMetas.js +++ b/utils/nftport/genericMetas.js @@ -1,6 +1,5 @@ const path = require("path"); -const isLocal = typeof process.pkg === "undefined"; -const basePath = isLocal ? process.cwd() : path.dirname(process.execPath); +const basePath = process.cwd(); const fs = require("fs"); const buildDir = path.join(basePath, "/build"); diff --git a/utils/nftport/mint.js b/utils/nftport/mint.js index a0788477..ba464d07 100644 --- a/utils/nftport/mint.js +++ b/utils/nftport/mint.js @@ -1,59 +1,113 @@ const fetch = require("node-fetch"); const path = require("path"); -const isLocal = typeof process.pkg === "undefined"; -const basePath = isLocal ? process.cwd() : path.dirname(process.execPath); +const basePath = process.cwd(); const fs = require("fs"); const AUTH = 'YOUR API KEY HERE'; const CONTRACT_ADDRESS = 'YOUR CONTRACT ADDRESS HERE'; const MINT_TO_ADDRESS = 'YOUR WALLET ADDRESS HERE'; const CHAIN = 'rinkeby'; +const TIMEOUT = 1000; // Milliseconds. This a timeout for errors only. If there is an error, it will wait then try again. 5000 = 5 seconds. -const ipfsMetas = JSON.parse( - fs.readFileSync(`${basePath}/build/json/_ipfsMetas.json`) -); - -fs.writeFileSync(`${basePath}/build/minted.json`, ""); -const writter = fs.createWriteStream(`${basePath}/build/minted.json`, { - flags: "a", -}); -writter.write("["); -nftCount = ipfsMetas.length; - -ipfsMetas.forEach((meta) => { - let url = "https://api.nftport.xyz/v0/mints/customizable"; - - const mintInfo = { - chain: CHAIN, - contract_address: CONTRACT_ADDRESS, - metadata_uri: meta.metadata_uri, - mint_to_address: MINT_TO_ADDRESS, - token_id: meta.custom_fields.edition, - }; - - let options = { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: AUTH, - }, - body: JSON.stringify(mintInfo), - }; - - fetch(url, options) - .then((res) => res.json()) - .then((json) => { - writter.write(JSON.stringify(json, null, 2)); - nftCount--; - - if (nftCount === 0) { - writter.write("]"); - writter.end(); - } else { - writter.write(",\n"); +if (!fs.existsSync(path.join(`${basePath}/build`, "/minted"))) { + fs.mkdirSync(path.join(`${basePath}/build`, "minted")); +} + +async function main() { + const ipfsMetas = JSON.parse( + fs.readFileSync(`${basePath}/build/ipfsMetas/_ipfsMetas.json`) + ); + + for (const meta of ipfsMetas) { + const mintFile = `${basePath}/build/minted/${meta.custom_fields.edition}.json`; + + try { + fs.accessSync(mintFile); + const mintedFile = fs.readFileSync(mintFile) + if(mintedFile.length > 0) { + const mintedMeta = JSON.parse(mintedFile) + if(mintedMeta.mintData.response !== "OK") throw 'not minted' } + console.log(`${meta.name} already minted`); + } catch(err) { + try { + let mintData = await fetchWithRetry(meta) + const combinedData = { + metaData: meta, + mintData: mintData + } + writeMintData(meta.custom_fields.edition, combinedData) + console.log(`Minted: ${meta.name}!`); + } catch(err) { + console.log(`Catch: ${err}`) + } + } + } +} + +main(); + +function timer(ms) { + return new Promise(res => setTimeout(res, ms)); +} + +async function fetchWithRetry(meta) { + await timer(TIMEOUT); + return new Promise((resolve, reject) => { + const fetch_retry = (_meta) => { + let url = "https://api.nftport.xyz/v0/mints/customizable"; + + const mintInfo = { + chain: CHAIN, + contract_address: CONTRACT_ADDRESS, + metadata_uri: _meta.metadata_uri, + mint_to_address: MINT_TO_ADDRESS, + token_id: _meta.custom_fields.edition, + }; + + let options = { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: AUTH, + }, + body: JSON.stringify(mintInfo), + }; + + return fetch(url, options).then(async (res) => { + const status = res.status; + + if(status === 200) { + return res.json(); + } + else { + console.error(`ERROR STATUS: ${status}`) + console.log('Retrying') + await timer(TIMEOUT) + fetch_retry(_meta) + } + }) + .then(async (json) => { + if(json.response === "OK"){ + return resolve(json); + } else { + console.error(`NOK: ${json.error}`) + console.log('Retrying') + await timer(TIMEOUT) + fetch_retry(_meta) + } + }) + .catch(async (error) => { + console.error(`CATCH ERROR: ${error}`) + console.log('Retrying') + await timer(TIMEOUT) + fetch_retry(_meta) + }); + } + return fetch_retry(meta); + }); +} - console.log(`Minted: ${json.transaction_external_url}`); - }) - .catch((err) => console.error("error:" + err)); -}); +const writeMintData = (_edition, _data) => { + fs.writeFileSync(`${basePath}/build/minted/${_edition}.json`, JSON.stringify(_data, null, 2)); +}; diff --git a/utils/nftport/uploadFiles.js b/utils/nftport/uploadFiles.js index 932af2d3..0b721b37 100644 --- a/utils/nftport/uploadFiles.js +++ b/utils/nftport/uploadFiles.js @@ -1,42 +1,97 @@ const FormData = require("form-data"); const fetch = require("node-fetch"); - const path = require("path"); -const isLocal = typeof process.pkg === "undefined"; -const basePath = isLocal ? process.cwd() : path.dirname(process.execPath); +const basePath = process.cwd(); const fs = require("fs"); const AUTH = 'YOUR API KEY HERE'; +const TIMEOUT = 1000; // Milliseconds. Extend this if needed to wait for each upload. 1000 = 1 second. -fs.readdirSync(`${basePath}/build/images`).forEach((file) => { - const formData = new FormData(); - const fileStream = fs.createReadStream(`${basePath}/build/images/${file}`); - formData.append("file", fileStream); - - let url = "https://api.nftport.xyz/v0/files"; - let options = { - method: "POST", - headers: { - Authorization: AUTH, - }, - body: formData, - }; - - fetch(url, options) - .then((res) => res.json()) - .then((json) => { - const fileName = path.parse(json.file_name).name; - let rawdata = fs.readFileSync(`${basePath}/build/json/${fileName}.json`); - let metaData = JSON.parse(rawdata); - - metaData.file_url = json.ipfs_url; +const allMetadata = []; +async function main() { + const files = fs.readdirSync(`${basePath}/build/images`); + files.sort(function(a, b){ + return a.split(".")[0] - b.split(".")[0]; + }); + for (const file of files) { + const fileName = path.parse(file).name; + let jsonFile = fs.readFileSync(`${basePath}/build/json/${fileName}.json`); + let metaData = JSON.parse(jsonFile); + if(!metaData.file_url.includes('https://')) { + const response = await fetchWithRetry(file); + metaData.file_url = response.ipfs_url; + fs.writeFileSync( `${basePath}/build/json/${fileName}.json`, JSON.stringify(metaData, null, 2) ); + console.log(`${response.file_name} uploaded & ${fileName}.json updated!`); + } else { + console.log(`${fileName} already uploaded.`); + } + + allMetadata.push(metaData); + } + fs.writeFileSync( + `${basePath}/build/json/_metadata.json`, + JSON.stringify(allMetadata, null, 2) + ); +} + +main(); + +function timer(ms) { + return new Promise(res => setTimeout(res, ms)); +} + +async function fetchWithRetry(file) { + await timer(TIMEOUT) + return new Promise((resolve, reject) => { + const fetch_retry = (_file) => { + const formData = new FormData(); + const fileStream = fs.createReadStream(`${basePath}/build/images/${_file}`); + formData.append("file", fileStream); + + let url = "https://api.nftport.xyz/v0/files"; + let options = { + method: "POST", + headers: { + Authorization: AUTH, + }, + body: formData, + }; + + return fetch(url, options).then(async (res) => { + const status = res.status; - console.log(`${json.file_name} uploaded & ${fileName}.json updated!`); - }) - .catch((err) => console.error("error:" + err)); -}); + if(status === 200) { + return res.json(); + } + else { + console.error(`ERROR STATUS: ${status}`) + console.log('Retrying') + await timer(TIMEOUT) + fetch_retry(_file) + } + }) + .then(async (json) => { + if(json.response === "OK"){ + return resolve(json); + } else { + console.error(`NOK: ${json.error}`) + console.log('Retrying') + await timer(TIMEOUT) + fetch_retry(_file) + } + }) + .catch(async (error) => { + console.error(`CATCH ERROR: ${error}`) + console.log('Retrying') + await timer(TIMEOUT) + fetch_retry(_file) + }); + } + return fetch_retry(file); + }); +} \ No newline at end of file diff --git a/utils/nftport/uploadMetas.js b/utils/nftport/uploadMetas.js index 1c8ff5db..7b29b5b7 100644 --- a/utils/nftport/uploadMetas.js +++ b/utils/nftport/uploadMetas.js @@ -1,49 +1,112 @@ const fetch = require("node-fetch"); - const path = require("path"); -const isLocal = typeof process.pkg === "undefined"; -const basePath = isLocal ? process.cwd() : path.dirname(process.execPath); +const basePath = process.cwd(); const fs = require("fs"); +const readDir = `${basePath}/build/json`; // change this directory if you are uploading generic images first in order to do a reveal. const AUTH = 'YOUR API KEY HERE'; +const TIMEOUT = 1000; // Milliseconds. Extend this if needed to wait for each upload. 1000 = 1 second. + +const allMetadata = []; + +if (!fs.existsSync(path.join(`${basePath}/build`, "/ipfsMetas"))) { + fs.mkdirSync(path.join(`${basePath}/build`, "ipfsMetas")); +} -fs.writeFileSync(`${basePath}/build/json/_ipfsMetas.json`, ""); -const writter = fs.createWriteStream(`${basePath}/build/json/_ipfsMetas.json`, { - flags: "a", -}); -writter.write("["); -const readDir = `${basePath}/build/json`; -fileCount = fs.readdirSync(readDir).length - 2; - -fs.readdirSync(readDir).forEach((file) => { - if (file === "_metadata.json" || file === "_ipfsMetas.json") return; - - const jsonFile = fs.readFileSync(`${readDir}/${file}`); - - let url = "https://api.nftport.xyz/v0/metadata"; - let options = { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: AUTH, - }, - body: jsonFile, - }; - - fetch(url, options) - .then((res) => res.json()) - .then((json) => { - writter.write(JSON.stringify(json, null, 2)); - fileCount--; - - if (fileCount === 0) { - writter.write("]"); - writter.end(); - } else { - writter.write(",\n"); +async function main() { + const files = fs.readdirSync(readDir); + files.sort(function(a, b){ + return a.split(".")[0] - b.split(".")[0]; + }); + for (const file of files) { + if (file !== "_metadata.json" && file !== "_ipfsMetas.json") { + let jsonFile = fs.readFileSync(`${readDir}/${file}`); + let metaData = JSON.parse(jsonFile); + const uploadedMeta = `${basePath}/build/ipfsMetas/${metaData.custom_fields.edition}.json`; + + try { + fs.accessSync(uploadedMeta); + const uploadedMetaFile = fs.readFileSync(uploadedMeta) + if(uploadedMetaFile.length > 0) { + const ipfsMeta = JSON.parse(uploadedMetaFile) + if(ipfsMeta.response !== "OK") throw 'metadata not uploaded' + allMetadata.push(ipfsMeta); + console.log(`${metaData.name} metadata already uploaded`); + } else { + throw 'metadata not uploaded' + } + } catch(err) { + try { + const response = await fetchWithRetry(jsonFile); + allMetadata.push(response); + writeResponseMetaData(response) + console.log(`${response.name} metadata uploaded!`); + } catch(err) { + console.log(`Catch: ${err}`) + } } + } + fs.writeFileSync( + `${basePath}/build/ipfsMetas/_ipfsMetas.json`, + JSON.stringify(allMetadata, null, 2) + ); + } +} + +main(); + +function timer(ms) { + return new Promise(res => setTimeout(res, ms)); +} + +async function fetchWithRetry(file) { + await timer(TIMEOUT); + return new Promise((resolve, reject) => { + const fetch_retry = (_file) => { + let url = "https://api.nftport.xyz/v0/metadata"; + let options = { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: AUTH, + }, + body: _file, + }; + + return fetch(url, options).then(async (res) => { + const status = res.status; + + if(status === 200) { + return res.json(); + } + else { + console.error(`ERROR STATUS: ${status}`) + console.log('Retrying') + await timer(TIMEOUT) + fetch_retry(_file) + } + }) + .then(async (json) => { + if(json.response === "OK"){ + return resolve(json); + } else { + console.error(`NOK: ${json.error}`) + console.log('Retrying') + await timer(TIMEOUT) + fetch_retry(_file) + } + }) + .catch(async (error) => { + console.error(`CATCH ERROR: ${error}`) + console.log('Retrying') + await timer(TIMEOUT) + fetch_retry(_file) + }); + } + return fetch_retry(file); + }); +} - console.log(`${json.name} metadata uploaded & added to _ipfsMetas.json`); - }) - .catch((err) => console.error("error:" + err)); -}); +const writeResponseMetaData = (_data) => { + fs.writeFileSync(`${basePath}/build/ipfsMetas/${_data.custom_fields.edition}.json`, JSON.stringify(_data, null, 2)); +};