commit
df491c63df
38 changed files with 6749 additions and 0 deletions
@ -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 |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
.anchor |
||||||
|
.DS_Store |
||||||
|
target |
||||||
|
**/*.rs.bk |
||||||
|
node_modules |
||||||
|
test-ledger |
||||||
|
.yarn |
||||||
@ -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" |
||||||
@ -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 |
||||||
@ -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 |
||||||
|
``` |
||||||
@ -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"] |
||||||
@ -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(); |
||||||
|
} |
||||||
|
})(); |
||||||
|
After Width: | Height: | Size: 46 KiB |
@ -0,0 +1,6 @@ |
|||||||
|
{ |
||||||
|
"cluster": "https://api.devnet.solana.com", |
||||||
|
"programId": "CQKiz5PKgoQjxNDkGowPWWXu8MJCm2HgonfYAtcFZX87", |
||||||
|
"mint": "8cJ8APx5TwwmpeRk4Zhz1Qbyi1T3HHdHUEw4GUJqxAGN", |
||||||
|
"devWallet": "GPFYcM3svcM4Srko6ExLYeSW4Gjwf6XoV4k1eSXoGHoK" |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"programId": "8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf", |
||||||
|
"mintAddress": "GrCHRSRHdZtiz2fiv1iUgigXCfB5Ta3j8gXepZUUTPP2", |
||||||
|
"devWallet": "GPFYcM3svcM4Srko6ExLYeSW4Gjwf6XoV4k1eSXoGHoK", |
||||||
|
"cluster": "devnet", |
||||||
|
"rpcUrl": "https://api.devnet.solana.com" |
||||||
|
} |
||||||
@ -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 |
||||||
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 642 B |
|
After Width: | Height: | Size: 5.3 KiB |
@ -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> |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
.anchor |
||||||
|
.DS_Store |
||||||
|
target |
||||||
|
**/*.rs.bk |
||||||
|
node_modules |
||||||
|
test-ledger |
||||||
|
.yarn |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
.anchor |
||||||
|
.DS_Store |
||||||
|
target |
||||||
|
node_modules |
||||||
|
dist |
||||||
|
build |
||||||
|
test-ledger |
||||||
@ -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" |
||||||
@ -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] |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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" |
||||||
|
} |
||||||
|
} |
||||||
@ -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" |
||||||
@ -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, |
||||||
|
} |
||||||
@ -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;} |
||||||
|
} |
||||||
@ -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> |
||||||
@ -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']); |
||||||
|
} |
||||||
|
?> |
||||||
@ -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…
Reference in new issue