Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179#!/usr/bin/env node
/** * Rebake $air token metadata on v4 staging contract * This will regenerate the bundle and update on-chain metadata */
import { TezosToolkit } from '@taquito/taquito';import { InMemorySigner } from '@taquito/signer';import fs from 'fs';import path from 'path';import { fileURLToPath } from 'url';import https from 'https';
const __filename = fileURLToPath(import.meta.url);const __dirname = path.dirname(__filename);
// V4 staging contractconst V4_CONTRACT = 'KT1ER1GyoeRNhkv6E57yKbBbEKi5ynKbaH3W';const TOKEN_ID = 3; // $airconst PIECE_CODE = 'air';
// Load staging wallet credentialsconst stagingEnvPath = path.join(__dirname, 'staging/.env');const envContent = fs.readFileSync(stagingEnvPath, 'utf8');let stagingAddress, stagingKey;for (const line of envContent.split('\n')) { if (line.startsWith('STAGING_ADDRESS=') || line.startsWith('ADDRESS=')) { stagingAddress = line.split('=')[1].trim().replace(/"/g, ''); } else if (line.startsWith('STAGING_KEY=') || line.startsWith('KEY=') || line.startsWith('SECRET_KEY=')) { stagingKey = line.split('=')[1].trim().replace(/"/g, ''); }}
if (!stagingAddress || !stagingKey) { console.error('❌ Could not load staging wallet credentials'); process.exit(1);}
const tezos = new TezosToolkit('https://mainnet.api.tez.ie');tezos.setProvider({ signer: new InMemorySigner(stagingKey) });
console.log('\n╔══════════════════════════════════════════════════════════════╗');console.log('║ 🔄 Rebake $air Metadata ║');console.log('╚══════════════════════════════════════════════════════════════╝\n');
console.log(`📍 Contract: ${V4_CONTRACT}`);console.log(`🎨 Token: #${TOKEN_ID} ($${PIECE_CODE})`);console.log(`👤 Admin: ${stagingAddress}\n`);
// Fetch new bundle from bundle-html APIconsole.log('📦 Generating fresh bundle...');const bundleUrl = `https://aesthetic.computer/api/bundle-html?code=${PIECE_CODE}&format=json`;
function fetchUrl(url) { return new Promise((resolve, reject) => { https.get(url, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { if (res.statusCode === 200) { resolve(JSON.parse(data)); } else { reject(new Error(`HTTP ${res.statusCode}: ${data}`)); } }); }).on('error', reject); });}
const bundleData = await fetchUrl(bundleUrl);console.log(` ✓ Bundle generated: ${bundleData.sizeKB} KB`);console.log(` ✓ Filename: ${bundleData.filename}\n`);
// Upload to IPFS via Pinataconsole.log('📤 Uploading to IPFS...');
// Load Pinata credentialsconst pinataEnvPath = path.join(__dirname, '..', 'aesthetic-computer-vault', '.env.pinata');const pinataContent = fs.readFileSync(pinataEnvPath, 'utf8');let pinataKey, pinataSecret;for (const line of pinataContent.split('\n')) { if (line.startsWith('PINATA_API_KEY=')) { pinataKey = line.split('=')[1].trim().replace(/"/g, ''); } else if (line.startsWith('PINATA_API_SECRET=')) { pinataSecret = line.split('=')[1].trim().replace(/"/g, ''); }}
function uploadToPinata(content, filename, pinataApiKey, pinataApiSecret) { return new Promise((resolve, reject) => { const boundary = '----WebKitFormBoundary' + Math.random().toString(36); const bodyParts = [ `--${boundary}\r\n`, `Content-Disposition: form-data; name="file"; filename="${filename}"\r\n`, 'Content-Type: text/html\r\n\r\n', Buffer.from(content, 'base64').toString('utf8'), `\r\n--${boundary}--\r\n` ]; const body = bodyParts.join('');
const options = { hostname: 'api.pinata.cloud', path: '/pinning/pinFileToIPFS', method: 'POST', headers: { 'Content-Type': `multipart/form-data; boundary=${boundary}`, 'Content-Length': Buffer.byteLength(body), 'pinata_api_key': pinataApiKey, 'pinata_secret_api_key': pinataApiSecret } };
const req = https.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { if (res.statusCode === 200) { resolve(JSON.parse(data)); } else { reject(new Error(`Pinata upload failed: ${res.statusCode} ${data}`)); } }); });
req.on('error', reject); req.write(body); req.end(); });}
const pinataResult = await uploadToPinata(bundleData.content, bundleData.filename, pinataKey, pinataSecret);const ipfsHash = pinataResult.IpfsHash;const ipfsUri = `ipfs://${ipfsHash}`;
console.log(` ✓ Uploaded: ${ipfsUri}\n`);
// Build updated token_info with new artifact URIconsole.log('📝 Building updated metadata...');
function stringToBytes(str) { return Buffer.from(str, 'utf8').toString('hex');}
const tokenInfo = { 'name': stringToBytes(`$${PIECE_CODE}`), 'description': stringToBytes(bundleData.sourceCode || ''), 'artifactUri': stringToBytes(ipfsUri), 'displayUri': stringToBytes(ipfsUri), 'thumbnailUri': stringToBytes(ipfsUri), // Will use existing thumbnail 'decimals': stringToBytes('0'), 'symbol': stringToBytes('KEEP'), 'isBooleanAmount': stringToBytes('true'), 'shouldPreferSymbol': stringToBytes('false'), '': stringToBytes(ipfsUri) // metadata URI};
console.log('📤 Calling edit_metadata...');
try { const contract = await tezos.contract.at(V4_CONTRACT);
const op = await contract.methods.edit_metadata(TOKEN_ID, tokenInfo).send();
console.log(` ⏳ Operation hash: ${op.hash}`); console.log(' ⏳ Waiting for confirmation...');
await op.confirmation(1);
console.log('\n✅ Metadata updated!'); console.log(` 🔗 Explorer: https://tzkt.io/${op.hash}`); console.log(` 🎨 View on objkt: https://objkt.com/tokens/${V4_CONTRACT}/${TOKEN_ID}\n`);
} catch (error) { console.error('\n❌ Update failed!'); console.error(` Error: ${error.message}\n`); process.exit(1);}