<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mini Solana Store (devnet)</title>
<style>
:root { --bg:#0b1020; --card:#121935; --ink:#e6ecff; --muted:#9fb0ff; --accent:#6ee7b7; }
body { margin:0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto; background:linear-gradient(120deg,#0b1020,#0f1634); color:var(--ink); }
.wrap { max-width: 880px; margin: 40px auto; padding: 0 20px; }
header { display:flex; gap:16px; align-items:center; justify-content:space-between; margin-bottom:24px; }
.btn { background: #2b356b; color: var(--ink); border:1px solid #3b4786; padding:10px 14px; border-radius:14px; cursor:pointer; }
.btn[disabled] { opacity:.5; cursor:not-allowed; }
.row { display:grid; grid-template-columns: repeat(auto-fit,minmax(240px,1fr)); gap:16px; }
.card { background:var(--card); border:1px solid #273163; padding:16px; border-radius:18px; box-shadow:0 10px 30px rgba(0,0,0,.25); }
.price { color: var(--accent); font-weight:700; }
.muted { color: var(--muted); font-size: 12px; }
.badge { font-size: 12px; padding:4px 8px; border-radius:999px; background:#1b2552; border:1px solid #2a3771; display:inline-block; }
a.explorer { color:#9ad5ff; text-decoration: underline; }
.stack { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
footer { margin-top:28px; font-size:12px; color:#b7c4ff; opacity:.85 }
code { background:#0c1329; border:1px solid #1e2a58; padding:2px 6px; border-radius:8px; }
</style>
<!-- Solana web3.js (IIFE build exposes global solanaWeb3) -->
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
</head>
<body>
<div class="wrap">
<header>
<div class="stack">
<h1 style="margin:0">Mini Solana Store</h1>
<span class="badge">devnet</span>
</div>
<div class="stack">
<button id="connect" class="btn">Connect Wallet</button>
<button id="airdrop" class="btn" title="Devnet only">Airdrop 1 SOL</button>
</div>
</header>
<div class="card" id="walletPanel">
<div class="stack" style="justify-content:space-between">
<div>
<div>Wallet: <span id="addr">—</span></div>
<div>Balance: <span id="bal">—</span> SOL</div>
<div class="muted">Network should be <b>devnet</b>. Switch in Phantom if needed.</div>
</div>
<div><button class="btn" id="refresh">Refresh</button></div>
</div>
<div id="status" class="muted" style="margin-top:10px"></div>
<div id="lastTx" style="margin-top:6px"></div>
</div>
<h2 style="margin:16px 0 8px">Items for sale</h2>
<div class="row" id="items"></div>
<footer>
<p>
This is a single HTML file. It connects Phantom, requests a devnet airdrop, and sends a SOL transfer to a merchant address.
Edit prices/labels inside <code>ITEMS</code> below. When ready to go real, change <code>RPC_URL</code> to a mainnet RPC and remove airdrop.
</p>
</footer>
</div>
<script>
(async function(){
// ======== Config you can edit ========
const RPC_URL = 'https://api.devnet.solana.com'; // keep devnet for testing
const MERCHANT = new solanaWeb3.PublicKey('7wqLk9nJG2qWw7HjC8R8xC2xJ2v2m7R9y7xj2o9V3r3o'); // replace with your public key
const ITEMS = [
{ id:'cap', name:'Collection Connection Cap', priceSol: 0.02, img: '', desc:'Embroidered dad cap' },
{ id:'tee', name:'Vintage Graphic Tee', priceSol: 0.05, img: '', desc:'Soft, pre‑shrunk cotton' },
{ id:'card', name:'’86 Fleer Replica Print', priceSol: 0.03, img: '', desc:'Limited run 8×10' },
];
// =====================================
const conn = new solanaWeb3.Connection(RPC_URL, 'confirmed');
const $ = s => document.querySelector(s);
const $$ = s => document.querySelectorAll(s);
const set = (el, v) => (el.textContent = v);
const short = a => a ? a.slice(0,4)+'…'+a.slice(-4) : '—';
const ui = {
connect: $('#connect'), airdrop: $('#airdrop'), refresh: $('#refresh'),
addr: $('#addr'), bal: $('#bal'), status: $('#status'), lastTx: $('#lastTx'), items: $('#items')
};
// Render items
ui.items.innerHTML = ITEMS.map(it => `
<div class="card">
<div style="font-weight:600">${it.name}</div>
<div class="muted" style="margin:6px 0 10px">${it.desc}</div>
<div class="price">${it.priceSol} SOL</div>
<div style="margin-top:10px" class="stack">
<button class="btn buy" data-id="${it.id}">Buy with SOL</button>
</div>
</div>`).join('');
const provider = window.solana; // Phantom injects this
let pubkey = null;
function assertProvider(){
if(!provider){
ui.status.innerHTML = 'Phantom not found. Install it from the extension store.';
throw new Error('No provider');
}
}
async function refresh(){
if(!pubkey) return;
const lamports = await conn.getBalance(new solanaWeb3.PublicKey(pubkey));
set(ui.bal,(lamports/solanaWeb3.LAMPORTS_PER_SOL).toFixed(4));
}
async function connect(){
assertProvider();
const res = await provider.connect();
pubkey = res.publicKey.toString();
set(ui.addr, short(pubkey));
await refresh();
ui.status.textContent = 'Connected.';
}
async function airdrop(){
if(!pubkey){ ui.status.textContent = 'Connect first.'; return; }
ui.status.textContent = 'Requesting 1 SOL airdrop (devnet)…';
const sig = await conn.requestAirdrop(new solanaWeb3.PublicKey(pubkey), solanaWeb3.LAMPORTS_PER_SOL);
await conn.confirmTransaction(sig, 'confirmed');
await refresh();
ui.status.textContent = 'Airdrop received.';
}
async function buy(item){
if(!pubkey){ ui.status.textContent = 'Connect first.'; return; }
try{
ui.status.textContent = `Creating transaction for ${item.name}…`;
const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash();
const tx = new solanaWeb3.Transaction({ feePayer: new solanaWeb3.PublicKey(pubkey), blockhash, lastValidBlockHeight });
tx.add(solanaWeb3.SystemProgram.transfer({ fromPubkey: new solanaWeb3.PublicKey(pubkey), toPubkey: MERCHANT, lamports: Math.round(item.priceSol * solanaWeb3.LAMPORTS_PER_SOL) }));
const signed = await provider.signAndSendTransaction(tx);
ui.lastTx.innerHTML = `Last payment: <a class="explorer" target="_blank" rel="noreferrer" href="https://explorer.solana.com/tx/${signed.signature}?cluster=devnet">View in Explorer</a>`;
ui.status.textContent = 'Submitted. Waiting for confirmation…';
await conn.confirmTransaction({ signature: signed.signature, blockhash, lastValidBlockHeight }, 'confirmed');
ui.status.textContent = 'Payment confirmed ✅';
await refresh();
}catch(e){ ui.status.textContent = 'Payment failed: ' + (e.message || e); }
}
// Wire up UI
ui.connect.onclick = connect;
ui.airdrop.onclick = airdrop;
ui.refresh.onclick = refresh;
ui.items.addEventListener('click', (ev)=>{
const btn = ev.target.closest('.buy');
if(!btn) return;
const item = ITEMS.find(x => x.id === btn.dataset.id);
buy(item);
});
})();
</script>
</body>
</html>