Browse Source

Initial commit: Solana lottery program

master
crappyrules 3 months ago
commit
df491c63df
  1. 46
      .dockerignore
  2. 7
      .gitignore
  3. 25
      Anchor.toml
  4. 2898
      Cargo.lock
  5. 15
      Cargo.toml
  6. 85
      DEPLOYMENT.md
  7. 101
      DEVNET-STATUS.md
  8. 38
      Dockerfile
  9. 838
      app.js
  10. BIN
      apple-touch-icon.png
  11. 87
      burn-all-tokens.sh
  12. 111
      close-all-token-accounts.sh
  13. 6
      config.json
  14. 35
      deploy.sh
  15. 7
      devnet-config.json
  16. 15
      docker-compose.yml
  17. BIN
      favicon-16.png
  18. BIN
      favicon-32.png
  19. BIN
      favicon.ico
  20. 76
      favicon.svg
  21. 198
      index.html
  22. 95
      init-program.js
  23. 7
      lottery_program/.gitignore
  24. 7
      lottery_program/.prettierignore
  25. 19
      lottery_program/Anchor.toml
  26. 1
      mint-keypair.json
  27. 38
      nginx.conf
  28. 755
      package-lock.json
  29. 16
      package.json
  30. 21
      programs/lottery-simple/Cargo.toml
  31. 457
      programs/lottery-simple/src/lib.rs
  32. 98
      send-to-burn.sh
  33. 119
      styles.css
  34. 57
      switch-to-mainnet.sh
  35. 86
      test-devnet.sh
  36. 289
      test-program.html
  37. 79
      ticker-api.php
  38. 17
      ticker-data.json

46
.dockerignore

@ -0,0 +1,46 @@
# Development files
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Anchor build artifacts
target/
.anchor/
Cargo.lock
# Program files (not needed for frontend)
programs/
migrations/
tests/
client/
# Development configs
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Git
.git/
.gitignore
# Build files
*.log
temp/
tmp/
# Test files
test-*.js
*test.js

7
.gitignore vendored

