Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Solana AI Studio</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdn.jsdelivr.net/npm/@solana/[email protected]/lib/index.iife.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/@solana/[email protected]/lib/index.iife.min.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.gradient-bg { | |
background: linear-gradient(135deg, #9945FF 0%, #14F195 100%); | |
} | |
.terminal { | |
background-color: #1e1e1e; | |
font-family: 'Courier New', monospace; | |
} | |
.blinking-cursor { | |
animation: blink 1s step-end infinite; | |
} | |
@keyframes blink { | |
from, to { opacity: 1; } | |
50% { opacity: 0; } | |
} | |
.artwork-container { | |
transition: all 0.3s ease; | |
} | |
.artwork-container:hover { | |
transform: scale(1.02); | |
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); | |
} | |
.token-badge { | |
transition: all 0.2s ease; | |
} | |
.token-badge:hover { | |
transform: translateY(-2px); | |
} | |
.loading-spinner { | |
animation: spin 1s linear infinite; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
.nft-card { | |
transition: all 0.2s ease; | |
} | |
.nft-card:hover { | |
transform: translateY(-5px); | |
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); | |
} | |
</style> | |
</head> | |
<body class="bg-gray-900 text-white min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<!-- Header --> | |
<header class="flex justify-between items-center mb-8"> | |
<div class="flex items-center space-x-2"> | |
<div class="gradient-bg p-2 rounded-lg"> | |
<i class="fas fa-robot text-white text-2xl"></i> | |
</div> | |
<h1 class="text-2xl font-bold bg-clip-text text-transparent gradient-bg">Solana AI Studio</h1> | |
</div> | |
<div id="wallet-connect" class="flex items-center space-x-4"> | |
<button id="connect-wallet-btn" class="px-4 py-2 rounded-lg gradient-bg hover:opacity-90 transition"> | |
Connect Wallet | |
</button> | |
<div id="wallet-info" class="hidden items-center space-x-2"> | |
<div class="w-8 h-8 rounded-full gradient-bg flex items-center justify-center"> | |
<i class="fas fa-wallet text-white"></i> | |
</div> | |
<span id="wallet-address" class="text-sm font-mono"></span> | |
</div> | |
</div> | |
</header> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
<!-- Left Panel - AI Studio --> | |
<div class="lg:col-span-2 space-y-6"> | |
<!-- Prompt Input --> | |
<div class="bg-gray-800 rounded-xl p-6 shadow-lg"> | |
<div class="flex items-center justify-between mb-4"> | |
<h2 class="text-xl font-semibold">AI Art Generator</h2> | |
<div class="flex items-center space-x-2"> | |
<span class="text-xs bg-gray-700 px-2 py-1 rounded">XAI API</span> | |
<span class="text-xs bg-purple-600 px-2 py-1 rounded">GROK-2-IMAGE</span> | |
</div> | |
</div> | |
<textarea id="prompt-input" class="w-full bg-gray-700 rounded-lg p-4 h-32 focus:outline-none focus:ring-2 focus:ring-purple-500" placeholder="Describe the artwork you want to generate..."></textarea> | |
<div class="flex justify-between items-center mt-4"> | |
<div class="flex space-x-2"> | |
<button id="random-prompt-btn" class="px-3 py-1 bg-gray-700 rounded-lg text-sm hover:bg-gray-600 transition"> | |
<i class="fas fa-random mr-1"></i> Random | |
</button> | |
<button id="clear-prompt-btn" class="px-3 py-1 bg-gray-700 rounded-lg text-sm hover:bg-gray-600 transition"> | |
<i class="fas fa-trash-alt mr-1"></i> Clear | |
</button> | |
</div> | |
<div class="flex items-center space-x-4"> | |
<div class="flex items-center"> | |
<label class="mr-2 text-sm">Images:</label> | |
<select id="image-count" class="bg-gray-700 rounded px-2 py-1 text-sm"> | |
<option value="1">1</option> | |
<option value="2">2</option> | |
<option value="3">3</option> | |
<option value="4">4</option> | |
</select> | |
</div> | |
<button id="generate-btn" class="px-4 py-2 rounded-lg gradient-bg hover:opacity-90 transition flex items-center"> | |
<i class="fas fa-magic mr-2"></i> Generate | |
</button> | |
</div> | |
</div> | |
</div> | |
<!-- Generated Artwork --> | |
<div id="artwork-results" class="bg-gray-800 rounded-xl p-6 shadow-lg"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold">Generated Artwork</h2> | |
<div id="generation-cost" class="text-sm bg-gray-700 px-3 py-1 rounded-lg hidden"> | |
<span id="cost-amount">10</span> <span id="cost-token">AIART</span> | |
</div> | |
</div> | |
<div id="artwork-placeholder" class="flex flex-col items-center justify-center py-12 text-gray-500"> | |
<i class="fas fa-image text-4xl mb-4"></i> | |
<p>Your generated artwork will appear here</p> | |
</div> | |
<div id="artwork-grid" class="grid grid-cols-1 md:grid-cols-2 gap-4 hidden"> | |
<!-- Artwork will be inserted here --> | |
</div> | |
<div id="loading-spinner" class="flex flex-col items-center justify-center py-12 hidden"> | |
<div class="loading-spinner w-12 h-12 border-4 border-purple-500 border-t-transparent rounded-full mb-4"></div> | |
<p>Generating your artwork...</p> | |
<p class="text-sm text-gray-400 mt-2" id="generation-status">Initializing AI model...</p> | |
</div> | |
</div> | |
</div> | |
<!-- Right Panel - Terminal & Wallet --> | |
<div class="space-y-6"> | |
<!-- Terminal --> | |
<div class="terminal rounded-xl p-6 shadow-lg h-96 overflow-hidden"> | |
<div class="flex items-center justify-between mb-4"> | |
<h2 class="text-xl font-semibold">AI Terminal</h2> | |
<div class="flex items-center space-x-2"> | |
<div class="w-2 h-2 rounded-full bg-green-500"></div> | |
<span class="text-xs">SOLANA</span> | |
</div> | |
</div> | |
<div id="terminal-output" class="h-64 overflow-y-auto mb-2 font-mono text-sm"> | |
<div class="text-green-400">Solana AI Studio v1.0.0</div> | |
<div class="text-green-400">Connected to Solana Mainnet</div> | |
<div class="text-gray-400">> Ready for commands...</div> | |
</div> | |
<div class="flex items-center border-t border-gray-700 pt-2"> | |
<span class="text-green-400 mr-2">$</span> | |
<input id="terminal-input" class="flex-1 bg-transparent focus:outline-none" placeholder="Type commands here..."> | |
<span class="blinking-cursor">|</span> | |
</div> | |
</div> | |
<!-- Wallet Assets --> | |
<div class="bg-gray-800 rounded-xl p-6 shadow-lg"> | |
<div class="flex items-center justify-between mb-4"> | |
<h2 class="text-xl font-semibold">Wallet Assets</h2> | |
<button id="refresh-assets-btn" class="p-1 hover:bg-gray-700 rounded"> | |
<i class="fas fa-sync-alt"></i> | |
</button> | |
</div> | |
<div id="wallet-assets" class="space-y-3 max-h-64 overflow-y-auto"> | |
<div class="text-center py-8 text-gray-500"> | |
<i class="fas fa-wallet text-2xl mb-2"></i> | |
<p>Connect your wallet to view assets</p> | |
</div> | |
</div> | |
</div> | |
<!-- Burn Tokens --> | |
<div class="bg-gray-800 rounded-xl p-6 shadow-lg"> | |
<div class="flex items-center justify-between mb-4"> | |
<h2 class="text-xl font-semibold">Burn Tokens</h2> | |
<div class="text-xs bg-red-600 px-2 py-1 rounded">DESTRUCTIVE</div> | |
</div> | |
<p class="text-sm text-gray-400 mb-4">Burn tokens to power AI generation or clean up your wallet</p> | |
<div id="burnable-assets" class="space-y-2 mb-4 max-h-32 overflow-y-auto"> | |
<div class="text-center py-4 text-gray-500 text-sm"> | |
No burnable assets found | |
</div> | |
</div> | |
<button id="burn-selected-btn" class="w-full py-2 bg-red-600 rounded-lg hover:bg-red-700 transition flex items-center justify-center disabled:opacity-50" disabled> | |
<i class="fas fa-fire mr-2"></i> Burn Selected | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Constants | |
const PAYMENT_TOKEN_MINT = "AiArTk..."; // Replace with actual mint address | |
const PAYMENT_TOKEN_DECIMALS = 6; | |
const GENERATION_COST = 10; // 10 AIART tokens per generation | |
const BURN_BLACKLIST_MINTS = [ | |
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC | |
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", // USDT | |
"So11111111111111111111111111111111111111112" // SOL | |
]; | |
const HELIUS_API_KEY = "YOUR_HELIUS_API_KEY"; // Replace with your Helius API key | |
const XAI_API_KEY = "YOUR_XAI_API_KEY"; // Replace with your XAI API key | |
// State | |
let walletConnected = false; | |
let publicKey = null; | |
let connection = null; | |
let selectedAssets = []; | |
let tokenAccounts = []; | |
let nfts = []; | |
// DOM Elements | |
const connectWalletBtn = document.getElementById('connect-wallet-btn'); | |
const walletInfo = document.getElementById('wallet-info'); | |
const walletAddress = document.getElementById('wallet-address'); | |
const walletAssetsContainer = document.getElementById('wallet-assets'); | |
const burnableAssetsContainer = document.getElementById('burnable-assets'); | |
const burnSelectedBtn = document.getElementById('burn-selected-btn'); | |
const refreshAssetsBtn = document.getElementById('refresh-assets-btn'); | |
const promptInput = document.getElementById('prompt-input'); | |
const randomPromptBtn = document.getElementById('random-prompt-btn'); | |
const clearPromptBtn = document.getElementById('clear-prompt-btn'); | |
const generateBtn = document.getElementById('generate-btn'); | |
const artworkPlaceholder = document.getElementById('artwork-placeholder'); | |
const artworkGrid = document.getElementById('artwork-grid'); | |
const loadingSpinner = document.getElementById('loading-spinner'); | |
const generationCost = document.getElementById('generation-cost'); | |
const costAmount = document.getElementById('cost-amount'); | |
const costToken = document.getElementById('cost-token'); | |
const generationStatus = document.getElementById('generation-status'); | |
const terminalOutput = document.getElementById('terminal-output'); | |
const terminalInput = document.getElementById('terminal-input'); | |
const imageCount = document.getElementById('image-count'); | |
// Initialize | |
document.addEventListener('DOMContentLoaded', () => { | |
// Initialize Solana connection | |
initializeSolana(); | |
// Set up event listeners | |
setupEventListeners(); | |
// Set generation cost | |
costAmount.textContent = GENERATION_COST; | |
generationCost.classList.remove('hidden'); | |
}); | |
function initializeSolana() { | |
// Check if Phantom wallet is installed | |
if ('solana' in window) { | |
const provider = window.solana; | |
if (provider.isPhantom) { | |
logToTerminal("Phantom wallet detected"); | |
// Try to connect automatically | |
provider.connect({ onlyIfTrusted: true }).then(() => { | |
publicKey = provider.publicKey; | |
walletConnected = true; | |
updateWalletUI(); | |
logToTerminal("Auto-connected to wallet"); | |
fetchWalletAssets(); | |
}).catch(err => { | |
logToTerminal("Auto-connect failed: " + err.message); | |
}); | |
// Listen for account changes | |
provider.on('accountChanged', (newPublicKey) => { | |
if (newPublicKey) { | |
publicKey = newPublicKey; | |
updateWalletUI(); | |
fetchWalletAssets(); | |
logToTerminal("Account changed"); | |
} else { | |
// Wallet disconnected | |
publicKey = null; | |
walletConnected = false; | |
updateWalletUI(); | |
clearWalletAssets(); | |
logToTerminal("Wallet disconnected"); | |
} | |
}); | |
} | |
} else { | |
logToTerminal("Phantom wallet not detected"); | |
} | |
// Initialize Solana connection | |
connection = new solanaWeb3.Connection( | |
solanaWeb3.clusterApiUrl('mainnet-beta'), | |
'confirmed' | |
); | |
} | |
function setupEventListeners() { | |
// Wallet connection | |
connectWalletBtn.addEventListener('click', connectWallet); | |
// Asset management | |
refreshAssetsBtn.addEventListener('click', fetchWalletAssets); | |
burnSelectedBtn.addEventListener('click', burnSelectedAssets); | |
// Prompt controls | |
randomPromptBtn.addEventListener('click', generateRandomPrompt); | |
clearPromptBtn.addEventListener('click', clearPrompt); | |
generateBtn.addEventListener('click', generateArt); | |
// Terminal input | |
terminalInput.addEventListener('keypress', (e) => { | |
if (e.key === 'Enter') { | |
const command = terminalInput.value.trim(); | |
if (command) { | |
handleTerminalCommand(command); | |
terminalInput.value = ''; | |
} | |
} | |
}); | |
} | |
async function connectWallet() { | |
if (!('solana' in window)) { | |
logToTerminal("Error: Phantom wallet not installed"); | |
alert("Please install Phantom wallet from https://phantom.app/"); | |
return; | |
} | |
const provider = window.solana; | |
try { | |
const response = await provider.connect(); | |
publicKey = response.publicKey; | |
walletConnected = true; | |
updateWalletUI(); | |
logToTerminal("Successfully connected to wallet"); | |
fetchWalletAssets(); | |
} catch (err) { | |
logToTerminal("Wallet connection failed: " + err.message); | |
} | |
} | |
function updateWalletUI() { | |
if (walletConnected && publicKey) { | |
connectWalletBtn.classList.add('hidden'); | |
walletInfo.classList.remove('hidden'); | |
walletAddress.textContent = publicKey.toString().slice(0, 6) + '...' + publicKey.toString().slice(-4); | |
} else { | |
connectWalletBtn.classList.remove('hidden'); | |
walletInfo.classList.add('hidden'); | |
} | |
} | |
async function fetchWalletAssets() { | |
if (!walletConnected || !publicKey || !connection) { | |
logToTerminal("Wallet not connected, cannot fetch assets"); | |
return; | |
} | |
logToTerminal("Fetching wallet assets..."); | |
try { | |
// Show loading state | |
walletAssetsContainer.innerHTML = ` | |
<div class="flex items-center justify-center py-4"> | |
<div class="loading-spinner w-6 h-6 border-2 border-purple-500 border-t-transparent rounded-full mr-2"></div> | |
<span>Loading assets...</span> | |
</div> | |
`; | |
// Fetch token accounts | |
const accounts = await connection.getParsedTokenAccountsByOwner( | |
publicKey, | |
{ programId: solanaWeb3.TOKEN_PROGRAM_ID } | |
); | |
tokenAccounts = accounts.value; | |
// Fetch NFTs using Helius DAS API | |
const response = await fetch(`https://mainnet.helius-rpc.com/?api-key=${HELIUS_API_KEY}`, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ | |
jsonrpc: '2.0', | |
id: 'my-id', | |
method: 'getAssetsByOwner', | |
params: { | |
ownerAddress: publicKey.toString(), | |
page: 1, | |
limit: 1000, | |
displayOptions: { | |
showFungible: true, | |
showNativeBalance: true | |
} | |
}, | |
}), | |
}); | |
const { result } = await response.json(); | |
nfts = result.items || []; | |
// Update UI | |
renderWalletAssets(); | |
renderBurnableAssets(); | |
logToTerminal(`Found ${tokenAccounts.length} token accounts and ${nfts.length} NFTs`); | |
} catch (err) { | |
logToTerminal("Error fetching assets: " + err.message); | |
walletAssetsContainer.innerHTML = ` | |
<div class="text-center py-4 text-red-500"> | |
<i class="fas fa-exclamation-triangle mr-2"></i> | |
Error loading assets | |
</div> | |
`; | |
} | |
} | |
function renderWalletAssets() { | |
if (!tokenAccounts.length && !nfts.length) { | |
walletAssetsContainer.innerHTML = ` | |
<div class="text-center py-4 text-gray-500"> | |
No assets found in wallet | |
</div> | |
`; | |
return; | |
} | |
let html = ''; | |
// Render token accounts | |
tokenAccounts.forEach(account => { | |
const info = account.account.data.parsed.info; | |
const mint = info.mint; | |
const balance = info.tokenAmount.uiAmount; | |
const decimals = info.tokenAmount.decimals; | |
const rawBalance = info.tokenAmount.amount; | |
// Skip blacklisted tokens | |
if (BURN_BLACKLIST_MINTS.includes(mint)) return; | |
html += ` | |
<div class="bg-gray-700 rounded-lg p-3 flex justify-between items-center"> | |
<div> | |
<div class="font-medium">${mint.slice(0, 4)}...${mint.slice(-4)}</div> | |
<div class="text-sm text-gray-400">${balance} tokens</div> | |
</div> | |
<div class="flex space-x-2"> | |
<button | |
class="token-badge px-2 py-1 bg-gray-600 rounded text-xs hover:bg-gray-500 transition" | |
data-mint="${mint}" | |
data-account="${account.pubkey.toString()}" | |
data-balance="${rawBalance}" | |
data-decimals="${decimals}" | |
onclick="selectAssetForBurn(this)" | |
> | |
<i class="fas fa-fire mr-1"></i> Burn | |
</button> | |
</div> | |
</div> | |
`; | |
}); | |
// Render NFTs | |
nfts.forEach(nft => { | |
if (!nft.content || !nft.content.metadata) return; | |
let imageUrl = 'https://via.placeholder.com/150'; | |
if (nft.content.files && nft.content.files[0] && nft.content.files[0].uri) { | |
imageUrl = nft.content.files[0].uri; | |
} else if (nft.content.files && nft.content.files[0] && nft.content.files[0].url) { | |
imageUrl = nft.content.files[0].url; | |
} | |
html += ` | |
<div class="nft-card bg-gray-700 rounded-lg overflow-hidden hover:shadow-lg transition"> | |
<img src="${imageUrl}" alt="${nft.content.metadata.name}" class="w-full h-32 object-cover"> | |
<div class="p-3"> | |
<div class="font-medium truncate">${nft.content.metadata.name || 'Unnamed NFT'}</div> | |
<div class="flex justify-between items-center mt-2"> | |
<span class="text-xs text-gray-400">${nft.id.slice(0, 4)}...${nft.id.slice(-4)}</span> | |
<button | |
class="token-badge px-2 py-1 bg-gray-600 rounded text-xs hover:bg-gray-500 transition" | |
data-mint="${nft.id}" | |
data-account="${nft.id}" | |
data-is-nft="true" | |
onclick="selectAssetForBurn(this)" | |
> | |
<i class="fas fa-fire mr-1"></i> Burn | |
</button> | |
</div> | |
</div> | |
</div> | |
`; | |
}); | |
walletAssetsContainer.innerHTML = html; | |
} | |
function renderBurnableAssets() { | |
if (selectedAssets.length === 0) { | |
burnableAssetsContainer.innerHTML = ` | |
<div class="text-center py-4 text-gray-500 text-sm"> | |
Select assets to burn | |
</div> | |
`; | |
burnSelectedBtn.disabled = true; | |
return; | |
} | |
let html = ''; | |
selectedAssets.forEach(assetId => { | |
// Check if it's a token account or NFT | |
const tokenAccount = tokenAccounts.find( | |
account => account.pubkey.toString() === assetId | |
); | |
if (tokenAccount) { | |
const info = tokenAccount.account.data.parsed.info; | |
html += ` | |
<div class="bg-gray-700 rounded p-2 flex justify-between items-center text-sm"> | |
<span>${info.mint.slice(0, 4)}...${info.mint.slice(-4)}</span> | |
<span>${info.tokenAmount.uiAmountString}</span> | |
</div> | |
`; | |
} else { | |
// It's an NFT | |
const nft = nfts.find(n => n.id === assetId); | |
if (nft) { | |
html += ` | |
<div class="bg-gray-700 rounded p-2 flex justify-between items-center text-sm"> | |
<span>NFT: ${nft.content.metadata.name || 'Unnamed NFT'}</span> | |
<span>1</span> | |
</div> | |
`; | |
} | |
} | |
}); | |
burnableAssetsContainer.innerHTML = html; | |
burnSelectedBtn.disabled = false; | |
} | |
function selectAssetForBurn(element) { | |
const assetId = element.getAttribute('data-account'); | |
if (selectedAssets.includes(assetId)) { | |
// Deselect | |
selectedAssets = selectedAssets.filter(id => id !== assetId); | |
element.classList.remove('bg-red-600'); | |
element.classList.add('bg-gray-600'); | |
} else { | |
// Select | |
selectedAssets.push(assetId); | |
element.classList.remove('bg-gray-600'); | |
element.classList.add('bg-red-600'); | |
} | |
renderBurnableAssets(); | |
} | |
async function burnSelectedAssets() { | |
if (!walletConnected || !publicKey || !connection || selectedAssets.length === 0) { | |
logToTerminal("Cannot burn assets: wallet not connected or no assets selected"); | |
return; | |
} | |
try { | |
logToTerminal(`Preparing to burn ${selectedAssets.length} assets...`); | |
burnSelectedBtn.disabled = true; | |
burnSelectedBtn.innerHTML = '<div class="loading-spinner w-4 h-4 border-2 border-white border-t-transparent rounded-full mr-2"></div> Processing...'; | |
const provider = window.solana; | |
const { blockhash } = await connection.getLatestBlockhash(); | |
// Create instructions for each selected asset | |
const instructions = []; | |
// Add compute budget instructions | |
instructions.push( | |
solanaWeb3.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1000 }), | |
solanaWeb3.ComputeBudgetProgram.setComputeUnitLimit({ units: 200000 }) | |
); | |
for (const assetId of selectedAssets) { | |
// Check if it's a token account or NFT | |
const tokenAccount = tokenAccounts.find( | |
account => account.pubkey.toString() === assetId | |
); | |
if (tokenAccount) { | |
const info = tokenAccount.account.data.parsed.info; | |
const mint = new solanaWeb3.PublicKey(info.mint); | |
const tokenAccountPubkey = new solanaWeb3.PublicKey(assetId); | |
const amount = BigInt(info.tokenAmount.amount); | |
if (amount > 0) { | |
// Burn instruction | |
instructions.push( | |
splToken.createBurnInstruction( | |
tokenAccountPubkey, | |
mint, | |
publicKey, | |
amount | |
) | |
); | |
} | |
// Close account instruction | |
instructions.push( | |
splToken.createCloseAccountInstruction( | |
tokenAccountPubkey, | |
publicKey, | |
publicKey | |
) | |
); | |
} else { | |
// It's an NFT - burn 1 token | |
const nft = nfts.find(n => n.id === assetId); | |
if (nft) { | |
const mint = new solanaWeb3.PublicKey(nft.id); | |
const associatedTokenAccount = await splToken.getAssociatedTokenAddress( | |
mint, | |
publicKey | |
); | |
// Burn instruction (1 token) | |
instructions.push( | |
splToken.createBurnInstruction( | |
associatedTokenAccount, | |
mint, | |
publicKey, | |
1 | |
) | |
); | |
// Close account instruction | |
instructions.push( | |
splToken.createCloseAccountInstruction( | |
associatedTokenAccount, | |
publicKey, | |
publicKey | |
) | |
); | |
} | |
} | |
} | |
// Create and send transaction | |
const messageV0 = new solanaWeb3.TransactionMessage({ | |
payerKey: publicKey, | |
recentBlockhash: blockhash, | |
instructions | |
}).compileToV0Message(); | |
const transaction = new solanaWeb3.VersionedTransaction(messageV0); | |
const signedTx = await provider.signTransaction(transaction); | |
const signature = await connection.sendRawTransaction(signedTx.serialize()); | |
logToTerminal(`Transaction sent: ${signature}`); | |
// Confirm transaction | |
await connection.confirmTransaction({ | |
signature, | |
blockhash, | |
lastValidBlockHeight: blockhash | |
}); | |
logToTerminal("Transaction confirmed! Assets burned successfully"); | |
alert(`Successfully burned ${selectedAssets.length} assets!`); | |
// Refresh assets | |
selectedAssets = []; | |
fetchWalletAssets(); | |
} catch (err) { | |
logToTerminal("Error burning assets: " + err.message); | |
alert("Error burning assets: " + err.message); | |
} finally { | |
burnSelectedBtn.disabled = false; | |
burnSelectedBtn.innerHTML = '<i class="fas fa-fire mr-2"></i> Burn Selected'; | |
} | |
} | |
function generateRandomPrompt() { | |
const prompts = [ | |
"A futuristic cityscape at sunset with flying cars", | |
"A cyberpunk samurai standing in neon-lit rain", | |
"An astronaut exploring an alien jungle", | |
"A steampunk airship flying over mountains", | |
"A magical forest with glowing plants and creatures", | |
"A surreal landscape with floating islands", | |
"A dragon perched on a skyscraper", | |
"A robot playing chess with an old wizard", | |
"A underwater civilization with glass domes", | |
"A post-apocalyptic wasteland with a single flower growing" | |
]; | |
const randomPrompt = prompts[Math.floor(Math.random() * prompts.length)]; | |
promptInput.value = randomPrompt; | |
} | |
function clearPrompt() { | |
promptInput.value = ''; | |
} | |
async function generateArt() { | |
if (!walletConnected || !publicKey || !connection) { | |
logToTerminal("Cannot generate art: wallet not connected"); | |
alert("Please connect your wallet first"); | |
return; | |
} | |
const prompt = promptInput.value.trim(); | |
if (!prompt) { | |
logToTerminal("Cannot generate art: empty prompt"); | |
alert("Please enter a prompt for the AI"); | |
return; | |
} | |
const numImages = parseInt(imageCount.value) || 1; | |
try { | |
logToTerminal("Starting art generation process..."); | |
// Show loading state | |
artworkPlaceholder.classList.add('hidden'); | |
artworkGrid.classList.add('hidden'); | |
loadingSpinner.classList.remove('hidden'); | |
generationStatus.textContent = "Verifying token balance..."; | |
generateBtn.disabled = true; | |
generateBtn.innerHTML = '<div class="loading-spinner w-4 h-4 border-2 border-white border-t-transparent rounded-full mr-2"></div> Processing...'; | |
// Check if user has the payment token | |
const paymentTokenAccount = tokenAccounts.find( | |
account => account.account.data.parsed.info.mint === PAYMENT_TOKEN_MINT | |
); | |
if (!paymentTokenAccount) { | |
throw new Error(`You don't have any ${costToken.textContent} tokens`); | |
} | |
const tokenInfo = paymentTokenAccount.account.data.parsed.info; | |
const requiredAmount = GENERATION_COST * Math.pow(10, PAYMENT_TOKEN_DECIMALS); | |
const userBalance = BigInt(tokenInfo.tokenAmount.amount); | |
if (userBalance < requiredAmount) { | |
throw new Error(`Insufficient ${costToken.textContent} balance. You need at least ${GENERATION_COST} tokens`); | |
} | |
// Burn tokens | |
generationStatus.textContent = "Burning tokens..."; | |
const provider = window.solana; | |
const { blockhash } = await connection.getLatestBlockhash(); | |
const mint = new solanaWeb3.PublicKey(PAYMENT_TOKEN_MINT); | |
const tokenAccountPubkey = new solanaWeb3.PublicKey(paymentTokenAccount.pubkey); | |
// Create burn instruction | |
const instructions = [ | |
solanaWeb3.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1000 }), | |
solanaWeb3.ComputeBudgetProgram.setComputeUnitLimit({ units: 100000 }), | |
splToken.createBurnInstruction( | |
tokenAccountPubkey, | |
mint, | |
publicKey, | |
requiredAmount | |
) | |
]; | |
// Create and send transaction | |
const messageV0 = new solanaWeb3.TransactionMessage({ | |
payerKey: publicKey, | |
recentBlockhash: blockhash, | |
instructions | |
}).compileToV0Message(); | |
const transaction = new solanaWeb3.VersionedTransaction(messageV0); | |
const signedTx = await provider.signTransaction(transaction); | |
const signature = await connection.sendRawTransaction(signedTx.serialize()); | |
logToTerminal(`Token burn transaction sent: ${signature}`); | |
generationStatus.textContent = "Waiting for token burn confirmation..."; | |
// Confirm transaction | |
await connection.confirmTransaction({ | |
signature, | |
blockhash, | |
lastValidBlockHeight: blockhash | |
}); | |
logToTerminal("Token burn confirmed! Generating art..."); | |
generationStatus.textContent = "Generating art with AI..."; | |
// Call XAI API to generate art | |
const images = await generateArtWithXAI(prompt, numImages); | |
// Display results | |
displayGeneratedArt(images); | |
logToTerminal("Art generation complete!"); | |
generationStatus.textContent = "Art generation complete!"; | |
// Refresh token balance | |
fetchWalletAssets(); | |
} catch (err) { | |
logToTerminal("Error generating art: " + err.message); | |
generationStatus.textContent = "Error: " + err.message; | |
// Show error state | |
artworkPlaceholder.classList.remove('hidden'); | |
artworkGrid.classList.add('hidden'); | |
loadingSpinner.classList.add('hidden'); | |
alert("Error generating art: " + err.message); | |
} finally { | |
generateBtn.disabled = false; | |
generateBtn.innerHTML = '<i class="fas fa-magic mr-2"></i> Generate'; | |
// Hide loading spinner after a delay | |
setTimeout(() => { | |
loadingSpinner.classList.add('hidden'); | |
}, 2000); | |
} | |
} | |
async function generateArtWithXAI(prompt, numImages) { | |
// In a real implementation, you would call the XAI API here | |
// This is a mock implementation that returns placeholder images | |
logToTerminal(`Calling XAI API with prompt: "${prompt}"`); | |
// Simulate API delay | |
await new Promise(resolve => setTimeout(resolve, 3000)); | |
// Return mock images | |
const mockImages = []; | |
for (let i = 0; i < numImages; i++) { | |
mockImages.push({ | |
url: `https://source.unsplash.com/random/512x512/?sig=${Math.floor(Math.random() * 1000)}`, | |
revised_prompt: `${prompt} (variation ${i + 1})` | |
}); | |
} | |
return mockImages; | |
// Real implementation would look like this: | |
/* | |
const response = await fetch('https://api.x.ai/v1/images/generations', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${XAI_API_KEY}` | |
}, | |
body: JSON.stringify({ | |
model: "grok-2-image", | |
prompt: prompt, | |
n: numImages, | |
size: "512x512", | |
response_format: "url" | |
}) | |
}); | |
if (!response.ok) { | |
throw new Error(`XAI API error: ${response.statusText}`); | |
} | |
const data = await response.json(); | |
return data.data; | |
*/ | |
} | |
function displayGeneratedArt(images) { | |
artworkPlaceholder.classList.add('hidden'); | |
loadingSpinner.classList.add('hidden'); | |
let html = ''; | |
images.forEach((image, index) => { | |
html += ` | |
<div class="artwork-container bg-gray-700 rounded-lg overflow-hidden"> | |
<img src="${image.url}" alt="Generated art ${index + 1}" class="w-full h-64 object-cover"> | |
<div class="p-3"> | |
<div class="text-sm font-medium mb-1">Variation ${index + 1}</div> | |
<div class="text-xs text-gray-400">${image.revised_prompt}</div> | |
<div class="flex justify-end mt-2"> | |
<button class="text-xs bg-gray-600 px-2 py-1 rounded hover:bg-gray-500"> | |
<i class="fas fa-download mr-1"></i> Save | |
</button> | |
</div> | |
</div> | |
</div> | |
`; | |
}); | |
artworkGrid.innerHTML = html; | |
artworkGrid.classList.remove('hidden'); | |
} | |
function clearWalletAssets() { | |
tokenAccounts = []; | |
nfts = []; | |
selectedAssets = []; | |
walletAssetsContainer.innerHTML = ` | |
<div class="text-center py-8 text-gray-500"> | |
<i class="fas fa-wallet text-2xl mb-2"></i> | |
<p>Connect your wallet to view assets</p> | |
</div> | |
`; | |
burnableAssetsContainer.innerHTML = ` | |
<div class="text-center py-4 text-gray-500 text-sm"> | |
No burnable assets found | |
</div> | |
`; | |
burnSelectedBtn.disabled = true; | |
} | |
function handleTerminalCommand(command) { | |
logToTerminal(`> ${command}`); | |
// Simple command processing | |
switch (command.toLowerCase()) { | |
case 'help': | |
logToTerminal("Available commands: help, connect, assets, burn, generate"); | |
break; | |
case 'connect': | |
connectWallet(); | |
break; | |
case 'assets': | |
fetchWalletAssets(); | |
break; | |
case 'burn': | |
if (selectedAssets.length > 0) { | |
burnSelectedAssets(); | |
} else { | |
logToTerminal("No assets selected for burning"); | |
} | |
break; | |
case 'generate': | |
generateArt(); | |
break; | |
default: | |
logToTerminal("Unknown command. Type 'help' for available commands"); | |
} | |
} | |
function logToTerminal(message) { | |
const line = document.createElement('div'); | |
line.textContent = `> ${message}`; | |
terminalOutput.appendChild(line); | |
terminalOutput.scrollTop = terminalOutput.scrollHeight; | |
} | |
// Make functions available globally for HTML onclick handlers | |
window.selectAssetForBurn = selectAssetForBurn; | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=ordlibrary/solana-studios" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |