diff --git a/armoury/lab.html b/armoury/lab.html
index 16d2af1..dc76f8d 100644
--- a/armoury/lab.html
+++ b/armoury/lab.html
@@ -35,6 +35,13 @@
box-shadow: 0 4px 20px rgba(20, 241, 149, 0.15);
background: rgba(20, 20, 20, 0.95);
}
+ .deploy-card--amber {
+ border-left-color: rgba(245, 166, 35, 0.6);
+ }
+ .deploy-card--amber:hover {
+ border-left-color: #F5A623;
+ box-shadow: 0 4px 20px rgba(245, 166, 35, 0.15);
+ }
.deploy-card-status {
font-family: 'JetBrains Mono', monospace;
font-size: 0.6rem;
@@ -125,6 +132,18 @@
' +
+ '
CONFIRM TOKEN DEPLOYMENT
' +
+ mRow('NAME', esc(d.name)) +
+ mRow('SYMBOL', esc(d.symbol)) +
+ mRow('SUPPLY', supFmt) +
+ mRow('DECIMALS', String(d.decimals)) +
+ (d.imageUrl ? mRow('IMAGE', '
' + esc(d.imageUrl.length > 50 ? d.imageUrl.slice(0, 47) + '...' : d.imageUrl) + ' ') : '') +
+ (d.revokeMint ? mRow('REVOKE MINT', '
YES \u2014 PERMANENT ') : '') +
+ (d.revokeFreeze ? mRow('REVOKE FREEZE', '
YES \u2014 PERMANENT ') : '') +
+ mRow('DEPLOYER', truncAddr(walletAddress)) +
+ '
' +
+ mRow('SERVICE FEE', '0.1 SOL') +
+ mRow('NETWORK FEES', '~0.015 SOL') +
+ mRow('ESTIMATED TOTAL', '~0.115 SOL ') +
+ '
' +
+ (hasRevoke ? '
\u26A0 AUTHORITY REVOCATION IS PERMANENT AND CANNOT BE UNDONE.' +
+ (d.revokeMint ? ' You will not be able to mint additional tokens.' : '') +
+ (d.revokeFreeze ? ' You will not be able to freeze accounts.' : '') + '
' : '') +
+ '
\u26A0 THIS IS A REAL ON-CHAIN TRANSACTION. SOL WILL BE DEDUCTED FROM YOUR WALLET. VERIFY ALL DETAILS BEFORE CONFIRMING.
' +
+ '
' +
+ 'CANCEL ' +
+ 'DEPLOY TOKEN ' +
+ '
' +
+ '
';
+
+ m.classList.add('active');
+ m.querySelector('.tf-modal-backdrop').onclick = () => m.classList.remove('active');
+ m.querySelector('#confirm-cancel').onclick = () => m.classList.remove('active');
+ m.querySelector('#confirm-deploy').onclick = () => { m.classList.remove('active'); executeDeploy(d); };
+ }
+
+ async function executeDeploy(data) {
+ try {
+ showStatus('building', 'BUILDING TRANSACTION', 'Generating mint keypair and constructing instructions...');
+ disableForm(true);
+
+ const conn = new solanaWeb3.Connection(RPC_URL, 'confirmed');
+ const payer = new solanaWeb3.PublicKey(walletAddress);
+ const feeWallet = new solanaWeb3.PublicKey(FEE_WALLET);
+ const mintKp = solanaWeb3.Keypair.generate();
+ const mint = mintKp.publicKey;
+
+ // Rent exemption for mint account
+ const mintRent = await conn.getMinimumBalanceForRentExemption(MINT_ACCOUNT_SIZE);
+
+ // Derive Associated Token Account address
+ const [ata] = solanaWeb3.PublicKey.findProgramAddressSync(
+ [payer.toBytes(), TOKEN_PROGRAM.toBytes(), mint.toBytes()],
+ ATA_PROGRAM
+ );
+
+ // Derive Metaplex metadata PDA
+ const [metaPDA] = solanaWeb3.PublicKey.findProgramAddressSync(
+ [new TextEncoder().encode('metadata'), METADATA_PROGRAM.toBytes(), mint.toBytes()],
+ METADATA_PROGRAM
+ );
+
+ // Calculate raw supply with decimals
+ const rawSupply = BigInt(Math.floor(Number(data.supply))) * (10n ** BigInt(data.decimals));
+
+ // ── Build instruction list ──
+ const ixs = [];
+
+ // 1. Create mint account (SystemProgram)
+ ixs.push(solanaWeb3.SystemProgram.createAccount({
+ fromPubkey: payer,
+ newAccountPubkey: mint,
+ space: MINT_ACCOUNT_SIZE,
+ lamports: mintRent,
+ programId: TOKEN_PROGRAM,
+ }));
+
+ // 2. InitializeMint2 — SPL Token instruction 20
+ ixs.push(ixInitMint2(mint, data.decimals, payer, payer));
+
+ // 3. Create Associated Token Account (idempotent)
+ ixs.push(ixCreateATA(payer, ata, payer, mint));
+
+ // 4. MintTo — mint initial supply to user's ATA
+ ixs.push(ixMintTo(mint, ata, payer, rawSupply));
+
+ // 5. Create Metaplex metadata
+ ixs.push(ixCreateMetadata(metaPDA, mint, payer, data));
+
+ // 6. Revoke mint authority (optional)
+ if (data.revokeMint) ixs.push(ixSetAuthority(mint, payer, 0, null));
+
+ // 7. Revoke freeze authority (optional)
+ if (data.revokeFreeze) ixs.push(ixSetAuthority(mint, payer, 1, null));
+
+ // 8. Service fee transfer — 0.1 SOL to site wallet
+ ixs.push(solanaWeb3.SystemProgram.transfer({
+ fromPubkey: payer,
+ toPubkey: feeWallet,
+ lamports: SERVICE_FEE_LAMPORTS,
+ }));
+
+ // ── Build transaction ──
+ const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash('confirmed');
+ const tx = new solanaWeb3.Transaction();
+ tx.recentBlockhash = blockhash;
+ tx.feePayer = payer;
+ ixs.forEach(ix => tx.add(ix));
+
+ // Partial sign with the mint keypair (payer signs via wallet)
+ tx.partialSign(mintKp);
+
+ // ── Sign & send ──
+ showStatus('awaiting', 'AWAITING WALLET SIGNATURE', 'Please approve the transaction in your wallet...');
+
+ let sig;
+ if (window.solWallet.isWalletStandard) {
+ sig = await wsSend(tx, conn);
+ } else {
+ sig = await legacySend(tx, conn);
+ }
+
+ // ── Confirm on-chain ──
+ showStatus('confirming', 'CONFIRMING ON-CHAIN',
+ 'Transaction submitted \u2014 awaiting confirmation...