@ -0,0 +1,7 @@
.anchor
.DS_Store
target
**/*.rs.bk
node_modules
test-ledger
.yarn

25
Anchor.toml

@ -0,0 +1,25 @@
[toolchain]
anchor_version = "0.31.0"
[features]
seeds = false
skip-lint = false
[programs.localnet]
lottery_simple = "CQKiz5PKgoQjxNDkGowPWWXu8MJCm2HgonfYAtcFZX87"
[programs.devnet]
lottery_simple = "8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf"
[programs.mainnet]
lottery_simple = "CQKiz5PKgoQjxNDkGowPWWXu8MJCm2HgonfYAtcFZX87"
[registry]
url = "https://api.apr.dev"
[provider]
cluster = "Devnet"
wallet = "~/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

2898
Cargo.lock generated

File diff suppressed because it is too large Load Diff

15
Cargo.toml

@ -0,0 +1,15 @@
[workspace]
members = [
"programs/*"
]
resolver = "2"
[profile.release]
overflow-checks = true
lto = "fat"
codegen-units = 1
[profile.release.build-override]
opt-level = 3
incremental = false
codegen-units = 1

85
DEPLOYMENT.md

@ -0,0 +1,85 @@
# Lottery Simple DApp - Deployment Guide
## Quick Deployment
### Local Testing
```bash
# Build and run with Docker
./deploy.sh
# Or manually:
docker-compose up --build -d
# Access at: http://localhost:14888
```
### Remote Server Deployment
1. **Sync files to server:**
```bash
# Only sync the necessary frontend files
rsync -av --exclude='.git' --exclude='node_modules' --exclude='target' \
--exclude='programs' --exclude='client' --exclude='tests' \
/home/crappy/lottery/lottery-simple/ user@your-server:/opt/lottery-simple/
```
2. **On the remote server:**
```bash
cd /opt/lottery-simple
./deploy.sh
```
3. **Nginx reverse proxy (optional):**
```nginx
location /lottery/ {
proxy_pass http://localhost:14888/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
```
## Configuration
### Environment Variables
- Edit `config.json` for different environments
- Update `programId`, `mint`, and `devWallet` as needed
### Port Configuration
- Default: `14888` (configured in docker-compose.yml)
- Health check: `/health`
## File Structure (Clean)
```
lottery-simple/
├── index.html # Frontend HTML
├── styles.css # CSS styles
├── app.js # Frontend JavaScript
├── config.json # Configuration
├── Dockerfile # Docker build
├── docker-compose.yml # Docker orchestration
├── nginx.conf # Web server config
├── deploy.sh # Deployment script
└── .dockerignore # Build exclusions
```
## Troubleshooting
### Check container status:
```bash
docker-compose ps
docker-compose logs
```
### Manual container management:
```bash
docker-compose stop
docker-compose start
docker-compose restart
```
### Clean rebuild:
```bash
docker-compose down
docker-compose build --no-cache
docker-compose up -d
```

101
DEVNET-STATUS.md

@ -0,0 +1,101 @@
# 🧪 Devnet Deployment Status
## ✅ Successfully Completed
### 1. Program Deployment
- **Program ID**: `8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf`
- **Status**: ✅ Successfully deployed to Devnet
- **IDL**: ✅ Successfully created and uploaded
- **Cluster**: Devnet (safe testing environment)
### 2. Token Mint
- **Mint Address**: `GrCHRSRHdZtiz2fiv1iUgigXCfB5Ta3j8gXepZUUTPP2`
- **Status**: ✅ Created and ready for use
- **Decimals**: 9 (standard SPL token format)
### 3. Program Features
- ✅ Working Anchor v0.31.0 program
- ✅ All previous fixes implemented:
- Direct lamport manipulation for vault transfers
- Realistic bonding curve math (price increases with supply)
- Dividend distribution system
- Dev fee collection (3%)
- Proper rent exemption handling
- Buy/sell functionality with safety checks
### 4. Configuration
- ✅ `devnet-config.json` contains all deployment details
- ✅ Program ID matches deployed program
- ✅ Ready for frontend integration
## 🚀 Next Steps
### Option 1: Test with Browser Interface
1. Open `test-program.html` in your browser
2. Connect Phantom wallet (set to Devnet)
3. Initialize the program
4. Test buy/sell functionality
### Option 2: Update Your Frontend
1. Update your frontend's config to use `devnet-config.json`
2. Test all functionality with devnet SOL (free!)
3. Verify buy/sell transactions work correctly
4. Test dividend withdrawal
### Option 3: CLI Testing (if needed)
```bash
# Check program account
solana account 8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf --url devnet
# Check state PDA (after initialization)
solana account [STATE_PDA] --url devnet
# Check vault PDA balance
solana account [VAULT_PDA] --url devnet
```
## 📋 Configuration Details
```json
{
"programId": "8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf",
"mintAddress": "GrCHRSRHdZtiz2fiv1iUgigXCfB5Ta3j8gXepZUUTPP2",
"devWallet": "GPFYcM3svcM4Srko6ExLYeSW4Gjwf6XoV4k1eSXoGHoK",
"cluster": "devnet",
"rpcUrl": "https://api.devnet.solana.com"
}
```
## ⚠ Important Notes
1. **This is DEVNET** - No real money is involved
2. **Test thoroughly** before considering mainnet deployment
3. **Devnet SOL is free** - get more from faucets if needed
4. **Program must be initialized** before trading can begin
## 🎯 Testing Checklist
Before moving to mainnet, verify:
- [ ] Program initializes successfully
- [ ] Buy transactions work correctly
- [ ] Sell transactions work correctly
- [ ] Dev fees are collected properly
- [ ] Dividend distribution works
- [ ] Vault PDA maintains proper balance
- [ ] Frontend UI functions correctly
- [ ] Error handling works as expected
## 🚨 Mainnet Deployment
**Only proceed to mainnet AFTER:**
1. All devnet testing is complete
2. All functionality verified working
3. UI/UX is polished and tested
4. You're confident in the system
The same program code will work on mainnet - just need to change the cluster configuration and deploy with real SOL.
---
**Current Status**: ✅ DEVNET DEPLOYMENT COMPLETE - READY FOR TESTING

38
Dockerfile

@ -0,0 +1,38 @@
FROM php:8.1-fpm-alpine
# Install nginx
RUN apk add --no-cache nginx supervisor
# Copy site files
COPY index.html /var/www/html/
COPY styles.css /var/www/html/
COPY app.js /var/www/html/
COPY config.json /var/www/html/
COPY ticker-api.php /var/www/html/
COPY ticker-data.json /var/www/html/
# Copy favicon files
COPY favicon.ico /var/www/html/
COPY favicon.svg /var/www/html/
COPY favicon-16.png /var/www/html/
COPY favicon-32.png /var/www/html/
COPY apple-touch-icon.png /var/www/html/
# Copy nginx config
COPY nginx.conf /etc/nginx/http.d/default.conf
# Create basic robots.txt and sitemap.xml
RUN echo -e "User-agent: *\nAllow: /\n\nSitemap: /sitemap.xml" > /var/www/html/robots.txt && \
echo -e '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n <url>\n <loc>https://your-domain.com/</loc>\n <lastmod>2025-10-06</lastmod>\n <changefreq>weekly</changefreq>\n <priority>0.8</priority>\n </url>\n</urlset>' > /var/www/html/sitemap.xml
# Set permissions
RUN chown -R www-data:www-data /var/www/html && \
chmod -R 644 /var/www/html/* && \
chmod 755 /var/www/html/ && \
chmod 666 /var/www/html/ticker-data.json
# Create supervisor config
RUN echo -e '[supervisord]\nnodaemon=true\n\n[program:php-fpm]\ncommand=php-fpm\nautostart=true\nautorestart=true\n\n[program:nginx]\ncommand=nginx -g "daemon off;"\nautostart=true\nautorestart=true' > /etc/supervisord.conf
EXPOSE 80
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

838
app.js

@ -0,0 +1,838 @@
/* global solanaWeb3 */
(() => {
const { Connection, PublicKey, SystemProgram, Transaction, TransactionInstruction } = solanaWeb3;
// DOM helpers
const $ = (id) => document.getElementById(id);
const DEBUG = new URLSearchParams(location.search).has('debug');
const log = (m, cls='') => {
if (cls === 'err') console.error(m);
else if (cls === 'warn') console.warn(m);
if (DEBUG) console.debug(m);
};
// SNS (Solana Name Service) resolution
const snsCache = new Map();
const NAME_PROGRAM_ID = new PublicKey('namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX');
async function findOwnedNameAccountsForUser(connection, userAccount) {
try {
const filters = [
{
memcmp: {
offset: 32,
bytes: userAccount.toBase58(),
},
},
];
const accounts = await connection.getProgramAccounts(NAME_PROGRAM_ID, {
filters,
});
return accounts.map((a) => a.publicKey);
} catch (e) {
return [];
}
}
async function resolveSNSAddress(address) {
try {
if (snsCache.has(address)) {
return snsCache.get(address);
}
const { cluster } = getCfg();
const conn = new Connection(cluster, 'confirmed');
const publicKey = new PublicKey(address);
// Find owned name accounts
const nameAccounts = await findOwnedNameAccountsForUser(conn, publicKey);
if (nameAccounts.length > 0) {
// Get the first domain name account data
const accountInfo = await conn.getAccountInfo(nameAccounts[0]);
if (accountInfo && accountInfo.data) {
// Parse the domain name from account data
// SNS account structure: [96 bytes header] + [4 bytes name length] + [name data]
const data = accountInfo.data;
if (data.length > 100) {
const nameLength = data.readUInt32LE(96);
if (nameLength > 0 && nameLength < 64) {
const domainBytes = data.slice(100, 100 + nameLength);
const domain = new TextDecoder().decode(domainBytes);
if (domain && domain.includes('.')) {
snsCache.set(address, domain);
return domain;
}
}
}
}
}
// If no SNS name found, cache the negative result
snsCache.set(address, null);
return null;
} catch (e) {
// Silently fail and return null
snsCache.set(address, null);
return null;
}
}
async function formatAddressWithSNS(address) {
const snsName = await resolveSNSAddress(address);
if (snsName) {
return snsName;
}
return `${address.slice(0,4)}${address.slice(-4)}`;
}
// Providers
function standardToLegacyProvider(stdWallet) {
const features = stdWallet?.features || {};
const connectFeature = features['standard:connect'];
const eventsFeature = features['standard:events'];
const disconnectFeature = features['standard:disconnect'];
const signTxFeature = features['solana:signTransactions'];
const signAndSendFeature = features['solana:signAndSendTransaction'];
const signMsgFeature = features['solana:signMessage'];
let currentAccount = null;
const listeners = { connect: [], disconnect: [] };
const emit = (evt, ...args) => (listeners[evt] || []).forEach((fn) => {
try { fn(...args); } catch {}
});
// Forward standard events into legacy-style events
if (eventsFeature?.on) {
try {
eventsFeature.on('change', ({ accounts }) => {
if (accounts && accounts[0]) {
currentAccount = accounts[0];
provider.publicKey = {
toString: () => accounts[0].address,
toBase58: () => accounts[0].address
};
emit('connect');
} else {
currentAccount = null;
provider.publicKey = null;
emit('disconnect');
}
});
} catch {}
}
const provider = {
isStandard: true,
publicKey: null,
on: (evt, fn) => {
if (!listeners[evt]) listeners[evt] = [];
listeners[evt].push(fn);
},
off: (evt, fn) => {
listeners[evt] = (listeners[evt] || []).filter((f) => f !== fn);
},
connect: async () => {
if (!connectFeature?.connect) throw new Error('Wallet Standard connect not available');
const { accounts } = await connectFeature.connect();
if (!accounts?.length) throw new Error('No account returned');
currentAccount = accounts[0];
provider.publicKey = {
toString: () => accounts[0].address,
toBase58: () => accounts[0].address
};
emit('connect');
return { publicKey: provider.publicKey };
},
disconnect: async () => {
try {
if (disconnectFeature?.disconnect) await disconnectFeature.disconnect();
} finally {
currentAccount = null;
provider.publicKey = null;
emit('disconnect');
}
},
signTransaction: async (tx) => {
if (signTxFeature?.signTransactions) {
const signed = await signTxFeature.signTransactions({ transactions: [tx] });
// Some implementations return { signedTransactions }, others return array directly
const arr = Array.isArray(signed) ? signed : (signed?.signedTransactions || []);
if (!arr[0]) throw new Error('signTransactions returned no result');
return arr[0];
}
throw new Error('Wallet Standard wallet does not support signTransactions');
},
_standard: {
signTransactions: signTxFeature?.signTransactions,
signAndSendTransaction: signAndSendFeature?.signAndSendTransaction,
signMessage: signMsgFeature?.signMessage
}
};
return provider;
}
function detectProviders() {
const map = new Map();
const add = (id, name, provider) => { if (provider && !map.has(id)) map.set(id, { id, name, provider }); };
// Injected
add('phantom','Phantom', window.solana?.isPhantom ? window.solana : null);
add('solflare','Solflare', window.solflare?.isSolflare ? window.solflare : null);
add('backpack','Backpack', window.backpack?.isBackpack ? window.backpack : null);
add('glow','Glow', window.glow?.solana || null);
add('exodus','Exodus', window.exodus?.solana || null);
// Wallet Standard (if present)
try {
const std = window.navigator?.wallets?.get?.();
if (Array.isArray(std)) {
for (const w of std) {
const id = w.name?.toLowerCase?.().replace(/\s+/g,'-') || 'standard';
add(id, w.name || id, standardToLegacyProvider(w));
}
}
} catch {}
return Array.from(map.values());
}
function populateWalletSelect(){
const select = $('walletSelect'); if(!select) return;
const detected = detectProviders();
// Keep first option as Auto-detect, remove the rest, then append detected
select.innerHTML = '<option value="auto">Auto-detect</option>' + detected.map(d=>`<option value="${d.id}">${d.name}</option>`).join('');
// If none detected, keep auto only
}
// Centralized ticker functions
async function loadTickerFromServer() {
try {
const response = await fetch('ticker-api.php', {
method: 'GET',
cache: 'no-cache'
});
if (!response.ok) return;
const tickerData = await response.json();
const track = document.getElementById('ticker');
if (!track) return;
// Clear existing ticker items
track.innerHTML = '';
// Add items from server (reverse order since server stores newest first)
for (const item of tickerData.reverse()) {
const span = document.createElement('span');
span.className = 'ticker-item ' + (item.type || '');
// Try to resolve SNS names in existing ticker text
let displayText = item.text;
if (item.snsName) {
// If SNS name is already stored, use it
displayText = displayText.replace(/[A-Za-z0-9]{4}…[A-Za-z0-9]{4}/, item.snsName);
} else {
// Try to extract address and resolve SNS
const addressMatch = displayText.match(/([A-Za-z0-9]{4})…([A-Za-z0-9]{4})/);
if (addressMatch && item.fullAddress) {
const snsName = await resolveSNSAddress(item.fullAddress);
if (snsName) {
displayText = displayText.replace(addressMatch[0], snsName);
}
}
}
span.textContent = displayText;
track.appendChild(span);
}
} catch (e) {
log('Failed to load ticker data from server', 'warn');
}
}
async function saveTickerToServer(text, type, fullAddress = null, snsName = null) {
try {
const payload = { text, type };
if (fullAddress) payload.fullAddress = fullAddress;
if (snsName) payload.snsName = snsName;
await fetch('ticker-api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
} catch (e) {
log('Failed to save ticker item to server', 'warn');
}
}
async function pushTickerItem(text, cls, address = null) {
const track = document.getElementById('ticker');
if (!track) return;
let displayText = text;
let snsName = null;
// Try to resolve SNS name if address is provided
if (address) {
snsName = await resolveSNSAddress(address);
if (snsName) {
displayText = text.replace(/[A-Za-z0-9]{4}…[A-Za-z0-9]{4}/, snsName);
}
}
// Add to local DOM immediately for responsiveness
const span = document.createElement('span');
span.className = 'ticker-item ' + (cls || '');
span.textContent = displayText;
track.appendChild(span);
// Limit items to prevent DOM bloat (keep more than server since DOM updates faster)
while (track.childNodes.length > 50) {
track.removeChild(track.firstChild);
}
// Save to server for other users
saveTickerToServer(displayText, cls || '', address, snsName);
}
// Updated discriminator functions for our new program
async function getDiscriminators(){
const buyTokens = await anchorDisc('buy_tokens');
const sellTokens = await anchorDisc('sell_tokens');
return { buy: buyTokens, sell: sellTokens };
}
function b64ToBytes(b64){ const bin = atob(b64); const arr = new Uint8Array(bin.length); for(let i=0;i<bin.length;i++) arr[i]=bin.charCodeAt(i); return arr; }
function startsWith(buf, prefix){ if(prefix.length>buf.length) return false; for(let i=0;i<prefix.length;i++){ if(buf[i]!==prefix[i]) return false; } return true; }
function readLeU64(bytes, offset){
let v=0n; for(let i=0;i<8;i++){ v |= BigInt(bytes[offset+i]) << (8n*BigInt(i)); }
return v;
}
let __seenSigs = new Set();
async function pollRecentActivity(){
try{
const { programId, cluster } = getCfg();
const conn = new Connection(cluster,'confirmed');
const discs = await getDiscriminators();
const sigs = await conn.getSignaturesForAddress(programId, { limit: 15 });
for(const s of sigs){
if(__seenSigs.has(s.signature)) continue;
__seenSigs.add(s.signature);
const tx = await conn.getTransaction(s.signature, { maxSupportedTransactionVersion: 0 });
if(!tx?.transaction) continue;
const msg = tx.transaction.message;
const keys = msg.accountKeys.map(k=>k.toString());
const feePayer = keys[0] || '';
const ixList = msg.instructions || msg.compiledInstructions || [];
for(const ix of ixList){
const progIdx = ix.programIdIndex ?? undefined;
const prog = progIdx !== undefined ? keys[progIdx] : ix.programId?.toString();
if(prog !== programId.toString()) continue;
const dataB64 = ix.data || '';
const data = typeof dataB64 === 'string' ? b64ToBytes(dataB64) : new Uint8Array();
if(startsWith(data, discs.buy)){
const lamports = Number(readLeU64(data, 8));
const sol = lamports / 1_000_000_000;
await pushTickerItem(`BUY ${sol.toFixed(3)} SOL • ${feePayer.slice(0,4)}${feePayer.slice(-4)}`, 'buy', feePayer);
} else if(startsWith(data, discs.sell)){
const tokensRaw = Number(readLeU64(data, 8));
const t = tokensRaw / 1_000_000_000;
await pushTickerItem(`SELL ${t.toFixed(3)} tk • ${feePayer.slice(0,4)}${feePayer.slice(-4)}`, 'sell', feePayer);
}
}
}
}catch(e){ /*silent*/ }
}
function pickProvider() {
const sel = $('walletSelect').value;
const detected = detectProviders();
if (sel !== 'auto') return detected.find(p => p.id === sel)?.provider;
return detected[0]?.provider;
}
// Buffer helpers
function le64(n) { const b=new ArrayBuffer(8); new DataView(b).setBigUint64(0, BigInt(n), true); return new Uint8Array(b); }
async function anchorDisc(name){ const data=new TextEncoder().encode(`global:${name}`); const h=await crypto.subtle.digest('SHA-256', data); return new Uint8Array(h).slice(0,8); }
async function buildIx(name, args){ const disc=await anchorDisc(name); const out=new Uint8Array(disc.length+args.length); out.set(disc,0); out.set(args,disc.length); return out; }
// ATAs
const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
const ATA_PROGRAM_ID = new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');
async function findAta(mint, owner){
const [addr] = await PublicKey.findProgramAddress([
owner.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()
], ATA_PROGRAM_ID);
return addr;
}
function createAtaIx(payer, ata, owner, mint){
const keys=[
{ pubkey:payer, isSigner:true, isWritable:true },
{ pubkey:ata, isSigner:false, isWritable:true },
{ pubkey:owner, isSigner:false, isWritable:false },
{ pubkey:mint, isSigner:false, isWritable:false },
{ pubkey:SystemProgram.programId, isSigner:false, isWritable:false },
{ pubkey:TOKEN_PROGRAM_ID, isSigner:false, isWritable:false },
{ pubkey:solanaWeb3.SYSVAR_RENT_PUBKEY, isSigner:false, isWritable:false },
];
return new TransactionInstruction({ keys, programId: ATA_PROGRAM_ID, data: new Uint8Array([]) });
}
// Config via hidden meta tag
function getCfg(){
const meta = document.getElementById('app-config');
const cluster = meta?.dataset.cluster || 'https://api.devnet.solana.com';
const programIdStr = meta?.dataset.programId || '';
const mintStr = meta?.dataset.mint || '';
const devWalletStr = meta?.dataset.devWallet || '';
const programId = programIdStr ? new PublicKey(programIdStr) : null;
const mint = mintStr ? new PublicKey(mintStr) : null;
const devWallet = devWalletStr ? new PublicKey(devWalletStr) : null;
return { programId, mint, cluster, devWallet };
}
async function applyConfig(){
try{
const res = await fetch('config.json', { cache:'no-store' });
if(!res.ok) return;
const cfg = await res.json();
const meta = document.getElementById('app-config'); if(!meta) return;
if(cfg.cluster) meta.dataset.cluster = cfg.cluster;
if(cfg.programId) meta.dataset.programId = cfg.programId;
if(cfg.mint) meta.dataset.mint = cfg.mint;
if(cfg.devWallet) meta.dataset.devWallet = cfg.devWallet;
}catch(e){ /*silent*/ }
}
let pendingReferrer = '';
async function readRefFromUrl(){
const params = new URLSearchParams(location.search);
const ref = params.get('ref');
if(ref){
const el = $('referrer');
if (el) el.value = ref; else pendingReferrer = ref;
}
}
let walletProvider = null; let walletPubkey = null;
function shortAddr(pk){ const s=pk.toBase58(); return s.slice(0,4)+'…'+s.slice(-4); }
function updateUIDisconnected(){
walletPubkey = null;
const chip = $('walletChip'); if(chip){ chip.textContent = 'Not connected'; chip.style.display='none'; }
const wa = $('walletAddr'); if(wa) wa.value = '';
const ref = $('reflinkRow'); if(ref) ref.style.display = 'none';
const ap = $('actionsPanel'); if(ap) ap.style.display = 'none';
const d = $('disconnectBtn'); if(d) d.style.display = 'none';
const c = $('connectBtn'); if(c) c.style.display = '';
const st = $('statusValue'); if(st) st.value = 'Connect wallet to participate';
}
function updateUIConnected(){
if(!walletPubkey) return;
const chip = $('walletChip'); if(chip){ chip.textContent = shortAddr(walletPubkey); chip.style.display=''; }
const ref = $('reflinkRow'); if(ref) ref.style.display = '';
const ap = $('actionsPanel'); if(ap) ap.style.display = '';
const d = $('disconnectBtn'); if(d) d.style.display = '';
const c = $('connectBtn'); if(c) c.style.display = 'none';
const st = $('statusValue'); if(st) st.value = 'Ready';
}
async function disconnectWallet(){
try{
if(walletProvider && typeof walletProvider.disconnect === 'function'){
try { await walletProvider.disconnect(); } catch { /* some wallets throw if not connected */ }
}
} finally {
walletProvider = null;
updateUIDisconnected();
}
}
async function connectWallet(){
try{
walletProvider = pickProvider();
if(!walletProvider){ log('No compatible wallet detected. Install Phantom/Solflare/Backpack/Glow/Exodus.', 'err'); return; }
// Attach events if provided by wallet
try{
if (walletProvider.on) {
walletProvider.on('connect', () => { if(walletProvider?.publicKey){ walletPubkey = new PublicKey(walletProvider.publicKey.toString()); $('walletAddr').value = walletPubkey.toBase58(); updateUIConnected(); updateUserBalance(); updateReflink(); } });
walletProvider.on('disconnect', () => { updateUIDisconnected(); });
}
}catch{}
const res = await walletProvider.connect();
const pubkey = new PublicKey((res?.publicKey ?? walletProvider.publicKey).toString());
walletPubkey = pubkey; $('walletAddr').value = pubkey.toBase58();
await readRefFromUrl();
log(`Connected ${pubkey.toBase58()}`, 'ok');
updateUIConnected();
updateUserBalance();
updateReflink();
// Check if user account exists and toggle Prepare button visibility
const { programId } = getCfg();
await withConn(async (conn)=>{
const exists = await userExists(conn, programId, walletPubkey);
const prep = document.getElementById('registerBtn');
if (prep) prep.style.display = exists ? 'none' : '';
const st = $('statusValue'); if(st) st.value = exists ? 'Ready' : 'Ready — account will be prepared on your first purchase';
});
}catch(e){ log(`Connect failed: ${e.message}`,'err'); console.error(e); }
}
async function withConn(fn){ const {cluster}=getCfg(); const conn=new Connection(cluster,'confirmed'); return fn(conn); }
async function userExists(conn, programId, owner){
try {
const [userPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('user'), owner.toBuffer()], programId);
const info = await conn.getAccountInfo(userPda);
return !!info;
} catch { return false; }
}
function logSendError(e){
try{
if (e && typeof e.getLogs === 'function') {
e.getLogs().then((logs)=>console.error('Tx logs:', logs)).catch(()=>{});
} else if (e?.logs) {
console.error('Tx logs:', e.logs);
}
}catch{}
}
async function ensureAta(conn, owner, mint){
const ata = await findAta(mint, owner);
const acc = await conn.getAccountInfo(ata);
if(!acc) return { ata, createIx: createAtaIx(owner, ata, owner, mint) };
return { ata };
}
// Actions - Updated for our new program
let __lastPot = null;
async function updatePot(){
try{
const { programId, cluster } = getCfg();
if(!programId){ const s=$('statusValue'); if(s) s.value='Not configured'; return; }
const conn = new Connection(cluster,'confirmed');
// Use our new vault PDA
const [vaultPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('vault')], programId);
const lamports = await conn.getBalance(vaultPda, 'confirmed');
const solNum = lamports/1_000_000_000;
const sol = solNum.toFixed(3);
const potEl = $('potValue'); if(potEl) potEl.value = `${sol} SOL`;
const potEl2 = $('potValue2'); if(potEl2) potEl2.value = `${sol} SOL`;
const potBig = document.getElementById('potBig'); if(potBig) potBig.textContent = sol;
// delta animation
const deltaEl = document.getElementById('potDelta');
if(deltaEl !== null){
if(__lastPot !== null){
const d = solNum - __lastPot;
if(Math.abs(d) >= 0.001){
deltaEl.textContent = (d>0?'+':'')+d.toFixed(3);
deltaEl.classList.remove('pos','neg','show');
deltaEl.classList.add(d>0?'pos':'neg');
// force reflow for animation restart
void deltaEl.offsetWidth;
deltaEl.classList.add('show');
setTimeout(()=>deltaEl.classList.remove('show'), 1200);
}
}
__lastPot = solNum;
}
}catch(e){ /*silent*/ }
}
async function updateUserBalance(){
try{
if(!walletPubkey) return;
const { mint, cluster } = getCfg();
const conn = new Connection(cluster,'confirmed');
const ata = await findAta(mint, walletPubkey);
const bal = await conn.getTokenAccountBalance(ata).catch(()=>null);
const v = bal?.value?.uiAmountString ?? '0';
const el = $('userBalance'); if(el) el.value = v;
// Auto-populate sell field with user's raw token balance (9 decimals)
const rawAmount = bal?.value?.amount ?? '0';
const sellEl = $('sellAmount'); if(sellEl) sellEl.value = rawAmount;
// Update vault fields
const keysEl = $('userKeys'); if(keysEl) keysEl.value = v;
const keys2El = $('userKeys2'); if(keys2El) keys2El.value = v; // if there's a second field
const earningsEl = $('userEarnings'); if(earningsEl) earningsEl.value = '0.000 SOL'; // placeholder
}catch(e){ /*silent*/ }
}
function updateReflink(){
if(!walletPubkey) return;
const url = new URL(location.href); url.searchParams.set('ref', walletPubkey.toBase58());
const el = $('reflink'); if(el) el.value = url.toString();
const el2 = $('reflink2'); if(el2) el2.value = url.toString();
}
async function register(){
try{
if(!walletPubkey) return log('Connect wallet first','warn');
const { programId, mint } = getCfg(); if(!programId||!mint){ const s=$('statusValue'); if(s) s.value='Not configured'; return; } const owner=walletPubkey;
await withConn(async (conn)=>{
// Use our new PDA seeds
const [statePda] = await PublicKey.findProgramAddress([new TextEncoder().encode('state')], programId);
const [userPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('user'), owner.toBuffer()], programId);
const ata = await findAta(mint, owner);
// Our new program uses register_user without referrer args
const data = await buildIx('register_user', new Uint8Array([]));
const keys=[
{ pubkey:statePda, isSigner:false, isWritable:false },
{ pubkey:userPda, isSigner:false, isWritable:true },
{ pubkey:ata, isSigner:false, isWritable:true },
{ pubkey:mint, isSigner:false, isWritable:false },
{ pubkey:owner, isSigner:true, isWritable:true },
{ pubkey:SystemProgram.programId, isSigner:false, isWritable:false },
{ pubkey:TOKEN_PROGRAM_ID, isSigner:false, isWritable:false },
{ pubkey:ATA_PROGRAM_ID, isSigner:false, isWritable:false },
];
const ix = new TransactionInstruction({ keys, programId, data });
const tx = new Transaction().add(ix); // No manual ATA creation needed
const {blockhash,lastValidBlockHeight}=await conn.getLatestBlockhash('confirmed');
tx.recentBlockhash=blockhash; tx.feePayer=owner;
const signed = await walletProvider.signTransaction(tx);
const sig = await conn.sendRawTransaction(signed.serialize());
await conn.confirmTransaction({signature:sig,blockhash,lastValidBlockHeight},'confirmed');
log(`✅ Registered: ${sig}`,'ok');
});
}catch(e){ log(`Register failed: ${e.message}`,'err'); logSendError(e); }
}
async function buy(){
try{
if(!walletPubkey) return log('Connect wallet first','warn');
const { programId, mint, devWallet } = getCfg(); if(!programId||!mint){ const s=$('statusValue'); if(s) s.value='Not configured'; return; } const owner=walletPubkey;
const solAmount = parseFloat($('buyAmount').value||'0'); const lamports = Math.floor(solAmount*1_000_000_000);
if(lamports<=0) return log('Invalid amount','warn');
await withConn(async (conn)=>{
// Ensure user exists; auto-register if needed
const exists = await userExists(conn, programId, owner);
if(!exists){
log('User not found; registering...','warn');
await register();
}
// Use our new PDA seeds and account structure
const [statePda] = await PublicKey.findProgramAddress([new TextEncoder().encode('state')], programId);
const [vaultPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('vault')], programId);
const ata = await findAta(mint, owner);
// Check if ATA exists, create if needed
const ataAccount = await conn.getAccountInfo(ata);
if (!ataAccount) {
log('Creating token account first...', 'warn');
const createAtaTx = new Transaction().add(createAtaIx(owner, ata, owner, mint));
const {blockhash: ataBlockhash, lastValidBlockHeight: ataLastValid} = await conn.getLatestBlockhash('confirmed');
createAtaTx.recentBlockhash = ataBlockhash;
createAtaTx.feePayer = owner;
const signedAtaTx = await walletProvider.signTransaction(createAtaTx);
const ataSig = await conn.sendRawTransaction(signedAtaTx.serialize());
await conn.confirmTransaction({signature: ataSig, blockhash: ataBlockhash, lastValidBlockHeight: ataLastValid}, 'confirmed');
log(`ATA created: ${ataSig}`, 'ok');
}
const data = await buildIx('buy_tokens', le64(lamports));
const devWalletPk = devWallet ?? owner;
// Build keys for our new buy_tokens instruction
const keys = [
{ pubkey: statePda, isSigner:false, isWritable:true }, // state
{ pubkey: vaultPda, isSigner:false, isWritable:true }, // vault
{ pubkey: mint, isSigner:false, isWritable:true }, // mint
{ pubkey: ata, isSigner:false, isWritable:true }, // buyer_token_account
{ pubkey: devWalletPk, isSigner:false, isWritable:true }, // dev_wallet
{ pubkey: owner, isSigner:true, isWritable:true }, // buyer
{ pubkey: SystemProgram.programId, isSigner:false, isWritable:false }, // system_program
{ pubkey: TOKEN_PROGRAM_ID, isSigner:false, isWritable:false } // token_program
];
const ix = new TransactionInstruction({ keys, programId, data });
const tx = new Transaction().add(ix);
const {blockhash,lastValidBlockHeight}=await conn.getLatestBlockhash('confirmed');
tx.recentBlockhash=blockhash; tx.feePayer=owner;
const signed = await walletProvider.signTransaction(tx);
const sig = await conn.sendRawTransaction(signed.serialize());
await conn.confirmTransaction({signature:sig,blockhash,lastValidBlockHeight},'confirmed');
await pushTickerItem(`BUY ${solAmount.toFixed(3)} SOL • ${shortAddr(owner)}`,'buy', owner.toBase58());
log(`✅ Buy: ${sig}`,'ok');
// Refresh balances after successful buy
setTimeout(() => { updateUserBalance(); updatePot(); }, 2000);
});
}catch(e){ log(`Buy failed: ${e.message}`,'err'); logSendError(e); }
}
async function sell(){
try{
if(!walletPubkey) return log('Connect wallet first','warn');
const { programId, mint } = getCfg(); const owner=walletPubkey;
const tokensRaw = BigInt($('sellAmount').value||'0'); if(tokensRaw<=0n) return log('Invalid amount','warn');
await withConn(async (conn)=>{
// Use our new PDA seeds
const [statePda] = await PublicKey.findProgramAddress([new TextEncoder().encode('state')], programId);
const [vaultPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('vault')], programId);
const ata = await findAta(mint, owner);
const data = await buildIx('sell_tokens', le64(tokensRaw));
const keys=[
{ pubkey: statePda, isSigner:false, isWritable:true },
{ pubkey: vaultPda, isSigner:false, isWritable:true },
{ pubkey: mint, isSigner:false, isWritable:true },
{ pubkey: ata, isSigner:false, isWritable:true },
{ pubkey: owner, isSigner:true, isWritable:true },
{ pubkey: SystemProgram.programId, isSigner:false, isWritable:false },
{ pubkey: TOKEN_PROGRAM_ID, isSigner:false, isWritable:false },
];
const ix = new TransactionInstruction({ keys, programId, data });
const tx = new Transaction().add(ix);
const {blockhash,lastValidBlockHeight}=await conn.getLatestBlockhash('confirmed');
tx.recentBlockhash=blockhash; tx.feePayer=owner;
const signed = await walletProvider.signTransaction(tx);
const sig = await conn.sendRawTransaction(signed.serialize());
await conn.confirmTransaction({signature:sig,blockhash,lastValidBlockHeight},'confirmed');
const tk = Number(tokensRaw)/1_000_000_000; await pushTickerItem(`SELL ${tk.toFixed(3)} tk • ${shortAddr(owner)}`,'sell', owner.toBase58());
log(`✅ Sell: ${sig}`,'ok');
// Refresh balances after successful sell
setTimeout(() => { updateUserBalance(); updatePot(); }, 2000);
});
}catch(e){ log(`Sell failed: ${e.message}`,'err'); logSendError(e); }
}
async function withdraw(){
try{
if(!walletPubkey) return log('Connect wallet first','warn');
const { programId, mint } = getCfg(); const owner=walletPubkey;
await withConn(async (conn)=>{
// Use our new PDA seeds and withdraw_dividends instruction
const [statePda] = await PublicKey.findProgramAddress([new TextEncoder().encode('state')], programId);
const [vaultPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('vault')], programId);
const [userPda] = await PublicKey.findProgramAddress([new TextEncoder().encode('user'), owner.toBuffer()], programId);
const ata = await findAta(mint, owner);
const data = await buildIx('withdraw_dividends', new Uint8Array([]));
const keys=[
{ pubkey: statePda, isSigner:false, isWritable:false },
{ pubkey: vaultPda, isSigner:false, isWritable:true },
{ pubkey: userPda, isSigner:false, isWritable:true },
{ pubkey: ata, isSigner:false, isWritable:false },
{ pubkey: mint, isSigner:false, isWritable:false },
{ pubkey: owner, isSigner:true, isWritable:true },
{ pubkey: SystemProgram.programId, isSigner:false, isWritable:false },
];
const ix = new TransactionInstruction({ keys, programId, data });
const tx = new Transaction().add(ix);
const {blockhash,lastValidBlockHeight}=await conn.getLatestBlockhash('confirmed');
tx.recentBlockhash=blockhash; tx.feePayer=owner;
const signed = await walletProvider.signTransaction(tx);
const sig = await conn.sendRawTransaction(signed.serialize());
await conn.confirmTransaction({signature:sig,blockhash,lastValidBlockHeight},'confirmed');
log(`✅ Withdraw: ${sig}`,'ok');
// Refresh balances after successful withdraw
setTimeout(() => { updateUserBalance(); updatePot(); }, 2000);
});
}catch(e){ log(`Withdraw failed: ${e.message}`,'err'); logSendError(e); }
}
// Reflink copy
function copyRef(){
const el = $('reflink'); if(!el || !el.value) return log('Nothing to copy','warn');
navigator.clipboard.writeText(el.value);
log('Reflink copied to clipboard','ok');
}
// Tabs & countdown
function initTabs(){
const tabs = document.querySelectorAll('.tab');
const contents = {
purchase: document.getElementById('tab-purchase'),
vault: document.getElementById('tab-vault'),
referrals: document.getElementById('tab-referrals')
};
tabs.forEach((tab) => {
tab.addEventListener('click', () => {
const target = tab.dataset.tab;
tabs.forEach((t) => t.classList.remove('active'));
Object.values(contents).forEach((c) => c && c.classList.remove('active'));
tab.classList.add('active');
if (contents[target]) {
contents[target].classList.add('active');
contents[target].style.display = '';
}
});
});
}
function addEventListeners(){
const connectBtn = $('connectBtn'); if(connectBtn) connectBtn.addEventListener('click', connectWallet);
const disconnectBtn = $('disconnectBtn'); if(disconnectBtn) disconnectBtn.addEventListener('click', disconnectWallet);
const registerBtn = $('registerBtn'); if(registerBtn) registerBtn.addEventListener('click', register);
const buyBtn = $('buyBtn'); if(buyBtn) buyBtn.addEventListener('click', buy);
const sellBtn = $('sellBtn'); if(sellBtn) sellBtn.addEventListener('click', sell);
const withdrawBtn = $('withdrawBtn'); if(withdrawBtn) withdrawBtn.addEventListener('click', withdraw);
const withdrawBtn2 = $('withdrawBtn2'); if(withdrawBtn2) withdrawBtn2.addEventListener('click', withdraw);
const copyRefBtn = $('copyRef'); if(copyRefBtn) copyRefBtn.addEventListener('click', copyRef);
const copyRefBtn2 = $('copyRef2'); if(copyRefBtn2) copyRefBtn2.addEventListener('click', copyRef);
}
// Money rain animation (working version from website-solana)
function initMoneyRain(count=72){
const wrap = document.getElementById('moneyRain'); if(!wrap) return;
wrap.innerHTML = '';
const vw = Math.max(window.innerWidth, document.documentElement.clientWidth);
for(let i=0;i<count;i++){
const el = document.createElement('div');
el.className='money';
// Evenly distribute across width with small jitter so the right side gets coverage
const base = (i + 0.5) / count;
const jitter = (Math.random() - 0.5) * (1 / count);
const left = Math.max(0, Math.min(vw - 40, Math.floor((base + jitter) * vw)));
const dur = 8 + Math.random()*10; // 8-18s
const delay = -Math.random()*dur; // stagger
const scale = 0.7 + Math.random()*0.8;
el.style.left = `${left}px`;
el.style.animationDuration = `${dur}s`;
el.style.animationDelay = `${delay}s`;
el.style.transform = `scale(${scale})`;
wrap.appendChild(el);
}
// Reflow on resize (debounced)
let to=null; window.addEventListener('resize', ()=>{ clearTimeout(to); to=setTimeout(()=>initMoneyRain(count), 200); }, { passive:true });
}
// Initialize
async function init(){
log('🚀 PoWH3d Lottery initializing...');
populateWalletSelect();
await applyConfig();
initTabs();
addEventListeners();
updateUIDisconnected();
// Load existing ticker data from server
await loadTickerFromServer();
// Set up periodic updates
setInterval(updatePot, 5000);
setInterval(pollRecentActivity, 10000);
// Periodically refresh ticker from server to get updates from other users
setInterval(loadTickerFromServer, 15000); // Every 15 seconds
initMoneyRain(); // Start the money rain!
updatePot(); // initial load
log('✅ Ready!');
}
// Start when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();

