├── 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 |
--------------------------------------------------------------------------------