Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 178 lines 6.2 kB view raw
1#!/usr/bin/env node 2 3/** 4 * Rebake $air token metadata on v4 staging contract 5 * This will regenerate the bundle and update on-chain metadata 6 */ 7 8import { TezosToolkit } from '@taquito/taquito'; 9import { InMemorySigner } from '@taquito/signer'; 10import fs from 'fs'; 11import path from 'path'; 12import { fileURLToPath } from 'url'; 13import https from 'https'; 14 15const __filename = fileURLToPath(import.meta.url); 16const __dirname = path.dirname(__filename); 17 18// V4 staging contract 19const V4_CONTRACT = 'KT1ER1GyoeRNhkv6E57yKbBbEKi5ynKbaH3W'; 20const TOKEN_ID = 3; // $air 21const PIECE_CODE = 'air'; 22 23// Load staging wallet credentials 24const stagingEnvPath = path.join(__dirname, 'staging/.env'); 25const envContent = fs.readFileSync(stagingEnvPath, 'utf8'); 26let stagingAddress, stagingKey; 27for (const line of envContent.split('\n')) { 28 if (line.startsWith('STAGING_ADDRESS=') || line.startsWith('ADDRESS=')) { 29 stagingAddress = line.split('=')[1].trim().replace(/"/g, ''); 30 } else if (line.startsWith('STAGING_KEY=') || line.startsWith('KEY=') || line.startsWith('SECRET_KEY=')) { 31 stagingKey = line.split('=')[1].trim().replace(/"/g, ''); 32 } 33} 34 35if (!stagingAddress || !stagingKey) { 36 console.error('❌ Could not load staging wallet credentials'); 37 process.exit(1); 38} 39 40const tezos = new TezosToolkit('https://mainnet.api.tez.ie'); 41tezos.setProvider({ signer: new InMemorySigner(stagingKey) }); 42 43console.log('\n╔══════════════════════════════════════════════════════════════╗'); 44console.log('║ 🔄 Rebake $air Metadata ║'); 45console.log('╚══════════════════════════════════════════════════════════════╝\n'); 46 47console.log(`📍 Contract: ${V4_CONTRACT}`); 48console.log(`🎨 Token: #${TOKEN_ID} ($${PIECE_CODE})`); 49console.log(`👤 Admin: ${stagingAddress}\n`); 50 51// Fetch new bundle from bundle-html API 52console.log('📦 Generating fresh bundle...'); 53const bundleUrl = `https://aesthetic.computer/api/bundle-html?code=${PIECE_CODE}&format=json`; 54 55function fetchUrl(url) { 56 return new Promise((resolve, reject) => { 57 https.get(url, (res) => { 58 let data = ''; 59 res.on('data', chunk => data += chunk); 60 res.on('end', () => { 61 if (res.statusCode === 200) { 62 resolve(JSON.parse(data)); 63 } else { 64 reject(new Error(`HTTP ${res.statusCode}: ${data}`)); 65 } 66 }); 67 }).on('error', reject); 68 }); 69} 70 71const bundleData = await fetchUrl(bundleUrl); 72console.log(` ✓ Bundle generated: ${bundleData.sizeKB} KB`); 73console.log(` ✓ Filename: ${bundleData.filename}\n`); 74 75// Upload to IPFS via Pinata 76console.log('📤 Uploading to IPFS...'); 77 78// Load Pinata credentials 79const pinataEnvPath = path.join(__dirname, '..', 'aesthetic-computer-vault', '.env.pinata'); 80const pinataContent = fs.readFileSync(pinataEnvPath, 'utf8'); 81let pinataKey, pinataSecret; 82for (const line of pinataContent.split('\n')) { 83 if (line.startsWith('PINATA_API_KEY=')) { 84 pinataKey = line.split('=')[1].trim().replace(/"/g, ''); 85 } else if (line.startsWith('PINATA_API_SECRET=')) { 86 pinataSecret = line.split('=')[1].trim().replace(/"/g, ''); 87 } 88} 89 90function uploadToPinata(content, filename, pinataApiKey, pinataApiSecret) { 91 return new Promise((resolve, reject) => { 92 const boundary = '----WebKitFormBoundary' + Math.random().toString(36); 93 const bodyParts = [ 94 `--${boundary}\r\n`, 95 `Content-Disposition: form-data; name="file"; filename="${filename}"\r\n`, 96 'Content-Type: text/html\r\n\r\n', 97 Buffer.from(content, 'base64').toString('utf8'), 98 `\r\n--${boundary}--\r\n` 99 ]; 100 const body = bodyParts.join(''); 101 102 const options = { 103 hostname: 'api.pinata.cloud', 104 path: '/pinning/pinFileToIPFS', 105 method: 'POST', 106 headers: { 107 'Content-Type': `multipart/form-data; boundary=${boundary}`, 108 'Content-Length': Buffer.byteLength(body), 109 'pinata_api_key': pinataApiKey, 110 'pinata_secret_api_key': pinataApiSecret 111 } 112 }; 113 114 const req = https.request(options, (res) => { 115 let data = ''; 116 res.on('data', chunk => data += chunk); 117 res.on('end', () => { 118 if (res.statusCode === 200) { 119 resolve(JSON.parse(data)); 120 } else { 121 reject(new Error(`Pinata upload failed: ${res.statusCode} ${data}`)); 122 } 123 }); 124 }); 125 126 req.on('error', reject); 127 req.write(body); 128 req.end(); 129 }); 130} 131 132const pinataResult = await uploadToPinata(bundleData.content, bundleData.filename, pinataKey, pinataSecret); 133const ipfsHash = pinataResult.IpfsHash; 134const ipfsUri = `ipfs://${ipfsHash}`; 135 136console.log(` ✓ Uploaded: ${ipfsUri}\n`); 137 138// Build updated token_info with new artifact URI 139console.log('📝 Building updated metadata...'); 140 141function stringToBytes(str) { 142 return Buffer.from(str, 'utf8').toString('hex'); 143} 144 145const tokenInfo = { 146 'name': stringToBytes(`$${PIECE_CODE}`), 147 'description': stringToBytes(bundleData.sourceCode || ''), 148 'artifactUri': stringToBytes(ipfsUri), 149 'displayUri': stringToBytes(ipfsUri), 150 'thumbnailUri': stringToBytes(ipfsUri), // Will use existing thumbnail 151 'decimals': stringToBytes('0'), 152 'symbol': stringToBytes('KEEP'), 153 'isBooleanAmount': stringToBytes('true'), 154 'shouldPreferSymbol': stringToBytes('false'), 155 '': stringToBytes(ipfsUri) // metadata URI 156}; 157 158console.log('📤 Calling edit_metadata...'); 159 160try { 161 const contract = await tezos.contract.at(V4_CONTRACT); 162 163 const op = await contract.methods.edit_metadata(TOKEN_ID, tokenInfo).send(); 164 165 console.log(` ⏳ Operation hash: ${op.hash}`); 166 console.log(' ⏳ Waiting for confirmation...'); 167 168 await op.confirmation(1); 169 170 console.log('\n✅ Metadata updated!'); 171 console.log(` 🔗 Explorer: https://tzkt.io/${op.hash}`); 172 console.log(` 🎨 View on objkt: https://objkt.com/tokens/${V4_CONTRACT}/${TOKEN_ID}\n`); 173 174} catch (error) { 175 console.error('\n❌ Update failed!'); 176 console.error(` Error: ${error.message}\n`); 177 process.exit(1); 178}