BIN
apple-touch-icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

87
burn-all-tokens.sh

@ -0,0 +1,87 @@
#!/bin/bash
echo "🔥 Burning All Tokens in Wallet"
echo "================================"
echo "⚠ This will permanently destroy ALL tokens in your wallet!"
echo "📍 Cluster: devnet (safe for testing)"
echo ""
# Confirm before proceeding
read -p "Are you sure you want to burn ALL tokens? (type 'yes' to confirm): " confirm
if [ "$confirm" != "yes" ]; then
echo "❌ Aborted - no tokens burned"
exit 1
fi
echo "🔥 Starting token burning process..."
echo ""
# List of token mints to burn (from spl-token accounts output)
TOKENS=(
"4auc435LJ9AfprLeMziHTJbP8wQmbSi7o4ZpeXdR66jQ"
"5xEQF57sR2L3tJBfdiiqaVtepTMa7nQR9uMybMojEY8n"
"8cJ8APx5TwwmpeRk4Zhz1Qbyi1T3HHdHUEw4GUJqxAGN"
"AK9fXWyDNWurYjZSDhbqBo6fczqLmCLbCZ3vq9Vftsqb"
)
# Set to devnet
solana config set --url devnet > /dev/null
echo "💰 Current wallet: $(solana address)"
echo "🌐 Cluster: devnet"
echo ""
# Function to burn tokens for a specific mint
burn_token() {
local mint=$1
echo "🔥 Processing mint: $mint"
# Get the token account balance
local balance=$(spl-token balance $mint --url devnet 2>/dev/null || echo "0")
if [ "$balance" == "0" ] || [ -z "$balance" ]; then
echo " ℹ No balance or account doesn't exist - skipping"
return
fi
echo " 💰 Current balance: $balance"
# Try to burn all tokens
echo " 🔥 Burning $balance tokens..."
local result=$(spl-token burn $mint $balance --url devnet 2>&1)
if [ $? -eq 0 ]; then
echo " ✅ Successfully burned $balance tokens"
# Try to close the token account to reclaim SOL
echo " 🗑 Attempting to close token account..."
spl-token close $mint --url devnet 2>/dev/null
if [ $? -eq 0 ]; then
echo " ✅ Token account closed and SOL reclaimed"
else
echo " ⚠ Token account could not be closed (may still have remaining dust)"
fi
else
echo " ❌ Failed to burn tokens: $result"
fi
echo ""
}
# Burn tokens for each mint
for mint in "${TOKENS[@]}"; do
burn_token $mint
done
echo "🎯 Token burning process complete!"
echo ""
echo "📊 Final token account status:"
spl-token accounts --url devnet
echo ""
echo "💰 Final SOL balance:"
solana balance --url devnet
echo ""
echo "✅ Ready for clean testing!"
echo "🧪 You can now test the PoWH program from a clean state"

