Initial commit: Solana lottery program
This commit is contained in:
@@ -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
|
||||||
+25
@@ -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"
|
||||||
Generated
+2898
File diff suppressed because it is too large
Load Diff
+15
@@ -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,101 @@
|
|||||||
|
# 🧪 Devnet Deployment Status
|
||||||
|
|
||||||
|
## ✅ Successfully Completed
|
||||||
|
|
||||||
|
### 1. Program Deployment
|
||||||
|
- **Program ID**: `8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf`
|
||||||
|
- **Status**: ✅ Successfully deployed to Devnet
|
||||||
|
- **IDL**: ✅ Successfully created and uploaded
|
||||||
|
- **Cluster**: Devnet (safe testing environment)
|
||||||
|
|
||||||
|
### 2. Token Mint
|
||||||
|
- **Mint Address**: `GrCHRSRHdZtiz2fiv1iUgigXCfB5Ta3j8gXepZUUTPP2`
|
||||||
|
- **Status**: ✅ Created and ready for use
|
||||||
|
- **Decimals**: 9 (standard SPL token format)
|
||||||
|
|
||||||
|
### 3. Program Features
|
||||||
|
- ✅ Working Anchor v0.31.0 program
|
||||||
|
- ✅ All previous fixes implemented:
|
||||||
|
- Direct lamport manipulation for vault transfers
|
||||||
|
- Realistic bonding curve math (price increases with supply)
|
||||||
|
- Dividend distribution system
|
||||||
|
- Dev fee collection (3%)
|
||||||
|
- Proper rent exemption handling
|
||||||
|
- Buy/sell functionality with safety checks
|
||||||
|
|
||||||
|
### 4. Configuration
|
||||||
|
- ✅ `devnet-config.json` contains all deployment details
|
||||||
|
- ✅ Program ID matches deployed program
|
||||||
|
- ✅ Ready for frontend integration
|
||||||
|
|
||||||
|
## 🚀 Next Steps
|
||||||
|
|
||||||
|
### Option 1: Test with Browser Interface
|
||||||
|
1. Open `test-program.html` in your browser
|
||||||
|
2. Connect Phantom wallet (set to Devnet)
|
||||||
|
3. Initialize the program
|
||||||
|
4. Test buy/sell functionality
|
||||||
|
|
||||||
|
### Option 2: Update Your Frontend
|
||||||
|
1. Update your frontend's config to use `devnet-config.json`
|
||||||
|
2. Test all functionality with devnet SOL (free!)
|
||||||
|
3. Verify buy/sell transactions work correctly
|
||||||
|
4. Test dividend withdrawal
|
||||||
|
|
||||||
|
### Option 3: CLI Testing (if needed)
|
||||||
|
```bash
|
||||||
|
# Check program account
|
||||||
|
solana account 8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf --url devnet
|
||||||
|
|
||||||
|
# Check state PDA (after initialization)
|
||||||
|
solana account [STATE_PDA] --url devnet
|
||||||
|
|
||||||
|
# Check vault PDA balance
|
||||||
|
solana account [VAULT_PDA] --url devnet
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 Configuration Details
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"programId": "8hcuEUBcuVBYyD53QPFQrRSWJjQy3UVURiqJpcuvgrTf",
|
||||||
|
"mintAddress": "GrCHRSRHdZtiz2fiv1iUgigXCfB5Ta3j8gXepZUUTPP2",
|
||||||
|
"devWallet": "GPFYcM3svcM4Srko6ExLYeSW4Gjwf6XoV4k1eSXoGHoK",
|
||||||
|
"cluster": "devnet",
|
||||||
|
"rpcUrl": "https://api.devnet.solana.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ Important Notes
|
||||||
|
|
||||||
|
1. **This is DEVNET** - No real money is involved
|
||||||
|
2. **Test thoroughly** before considering mainnet deployment
|
||||||
|
3. **Devnet SOL is free** - get more from faucets if needed
|
||||||
|
4. **Program must be initialized** before trading can begin
|
||||||
|
|
||||||
|
## 🎯 Testing Checklist
|
||||||
|
|
||||||
|
Before moving to mainnet, verify:
|
||||||
|
|
||||||
|
- [ ] Program initializes successfully
|
||||||
|
- [ ] Buy transactions work correctly
|
||||||
|
- [ ] Sell transactions work correctly
|
||||||
|
- [ ] Dev fees are collected properly
|
||||||
|
- [ ] Dividend distribution works
|
||||||
|
- [ ] Vault PDA maintains proper balance
|
||||||
|
- [ ] Frontend UI functions correctly
|
||||||
|
- [ ] Error handling works as expected
|
||||||
|
|
||||||
|
## 🚨 Mainnet Deployment
|
||||||
|
|
||||||
|
**Only proceed to mainnet AFTER:**
|
||||||
|
1. All devnet testing is complete
|
||||||
|
2. All functionality verified working
|
||||||
|
3. UI/UX is polished and tested
|
||||||
|
4. You're confident in the system
|
||||||
|
|
||||||
|
The same program code will work on mainnet - just need to change the cluster configuration and deploy with real SOL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Current Status**: ✅ DEVNET DEPLOYMENT COMPLETE - READY FOR TESTING
|
||||||
+38
@@ -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();
|
||||||
|
}
|
||||||
|
})();
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Executable
+87
@@ -0,0 +1,87 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "🔥 Burning All Tokens in Wallet"
|
||||||
|
echo "================================"
|
||||||
|
echo "⚠️ This will permanently destroy ALL tokens in your wallet!"
|
||||||
|
echo "📍 Cluster: devnet (safe for testing)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Confirm before proceeding
|
||||||
|
read -p "Are you sure you want to burn ALL tokens? (type 'yes' to confirm): " confirm
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
echo "❌ Aborted - no tokens burned"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🔥 Starting token burning process..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# List of token mints to burn (from spl-token accounts output)
|
||||||
|
TOKENS=(
|
||||||
|
"4auc435LJ9AfprLeMziHTJbP8wQmbSi7o4ZpeXdR66jQ"
|
||||||
|
"5xEQF57sR2L3tJBfdiiqaVtepTMa7nQR9uMybMojEY8n"
|
||||||
|
"8cJ8APx5TwwmpeRk4Zhz1Qbyi1T3HHdHUEw4GUJqxAGN"
|
||||||
|
"AK9fXWyDNWurYjZSDhbqBo6fczqLmCLbCZ3vq9Vftsqb"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set to devnet
|
||||||
|
solana config set --url devnet > /dev/null
|
||||||
|
|
||||||
|
echo "💰 Current wallet: $(solana address)"
|
||||||
|
echo "🌐 Cluster: devnet"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Function to burn tokens for a specific mint
|
||||||
|
burn_token() {
|
||||||
|
local mint=$1
|
||||||
|
echo "🔥 Processing mint: $mint"
|
||||||
|
|
||||||
|
# Get the token account balance
|
||||||
|
local balance=$(spl-token balance $mint --url devnet 2>/dev/null || echo "0")
|
||||||
|
|
||||||
|
if [ "$balance" == "0" ] || [ -z "$balance" ]; then
|
||||||
|
echo " ℹ️ No balance or account doesn't exist - skipping"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " 💰 Current balance: $balance"
|
||||||
|
|
||||||
|
# Try to burn all tokens
|
||||||
|
echo " 🔥 Burning $balance tokens..."
|
||||||
|
local result=$(spl-token burn $mint $balance --url devnet 2>&1)
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " ✅ Successfully burned $balance tokens"
|
||||||
|
|
||||||
|
# Try to close the token account to reclaim SOL
|
||||||
|
echo " 🗑️ Attempting to close token account..."
|
||||||
|
spl-token close $mint --url devnet 2>/dev/null
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " ✅ Token account closed and SOL reclaimed"
|
||||||
|
else
|
||||||
|
echo " ⚠️ Token account could not be closed (may still have remaining dust)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ❌ Failed to burn tokens: $result"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Burn tokens for each mint
|
||||||
|
for mint in "${TOKENS[@]}"; do
|
||||||
|
burn_token $mint
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "🎯 Token burning process complete!"
|
||||||
|
echo ""
|
||||||
|
echo "📊 Final token account status:"
|
||||||
|
spl-token accounts --url devnet
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "💰 Final SOL balance:"
|
||||||
|
solana balance --url devnet
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Ready for clean testing!"
|
||||||
|
echo "🧪 You can now test the PoWH program from a clean state"
|
||||||
Executable
+111
@@ -0,0 +1,111 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "🗑️ Closing All Token Accounts"
|
||||||
|
echo "=============================="
|
||||||
|
echo "⚠️ This will close ALL token accounts and burn any remaining tokens!"
|
||||||
|
echo "💰 You will reclaim the SOL used for rent"
|
||||||
|
echo "📍 Cluster: devnet (safe for testing)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Confirm before proceeding
|
||||||
|
read -p "Are you sure you want to close ALL token accounts? (type 'yes' to confirm): " confirm
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
echo "❌ Aborted - no accounts closed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🗑️ Starting token account closure process..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Set to devnet
|
||||||
|
solana config set --url devnet > /dev/null
|
||||||
|
|
||||||
|
echo "💰 Current wallet: $(solana address)"
|
||||||
|
echo "🌐 Cluster: devnet"
|
||||||
|
echo ""
|
||||||
|
echo "💰 SOL balance before: $(solana balance --url devnet)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get all token accounts and close them
|
||||||
|
echo "📊 Current token accounts:"
|
||||||
|
spl-token accounts --url devnet
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# List of specific token account addresses to close (from verbose output)
|
||||||
|
TOKEN_ACCOUNTS=(
|
||||||
|
"8zr6uF84gTzY5V1MP9EeEfy6m2LNULkH4VEFgxY1fKwh"
|
||||||
|
"679vTxSpUw55buSgPpUsEp66eiUCQs3Y7acpoeTt6W97"
|
||||||
|
"4t7iZeFsLXA2Ro1EyxoR88Aqnq62Da4txb74eiC2WovQ"
|
||||||
|
"AmNmTDXjESr1PZTjEC6QiWqLbeZxdxdQkM2TasSUL88QK"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Function to close a token account
|
||||||
|
close_account() {
|
||||||
|
local account=$1
|
||||||
|
echo "🗑️ Processing token account: $account"
|
||||||
|
|
||||||
|
# Try to close the account directly
|
||||||
|
local result=$(spl-token close-account $account --url devnet 2>&1)
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " ✅ Successfully closed account and reclaimed SOL"
|
||||||
|
else
|
||||||
|
echo " ❌ Failed to close account: $result"
|
||||||
|
|
||||||
|
# If direct closure failed, try to get the mint and burn first
|
||||||
|
echo " 🔄 Trying alternative approach..."
|
||||||
|
|
||||||
|
# Get account info to find the mint
|
||||||
|
local account_info=$(solana account $account --url devnet --output json 2>/dev/null)
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " ℹ️ Account exists, attempting force closure..."
|
||||||
|
spl-token close-account $account --force --url devnet 2>/dev/null
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " ✅ Force closure successful"
|
||||||
|
else
|
||||||
|
echo " ⚠️ Account could not be closed (may have non-zero balance)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ℹ️ Account may already be closed or doesn't exist"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Close all token accounts
|
||||||
|
for account in "${TOKEN_ACCOUNTS[@]}"; do
|
||||||
|
close_account $account
|
||||||
|
done
|
||||||
|
|
||||||
|
# Also try to close by mint address (alternative approach)
|
||||||
|
echo "🔄 Attempting closure by mint address as backup..."
|
||||||
|
MINTS=(
|
||||||
|
"4auc435LJ9AfprLeMziHTJbP8wQmbSi7o4ZpeXdR66jQ"
|
||||||
|
"5xEQF57sR2L3tJBfdiiqaVtepTMa7nQR9uMybMojEY8n"
|
||||||
|
"8cJ8APx5TwwmpeRk4Zhz1Qbyi1T3HHdHUEw4GUJqxAGN"
|
||||||
|
"AK9fXWyDNWurYjZSDhbqBo6fczqLmCLbCZ3vq9Vftsqb"
|
||||||
|
)
|
||||||
|
|
||||||
|
for mint in "${MINTS[@]}"; do
|
||||||
|
echo "🗑️ Attempting to close account for mint: $mint"
|
||||||
|
spl-token close $mint --url devnet 2>/dev/null
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " ✅ Closed successfully"
|
||||||
|
else
|
||||||
|
echo " ⚠️ Could not close (may not exist or have balance)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🎯 Token account closure process complete!"
|
||||||
|
echo ""
|
||||||
|
echo "📊 Final token account status:"
|
||||||
|
spl-token accounts --url devnet
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "💰 Final SOL balance:"
|
||||||
|
solana balance --url devnet
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Token accounts processed!"
|
||||||
|
echo "🧪 Ready for clean testing with your PoWH program"
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"cluster": "https://api.devnet.solana.com",
|
||||||
|
"programId": "CQKiz5PKgoQjxNDkGowPWWXu8MJCm2HgonfYAtcFZX87",
|
||||||
|
"mint": "8cJ8APx5TwwmpeRk4Zhz1Qbyi1T3HHdHUEw4GUJqxAGN",
|
||||||
|
"devWallet": "GPFYcM3svcM4Srko6ExLYeSW4Gjwf6XoV4k1eSXoGHoK"
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Deployment script for Lottery Simple DApp
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🚀 Building and deploying Lottery Simple DApp..."
|
||||||
|
|
||||||
|
# Build the Docker image
|
||||||
|
echo "📦 Building Docker image..."
|
||||||
|
docker-compose build
|
||||||
|
|
||||||
|
# Stop existing container if running
|
||||||
|
echo "🛑 Stopping existing container..."
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Start the new container
|
||||||
|
echo "▶️ Starting new container..."
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Wait a moment for startup
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# Check if it's running
|
||||||
|
if curl -s http://localhost:14888/health > /dev/null; then
|
||||||
|
echo "✅ Deployment successful!"
|
||||||
|
echo "🌐 Frontend available at: http://localhost:14888"
|
||||||
|
echo "🏥 Health check: http://localhost:14888/health"
|
||||||
|
else
|
||||||
|
echo "❌ Deployment failed - health check failed"
|
||||||
|
echo "📋 Container logs:"
|
||||||
|
docker-compose logs
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🎉 Deployment complete!"
|
||||||
@@ -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
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 642 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
+76
@@ -0,0 +1,76 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
||||||
|
<!-- Background -->
|
||||||
|
<rect width="32" height="32" fill="#0b1020"/>
|
||||||
|
|
||||||
|
<!-- 8-bit style dollar sign -->
|
||||||
|
<g fill="#22d3ee">
|
||||||
|
<!-- Dollar sign pixels -->
|
||||||
|
<rect x="8" y="6" width="2" height="2"/>
|
||||||
|
<rect x="10" y="6" width="2" height="2"/>
|
||||||
|
<rect x="12" y="6" width="2" height="2"/>
|
||||||
|
<rect x="14" y="6" width="2" height="2"/>
|
||||||
|
<rect x="16" y="6" width="2" height="2"/>
|
||||||
|
<rect x="18" y="6" width="2" height="2"/>
|
||||||
|
<rect x="20" y="6" width="2" height="2"/>
|
||||||
|
<rect x="22" y="6" width="2" height="2"/>
|
||||||
|
|
||||||
|
<rect x="8" y="8" width="2" height="2"/>
|
||||||
|
<rect x="10" y="8" width="2" height="2"/>
|
||||||
|
|
||||||
|
<rect x="8" y="10" width="2" height="2"/>
|
||||||
|
<rect x="10" y="10" width="2" height="2"/>
|
||||||
|
<rect x="12" y="10" width="2" height="2"/>
|
||||||
|
<rect x="14" y="10" width="2" height="2"/>
|
||||||
|
<rect x="16" y="10" width="2" height="2"/>
|
||||||
|
<rect x="18" y="10" width="2" height="2"/>
|
||||||
|
|
||||||
|
<rect x="20" y="12" width="2" height="2"/>
|
||||||
|
<rect x="22" y="12" width="2" height="2"/>
|
||||||
|
|
||||||
|
<rect x="8" y="14" width="2" height="2"/>
|
||||||
|
<rect x="10" y="14" width="2" height="2"/>
|
||||||
|
<rect x="12" y="14" width="2" height="2"/>
|
||||||
|
<rect x="14" y="14" width="2" height="2"/>
|
||||||
|
<rect x="16" y="14" width="2" height="2"/>
|
||||||
|
<rect x="18" y="14" width="2" height="2"/>
|
||||||
|
|
||||||
|
<rect x="20" y="16" width="2" height="2"/>
|
||||||
|
<rect x="22" y="16" width="2" height="2"/>
|
||||||
|
|
||||||
|
<rect x="8" y="18" width="2" height="2"/>
|
||||||
|
<rect x="10" y="18" width="2" height="2"/>
|
||||||
|
<rect x="12" y="18" width="2" height="2"/>
|
||||||
|
<rect x="14" y="18" width="2" height="2"/>
|
||||||
|
<rect x="16" y="18" width="2" height="2"/>
|
||||||
|
<rect x="18" y="18" width="2" height="2"/>
|
||||||
|
<rect x="20" y="18" width="2" height="2"/>
|
||||||
|
<rect x="22" y="18" width="2" height="2"/>
|
||||||
|
|
||||||
|
<rect x="20" y="20" width="2" height="2"/>
|
||||||
|
<rect x="22" y="20" width="2" height="2"/>
|
||||||
|
|
||||||
|
<rect x="8" y="22" width="2" height="2"/>
|
||||||
|
<rect x="10" y="22" width="2" height="2"/>
|
||||||
|
<rect x="12" y="22" width="2" height="2"/>
|
||||||
|
<rect x="14" y="22" width="2" height="2"/>
|
||||||
|
<rect x="16" y="22" width="2" height="2"/>
|
||||||
|
<rect x="18" y="22" width="2" height="2"/>
|
||||||
|
<rect x="20" y="22" width="2" height="2"/>
|
||||||
|
<rect x="22" y="22" width="2" height="2"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Central vertical line -->
|
||||||
|
<g fill="#7c3aed">
|
||||||
|
<rect x="14" y="4" width="2" height="2"/>
|
||||||
|
<rect x="14" y="24" width="2" height="2"/>
|
||||||
|
<rect x="14" y="26" width="2" height="2"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Corner accents -->
|
||||||
|
<g fill="#22c55e" opacity="0.6">
|
||||||
|
<rect x="2" y="2" width="1" height="1"/>
|
||||||
|
<rect x="29" y="2" width="1" height="1"/>
|
||||||
|
<rect x="2" y="29" width="1" height="1"/>
|
||||||
|
<rect x="29" y="29" width="1" height="1"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
+198
@@ -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,95 @@
|
|||||||
|
const { Connection, PublicKey, Keypair } = require('@solana/web3.js');
|
||||||
|
const { Program, AnchorProvider, web3, utils } = require('@coral-xyz/anchor');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Load configuration
|
||||||
|
const config = JSON.parse(fs.readFileSync('./devnet-config.json', 'utf8'));
|
||||||
|
const idl = JSON.parse(fs.readFileSync('./target/idl/lottery_simple.json', 'utf8'));
|
||||||
|
|
||||||
|
async function initializeProgram() {
|
||||||
|
console.log('🚀 Initializing PoWH Program...');
|
||||||
|
console.log('===============================');
|
||||||
|
|
||||||
|
// Set up connection and provider
|
||||||
|
const connection = new Connection(config.rpcUrl, 'confirmed');
|
||||||
|
|
||||||
|
// Load wallet
|
||||||
|
const walletKeypair = Keypair.fromSecretKey(
|
||||||
|
Uint8Array.from(JSON.parse(fs.readFileSync(process.env.HOME + '/.config/solana/id.json', 'utf8')))
|
||||||
|
);
|
||||||
|
|
||||||
|
const wallet = {
|
||||||
|
publicKey: walletKeypair.publicKey,
|
||||||
|
signTransaction: async (tx) => {
|
||||||
|
tx.sign(walletKeypair);
|
||||||
|
return tx;
|
||||||
|
},
|
||||||
|
signAllTransactions: async (txs) => {
|
||||||
|
return txs.map(tx => {
|
||||||
|
tx.sign(walletKeypair);
|
||||||
|
return tx;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const provider = new AnchorProvider(connection, wallet, { commitment: 'confirmed' });
|
||||||
|
const program = new Program(idl, new PublicKey(config.programId), provider);
|
||||||
|
|
||||||
|
console.log('📋 Program ID:', config.programId);
|
||||||
|
console.log('💰 Authority:', walletKeypair.publicKey.toString());
|
||||||
|
console.log('🪙 Mint:', config.mintAddress);
|
||||||
|
console.log('🌐 Cluster:', config.cluster);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Derive PDAs
|
||||||
|
const [statePda] = PublicKey.findProgramAddressSync(
|
||||||
|
[Buffer.from("state")],
|
||||||
|
program.programId
|
||||||
|
);
|
||||||
|
|
||||||
|
const [vaultPda] = PublicKey.findProgramAddressSync(
|
||||||
|
[Buffer.from("vault")],
|
||||||
|
program.programId
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('🏛️ State PDA:', statePda.toString());
|
||||||
|
console.log('🏦 Vault PDA:', vaultPda.toString());
|
||||||
|
|
||||||
|
// Initialize the program
|
||||||
|
console.log('⏳ Sending initialize transaction...');
|
||||||
|
|
||||||
|
const tx = await program.methods
|
||||||
|
.initialize()
|
||||||
|
.accounts({
|
||||||
|
state: statePda,
|
||||||
|
vault: vaultPda,
|
||||||
|
mint: new PublicKey(config.mintAddress),
|
||||||
|
authority: walletKeypair.publicKey,
|
||||||
|
systemProgram: web3.SystemProgram.programId,
|
||||||
|
tokenProgram: utils.token.TOKEN_PROGRAM_ID,
|
||||||
|
})
|
||||||
|
.rpc();
|
||||||
|
|
||||||
|
console.log('✅ Program initialized successfully!');
|
||||||
|
console.log('📝 Transaction:', tx);
|
||||||
|
|
||||||
|
// Update config with PDAs
|
||||||
|
const updatedConfig = {
|
||||||
|
...config,
|
||||||
|
statePda: statePda.toString(),
|
||||||
|
vaultPda: vaultPda.toString(),
|
||||||
|
initialized: true,
|
||||||
|
initTx: tx
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync('./devnet-config.json', JSON.stringify(updatedConfig, null, 2));
|
||||||
|
console.log('💾 Configuration updated with PDA addresses');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Initialization failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the initialization
|
||||||
|
initializeProgram().catch(console.error);
|
||||||
@@ -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]
|
||||||
+38
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+755
@@ -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,
|
||||||
|
}
|
||||||
Executable
+98
@@ -0,0 +1,98 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "🔥 Sending All Tokens to Burn Address"
|
||||||
|
echo "====================================="
|
||||||
|
echo "🎯 Burn Address: 1nc1nerator11111111111111111111111111111111"
|
||||||
|
echo "📍 Cluster: devnet (safe for testing)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Confirm before proceeding
|
||||||
|
read -p "Send ALL tokens to burn address? (type 'yes' to confirm): " confirm
|
||||||
|
if [ "$confirm" != "yes" ]; then
|
||||||
|
echo "❌ Aborted"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🔥 Starting burn process..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Set to devnet
|
||||||
|
solana config set --url devnet > /dev/null
|
||||||
|
|
||||||
|
BURN_ADDRESS="1nc1nerator11111111111111111111111111111111"
|
||||||
|
|
||||||
|
echo "💰 Wallet: $(solana address)"
|
||||||
|
echo "🔥 Burn Address: $BURN_ADDRESS"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get all token accounts
|
||||||
|
echo "📊 Current token accounts:"
|
||||||
|
spl-token accounts --url devnet
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# List of token mints to burn
|
||||||
|
MINTS=(
|
||||||
|
"4auc435LJ9AfprLeMziHTJbP8wQmbSi7o4ZpeXdR66jQ"
|
||||||
|
"5xEQF57sR2L3tJBfdiiqaVtepTMa7nQR9uMybMojEY8n"
|
||||||
|
"8cJ8APx5TwwmpeRk4Zhz1Qbyi1T3HHdHUEw4GUJqxAGN"
|
||||||
|
"AK9fXWyDNWurYjZSDhbqBo6fczqLmCLbCZ3vq9Vftsqb"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Function to send tokens to burn address
|
||||||
|
burn_tokens() {
|
||||||
|
local mint=$1
|
||||||
|
echo "🔥 Processing mint: $mint"
|
||||||
|
|
||||||
|
# Get balance
|
||||||
|
local balance=$(spl-token balance $mint --url devnet 2>/dev/null || echo "0")
|
||||||
|
|
||||||
|
if [ "$balance" == "0" ] || [ -z "$balance" ]; then
|
||||||
|
echo " ℹ️ No balance - skipping"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " 💰 Current balance: $balance"
|
||||||
|
|
||||||
|
# Create associated token account for burn address if needed
|
||||||
|
echo " 🏭 Creating burn address token account..."
|
||||||
|
spl-token create-account $mint --owner $BURN_ADDRESS --url devnet 2>/dev/null || true
|
||||||
|
|
||||||
|
# Transfer all tokens to burn address
|
||||||
|
echo " 🔥 Sending $balance tokens to burn address..."
|
||||||
|
local result=$(spl-token transfer $mint $balance $BURN_ADDRESS --url devnet --allow-unfunded-recipient 2>&1)
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " ✅ Successfully sent $balance tokens to burn address"
|
||||||
|
|
||||||
|
# Close the now-empty account to reclaim rent
|
||||||
|
echo " 🗑️ Closing empty token account..."
|
||||||
|
spl-token close $mint --url devnet 2>/dev/null
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo " ✅ Token account closed, rent reclaimed"
|
||||||
|
else
|
||||||
|
echo " ⚠️ Could not close account"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " ❌ Transfer failed: $result"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process each mint
|
||||||
|
for mint in "${MINTS[@]}"; do
|
||||||
|
burn_tokens $mint
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "🎯 Burn process complete!"
|
||||||
|
echo ""
|
||||||
|
echo "📊 Final token account status:"
|
||||||
|
spl-token accounts --url devnet
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "💰 Final SOL balance:"
|
||||||
|
solana balance --url devnet
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🔥 All tokens have been sent to the incinerator!"
|
||||||
|
echo "✅ Ready for clean testing!"
|
||||||
+119
@@ -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;}
|
||||||
|
}
|
||||||
Executable
+57
@@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Simple script to switch from devnet to mainnet
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🔄 Switching to Mainnet Configuration..."
|
||||||
|
echo "⚠️ WARNING: You'll need to deploy the program to mainnet first!"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Get current config
|
||||||
|
if [[ ! -f "config.json" ]]; then
|
||||||
|
echo "❌ config.json not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show current config
|
||||||
|
echo "📋 Current config:"
|
||||||
|
cat config.json
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Get user inputs
|
||||||
|
read -p "🔑 Enter MAINNET program ID: " PROGRAM_ID
|
||||||
|
read -p "🪙 Enter MAINNET mint address: " MINT_ADDRESS
|
||||||
|
read -p "💰 Enter MAINNET dev wallet (or press enter for current): " DEV_WALLET
|
||||||
|
|
||||||
|
# Use current dev wallet if none provided
|
||||||
|
if [[ -z "$DEV_WALLET" ]]; then
|
||||||
|
DEV_WALLET=$(cat config.json | grep devWallet | cut -d'"' -f4)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create mainnet config
|
||||||
|
cat > config-mainnet.json << EOF
|
||||||
|
{
|
||||||
|
"cluster": "https://api.mainnet-beta.solana.com",
|
||||||
|
"programId": "$PROGRAM_ID",
|
||||||
|
"mint": "$MINT_ADDRESS",
|
||||||
|
"devWallet": "$DEV_WALLET"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Backup current config
|
||||||
|
cp config.json config-devnet-backup.json
|
||||||
|
|
||||||
|
# Switch to mainnet
|
||||||
|
cp config-mainnet.json config.json
|
||||||
|
|
||||||
|
echo "✅ Switched to mainnet!"
|
||||||
|
echo "📁 Devnet config backed up to: config-devnet-backup.json"
|
||||||
|
echo "📁 Mainnet config saved to: config-mainnet.json"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "📝 To deploy frontend with mainnet config:"
|
||||||
|
echo " ./deploy.sh"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "📝 To switch back to devnet:"
|
||||||
|
echo " cp config-devnet-backup.json config.json"
|
||||||
Executable
+86
@@ -0,0 +1,86 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "🧪 Lottery Devnet Testing Script"
|
||||||
|
echo "=============================="
|
||||||
|
|
||||||
|
# Set to devnet
|
||||||
|
solana config set --url devnet
|
||||||
|
echo "✅ Set Solana config to devnet"
|
||||||
|
|
||||||
|
# Check wallet balance
|
||||||
|
echo "💰 Checking wallet balance..."
|
||||||
|
BALANCE=$(solana balance)
|
||||||
|
echo "Current balance: $BALANCE"
|
||||||
|
|
||||||
|
# Ensure we have enough SOL (at least 5 SOL for testing)
|
||||||
|
MIN_BALANCE="5.0"
|
||||||
|
BALANCE_NUM=$(echo $BALANCE | cut -d' ' -f1)
|
||||||
|
|
||||||
|
if (( $(echo "$BALANCE_NUM < $MIN_BALANCE" | bc -l) )); then
|
||||||
|
echo "❌ Insufficient balance for testing. Need at least 5 SOL."
|
||||||
|
echo "💸 Requesting airdrop..."
|
||||||
|
solana airdrop 5
|
||||||
|
echo "⏳ Waiting 10 seconds for airdrop confirmation..."
|
||||||
|
sleep 10
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build the program
|
||||||
|
echo "🔨 Building Anchor program..."
|
||||||
|
anchor build
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ Build failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Deploy the program
|
||||||
|
echo "🚀 Deploying to devnet..."
|
||||||
|
anchor deploy --provider.cluster devnet
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "❌ Deploy failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate the IDL
|
||||||
|
echo "📝 Generating IDL..."
|
||||||
|
anchor idl init -f target/idl/lottery_simple.json $(solana-keygen pubkey target/deploy/lottery_simple-keypair.json)
|
||||||
|
|
||||||
|
echo "✅ Program deployed successfully!"
|
||||||
|
|
||||||
|
# Show deployment info
|
||||||
|
PROGRAM_ID=$(solana-keygen pubkey target/deploy/lottery_simple-keypair.json)
|
||||||
|
echo "📍 Program ID: $PROGRAM_ID"
|
||||||
|
|
||||||
|
# Create a simple test to verify the deployment
|
||||||
|
echo "🧪 Creating simple test..."
|
||||||
|
|
||||||
|
# Generate a new keypair for the mint
|
||||||
|
echo "🔑 Creating mint keypair..."
|
||||||
|
solana-keygen new --outfile mint-keypair.json --no-bip39-passphrase
|
||||||
|
|
||||||
|
# Create the mint
|
||||||
|
echo "🪙 Creating token mint..."
|
||||||
|
MINT_ADDRESS=$(solana-keygen pubkey mint-keypair.json)
|
||||||
|
spl-token create-token mint-keypair.json
|
||||||
|
echo "✅ Token mint created: $MINT_ADDRESS"
|
||||||
|
|
||||||
|
# Generate devnet config
|
||||||
|
echo "📄 Generating devnet configuration..."
|
||||||
|
cat > devnet-config.json << EOF
|
||||||
|
{
|
||||||
|
"programId": "$PROGRAM_ID",
|
||||||
|
"mintAddress": "$MINT_ADDRESS",
|
||||||
|
"devWallet": "$(solana address)",
|
||||||
|
"cluster": "devnet",
|
||||||
|
"rpcUrl": "https://api.devnet.solana.com"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✅ Devnet configuration saved to devnet-config.json"
|
||||||
|
echo ""
|
||||||
|
echo "🎉 Deployment complete!"
|
||||||
|
echo "📋 Next steps:"
|
||||||
|
echo " 1. Update your frontend to use devnet-config.json"
|
||||||
|
echo " 2. Initialize the program state using the frontend or CLI"
|
||||||
|
echo " 3. Test buy/sell functionality"
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ Remember: This is devnet SOL - not real money!"
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user