|
|
const axios = require("axios"); |
|
|
const crypto = require("crypto"); |
|
|
|
|
|
const Asset = require("../models/Asset"); |
|
|
const User = require("../models/User"); |
|
|
const Transaction = require("../models/Transaction"); |
|
|
const StrikeHistory = require("../models/StrikeHistory"); |
|
|
const { sendGlobal, sendToUser } = require('../utils/sendPush'); |
|
|
|
|
|
const WIN_PCT = { |
|
|
Silver: [0.30, 0.15, 0.08], |
|
|
Gold: [0.40, 0.20, 0.10], |
|
|
Platinum: [0.50, 0.25, 0.125] |
|
|
}; |
|
|
|
|
|
const circularDiff = (a, b, mod) => { |
|
|
const diff = Math.abs(a - b); |
|
|
return Math.min(diff, mod - diff); |
|
|
}; |
|
|
|
|
|
const secureShuffle = (arr) => { |
|
|
for (let i = arr.length - 1; i > 0; i--) { |
|
|
const j = crypto.randomInt(0, i + 1); |
|
|
[arr[i], arr[j]] = [arr[j], arr[i]]; |
|
|
} |
|
|
}; |
|
|
|
|
|
const sha256 = (payload) => |
|
|
crypto.createHash("sha256").update(JSON.stringify(payload)).digest("hex"); |
|
|
|
|
|
async function runStrike({ triggeredBy = "CRON" } = {}) { |
|
|
if (global.isStrikeRunning) return; |
|
|
|
|
|
global.isStrikeRunning = true; |
|
|
global.lastStrikeStatus = "RUNNING"; |
|
|
global.lastStrikeResult = null; |
|
|
|
|
|
try { |
|
|
|
|
|
const startOfDay = new Date(); |
|
|
startOfDay.setUTCHours(0, 0, 0, 0); |
|
|
|
|
|
const already = await StrikeHistory.findOne({ |
|
|
date: { $gte: startOfDay } |
|
|
}); |
|
|
|
|
|
if (already) { |
|
|
global.lastStrikeStatus = "COMPLETED"; |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
await sendGlobal( |
|
|
"β‘ Strike Started!", |
|
|
triggeredBy === "ADMIN" |
|
|
? "Admin manually triggered today's strike." |
|
|
: "The 8 PM Strike is LIVE.", |
|
|
"/" |
|
|
); |
|
|
|
|
|
|
|
|
const { data } = await axios.get( |
|
|
"https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT" |
|
|
); |
|
|
|
|
|
const price = parseFloat(data.price).toFixed(2); |
|
|
|
|
|
const btcInt = price.split(".")[0]; |
|
|
const btcLast3 = parseInt(btcInt.slice(-3)); |
|
|
const btcLast6 = parseInt(btcInt.slice(-6)); |
|
|
|
|
|
global.liveStrikeData = { |
|
|
price, |
|
|
btcLast3, |
|
|
btcLast6 |
|
|
}; |
|
|
|
|
|
|
|
|
const txs = await Transaction.find({ |
|
|
timestamp: { $gte: startOfDay }, |
|
|
status: "Verified" |
|
|
}); |
|
|
|
|
|
const totalPool = txs.reduce((s, t) => s + t.amount, 0); |
|
|
|
|
|
if (!totalPool) { |
|
|
global.lastStrikeStatus = "CANCELLED"; |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const assets = await Asset.find({ |
|
|
purchase_date: { $gte: startOfDay }, |
|
|
status: "Held" |
|
|
}).populate("owner_id"); |
|
|
|
|
|
|
|
|
if (assets.length <= 3) { |
|
|
const prices = { Silver: 500, Gold: 1000, Platinum: 2500 }; |
|
|
|
|
|
for (const a of assets) { |
|
|
await User.findByIdAndUpdate(a.owner_id._id, { |
|
|
$inc: { wallet_balance: prices[a.tier] } |
|
|
}); |
|
|
a.status = "Burned"; |
|
|
await a.save(); |
|
|
} |
|
|
|
|
|
global.lastStrikeStatus = "CANCELLED"; |
|
|
global.lastStrikeResult = { cancelled: true }; |
|
|
|
|
|
await sendGlobal( |
|
|
"π« Strike Cancelled", |
|
|
"Low participation. Refunds issued.", |
|
|
"/profile" |
|
|
); |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
let ranked = assets.map(asset => { |
|
|
const kusLast6 = parseInt(asset.kus_id.slice(-6)); |
|
|
const kusLast3 = kusLast6 % 1000; |
|
|
|
|
|
return { |
|
|
asset, |
|
|
diff3: circularDiff(kusLast3, btcLast3, 1000), |
|
|
diff6: circularDiff(kusLast6, btcLast6, 1_000_000) |
|
|
}; |
|
|
}); |
|
|
|
|
|
ranked.sort((a, b) => { |
|
|
if (a.diff3 !== b.diff3) return a.diff3 - b.diff3; |
|
|
if (a.diff6 !== b.diff6) return a.diff6 - b.diff6; |
|
|
return 0; |
|
|
}); |
|
|
|
|
|
secureShuffle(ranked.slice(0, 3)); |
|
|
|
|
|
|
|
|
const historyWinners = []; |
|
|
|
|
|
for (let i = 0; i < 3; i++) { |
|
|
const { asset } = ranked[i]; |
|
|
const pct = WIN_PCT[asset.tier][i]; |
|
|
if (!pct) continue; |
|
|
|
|
|
const gross = totalPool * pct; |
|
|
const net = gross * (1 - 0.312); |
|
|
|
|
|
await User.findByIdAndUpdate(asset.owner_id._id, { |
|
|
$inc: { wallet_balance: net } |
|
|
}); |
|
|
|
|
|
await Transaction.create({ |
|
|
user_id: asset.owner_id._id, |
|
|
amount: net, |
|
|
status: "Verified", |
|
|
tier: asset.tier, |
|
|
utr: `STRIKE-${Date.now()}` |
|
|
}); |
|
|
|
|
|
historyWinners.push({ |
|
|
tier: asset.tier, |
|
|
user_id: asset.owner_id._id, |
|
|
amount: net, |
|
|
rank: i + 1, |
|
|
kus_id: asset.kus_id |
|
|
}); |
|
|
|
|
|
await sendToUser( |
|
|
asset.owner_id._id, |
|
|
"π YOU WON!", |
|
|
`Rank #${i + 1} in today's strike.`, |
|
|
"/profile" |
|
|
); |
|
|
} |
|
|
|
|
|
|
|
|
const auditPayload = { |
|
|
price, |
|
|
btcLast3, |
|
|
btcLast6, |
|
|
pool: totalPool, |
|
|
participants: assets.map(a => a.kus_id), |
|
|
winners: historyWinners, |
|
|
triggeredBy |
|
|
}; |
|
|
|
|
|
const auditHash = sha256(auditPayload); |
|
|
|
|
|
await StrikeHistory.create({ |
|
|
target_price: price, |
|
|
winning_digits: btcLast3, |
|
|
total_pool: totalPool, |
|
|
audit_hash: auditHash, |
|
|
audit_payload: auditPayload, |
|
|
winners: historyWinners |
|
|
}); |
|
|
|
|
|
|
|
|
global.lastStrikeStatus = "COMPLETED"; |
|
|
global.lastStrikeResult = { |
|
|
cancelled: false, |
|
|
winningDigits: btcLast3, |
|
|
winners: historyWinners |
|
|
}; |
|
|
|
|
|
await sendGlobal( |
|
|
"π Results Announced", |
|
|
"Strike completed successfully.", |
|
|
"/" |
|
|
); |
|
|
|
|
|
} catch (err) { |
|
|
console.error("β Strike failed:", err); |
|
|
global.lastStrikeStatus = "FAILED"; |
|
|
} finally { |
|
|
global.isStrikeRunning = false; |
|
|
global.liveStrikeData = null; |
|
|
console.log("β‘ STRIKE ENDED β‘"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
module.exports = { runStrike }; |
|
|
|