111
close-all-token-accounts.sh

@ -0,0 +1,111 @@
#!/bin/bash
echo "🗑 Closing All Token Accounts"
echo "=============================="
echo "⚠ This will close ALL token accounts and burn any remaining tokens!"
echo "💰 You will reclaim the SOL used for rent"
echo "📍 Cluster: devnet (safe for testing)"
echo ""
# Confirm before proceeding
read -p "Are you sure you want to close ALL token accounts? (type 'yes' to confirm): " confirm
if [ "$confirm" != "yes" ]; then
echo "❌ Aborted - no accounts closed"
exit 1
fi
echo "🗑 Starting token account closure process..."
echo ""
# Set to devnet
solana config set --url devnet > /dev/null
echo "💰 Current wallet: $(solana address)"
echo "🌐 Cluster: devnet"
echo ""
echo "💰 SOL balance before: $(solana balance --url devnet)"
echo ""
# Get all token accounts and close them
echo "📊 Current token accounts:"
spl-token accounts --url devnet
echo ""
# List of specific token account addresses to close (from verbose output)
TOKEN_ACCOUNTS=(
"8zr6uF84gTzY5V1MP9EeEfy6m2LNULkH4VEFgxY1fKwh"
"679vTxSpUw55buSgPpUsEp66eiUCQs3Y7acpoeTt6W97"
"4t7iZeFsLXA2Ro1EyxoR88Aqnq62Da4txb74eiC2WovQ"
"AmNmTDXjESr1PZTjEC6QiWqLbeZxdxdQkM2TasSUL88QK"
)
# Function to close a token account
close_account() {
local account=$1
echo "🗑 Processing token account: $account"
# Try to close the account directly
local result=$(spl-token close-account $account --url devnet 2>&1)
if [ $? -eq 0 ]; then
echo " ✅ Successfully closed account and reclaimed SOL"
else
echo " ❌ Failed to close account: $result"
# If direct closure failed, try to get the mint and burn first
echo " 🔄 Trying alternative approach..."
# Get account info to find the mint
local account_info=$(solana account $account --url devnet --output json 2>/dev/null)
if [ $? -eq 0 ]; then
echo " ℹ Account exists, attempting force closure..."
spl-token close-account $account --force --url devnet 2>/dev/null
if [ $? -eq 0 ]; then
echo " ✅ Force closure successful"
else
echo " ⚠ Account could not be closed (may have non-zero balance)"
fi
else
echo " ℹ Account may already be closed or doesn't exist"
fi
fi
echo ""
}
# Close all token accounts
for account in "${TOKEN_ACCOUNTS[@]}"; do
close_account $account
done
# Also try to close by mint address (alternative approach)
echo "🔄 Attempting closure by mint address as backup..."
MINTS=(
"4auc435LJ9AfprLeMziHTJbP8wQmbSi7o4ZpeXdR66jQ"
"5xEQF57sR2L3tJBfdiiqaVtepTMa7nQR9uMybMojEY8n"
"8cJ8APx5TwwmpeRk4Zhz1Qbyi1T3HHdHUEw4GUJqxAGN"
"AK9fXWyDNWurYjZSDhbqBo6fczqLmCLbCZ3vq9Vftsqb"
)
for mint in "${MINTS[@]}"; do
echo "🗑 Attempting to close account for mint: $mint"
spl-token close $mint --url devnet 2>/dev/null
if [ $? -eq 0 ]; then
echo " ✅ Closed successfully"
else
echo " ⚠ Could not close (may not exist or have balance)"
fi
done
echo ""
echo "🎯 Token account closure process complete!"
echo ""
echo "📊 Final token account status:"
spl-token accounts --url devnet
echo ""
echo "💰 Final SOL balance:"
solana balance --url devnet
echo ""
echo "✅ Token accounts processed!"
echo "🧪 Ready for clean testing with your PoWH program"

6
config.json

@ -0,0 +1,6 @@
{
"cluster": "https://api.devnet.solana.com",
"programId": "CQKiz5PKgoQjxNDkGowPWWXu8MJCm2HgonfYAtcFZX87",
"mint": "8cJ8APx5TwwmpeRk4Zhz1Qbyi1T3HHdHUEw4GUJqxAGN",
"devWallet": "GPFYcM3svcM4Srko6ExLYeSW4Gjwf6XoV4k1eSXoGHoK"
}

35
deploy.sh

@ -0,0 +1,35 @@
#!/bin/bash
# Deployment script for Lottery Simple DApp
set -e
echo "🚀 Building and deploying Lottery Simple DApp..."
# Build the Docker image
echo "📦 Building Docker image..."
docker-compose build
# Stop existing container if running
echo "🛑 Stopping existing container..."
docker-compose down
# Start the new container
echo "▶ Starting new container..."
docker-compose up -d
# Wait a moment for startup
sleep 3
# Check if it's running
if curl -s http://localhost:14888/health > /dev/null; then
echo "✅ Deployment successful!"
echo "🌐 Frontend available at: http://localhost:14888"
echo "🏥 Health check: http://localhost:14888/health"
else
echo "❌ Deployment failed - health check failed"
echo "📋 Container logs:"
docker-compose logs
exit 1
fi
echo "🎉 Deployment complete!"

7
devnet-config.json

@ -0,0 +1,7 @@
{
"programId": "8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf",
"mintAddress": "GrCHRSRHdZtiz2fiv1iUgigXCfB5Ta3j8gXepZUUTPP2",
"devWallet": "GPFYcM3svcM4Srko6ExLYeSW4Gjwf6XoV4k1eSXoGHoK",
"cluster": "devnet",
"rpcUrl": "https://api.devnet.solana.com"
}

15
docker-compose.yml

@ -0,0 +1,15 @@
version: '3.8'
services:
lottery-simple-dapp:
build: .
container_name: lottery-simple-dapp
ports:
- "14888:80"
restart: unless-stopped
networks:
- lottery-dapp-net
networks:
lottery-dapp-net:
driver: bridge

BIN
favicon-16.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
favicon-32.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

BIN
favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

76
favicon.svg

@ -0,0 +1,76 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<!-- Background -->
<rect width="32" height="32" fill="#0b1020"/>
<!-- 8-bit style dollar sign -->
<g fill="#22d3ee">
<!-- Dollar sign pixels -->
<rect x="8" y="6" width="2" height="2"/>
<rect x="10" y="6" width="2" height="2"/>
<rect x="12" y="6" width="2" height="2"/>
<rect x="14" y="6" width="2" height="2"/>
<rect x="16" y="6" width="2" height="2"/>
<rect x="18" y="6" width="2" height="2"/>
<rect x="20" y="6" width="2" height="2"/>
<rect x="22" y="6" width="2" height="2"/>
<rect x="8" y="8" width="2" height="2"/>
<rect x="10" y="8" width="2" height="2"/>
<rect x="8" y="10" width="2" height="2"/>
<rect x="10" y="10" width="2" height="2"/>
<rect x="12" y="10" width="2" height="2"/>
<rect x="14" y="10" width="2" height="2"/>
<rect x="16" y="10" width="2" height="2"/>
<rect x="18" y="10" width="2" height="2"/>
<rect x="20" y="12" width="2" height="2"/>
<rect x="22" y="12" width="2" height="2"/>
<rect x="8" y="14" width="2" height="2"/>
<rect x="10" y="14" width="2" height="2"/>
<rect x="12" y="14" width="2" height="2"/>
<rect x="14" y="14" width="2" height="2"/>
<rect x="16" y="14" width="2" height="2"/>
<rect x="18" y="14" width="2" height="2"/>
<rect x="20" y="16" width="2" height="2"/>
<rect x="22" y="16" width="2" height="2"/>
<rect x="8" y="18" width="2" height="2"/>
<rect x="10" y="18" width="2" height="2"/>
<rect x="12" y="18" width="2" height="2"/>
<rect x="14" y="18" width="2" height="2"/>
<rect x="16" y="18" width="2" height="2"/>
<rect x="18" y="18" width="2" height="2"/>
<rect x="20" y="18" width="2" height="2"/>
<rect x="22" y="18" width="2" height="2"/>
<rect x="20" y="20" width="2" height="2"/>
<rect x="22" y="20" width="2" height="2"/>
<rect x="8" y="22" width="2" height="2"/>
<rect x="10" y="22" width="2" height="2"/>
<rect x="12" y="22" width="2" height="2"/>
<rect x="14" y="22" width="2" height="2"/>
<rect x="16" y="22" width="2" height="2"/>
<rect x="18" y="22" width="2" height="2"/>
<rect x="20" y="22" width="2" height="2"/>
<rect x="22" y="22" width="2" height="2"/>
</g>
<!-- Central vertical line -->
<g fill="#7c3aed">
<rect x="14" y="4" width="2" height="2"/>
<rect x="14" y="24" width="2" height="2"/>
<rect x="14" y="26" width="2" height="2"/>
</g>
<!-- Corner accents -->
<g fill="#22c55e" opacity="0.6">
<rect x="2" y="2" width="1" height="1"/>
<rect x="29" y="2" width="1" height="1"/>
<rect x="2" y="29" width="1" height="1"/>
<rect x="29" y="29" width="1" height="1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

198
index.html

