├── README.md ├── upload.ts ├── index.ts └── eclipsenft.sh /README.md: -------------------------------------------------------------------------------- 1 |

One click guide to create and mint NFT on Eclipse Mainnet or Testnet

2 | 3 | ## Prerequisites 4 | 5 | - Gas fee needed for paying gas fee, if you don't have any gas fee then use this : [Mainnet Bridge](https://bridge.eclipse.xyz) or [Testnet Bridge](https://bridge.validators.wtf) 6 | 7 | - You need to have `API_KEY` & `API_SECRET`, to get this API and SECRET, Visit [this site](https://pinata.cloud/), Register using email address and then click on the 3 line in the left corner, a side bar will open, here u will see `API KEYS` section , click on it and copy `API_KEY` and `API_SECRET`, Save it somewhere, it will be required in the middle of the code 8 | 9 | - You can use any linux based terminal, for example, [Codespace](https://github.com/codespaces) , [Gitpod](https://gitpod.io) , Ubuntu on Windows as well as on VPS 10 | 11 | ## Installation 12 | 13 | - Use this command to start the script 14 | 15 | ```bash 16 | [ -f "eclipsenft.sh" ] && rm eclipsenft.sh; wget -q https://raw.githubusercontent.com/zunxbt/Eclipse-NFT/main/eclipsenft.sh && chmod +x eclipsenft.sh && ./eclipsenft.sh 17 | ``` 18 | 19 | - There are 6 sections, you should start running one by one 20 | 21 | - If you want to mint unlimited time, u just need to run 1 to 4 commands one by one only 1 time after that u can enter 5 again and again to mint and create unlimited time 22 | -------------------------------------------------------------------------------- /upload.ts: -------------------------------------------------------------------------------- 1 | // upload.ts 2 | 3 | import { 4 | GenericFile, 5 | request, 6 | HttpInterface, 7 | HttpRequest, 8 | HttpResponse, 9 | } from '@metaplex-foundation/umi'; 10 | 11 | interface PinataUploadResponse { 12 | IpfsHash: string; 13 | PinSize: number; 14 | Timestamp: string; 15 | } 16 | 17 | const createPinataFetch = (): HttpInterface => ({ 18 | send: async (request: HttpRequest): Promise> => { 19 | let headers = new Headers( 20 | Object.entries(request.headers).map(([name, value]) => [name, value] as [string, string]) 21 | ); 22 | 23 | if (!headers.has('pinata_api_key') || !headers.has('pinata_secret_api_key')) { 24 | throw new Error('Missing Pinata API headers'); 25 | } 26 | 27 | const isJsonRequest = headers.get('content-type')?.includes('application/json') ?? false; 28 | const body = isJsonRequest && request.data ? JSON.stringify(request.data) : request.data as string | undefined; 29 | 30 | try { 31 | const response = await fetch(request.url, { 32 | method: request.method, 33 | headers, 34 | body, 35 | redirect: 'follow', 36 | signal: request.signal as AbortSignal, 37 | }); 38 | 39 | const bodyText = await response.text(); 40 | const isJsonResponse = response.headers.get('content-type')?.includes('application/json'); 41 | const data = isJsonResponse ? JSON.parse(bodyText) : bodyText; 42 | 43 | return { 44 | data, 45 | body: bodyText, 46 | ok: response.ok, 47 | status: response.status, 48 | statusText: response.statusText, 49 | headers: Object.fromEntries(response.headers.entries()), 50 | }; 51 | } catch (error) { 52 | console.error('Fetch request failed:', error); 53 | throw error; 54 | } 55 | }, 56 | }); 57 | 58 | const uploadToIpfs = async ( 59 | file: GenericFile, 60 | apiKey: string, 61 | secretKey: string 62 | ): Promise => { 63 | const http = createPinataFetch(); 64 | const endpoint = 'https://api.pinata.cloud/pinning/pinFileToIPFS'; 65 | const formData = new FormData(); 66 | 67 | // Handle null values for contentType 68 | const fileBlob = new Blob([file.buffer], { type: file.contentType || undefined }); 69 | 70 | formData.append('file', fileBlob, file.fileName); 71 | 72 | const pinataRequest = request() 73 | .withEndpoint('POST', endpoint) 74 | .withHeader('pinata_api_key', apiKey) 75 | .withHeader('pinata_secret_api_key', secretKey) 76 | .withData(formData); 77 | 78 | try { 79 | const response = await http.send(pinataRequest); 80 | if (!response.ok) throw new Error(`${response.status} - Failed to send request: ${response.statusText}`); 81 | return response.data.IpfsHash; // Get the IPFS hash from the response 82 | } catch (error) { 83 | console.error('Failed to send request:', error); 84 | throw error; 85 | } 86 | }; 87 | 88 | 89 | export { uploadToIpfs }; 90 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'; 2 | import { 3 | TransactionBuilderSendAndConfirmOptions, 4 | createGenericFile, 5 | createGenericFileFromJson, 6 | createSignerFromKeypair, 7 | generateSigner, 8 | keypairIdentity, 9 | } from '@metaplex-foundation/umi'; 10 | import { 11 | metadata, 12 | mint, 13 | niftyAsset, 14 | fetchAsset, 15 | Metadata, 16 | royalties, 17 | creators, 18 | Royalties, 19 | Creators, 20 | } from '@nifty-oss/asset'; 21 | import { readFile } from "fs/promises"; 22 | import { uploadToIpfs } from './upload'; 23 | import fs from 'fs'; 24 | const CLUSTERS = { 25 | 'mainnet': 'https://mainnetbeta-rpc.eclipse.xyz', 26 | 'testnet': 'https://testnet.dev2.eclipsenetwork.xyz', 27 | }; 28 | 29 | const OPTIONS: TransactionBuilderSendAndConfirmOptions = { 30 | confirm: { commitment: 'processed' } 31 | }; 32 | 33 | const NFT_DETAILS = { 34 | name: "NAME", 35 | symbol: "SYMBOL", 36 | royalties: 500, 37 | description: 'INFO, Guide by ZunXBT', 38 | imgType: 'image/jpg', 39 | attributes: [ 40 | { trait_type: 'Accuracy', value: 'Very High' }, 41 | ] 42 | }; 43 | 44 | const PINATA_API_KEY = 'ZUNXBT1'; // 👈 Replace with your Pinata API key 45 | const PINATA_SECRET_KEY = 'ZUNXBT2'; // 👈 Replace this with your IPFS API endpoint 46 | const umi = createUmi(CLUSTERS.ZUNXBT3, OPTIONS.confirm).use(niftyAsset()); // 👈 Replace this with your cluster 47 | const wallet = './eclipse-wallet.json'; // 👈 Replace this with your wallet path 48 | 49 | const secretKey = JSON.parse(fs.readFileSync(wallet, 'utf-8')); 50 | const keypair = umi.eddsa.createKeypairFromSecretKey(new Uint8Array(secretKey)); 51 | umi.use(keypairIdentity(keypair)); 52 | const creator = createSignerFromKeypair(umi, keypair); 53 | const owner = creator; // Mint to the creator 54 | const asset = generateSigner(umi); 55 | async function uploadImage(path: string, contentType = 'image/png'): Promise { 56 | try { 57 | const image = await readFile(path); 58 | const fileName = path.split('/').pop() ?? 'unknown.png'; 59 | const genericImage = createGenericFile(image, fileName, { contentType }); 60 | const cid = await uploadToIpfs(genericImage, PINATA_API_KEY, PINATA_SECRET_KEY); 61 | console.log(`1. ✅ - Uploaded image to IPFS`); 62 | return cid; 63 | } catch (error) { 64 | console.error('1. ❌ - Error uploading image:', error); 65 | throw error; 66 | } 67 | } 68 | 69 | async function uploadMetadata(imageUri: string): Promise { 70 | try { 71 | const gatewayUrl = 'https://gateway.pinata.cloud/ipfs'; // Add IPFS gateway URL 72 | const fullImageUri = `${gatewayUrl}${imageUri}`; // Full URI for the image 73 | 74 | const metadata = { 75 | name: NFT_DETAILS.name, 76 | description: NFT_DETAILS.description, 77 | image: fullImageUri, // Use full image URI 78 | attributes: NFT_DETAILS.attributes, 79 | properties: { 80 | files: [ 81 | { 82 | type: NFT_DETAILS.imgType, 83 | uri: fullImageUri, // Use full image URI 84 | }, 85 | ] 86 | } 87 | }; 88 | 89 | const file = createGenericFileFromJson(metadata, 'metadata.json'); 90 | const cid = await uploadToIpfs(file, PINATA_API_KEY, PINATA_SECRET_KEY); 91 | console.log(`2. ✅ - Uploaded metadata to IPFS`); 92 | return cid; 93 | } catch (error) { 94 | console.error('2. ❌ - Error uploading metadata:', error); 95 | throw error; 96 | } 97 | } 98 | async function mintAsset(metadataUri: string): Promise { 99 | try { 100 | await mint(umi, { 101 | asset, 102 | owner: owner.publicKey, 103 | authority: creator.publicKey, 104 | payer: umi.identity, 105 | mutable: false, 106 | standard: 0, 107 | name: NFT_DETAILS.name, 108 | extensions: [ 109 | metadata({ 110 | uri: metadataUri, 111 | symbol: NFT_DETAILS.symbol, 112 | description: NFT_DETAILS.description, 113 | }), 114 | royalties(NFT_DETAILS.royalties), 115 | creators([{ address: creator.publicKey, share: 100 }]), 116 | ] 117 | }).sendAndConfirm(umi, OPTIONS); 118 | const nftAddress = asset.publicKey.toString(); 119 | console.log(`3. ✅ - Minted a new Asset: ${nftAddress}`); 120 | } catch (error) { 121 | console.error('3. ❌ - Error minting a new NFT.', error); 122 | } 123 | } 124 | async function verifyOnChainData(metadataUri: string): Promise { 125 | try { 126 | const assetData = await fetchAsset(umi, asset.publicKey, OPTIONS.confirm); 127 | 128 | const onChainCreators = assetData.extensions.find(ext => ext.type === 3) as Creators; 129 | const onChainMetadata = assetData.extensions.find(ext => ext.type === 5) as Metadata; 130 | const onChainRoyalties = assetData.extensions.find(ext => ext.type === 7) as Royalties; 131 | 132 | const checks = [ 133 | // Asset Checks 134 | { condition: assetData.owner.toString() === owner.publicKey.toString(), message: 'Owner matches' }, 135 | { condition: assetData.publicKey.toString() === asset.publicKey.toString(), message: 'Public key matches' }, 136 | { condition: assetData.name === NFT_DETAILS.name, message: 'Asset name matches' }, 137 | 138 | // Creator Extension Checks 139 | { condition: !!onChainCreators, message: 'Creators extension not found' }, 140 | { condition: onChainCreators.values.length === 1, message: 'Creators length matches' }, 141 | { condition: onChainCreators.values[0].address.toString() === creator.publicKey.toString(), message: 'Creator address matches' }, 142 | { condition: onChainCreators.values[0].share === 100, message: 'Creator share matches' }, 143 | { condition: onChainCreators.values[0].verified === true, message: 'Creator not verified' }, 144 | 145 | // Metadata Extension Checks 146 | { condition: !!onChainMetadata, message: 'Metadata extension not found' }, 147 | { condition: onChainMetadata.symbol === NFT_DETAILS.symbol, message: 'Symbol matches' }, 148 | { condition: onChainMetadata.description === NFT_DETAILS.description, message: 'Description matches' }, 149 | { condition: onChainMetadata.uri === metadataUri, message: 'Metadata URI matches' }, 150 | 151 | // Royalties Extension Checks 152 | { condition: !!onChainRoyalties, message: 'Royalties extension not found' }, 153 | { condition: onChainRoyalties.basisPoints.toString() === NFT_DETAILS.royalties.toString(), message: 'Royalties basis points match' }, 154 | ]; 155 | 156 | checks.forEach(({ condition, message }) => { 157 | if (!condition) throw new Error(`Verification failed: ${message}`); 158 | }); 159 | 160 | console.log(`4. ✅ - Verified Asset Data`); 161 | } catch (error) { 162 | console.error('4. ❌ - Error verifying Asset Data:', error); 163 | } 164 | } 165 | async function main() { 166 | const imageCid = await uploadImage('./image.jpg'); 167 | console.log('Image CID:', imageCid); // Log the image CID 168 | const metadataCid = await uploadMetadata(imageCid); 169 | console.log('Metadata CID:', metadataCid); // Log the metadata CID 170 | await mintAsset(metadataCid); 171 | await verifyOnChainData(metadataCid); 172 | } 173 | 174 | main(); 175 | -------------------------------------------------------------------------------- /eclipsenft.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curl -s https://raw.githubusercontent.com/zunxbt/logo/main/logo.sh | bash 4 | sleep 3 5 | 6 | # Function to display messages 7 | show() { 8 | echo -e "\e[32m$1\e[0m" # Green colored message 9 | } 10 | 11 | mkdir -p Eclipse && cd Eclipse 12 | # Function to install Node.js, npm, Rust, and Solana 13 | install_all() { 14 | show "Installing Node.js and npm..." 15 | source <(wget -O - https://raw.githubusercontent.com/zunxbt/installation/main/node.sh) 16 | show "Node.js and npm installation completed." 17 | 18 | show "Installing Rust..." 19 | source <(wget -O - https://raw.githubusercontent.com/zunxbt/installation/main/rust.sh) 20 | show "Rust installation completed." 21 | 22 | if ! command -v solana &> /dev/null; then 23 | show "Solana not found. Installing Solana..." 24 | # Install Solana using the official installer 25 | sh -c "$(curl -sSfL https://release.solana.com/v1.18.18/install)" 26 | else 27 | show "Solana is already installed." 28 | fi 29 | 30 | # Add Solana to PATH if not already added 31 | if ! grep -q "$HOME/.local/share/solana/install/active_release/bin" ~/.bashrc; then 32 | echo 'export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"' >> ~/.bashrc 33 | show "Added Solana to PATH in .bashrc." 34 | fi 35 | 36 | if [ -n "$ZSH_VERSION" ]; then 37 | if ! grep -q "$HOME/.local/share/solana/install/active_release/bin" ~/.zshrc; then 38 | echo 'export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"' >> ~/.zshrc 39 | show "Added Solana to PATH in .zshrc." 40 | fi 41 | fi 42 | 43 | # Source the appropriate config file 44 | if [ -n "$BASH_VERSION" ]; then 45 | source ~/.bashrc 46 | elif [ -n "$ZSH_VERSION" ]; then 47 | source ~/.zshrc 48 | fi 49 | 50 | export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH" 51 | 52 | if command -v solana &> /dev/null; then 53 | show "Solana is available in the current session." 54 | else 55 | show "Failed to add Solana to the PATH. Exiting." 56 | exit 1 57 | fi 58 | } 59 | 60 | # Function to set up wallet 61 | setup_wallet() { 62 | KEYPAIR_DIR="$HOME/solana_keypairs" 63 | mkdir -p "$KEYPAIR_DIR" 64 | 65 | show "Do you want to use an existing wallet or create a new one?" 66 | PS3="Please enter your choice (1 or 2): " 67 | options=("Use existing wallet" "Create new wallet") 68 | select opt in "${options[@]}"; do 69 | case $opt in 70 | "Use existing wallet") 71 | show "Recovering from existing wallet..." 72 | KEYPAIR_PATH="$KEYPAIR_DIR/eclipse-wallet.json" 73 | solana-keygen recover -o "$KEYPAIR_PATH" --force 74 | if [[ $? -ne 0 ]]; then 75 | show "Failed to recover the existing wallet. Exiting." 76 | exit 1 77 | fi 78 | break 79 | ;; 80 | "Create new wallet") 81 | show "Creating a new wallet..." 82 | KEYPAIR_PATH="$KEYPAIR_DIR/eclipse-wallet.json" 83 | solana-keygen new -o "$KEYPAIR_PATH" --force 84 | if [[ $? -ne 0 ]]; then 85 | show "Failed to create a new wallet. Exiting." 86 | exit 1 87 | fi 88 | break 89 | ;; 90 | *) show "Invalid option. Please try again." ;; 91 | esac 92 | done 93 | 94 | solana config set --keypair "$KEYPAIR_PATH" 95 | show "Wallet setup completed!" 96 | 97 | cp "$KEYPAIR_PATH" "$PWD" 98 | } 99 | 100 | 101 | create_and_install_dependencies() { 102 | # Remove existing package.json if available 103 | rm -f package.json 104 | 105 | # Create package.json file 106 | cat < package.json 107 | { 108 | "name": "eclipse-nft", 109 | "version": "1.0.0", 110 | "main": "index.js", 111 | "scripts": { 112 | "test": "echo \\"Error: no test specified\\" && exit 1" 113 | }, 114 | "keywords": [], 115 | "author": "", 116 | "license": "ISC", 117 | "description": "", 118 | "dependencies": { 119 | "@metaplex-foundation/umi": "^0.9.2", 120 | "@metaplex-foundation/umi-bundle-defaults": "^0.9.2", 121 | "@nifty-oss/asset": "^0.6.1" 122 | }, 123 | "devDependencies": { 124 | "@types/node": "^22.7.4", 125 | "typescript": "^5.6.2" 126 | } 127 | } 128 | EOF 129 | 130 | show "package.json file created." 131 | 132 | show "Installing npm dependencies..." 133 | npm install --only=development 134 | show "Npm dependencies installation completed." 135 | } 136 | 137 | ts_file_Setup() { 138 | # Check if index.ts exists and remove it 139 | if [ -f index.ts ]; then 140 | rm index.ts 141 | else 142 | echo "index.ts does not exist. Skipping removal." 143 | fi 144 | 145 | # Download the new index.ts file 146 | wget -O index.ts https://raw.githubusercontent.com/zunxbt/Eclipse-NFT/main/index.ts 147 | 148 | # Ask the user for the required information 149 | read -p "Enter NFT Name: " nft_name 150 | read -p "Enter NFT Symbol: " nft_symbol 151 | read -p "Enter NFT Description (INFO): " nft_info 152 | read -p "Enter Pinata API Key: " pinata_api_key 153 | read -p "Enter Pinata Secret Key: " pinata_secret_key 154 | 155 | # Ask user for the network type 156 | echo "Select the network to create the NFT:" 157 | echo "1) Mainnet" 158 | echo "2) Testnet" 159 | read -p "Enter your choice (1 for Mainnet, 2 for Testnet): " network_choice 160 | 161 | # Set the network based on user choice 162 | if [ "$network_choice" == "1" ]; then 163 | network="mainnet" 164 | elif [ "$network_choice" == "2" ]; then 165 | network="testnet" 166 | else 167 | echo "Invalid choice. Exiting." 168 | exit 1 169 | fi 170 | 171 | # Define the file to modify (replace this with the actual file path) 172 | file_path="./index.ts" 173 | 174 | # Use sed to replace the placeholders with user input 175 | sed -i "s/NAME/$nft_name/" "$file_path" 176 | sed -i "s/SYMBOL/$nft_symbol/" "$file_path" 177 | sed -i "s/INFO/$nft_info/" "$file_path" 178 | sed -i "s/ZUNXBT1/$pinata_api_key/" "$file_path" 179 | sed -i "s/ZUNXBT2/$pinata_secret_key/" "$file_path" 180 | sed -i "s/ZUNXBT3/$network/" "$file_path" 181 | 182 | echo "NFT details and network have been updated in $file_path" 183 | 184 | 185 | if [ -f upload.ts ]; then 186 | rm upload.ts 187 | else 188 | echo "upload.ts does not exist. Skipping removal." 189 | fi 190 | 191 | # Download the new index.ts file 192 | wget -O upload.ts https://raw.githubusercontent.com/zunxbt/Eclipse-NFT/main/upload.ts 193 | rm -f tsconfig.json 194 | npx tsc --init 195 | } 196 | 197 | mint() { 198 | show "Minting..." 199 | wget https://picsum.photos/200 -O image.jpg 200 | npx ts-node index.ts 201 | } 202 | 203 | # Function to display the menu 204 | show_menu() { 205 | echo -e "\n\e[34m===== Eclipse NFT Setup Menu =====\e[0m" 206 | echo "1) Install Node.js, Rust, and Solana" 207 | echo "2) Set up Wallet" 208 | echo "3) Install npm dependencies" 209 | echo "4) Setup Mint File" 210 | echo "5) Start Minting" 211 | echo "6) Exit" 212 | echo -e "===================================\n" 213 | } 214 | 215 | # Main loop 216 | while true; do 217 | show_menu 218 | read -p "Choose an option [1-6]: " choice 219 | case $choice in 220 | 1) install_all ;; 221 | 2) setup_wallet ;; 222 | 3) create_and_install_dependencies ;; 223 | 4) ts_file_Setup ;; 224 | 5) mint ;; 225 | 6) show "Exiting the script."; exit 0 ;; 226 | *) show "Invalid option. Please try again." ;; 227 | esac 228 | done 229 | --------------------------------------------------------------------------------