@ -0,0 +1,198 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PoWH3d Lottery</title>
<!-- Favicons -->
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png" />
<link rel="stylesheet" href="styles.css" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Press+Start+2P&display=swap" rel="stylesheet" />
<meta id="app-config" data-cluster="https://api.devnet.solana.com" data-program-id="" data-mint="" data-dev-wallet="" />
<script defer src="https://analytics.temple-os.com/script.js" data-website-id="3f1f899a-29c2-48cf-b7c6-6c4ab80f7d1a"></script>
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
<script type="module">
import {
registerMwa,
createDefaultAuthorizationCache,
createDefaultChainSelector,
createDefaultWalletNotFoundHandler
} from 'https://esm.sh/@solana-mobile/wallet-standard-mobile';
registerMwa({
appIdentity: {
name: 'PoWH3d Lottery',
uri: window.location.origin,
icon: 'apple-touch-icon.png'
},
authorizationCache: createDefaultAuthorizationCache(),
chains: ['solana:mainnet', 'solana:devnet'],
chainSelector: createDefaultChainSelector(),
onWalletNotFound: createDefaultWalletNotFoundHandler()
// remoteHostAuthority: 'wss://<reflector-endpoint>' // enable when you have a reflector server
});
</script>
<script src="app.js" defer></script>
</head>
<body>
<div class="animated-background">
<svg class="animated-wave" viewBox="0 0 100 100" preserveAspectRatio="none">
<path class="wave" d="M0,90 L20,85 L40,80 L60,85 L80,70 L100,75" />
<path class="wave wave2" d="M0,90 L20,85 L40,80 L60,85 L80,70 L100,75" />
<path class="wave wave3" d="M0,90 L20,85 L40,80 L60,85 L80,70 L100,75" />
</svg>
</div>
<div class="money-rain" id="moneyRain"></div>
<div class="ticker-wrap"><div class="ticker" id="ticker"></div></div>
<header class="hero">
<div class="wrap">
<h1>PoWH3d Lottery</h1>
<div class="hero-pot">
<div class="amount"><span id="potBig">0.000</span> SOL <span id="potDelta" class="delta"></span></div>
</div>
<p class="sub">8‑bit bonding‑curve lottery on Solana • ultra‑low fees • instant settlements</p>
</div>
</header>
<main class="wrap">
<section class="panel tabs">
<div class="tabs-nav">
<button class="tab active" data-tab="purchase">Purchase</button>
<button class="tab" data-tab="vault">Vault</button>
<button class="tab" data-tab="referrals">Share & Earn</button>
</div>
<div class="tab-content active" id="tab-purchase">
<div class="row">
<div class="col">
<h2>Connect</h2>
<div class="wallet-row">
<select id="walletSelect" class="input">
<option value="auto">Auto-detect</option>
</select>
<button id="connectBtn" class="btn">Connect Wallet</button>
<button id="disconnectBtn" class="btn alt danger" style="display:none;">Disconnect</button>
<span id="walletChip" class="chip" style="display:none;">Not connected</span>
</div>
<div id="iosNotice" class="muted" style="display:none; margin-top:8px;">
On iOS, connect by opening this site inside your wallet’s in‑app browser, or use a wallet with a Safari extension (e.g., Glow, Nightly).
</div>
<div class="grid2">
<label class="label">Wallet</label>
<input id="walletAddr" class="input" readonly />
<label class="label">Your Token Balance</label>
<input id="userBalance" class="input" readonly />
</div>
<!-- Hidden referrer field (populated from ?ref=) -->
<input id="referrer" type="text" style="display:none" />
<div id="reflinkRow" class="wallet-row" style="display:none;">
<input id="reflink" class="input" readonly />
<button id="copyRef" class="btn alt">Copy Reflink</button>
</div>
</div>
<div class="col">
<h2>Pot</h2>
<div class="grid2">
<label class="label">Current Pot</label>
<input id="potValue" class="input" readonly />
<label class="label">Status</label>
<input id="statusValue" class="input" value="Connect wallet to participate" readonly />
</div>
</div>
</div>
</div>
<div class="panel" id="actionsPanel" style="display:none;">
<div class="row">
<div class="col">
<h2>Buy Tokens</h2>
<div class="grid2">
<label class="label">SOL amount</label>
<input id="buyAmount" class="input" type="number" step="0.001" value="0.1" />
</div>
<p class="muted">Buy tokens to earn dividends from future trades. Your account will be created automatically.</p>
<div class="wallet-row">
<button class="btn alt" id="buyBtn">Buy Tokens</button>
</div>
</div>
<div class="col">
<h2>Market Info</h2>
<div class="grid2">
<label class="label">Current Price</label>
<input id="currentPrice" class="input" readonly />
<label class="label">Total Supply</label>
<input id="totalSupply" class="input" readonly />
</div>
<p class="muted">Price increases as more tokens are minted. Early buyers get better prices.</p>
</div>
</div>
<div class="row">
<div class="col">
<h2>Sell</h2>
<div class="grid2">
<label class="label">Tokens to sell (raw, 9dp)</label>
<input id="sellAmount" class="input" type="number" step="1" value="100000000" />
</div>
<button id="sellBtn" class="btn danger">Sell Tokens</button>
</div>
<div class="col">
<h2>Withdraw</h2>
<p class="muted">Claims accumulated dividends.</p>
<button id="withdrawBtn" class="btn">Withdraw Dividends</button>
</div>
</div>
</div>
</div>
</section>
<section class="panel tab-content" id="tab-vault" style="display:none;">
<div class="row">
<div class="col">
<h2>Vault</h2>
<div class="grid2">
<label class="label">Total Gains</label>
<input id="userEarnings" class="input" readonly />
<label class="label">Your Keys</label>
<input id="userKeys" class="input" readonly />
</div>
<div class="wallet-row">
<button id="withdrawBtn2" class="btn">Withdraw</button>
</div>
</div>
<div class="col">
<h2>Round</h2>
<div class="grid2">
<label class="label">Active Pot</label>
<input id="potValue2" class="input" readonly />
</div>
</div>
</div>
</section>
<section class="panel tab-content" id="tab-referrals" style="display:none;">
<h2>Share & Earn</h2>
<p class="muted">Share your link and earn referral bonuses when friends buy. Your link is personalized after connecting.</p>
<div class="wallet-row">
<input id="reflink2" class="input" readonly />
<button id="copyRef2" class="btn alt">Copy Link</button>
</div>
</section>
<section class="panel">
<h2>How it Works</h2>
<p class="muted">Buy with SOL to mint lottery tokens on a bonding curve. A small protocol fee and referral bonus are taken on each trade; the rest boosts the community pot and holder rewards. Sell anytime; proceeds are net of fees. As activity grows, holders earn more.</p>
</section>
</main>
<footer class="foot wrap">
<span>© PoWH3d Lottery · Built for Solana</span>
<a href="#" class="muted">Privacy</a>
</footer>
</body>
</html>

95
init-program.js

@ -0,0 +1,95 @@
const { Connection, PublicKey, Keypair } = require('@solana/web3.js');
const { Program, AnchorProvider, web3, utils } = require('@coral-xyz/anchor');
const fs = require('fs');
// Load configuration
const config = JSON.parse(fs.readFileSync('./devnet-config.json', 'utf8'));
const idl = JSON.parse(fs.readFileSync('./target/idl/lottery_simple.json', 'utf8'));
async function initializeProgram() {
console.log('🚀 Initializing PoWH Program...');
console.log('===============================');
// Set up connection and provider
const connection = new Connection(config.rpcUrl, 'confirmed');
// Load wallet
const walletKeypair = Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(fs.readFileSync(process.env.HOME + '/.config/solana/id.json', 'utf8')))
);
const wallet = {
publicKey: walletKeypair.publicKey,
signTransaction: async (tx) => {
tx.sign(walletKeypair);
return tx;
},
signAllTransactions: async (txs) => {
return txs.map(tx => {
tx.sign(walletKeypair);
return tx;
});
}
};
const provider = new AnchorProvider(connection, wallet, { commitment: 'confirmed' });
const program = new Program(idl, new PublicKey(config.programId), provider);
console.log('📋 Program ID:', config.programId);
console.log('💰 Authority:', walletKeypair.publicKey.toString());
console.log('🪙 Mint:', config.mintAddress);
console.log('🌐 Cluster:', config.cluster);
try {
// Derive PDAs
const [statePda] = PublicKey.findProgramAddressSync(
[Buffer.from("state")],
program.programId
);
const [vaultPda] = PublicKey.findProgramAddressSync(
[Buffer.from("vault")],
program.programId
);
console.log('🏛 State PDA:', statePda.toString());
console.log('🏦 Vault PDA:', vaultPda.toString());
// Initialize the program
console.log('⏳ Sending initialize transaction...');
const tx = await program.methods
.initialize()
.accounts({
state: statePda,
vault: vaultPda,
mint: new PublicKey(config.mintAddress),
authority: walletKeypair.publicKey,
systemProgram: web3.SystemProgram.programId,
tokenProgram: utils.token.TOKEN_PROGRAM_ID,
})
.rpc();
console.log('✅ Program initialized successfully!');
console.log('📝 Transaction:', tx);
// Update config with PDAs
const updatedConfig = {
...config,
statePda: statePda.toString(),
vaultPda: vaultPda.toString(),
initialized: true,
initTx: tx
};
fs.writeFileSync('./devnet-config.json', JSON.stringify(updatedConfig, null, 2));
console.log('💾 Configuration updated with PDA addresses');
} catch (error) {
console.error('❌ Initialization failed:', error);
process.exit(1);
}
}
// Run the initialization
initializeProgram().catch(console.error);

7
lottery_program/.gitignore vendored

@ -0,0 +1,7 @@
.anchor
.DS_Store
target
**/*.rs.bk
node_modules
test-ledger
.yarn

7
lottery_program/.prettierignore

@ -0,0 +1,7 @@
.anchor
.DS_Store
target
node_modules
dist
build
test-ledger

19
lottery_program/Anchor.toml

@ -0,0 +1,19 @@
[toolchain]
package_manager = "yarn"
[features]
resolution = true
skip-lint = false
[programs.localnet]
lottery_program = "9Un1evsbgmJa9XH7baEeTSwpQjAPz8pmDFVccVp8HBY4"
[registry]
url = "https://api.apr.dev"
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[scripts]
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

1
mint-keypair.json

@ -0,0 +1 @@
[228,210,161,242,26,178,81,232,150,232,162,61,163,178,12,246,125,252,229,194,95,188,161,46,214,173,205,225,0,193,115,79,235,121,133,123,65,221,159,48,80,179,37,118,124,100,188,234,93,73,20,79,101,38,82,36,1,80,66,19,145,90,81,53]

38
nginx.conf

@ -0,0 +1,38 @@
server {
listen 80;
server_name _;
root /var/www/html;
index index.html index.php;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Enable gzip compression
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
# PHP location
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
# Main location
location / {
try_files $uri $uri/ /index.html;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}

755
package-lock.json generated

@ -0,0 +1,755 @@
{
"name": "lottery-simple",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lottery-simple",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@coral-xyz/anchor": "^0.31.1",
"@solana/web3.js": "^1.98.4"
}
},
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@coral-xyz/anchor": {
"version": "0.31.1",
"resolved": "https://registry.npmjs.org/@coral-xyz/anchor/-/anchor-0.31.1.tgz",
"integrity": "sha512-QUqpoEK+gi2S6nlYc2atgT2r41TT3caWr/cPUEL8n8Md9437trZ68STknq897b82p5mW0XrTBNOzRbmIRJtfsA==",
"license": "(MIT OR Apache-2.0)",
"dependencies": {
"@coral-xyz/anchor-errors": "^0.31.1",
"@coral-xyz/borsh": "^0.31.1",
"@noble/hashes": "^1.3.1",
"@solana/web3.js": "^1.69.0",
"bn.js": "^5.1.2",
"bs58": "^4.0.1",
"buffer-layout": "^1.2.2",
"camelcase": "^6.3.0",
"cross-fetch": "^3.1.5",
"eventemitter3": "^4.0.7",
"pako": "^2.0.3",
"superstruct": "^0.15.4",
"toml": "^3.0.0"
},
"engines": {
"node": ">=17"
}
},
"node_modules/@coral-xyz/anchor-errors": {
"version": "0.31.1",
"resolved": "https://registry.npmjs.org/@coral-xyz/anchor-errors/-/anchor-errors-0.31.1.tgz",
"integrity": "sha512-NhNEku4F3zzUSBtrYz84FzYWm48+9OvmT1Hhnwr6GnPQry2dsEqH/ti/7ASjjpoFTWRnPXrjAIT1qM6Isop+LQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=10"
}
},
"node_modules/@coral-xyz/borsh": {
"version": "0.31.1",
"resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.31.1.tgz",
"integrity": "sha512-9N8AU9F0ubriKfNE3g1WF0/4dtlGXoBN/hd1PvbNBamBNwRgHxH4P+o3Zt7rSEloW1HUs6LfZEchlx9fW7POYw==",
"license": "Apache-2.0",
"dependencies": {
"bn.js": "^5.1.2",
"buffer-layout": "^1.2.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"@solana/web3.js": "^1.69.0"
}
},
"node_modules/@noble/curves": {
"version": "1.9.7",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz",
"integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==",
"license": "MIT",
"dependencies": {
"@noble/hashes": "1.8.0"
},
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/hashes": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
"integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
"license": "MIT",
"engines": {
"node": "^14.21.3 || >=16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@solana/buffer-layout": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz",
"integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==",
"license": "MIT",
"dependencies": {
"buffer": "~6.0.3"
},
"engines": {
"node": ">=5.10"
}
},
"node_modules/@solana/codecs-core": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz",
"integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==",
"license": "MIT",
"dependencies": {
"@solana/errors": "2.3.0"
},
"engines": {
"node": ">=20.18.0"
},
"peerDependencies": {
"typescript": ">=5.3.3"
}
},
"node_modules/@solana/codecs-numbers": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz",
"integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==",
"license": "MIT",
"dependencies": {
"@solana/codecs-core": "2.3.0",
"@solana/errors": "2.3.0"
},
"engines": {
"node": ">=20.18.0"
},
"peerDependencies": {
"typescript": ">=5.3.3"
}
},
"node_modules/@solana/errors": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz",
"integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==",
"license": "MIT",
"dependencies": {
"chalk": "^5.4.1",
"commander": "^14.0.0"
},
"bin": {
"errors": "bin/cli.mjs"
},
"engines": {
"node": ">=20.18.0"
},
"peerDependencies": {
"typescript": ">=5.3.3"
}
},
"node_modules/@solana/web3.js": {
"version": "1.98.4",
"resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.4.tgz",
"integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.0",
"@noble/curves": "^1.4.2",
"@noble/hashes": "^1.4.0",
"@solana/buffer-layout": "^4.0.1",
"@solana/codecs-numbers": "^2.1.0",
"agentkeepalive": "^4.5.0",
"bn.js": "^5.2.1",
"borsh": "^0.7.0",
"bs58": "^4.0.1",
"buffer": "6.0.3",
"fast-stable-stringify": "^1.0.0",
"jayson": "^4.1.1",
"node-fetch": "^2.7.0",
"rpc-websockets": "^9.0.2",
"superstruct": "^2.0.2"
}
},
"node_modules/@solana/web3.js/node_modules/superstruct": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz",
"integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@swc/helpers": {
"version": "0.5.17",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.8.0"
}
},
"node_modules/@types/connect": {
"version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "12.20.55",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",
"integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",
"license": "MIT"
},
"node_modules/@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "7.4.7",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz",
"integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/agentkeepalive": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
"integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
"license": "MIT",
"dependencies": {
"humanize-ms": "^1.2.1"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/base-x": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz",
"integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/bn.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz",
"integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==",
"license": "MIT"
},
"node_modules/borsh": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz",
"integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==",
"license": "Apache-2.0",
"dependencies": {
"bn.js": "^5.2.0",
"bs58": "^4.0.0",
"text-encoding-utf-8": "^1.0.2"
}
},
"node_modules/bs58": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
"integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
"license": "MIT",
"dependencies": {
"base-x": "^3.0.2"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/buffer-layout": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/buffer-layout/-/buffer-layout-1.2.2.tgz",
"integrity": "sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==",
"license": "MIT",
"engines": {
"node": ">=4.5"
}
},
"node_modules/bufferutil": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz",
"integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/chalk": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
"license": "MIT",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/commander": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz",
"integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==",
"license": "MIT",
"engines": {
"node": ">=20"
}
},
"node_modules/cross-fetch": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
"integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==",
"license": "MIT",
"dependencies": {
"node-fetch": "^2.7.0"
}
},
"node_modules/delay": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz",
"integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
"license": "MIT"
},
"node_modules/es6-promisify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==",
"license": "MIT",
"dependencies": {
"es6-promise": "^4.0.3"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
"license": "MIT"
},
"node_modules/eyes": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
"integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==",
"engines": {
"node": "> 0.1.90"
}
},
"node_modules/fast-stable-stringify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz",
"integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==",
"license": "MIT"
},
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.0.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/isomorphic-ws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
"integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
"license": "MIT",
"peerDependencies": {
"ws": "*"
}
},
"node_modules/jayson": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/jayson/-/jayson-4.2.0.tgz",
"integrity": "sha512-VfJ9t1YLwacIubLhONk0KFeosUBwstRWQ0IRT1KDjEjnVnSOVHC3uwugyV7L0c7R9lpVyrUGT2XWiBA1UTtpyg==",
"license": "MIT",
"dependencies": {
"@types/connect": "^3.4.33",
"@types/node": "^12.12.54",
"@types/ws": "^7.4.4",
"commander": "^2.20.3",
"delay": "^5.0.0",
"es6-promisify": "^5.0.0",
"eyes": "^0.1.8",
"isomorphic-ws": "^4.0.1",
"json-stringify-safe": "^5.0.1",
"stream-json": "^1.9.1",
"uuid": "^8.3.2",
"ws": "^7.5.10"
},
"bin": {
"jayson": "bin/jayson.js"
},
"engines": {
"node": ">=8"
}
},
"node_modules/jayson/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"license": "ISC"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"optional": true,
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"license": "(MIT AND Zlib)"
},
"node_modules/rpc-websockets": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/rpc-websockets/-/rpc-websockets-9.2.0.tgz",
"integrity": "sha512-DS/XHdPxplQTtNRKiBCRWGBJfjOk56W7fyFUpiYi9fSTWTzoEMbUkn3J4gB0IMniIEVeAGR1/rzFQogzD5MxvQ==",
"license": "LGPL-3.0-only",
"dependencies": {
"@swc/helpers": "^0.5.11",
"@types/uuid": "^8.3.4",
"@types/ws": "^8.2.2",
"buffer": "^6.0.3",
"eventemitter3": "^5.0.1",
"uuid": "^8.3.2",
"ws": "^8.5.0"
},
"funding": {
"type": "paypal",
"url": "https://paypal.me/kozjak"
},
"optionalDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
}
},
"node_modules/rpc-websockets/node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/rpc-websockets/node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
"license": "MIT"
},
"node_modules/rpc-websockets/node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/stream-chain": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz",
"integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==",
"license": "BSD-3-Clause"
},
"node_modules/stream-json": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz",
"integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==",
"license": "BSD-3-Clause",
"dependencies": {
"stream-chain": "^2.2.5"
}
},
"node_modules/superstruct": {
"version": "0.15.5",
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.15.5.tgz",
"integrity": "sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ==",
"license": "MIT"
},
"node_modules/text-encoding-utf-8": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz",
"integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg=="
},
"node_modules/toml": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz",
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
"license": "MIT"
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"license": "MIT",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

16
package.json

@ -0,0 +1,16 @@
{
"name": "lottery-simple",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@coral-xyz/anchor": "^0.31.1",
"@solana/web3.js": "^1.98.4"
}
}

21
programs/lottery-simple/Cargo.toml

@ -0,0 +1,21 @@
[package]
name = "lottery-simple"
version = "0.1.0"
description = "A PoWH (Proof of Weak Hands) token with bonding curve"
edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]
name = "lottery_simple"
[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
default = []
[dependencies]
anchor-lang = { version = "0.31.0", features = ["init-if-needed"] }
anchor-spl = "0.31.0"

457
programs/lottery-simple/src/lib.rs

@ -0,0 +1,457 @@
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Burn, Mint, MintTo, Token, TokenAccount};
use anchor_spl::associated_token::AssociatedToken;
declare_id!("8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf");
#[program]
pub mod lottery_simple {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let state = &mut ctx.accounts.state;
state.bump = ctx.bumps.state;
state.vault_bump = ctx.bumps.vault;
state.authority = ctx.accounts.authority.key();
state.mint = ctx.accounts.mint.key();
state.vault = ctx.accounts.vault.key();
state.total_supply = 0;
state.dev_fee_bps = 300; // 3%
state.dividend_fee_bps = 200; // 2%
state.token_price_initial = 100_000; // 0.0001 SOL
state.token_price_increment = 10_000; // 0.00001 SOL per token
state.profit_per_share = 0;
msg!("PoWH initialized with mint: {}", ctx.accounts.mint.key());
Ok(())
}
pub fn register_user(ctx: Context<RegisterUser>) -> Result<()> {
let user = &mut ctx.accounts.user;
user.owner = ctx.accounts.owner.key();
user.token_account = ctx.accounts.token_account.key();
user.dividends_withdrawn = 0;
msg!("User registered: {}", ctx.accounts.owner.key());
Ok(())
}
pub fn buy_tokens(ctx: Context<BuyTokens>, sol_amount: u64) -> Result<()> {
// Capture needed values before mutable borrow
let state_bump = ctx.accounts.state.bump;
let buyer_key = ctx.accounts.buyer.key();
let state_account_info = ctx.accounts.state.to_account_info();
let state = &mut ctx.accounts.state;
// Calculate fees
let dev_fee = (sol_amount as u128 * state.dev_fee_bps as u128) / 10_000;
let dividend_fee = (sol_amount as u128 * state.dividend_fee_bps as u128) / 10_000;
let net_purchase = sol_amount as u128 - dev_fee - dividend_fee;
// Transfer dev fee from buyer to dev wallet
if dev_fee > 0 {
let transfer_ix = anchor_lang::solana_program::system_instruction::transfer(
&ctx.accounts.buyer.key(),
&ctx.accounts.dev_wallet.key(),
dev_fee as u64,
);
anchor_lang::solana_program::program::invoke(
&transfer_ix,
&[
ctx.accounts.buyer.to_account_info(),
ctx.accounts.dev_wallet.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
)?;
}
// Transfer net purchase + dividend fee to vault (vault holds everything except dev fee)
let vault_amount = net_purchase + dividend_fee;
if vault_amount > 0 {
let transfer_ix = anchor_lang::solana_program::system_instruction::transfer(
&ctx.accounts.buyer.key(),
&ctx.accounts.vault.key(),
vault_amount as u64,
);
anchor_lang::solana_program::program::invoke(
&transfer_ix,
&[
ctx.accounts.buyer.to_account_info(),
ctx.accounts.vault.to_account_info(),
ctx.accounts.system_program.to_account_info(),
],
)?;
}
// Distribute dividends to existing holders
if state.total_supply > 0 && dividend_fee > 0 {
let dividend_per_token = (dividend_fee * PRECISION) / state.total_supply as u128;
state.profit_per_share += dividend_per_token;
}
// Calculate tokens to mint using bonding curve
let tokens_to_mint = calculate_tokens_for_sol(net_purchase as u64, state.total_supply, state)?;
// Mint tokens to buyer
let seeds = &[b"state".as_ref(), &[state_bump]];
let signer_seeds = &[&seeds[..]];
let cpi_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.buyer_token_account.to_account_info(),
authority: state_account_info,
},
signer_seeds,
);
token::mint_to(cpi_ctx, tokens_to_mint)?;
state.total_supply += tokens_to_mint;
emit!(TradeEvent {
user: buyer_key,
tokens: tokens_to_mint,
sol_amount,
is_buy: true,
});
msg!("Bought {} tokens for {} SOL", tokens_to_mint, sol_amount);
Ok(())
}
pub fn sell_tokens(ctx: Context<SellTokens>, tokens_to_sell: u64) -> Result<()> {
let state = &mut ctx.accounts.state;
require!(tokens_to_sell > 0, ErrorCode::InvalidAmount);
require!(ctx.accounts.seller_token_account.amount >= tokens_to_sell, ErrorCode::InsufficientFunds);
// Calculate SOL to return using bonding curve
let calculated_sol = calculate_sol_for_tokens(tokens_to_sell, state.total_supply, state)?;
// Safety check: limit to vault balance to prevent errors
let vault_balance = ctx.accounts.vault.to_account_info().lamports();
let sol_to_return = if calculated_sol > vault_balance {
msg!("Calculated return {} exceeds vault balance {}, limiting to vault balance", calculated_sol, vault_balance);
// Leave enough for rent exemption - be more conservative
vault_balance.saturating_sub(5000) // Leave 5000 lamports for rent exemption
} else {
calculated_sol
};
// Burn tokens from seller
let cpi_ctx = CpiContext::new(
ctx.accounts.token_program.to_account_info(),
Burn {
mint: ctx.accounts.mint.to_account_info(),
from: ctx.accounts.seller_token_account.to_account_info(),
authority: ctx.accounts.seller.to_account_info(),
},
);
token::burn(cpi_ctx, tokens_to_sell)?;
state.total_supply -= tokens_to_sell;
// Transfer SOL from vault to seller (direct lamport manipulation)
// This is the correct approach for program-owned PDAs as per Solana documentation
if sol_to_return > 0 {
// Direct lamport manipulation - standard practice for program-owned accounts
**ctx.accounts.vault.to_account_info().try_borrow_mut_lamports()? -= sol_to_return;
**ctx.accounts.seller.to_account_info().try_borrow_mut_lamports()? += sol_to_return;
msg!("Transferred {} lamports from vault to seller", sol_to_return);
}
emit!(TradeEvent {
user: ctx.accounts.seller.key(),
tokens: tokens_to_sell,
sol_amount: sol_to_return,
is_buy: false,
});
msg!("Sold {} tokens for {} SOL", tokens_to_sell, sol_to_return);
Ok(())
}
pub fn withdraw_dividends(ctx: Context<WithdrawDividends>) -> Result<()> {
let state = &ctx.accounts.state;
let user = &mut ctx.accounts.user;
let token_balance = ctx.accounts.token_account.amount as u128;
// Calculate available dividends
let total_dividends = (state.profit_per_share * token_balance) / PRECISION;
let available_dividends = total_dividends - user.dividends_withdrawn;
require!(available_dividends > 0, ErrorCode::NoDividends);
user.dividends_withdrawn = total_dividends;
// Transfer dividends from vault to user (direct lamport manipulation)
// This is the correct approach for program-owned PDAs
**ctx.accounts.vault.to_account_info().try_borrow_mut_lamports()? -= available_dividends as u64;
**ctx.accounts.user_authority.to_account_info().try_borrow_mut_lamports()? += available_dividends as u64;
emit!(WithdrawalEvent {
user: ctx.accounts.user_authority.key(),
amount: available_dividends as u64,
});
msg!("Withdrew {} lamports in dividends", available_dividends);
Ok(())
}
}
// Realistic bonding curve based on actual SOL in vault
fn calculate_tokens_for_sol(sol: u64, supply: u64, _state: &PowhState) -> Result<u64> {
// Linear bonding curve: price increases with supply
// Base price: 0.0001 SOL per token (100,000 lamports per token)
// Price increases by 0.00001 SOL per billion tokens in supply
let base_price_lamports = 100_000u128; // 0.0001 SOL in lamports
let price_increment = 10_000u128; // 0.00001 SOL in lamports
let supply_factor = (supply as u128) / 1_000_000_000; // Per billion tokens
let current_price = base_price_lamports + (price_increment * supply_factor);
let tokens = (sol as u128 * 1_000_000_000) / current_price; // Convert lamports to tokens
msg!("Buy: {} lamports -> {} tokens at price {} lamports per token", sol, tokens, current_price);
Ok(tokens as u64)
}
fn calculate_sol_for_tokens(tokens: u64, supply: u64, _state: &PowhState) -> Result<u64> {
// Sell at 90% of current buy price to create spread
let base_price_lamports = 100_000u128;
let price_increment = 10_000u128;
let supply_factor = (supply as u128) / 1_000_000_000;
let current_price = base_price_lamports + (price_increment * supply_factor);
let sell_price = (current_price * 90) / 100; // 90% of buy price
let sol_lamports = (tokens as u128 * sell_price) / 1_000_000_000;
msg!("Sell: {} tokens -> {} lamports at price {} lamports per token", tokens, sol_lamports, sell_price);
Ok(sol_lamports as u64)
}
const PRECISION: u128 = 1_000_000_000_000_000_000; // 18 decimal places for dividend calculations
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(
init,
payer = authority,
space = 8 + PowhState::INIT_SPACE,
seeds = [b"state"],
bump
)]
pub state: Account<'info, PowhState>,
/// CHECK: This is the vault PDA for holding SOL
#[account(
init,
payer = authority,
space = 0,
seeds = [b"vault"],
bump
)]
pub vault: UncheckedAccount<'info>,
#[account(mut)]
pub mint: Account<'info, Mint>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct RegisterUser<'info> {
#[account(
seeds = [b"state"],
bump = state.bump
)]
pub state: Account<'info, PowhState>,
#[account(
init,
payer = owner,
space = 8 + User::INIT_SPACE,
seeds = [b"user", owner.key().as_ref()],
bump
)]
pub user: Account<'info, User>,
#[account(
init_if_needed,
payer = owner,
associated_token::mint = mint,
associated_token::authority = owner,
)]
pub token_account: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
#[account(mut)]
pub owner: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
}
#[derive(Accounts)]
pub struct BuyTokens<'info> {
#[account(
mut,
seeds = [b"state"],
bump = state.bump
)]
pub state: Account<'info, PowhState>,
/// CHECK: This is the vault PDA
#[account(
mut,
seeds = [b"vault"],
bump = state.vault_bump
)]
pub vault: UncheckedAccount<'info>,
#[account(mut)]
pub mint: Account<'info, Mint>,
#[account(
mut,
token::mint = mint,
token::authority = buyer,
)]
pub buyer_token_account: Account<'info, TokenAccount>,
/// CHECK: Dev wallet for fees
#[account(mut)]
pub dev_wallet: UncheckedAccount<'info>,
#[account(mut)]
pub buyer: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct SellTokens<'info> {
#[account(
mut,
seeds = [b"state"],
bump = state.bump
)]
pub state: Account<'info, PowhState>,
/// CHECK: This is the vault PDA
#[account(
mut,
seeds = [b"vault"],
bump = state.vault_bump
)]
pub vault: UncheckedAccount<'info>,
#[account(mut)]
pub mint: Account<'info, Mint>,
#[account(
mut,
token::mint = mint,
token::authority = seller,
)]
pub seller_token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub seller: Signer<'info>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct WithdrawDividends<'info> {
#[account(
seeds = [b"state"],
bump = state.bump
)]
pub state: Account<'info, PowhState>,
/// CHECK: This is the vault PDA
#[account(
mut,
seeds = [b"vault"],
bump = state.vault_bump
)]
pub vault: UncheckedAccount<'info>,
#[account(
mut,
seeds = [b"user", user_authority.key().as_ref()],
bump
)]
pub user: Account<'info, User>,
#[account(
token::mint = mint,
token::authority = user_authority,
)]
pub token_account: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
#[account(mut)]
pub user_authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
#[derive(InitSpace)]
pub struct PowhState {
pub bump: u8,
pub vault_bump: u8,
pub authority: Pubkey,
pub mint: Pubkey,
pub vault: Pubkey,
pub total_supply: u64,
pub dev_fee_bps: u16,
pub dividend_fee_bps: u16,
pub token_price_initial: u64,
pub token_price_increment: u64,
pub profit_per_share: u128,
}
#[account]
#[derive(InitSpace)]
pub struct User {
pub owner: Pubkey,
pub token_account: Pubkey,
pub dividends_withdrawn: u128,
}
#[event]
pub struct TradeEvent {
pub user: Pubkey,
pub tokens: u64,
pub sol_amount: u64,
pub is_buy: bool,
}
#[event]
pub struct WithdrawalEvent {
pub user: Pubkey,
pub amount: u64,
}
#[error_code]
pub enum ErrorCode {
#[msg("Insufficient funds")]
InsufficientFunds,
#[msg("Invalid amount")]
InvalidAmount,
#[msg("No dividends available")]
NoDividends,
}

98
send-to-burn.sh

@ -0,0 +1,98 @@
#!/bin/bash
echo "🔥 Sending All Tokens to Burn Address"
echo "====================================="
echo "🎯 Burn Address: 1nc1nerator11111111111111111111111111111111"
echo "📍 Cluster: devnet (safe for testing)"
echo ""
# Confirm before proceeding
read -p "Send ALL tokens to burn address? (type 'yes' to confirm): " confirm
if [ "$confirm" != "yes" ]; then
echo "❌ Aborted"
exit 1
fi
echo "🔥 Starting burn process..."
echo ""
# Set to devnet
solana config set --url devnet > /dev/null
BURN_ADDRESS="1nc1nerator11111111111111111111111111111111"
echo "💰 Wallet: $(solana address)"
echo "🔥 Burn Address: $BURN_ADDRESS"
echo ""
# Get all token accounts
echo "📊 Current token accounts:"
spl-token accounts --url devnet
echo ""
# List of token mints to burn
MINTS=(
"4auc435LJ9AfprLeMziHTJbP8wQmbSi7o4ZpeXdR66jQ"
"5xEQF57sR2L3tJBfdiiqaVtepTMa7nQR9uMybMojEY8n"
"8cJ8APx5TwwmpeRk4Zhz1Qbyi1T3HHdHUEw4GUJqxAGN"
"AK9fXWyDNWurYjZSDhbqBo6fczqLmCLbCZ3vq9Vftsqb"
)
# Function to send tokens to burn address
burn_tokens() {
local mint=$1
echo "🔥 Processing mint: $mint"
# Get balance
local balance=$(spl-token balance $mint --url devnet 2>/dev/null || echo "0")
if [ "$balance" == "0" ] || [ -z "$balance" ]; then
echo " ℹ No balance - skipping"
return
fi
echo " 💰 Current balance: $balance"
# Create associated token account for burn address if needed
echo " 🏭 Creating burn address token account..."
spl-token create-account $mint --owner $BURN_ADDRESS --url devnet 2>/dev/null || true
# Transfer all tokens to burn address
echo " 🔥 Sending $balance tokens to burn address..."
local result=$(spl-token transfer $mint $balance $BURN_ADDRESS --url devnet --allow-unfunded-recipient 2>&1)
if [ $? -eq 0 ]; then
echo " ✅ Successfully sent $balance tokens to burn address"
# Close the now-empty account to reclaim rent
echo " 🗑 Closing empty token account..."
spl-token close $mint --url devnet 2>/dev/null
if [ $? -eq 0 ]; then
echo " ✅ Token account closed, rent reclaimed"
else
echo " ⚠ Could not close account"
fi
else
echo " ❌ Transfer failed: $result"
fi
echo ""
}
# Process each mint
for mint in "${MINTS[@]}"; do
burn_tokens $mint
done
echo "🎯 Burn process complete!"
echo ""
echo "📊 Final token account status:"
spl-token accounts --url devnet
echo ""
echo "💰 Final SOL balance:"
solana balance --url devnet
echo ""
echo "🔥 All tokens have been sent to the incinerator!"
echo "✅ Ready for clean testing!"

119
styles.css

@ -0,0 +1,119 @@
/* Ticker */
.ticker-wrap{ position:fixed; top:0; left:0; width:100%; height:36px; background: rgba(11,16,32,0.92); z-index:9999; overflow:hidden; box-shadow:0 2px 10px rgba(0,0,0,0.4); }
.ticker{ display:inline-block; white-space:nowrap; will-change:transform; animation: ticker 28s linear infinite; padding-left:100%; }
.ticker:hover{ animation-play-state: paused; }
.ticker-item{ display:inline-block; padding:8px 18px; font-family:'Press Start 2P',cursive; font-size:11px; color:#cbd5ff; opacity:0.95; }
.ticker-item.buy{ color:#22c55e; }
.ticker-item.sell{ color:#ef4444; }
@keyframes ticker{ 0%{ transform:translateX(0);} 100%{ transform:translateX(-100%);} }
/* offset hero a bit since ticker is fixed */
.hero{ padding-top: 64px; }
/* Chip and delta styles */
.chip{ background:#0e1430; color:#cbd5ff; border:1px solid rgba(34,211,238,0.35); padding:6px 10px; border-radius:999px; font-size:11px; font-family:'Press Start 2P',cursive; }
.delta{ margin-left:8px; font-size:10px; color:#22d3ee; opacity:0; transition:opacity .3s, transform .3s; font-family: 'Press Start 2P', cursive; text-shadow: 1px 1px 0px rgba(0,0,0,0.8); }
.delta.show{ opacity:1; transform:translateY(-2px) }
.delta.pos{ color:#22d3ee; text-shadow: 0 0 6px #22d3ee; }
.delta.neg{ color:#ef4444; text-shadow: 0 0 6px #ef4444; }
/* Money rain (from nancymoneysite) */
.money-rain{ position:fixed; top:0; left:0; width:100%; height:100%; pointer-events:none; z-index:-1; }
.money{ position:absolute; width:30px; height:15px; background:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 50"><rect fill="%23116b32" width="100" height="50"/><text x="50" y="30" font-size="20" text-anchor="middle" fill="%23fff">$</text></svg>') no-repeat center/contain; opacity:.4; animation:rain linear infinite; }
@keyframes rain{ 0%{ transform: translateY(-100vh) rotate(0deg);} 100%{ transform: translateY(100vh) rotate(360deg);} }
/* Background animation from nancymoneysite */
.animated-background { position: fixed; top:0; left:0; width:100%; height:100%; z-index:-2; background: var(--bg); overflow:hidden; }
.animated-wave { width:100%; height:100%; }
.wave { fill:none; stroke:#22d3ee; stroke-width:1; stroke-opacity:0.18; animation: chartMove 6s ease-in-out infinite; filter: drop-shadow(0 0 5px rgba(34,211,238,0.25)); }
.wave2 { stroke-opacity:0.12; animation-delay:-2s; }
.wave3 { stroke-opacity:0.08; animation-delay:-1s; }
@keyframes chartMove {
0% { d: path("M0,90 L20,85 L40,80 L60,85 L80,70 L100,75"); }
50% { d: path("M0,85 L20,70 L40,50 L60,30 L80,20 L100,10"); }
100% { d: path("M0,90 L20,85 L40,80 L60,85 L80,70 L100,75"); }
}
/* Hero pot */
.hero-pot { display:flex; align-items:center; justify-content:center; gap:16px; margin: 12px 0 8px; }
.hero-pot .amount { font-family:'Press Start 2P',cursive; font-size:34px; color:#fff; text-shadow:0 0 12px rgba(124,58,237,0.8); }
.hero-pot .timer { color: var(--muted); font-size:10px; font-family: 'Press Start 2P', cursive; }
/* Tabs */
.tabs .tabs-nav { display:flex; gap:8px; margin-bottom:8px; }
.tabs .tab { background:#0e1430; color:#cbd5ff; border:1px solid rgba(124,58,237,0.25); padding:8px 12px; border-radius:8px; cursor:pointer; font-family:'Press Start 2P',cursive; font-size:11px; }
.tabs .tab.active { background: linear-gradient(135deg, rgba(124,58,237,0.5), rgba(34,211,238,0.35)); color:#0b1020; }
.tab-content { margin-top:8px; }
.tab-content.active { display:block; }
/* Numbers */
.big { font-size: 26px; font-family:'Press Start 2P',cursive; }
/* 8-bit headings */
h2, h3, h4 { font-family: 'Press Start 2P', cursive; color: var(--accent); text-shadow: 2px 2px 0px rgba(0,0,0,0.5); }
h2 { font-size: 14px; margin: 0 0 12px 0; }
h3 { font-size: 12px; margin: 0 0 8px 0; }
p { font-family: 'Press Start 2P', cursive; font-size: 10px; line-height: 1.6; }
/* Select styling */
select {
background: #0e1430;
color: var(--text);
border: 1px solid rgba(124,58,237,0.25);
border-radius: 8px;
padding: 10px;
font-family: 'Press Start 2P', cursive;
font-size: 10px;
}
/* Link styling */
a {
color: var(--accent);
text-decoration: none;
font-family: 'Press Start 2P', cursive;
font-size: 10px;
}
a:hover {
color: #fff;
text-shadow: 0 0 8px var(--accent);
}
/* 8-bit Dark Theme */
:root {
--bg: #0b1020;
--panel: #151b2e;
--text: #dbe4ff;
--muted: #8ea0c7;
--primary: #7c3aed;
--accent: #22d3ee;
}
*{box-sizing:border-box}
body { margin:0; background:var(--bg); color:var(--text); font-family: 'Press Start 2P', cursive; font-size: 11px; line-height: 1.6; }
.hero { padding: 48px 0 28px; background: radial-gradient(1200px 600px at 50% -10%, rgba(124,58,237,0.35), transparent 60%), #0b1020; text-align:center; }
.hero h1 { font-family:'Press Start 2P', cursive; margin:0 0 8px; font-size: 28px; letter-spacing: 1px; }
.hero .sub { color: var(--muted); margin:0; font-family: 'Press Start 2P', cursive; font-size: 10px; }
.wrap { max-width: 1100px; margin: 0 auto; padding: 0 16px; }
.panel { background: var(--panel); border:1px solid rgba(124,58,237,0.15); border-radius: 12px; padding: 16px; margin: 16px 0; box-shadow: 0 8px 30px rgba(124,58,237,0.10); }
.row { display:flex; gap:16px; flex-wrap:wrap; }
.col { flex:1 1 420px; }
.grid2 { display:grid; grid-template-columns: 180px 1fr; gap:8px 12px; align-items:center; }
.label { color: var(--muted); font-size: 10px; font-family: 'Press Start 2P', cursive; text-transform: uppercase; }
.input { background:#0e1430; color:var(--text); border:1px solid rgba(124,58,237,0.25); border-radius:8px; padding:10px; width:100%; font-family: 'Press Start 2P', cursive; font-size: 10px; }
.wallet-row { display:flex; gap:8px; margin: 8px 0 12px; }
.btn { background: var(--primary); color:#fff; border:0; border-radius:8px; padding:10px 14px; font-family:'Press Start 2P', cursive; box-shadow:0 0 0 3px #000, 0 8px 0 0 #000; cursor:pointer; }
.btn:active { position:relative; top:2px; box-shadow:0 0 0 3px #000, 0 6px 0 0 #000; }
.btn.alt { background: var(--accent); color:#0b1020; }
.btn.danger { background:#ef4444; }
.muted { color: var(--muted); font-family: 'Press Start 2P', cursive; font-size: 10px; }
.log { background:#060912; border:1px solid rgba(124,58,237,0.15); border-radius:8px; padding:12px; min-height:160px; font-family: ui-monospace, Menlo, Monaco, Consolas, 'Courier New', monospace; overflow:auto; }
.foot { display:flex; justify-content:space-between; align-items:center; padding: 16px 0 48px; color: var(--muted); font-family: 'Press Start 2P', cursive; font-size: 10px; }
@media (max-width: 720px){
.grid2{grid-template-columns:1fr; gap:12px;}
.label{font-size:9px; margin-bottom:4px;}
h2{font-size:12px;}
.hero h1{font-size:20px;}
.hero-pot .amount{font-size:24px;}
.btn{font-size:9px; padding:8px 10px;}
.input{font-size:9px;}
}

57
switch-to-mainnet.sh

@ -0,0 +1,57 @@
#!/bin/bash
# Simple script to switch from devnet to mainnet
set -e
echo "🔄 Switching to Mainnet Configuration..."
echo "⚠ WARNING: You'll need to deploy the program to mainnet first!"
echo
# Get current config
if [[ ! -f "config.json" ]]; then
echo "❌ config.json not found"
exit 1
fi
# Show current config
echo "📋 Current config:"
cat config.json
echo
# Get user inputs
read -p "🔑 Enter MAINNET program ID: " PROGRAM_ID
read -p "🪙 Enter MAINNET mint address: " MINT_ADDRESS
read -p "💰 Enter MAINNET dev wallet (or press enter for current): " DEV_WALLET
# Use current dev wallet if none provided
if [[ -z "$DEV_WALLET" ]]; then
DEV_WALLET=$(cat config.json | grep devWallet | cut -d'"' -f4)
fi
# Create mainnet config
cat > config-mainnet.json << EOF
{
"cluster": "https://api.mainnet-beta.solana.com",
"programId": "$PROGRAM_ID",
"mint": "$MINT_ADDRESS",
"devWallet": "$DEV_WALLET"
}
EOF
# Backup current config
cp config.json config-devnet-backup.json
# Switch to mainnet
cp config-mainnet.json config.json
echo "✅ Switched to mainnet!"
echo "📁 Devnet config backed up to: config-devnet-backup.json"
echo "📁 Mainnet config saved to: config-mainnet.json"
echo
echo "📝 To deploy frontend with mainnet config:"
echo " ./deploy.sh"
echo
echo "📝 To switch back to devnet:"
echo " cp config-devnet-backup.json config.json"

86
test-devnet.sh

@ -0,0 +1,86 @@
#!/bin/bash
echo "🧪 Lottery Devnet Testing Script"
echo "=============================="
# Set to devnet
solana config set --url devnet
echo "✅ Set Solana config to devnet"
# Check wallet balance
echo "💰 Checking wallet balance..."
BALANCE=$(solana balance)
echo "Current balance: $BALANCE"
# Ensure we have enough SOL (at least 5 SOL for testing)
MIN_BALANCE="5.0"
BALANCE_NUM=$(echo $BALANCE | cut -d' ' -f1)
if (( $(echo "$BALANCE_NUM < $MIN_BALANCE" | bc -l) )); then
echo "❌ Insufficient balance for testing. Need at least 5 SOL."
echo "💸 Requesting airdrop..."
solana airdrop 5
echo "⏳ Waiting 10 seconds for airdrop confirmation..."
sleep 10
fi
# Build the program
echo "🔨 Building Anchor program..."
anchor build
if [ $? -ne 0 ]; then
echo "❌ Build failed!"
exit 1
fi
# Deploy the program
echo "🚀 Deploying to devnet..."
anchor deploy --provider.cluster devnet
if [ $? -ne 0 ]; then
echo "❌ Deploy failed!"
exit 1
fi
# Generate the IDL
echo "📝 Generating IDL..."
anchor idl init -f target/idl/lottery_simple.json $(solana-keygen pubkey target/deploy/lottery_simple-keypair.json)
echo "✅ Program deployed successfully!"
# Show deployment info
PROGRAM_ID=$(solana-keygen pubkey target/deploy/lottery_simple-keypair.json)
echo "📍 Program ID: $PROGRAM_ID"
# Create a simple test to verify the deployment
echo "🧪 Creating simple test..."
# Generate a new keypair for the mint
echo "🔑 Creating mint keypair..."
solana-keygen new --outfile mint-keypair.json --no-bip39-passphrase
# Create the mint
echo "🪙 Creating token mint..."
MINT_ADDRESS=$(solana-keygen pubkey mint-keypair.json)
spl-token create-token mint-keypair.json
echo "✅ Token mint created: $MINT_ADDRESS"
# Generate devnet config
echo "📄 Generating devnet configuration..."
cat > devnet-config.json << EOF
{
"programId": "$PROGRAM_ID",
"mintAddress": "$MINT_ADDRESS",
"devWallet": "$(solana address)",
"cluster": "devnet",
"rpcUrl": "https://api.devnet.solana.com"
}
EOF
echo "✅ Devnet configuration saved to devnet-config.json"
echo ""
echo "🎉 Deployment complete!"
echo "📋 Next steps:"
echo " 1. Update your frontend to use devnet-config.json"
echo " 2. Initialize the program state using the frontend or CLI"
echo " 3. Test buy/sell functionality"
echo ""
echo "⚠ Remember: This is devnet SOL - not real money!"

289
test-program.html

@ -0,0 +1,289 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PoWH Program Test - Devnet</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #1a1a1a;
color: #ffffff;
}
.container {
background-color: #2d2d2d;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
}
button {
background-color: #4CAF50;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 5px;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #666;
cursor: not-allowed;
}
.status {
background-color: #333;
padding: 10px;
border-radius: 5px;
margin: 10px 0;
font-family: monospace;
}
.success { color: #4CAF50; }
.error { color: #f44336; }
.warning { color: #ff9800; }
.info { color: #2196F3; }
input {
padding: 10px;
margin: 5px 0;
border: 1px solid #555;
background-color: #444;
color: white;
border-radius: 3px;
width: 200px;
}
h1 {
color: #4CAF50;
text-align: center;
}
h2 {
color: #2196F3;
}
</style>
<script src="https://unpkg.com/@solana/web3.js@latest/lib/index.iife.js"></script>
<script src="https://unpkg.com/@coral-xyz/anchor@latest/dist/browser.js"></script>
</head>
<body>
<h1>🧪 PoWH Program Test - Devnet</h1>
<div class="container">
<h2>Connection Status</h2>
<div id="connection-status" class="status">Not connected</div>
<button onclick="connectWallet()">Connect Phantom Wallet</button>
</div>
<div class="container">
<h2>Program Information</h2>
<div class="status">
<div>Program ID: <span id="program-id">Loading...</span></div>
<div>Mint Address: <span id="mint-address">Loading...</span></div>
<div>Cluster: <span id="cluster">devnet</span></div>
<div>State PDA: <span id="state-pda">Not calculated</span></div>
<div>Vault PDA: <span id="vault-pda">Not calculated</span></div>
</div>
<button onclick="calculatePDAs()">Calculate PDAs</button>
</div>
<div class="container">
<h2>Program Initialization</h2>
<div id="init-status" class="status">Ready to initialize</div>
<button onclick="initializeProgram()">Initialize Program</button>
</div>
<div class="container">
<h2>Test Trading</h2>
<div>
<input type="number" id="sol-amount" placeholder="SOL Amount" step="0.1" value="0.1">
<button onclick="buyTokens()">Buy Tokens</button>
</div>
<div>
<input type="number" id="token-amount" placeholder="Token Amount" step="1000000" value="1000000">
<button onclick="sellTokens()">Sell Tokens</button>
</div>
<div id="trade-status" class="status">Ready to trade</div>
</div>
<script>
// Load configuration
const CONFIG = {
"programId": "8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf",
"mintAddress": "GrCHRSRHdZtiz2fiv1iUgigXCfB5Ta3j8gXepZUUTPP2",
"devWallet": "GPFYcM3svcM4Srko6ExLYeSW4Gjwf6XoV4k1eSXoGHoK",
"cluster": "devnet",
"rpcUrl": "https://api.devnet.solana.com"
};
let connection;
let wallet;
let program;
let statePda;
let vaultPda;
// Update UI with config
document.getElementById('program-id').textContent = CONFIG.programId;
document.getElementById('mint-address').textContent = CONFIG.mintAddress;
function log(message, type = 'info') {
console.log(message);
const status = document.getElementById('connection-status');
status.innerHTML = `<span class="${type}">${new Date().toLocaleTimeString()}: ${message}</span>`;
}
async function connectWallet() {
try {
if (window.solana) {
const response = await window.solana.connect();
wallet = window.solana;
connection = new solanaWeb3.Connection(CONFIG.rpcUrl, 'confirmed');
log(`Connected to wallet: ${response.publicKey.toString()}`, 'success');
// Check balance
const balance = await connection.getBalance(response.publicKey);
log(`Wallet balance: ${balance / solanaWeb3.LAMPORTS_PER_SOL} SOL`, 'info');
} else {
log('Phantom wallet not found!', 'error');
}
} catch (error) {
log(`Connection failed: ${error.message}`, 'error');
}
}
async function calculatePDAs() {
if (!connection) {
log('Connect wallet first!', 'error');
return;
}
try {
const programId = new solanaWeb3.PublicKey(CONFIG.programId);
[statePda] = await solanaWeb3.PublicKey.findProgramAddress(
[Buffer.from("state")],
programId
);
[vaultPda] = await solanaWeb3.PublicKey.findProgramAddress(
[Buffer.from("vault")],
programId
);
document.getElementById('state-pda').textContent = statePda.toString();
document.getElementById('vault-pda').textContent = vaultPda.toString();
log('PDAs calculated successfully', 'success');
} catch (error) {
log(`PDA calculation failed: ${error.message}`, 'error');
}
}
async function initializeProgram() {
if (!wallet || !connection || !statePda || !vaultPda) {
log('Please connect wallet and calculate PDAs first!', 'error');
return;
}
try {
document.getElementById('init-status').innerHTML = '<span class="warning">Initializing...</span>';
// Create transaction manually since we don't have working Anchor JS
const mintPubkey = new solanaWeb3.PublicKey(CONFIG.mintAddress);
const programId = new solanaWeb3.PublicKey(CONFIG.programId);
// Create initialize instruction data
// This is the discriminator for initialize function from the IDL
const initDiscriminator = [175, 175, 109, 31, 13, 152, 155, 237];
const data = Buffer.from(initDiscriminator);
const instruction = new solanaWeb3.TransactionInstruction({
keys: [
{ pubkey: statePda, isSigner: false, isWritable: true },
{ pubkey: vaultPda, isSigner: false, isWritable: true },
{ pubkey: mintPubkey, isSigner: false, isWritable: true },
{ pubkey: wallet.publicKey, isSigner: true, isWritable: true },
{ pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: new solanaWeb3.PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"), isSigner: false, isWritable: false },
],
programId: programId,
data: data
});
const transaction = new solanaWeb3.Transaction().add(instruction);
const signature = await wallet.signAndSendTransaction(transaction);
document.getElementById('init-status').innerHTML = `<span class="success">Initialized! Tx: ${signature}</span>`;
log(`Program initialized successfully: ${signature}`, 'success');
} catch (error) {
document.getElementById('init-status').innerHTML = `<span class="error">Failed: ${error.message}</span>`;
log(`Initialization failed: ${error.message}`, 'error');
}
}
async function buyTokens() {
if (!wallet || !connection) {
log('Connect wallet first!', 'error');
return;
}
const solAmount = parseFloat(document.getElementById('sol-amount').value);
if (!solAmount || solAmount <= 0) {
log('Enter a valid SOL amount!', 'error');
return;
}
try {
document.getElementById('trade-status').innerHTML = '<span class="warning">Creating buy transaction...</span>';
// You would need to implement the full buy transaction here
// For now, just show that we're connected and ready
document.getElementById('trade-status').innerHTML = `<span class="info">Ready to buy ${solAmount} SOL worth of tokens</span>`;
log(`Buy tokens functionality ready (${solAmount} SOL)`, 'info');
} catch (error) {
document.getElementById('trade-status').innerHTML = `<span class="error">Buy failed: ${error.message}</span>`;
log(`Buy failed: ${error.message}`, 'error');
}
}
async function sellTokens() {
if (!wallet || !connection) {
log('Connect wallet first!', 'error');
return;
}
const tokenAmount = parseInt(document.getElementById('token-amount').value);
if (!tokenAmount || tokenAmount <= 0) {
log('Enter a valid token amount!', 'error');
return;
}
try {
document.getElementById('trade-status').innerHTML = '<span class="warning">Creating sell transaction...</span>';
// You would need to implement the full sell transaction here
document.getElementById('trade-status').innerHTML = `<span class="info">Ready to sell ${tokenAmount} tokens</span>`;
log(`Sell tokens functionality ready (${tokenAmount} tokens)`, 'info');
} catch (error) {
document.getElementById('trade-status').innerHTML = `<span class="error">Sell failed: ${error.message}</span>`;
log(`Sell failed: ${error.message}`, 'error');
}
}
// Auto-calculate PDAs on page load
window.addEventListener('load', () => {
log('Test page loaded. Ready to test devnet deployment!', 'info');
});
</script>
</body>
</html>

79
ticker-api.php

@ -0,0 +1,79 @@
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header('Access-Control-Allow-Headers: Content-Type');
$tickerFile = 'ticker-data.json';
$maxItems = 25; // Keep last 25 ticker items
// Read existing ticker data
function readTickerData($file) {
if (!file_exists($file)) {
return [];
}
$content = file_get_contents($file);
return json_decode($content, true) ?: [];
}
// Write ticker data
function writeTickerData($file, $data) {
return file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT));
}
// Handle different HTTP methods
$method = $_SERVER['REQUEST_METHOD'];
if ($method === 'GET') {
// Return current ticker data
$tickerData = readTickerData($tickerFile);
echo json_encode($tickerData);
} elseif ($method === 'POST') {
// Add new ticker item
$input = json_decode(file_get_contents('php://input'), true);
if (!$input || !isset($input['text']) || !isset($input['type'])) {
http_response_code(400);
echo json_encode(['error' => 'Invalid input. Required: text, type']);
exit;
}
$tickerData = readTickerData($tickerFile);
// Add new item with timestamp
$newItem = [
'text' => $input['text'],
'type' => $input['type'], // 'buy' or 'sell'
'timestamp' => time()
];
// Add optional SNS fields if provided
if (isset($input['fullAddress'])) {
$newItem['fullAddress'] = $input['fullAddress'];
}
if (isset($input['snsName'])) {
$newItem['snsName'] = $input['snsName'];
}
// Add to beginning of array (most recent first)
array_unshift($tickerData, $newItem);
// Keep only the last N items
if (count($tickerData) > $maxItems) {
$tickerData = array_slice($tickerData, 0, $maxItems);
}
// Save to file
if (writeTickerData($tickerFile, $tickerData)) {
echo json_encode(['success' => true, 'itemsCount' => count($tickerData)]);
} else {
http_response_code(500);
echo json_encode(['error' => 'Failed to save ticker data']);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
?>

17
ticker-data.json

@ -0,0 +1,17 @@
[
{
"text": "BUY 0.250 SOL • 4Zp2…8Tv3",
"type": "buy",
"timestamp": 1728219420
},
{
"text": "SELL 15.000 tk • 8Rv1…2Mn4",
"type": "sell",
"timestamp": 1728219380
},
{
"text": "BUY 0.100 SOL • 2Qm9…5Lp6",
"type": "buy",
"timestamp": 1728219340
}
]
Loading…
Cancel
Save