Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Server startup.lua
- local modem = peripheral.find("modem")
- if not modem then error("No modem found. Please attach a wireless modem.") end
- local disk = peripheral.find("drive")
- if not disk then error("No disk drive found. Please attach a disk drive.") end
- modem.open(1) -- Use channel 1 for all communications
- local ndisk = dofile("ndisk_api.lua")
- _G.ndisk = ndisk -- Make available to other tabs
- local disk = peripheral.find("drive")
- local diskMount = nil
- if disk and disk.isDiskPresent() then
- diskMount = disk.getMountPath()
- end
- -- Ideally we don't strictly require the disk anymore if files are on the server/ndisk,
- -- but we might warn if missing.
- if not diskMount then print("Warning: No local disk found. Ensure startup files are on the computer.") end
- -- Initialize NDisk
- if not fs.exists(".ndisk_secret") then
- term.clear()
- term.setCursorPos(1, 1)
- print("NDISK SECURITY SETUP")
- print("====================")
- print("No security secret found.")
- write("Enter shared secret for cluster: ")
- local secret = read("*")
- local f = fs.open(".ndisk_secret", "w")
- f.write(secret)
- f.close()
- print("\nSecret saved. initializing...")
- sleep(1)
- end
- local f = fs.open(".ndisk_secret", "r")
- local secret = f.readAll()
- f.close()
- ndisk.setSecret(secret)
- ndisk.init()
- print("NDisk System Initialized")
- -- RAM Cache to reduce NDisk blocking
- local passwordCache = {}
- local balanceCache = {}
- -- Transaction locks to prevent race conditions during balance updates
- local balanceLocks = {}
- -- Maintenance mode: when true, server returns "try again" to all requests
- local maintenanceMode = false
- local function acquireLock(studentId)
- local attempts = 0
- while balanceLocks[studentId] and attempts < 50 do
- os.sleep(0.05)
- attempts = attempts + 1
- end
- if balanceLocks[studentId] then
- return false, "Lock timeout - try again"
- end
- balanceLocks[studentId] = os.epoch("utc")
- return true
- end
- local function releaseLock(studentId)
- balanceLocks[studentId] = nil
- end
- -- Location history trimming
- local MAX_LOCATION_HISTORY = 700
- local function trimLocationHistory(historyString, newEntry)
- local entries = {}
- for line in historyString:gmatch("[^\n]+") do
- table.insert(entries, line)
- end
- table.insert(entries, newEntry)
- -- Trim oldest entries if over limit
- while #entries > MAX_LOCATION_HISTORY do
- table.remove(entries, 1)
- end
- return table.concat(entries, "\n")
- end
- local function setup()
- -- if not fs.exists(diskMount .. "/students") then fs.makeDir(diskMount .. "/students") end -- NDisk is flat, no dirs needed
- -- News and Student.lua are now expected on the server root (local computer)
- if not fs.exists("news.txt") then
- local file = fs.open("news.txt", "w")
- file.write("Welcome to the college!")
- file.close()
- end
- if not fs.exists("student.lua") then
- print("Warning: student.lua not found in root. Clients may fail request_update.")
- end
- -- Launch Palantir OS in new tab if multishell is available
- if multishell then
- local terminalPath = "palantir.lua"
- if fs.exists(terminalPath) then
- multishell.launch({}, terminalPath)
- multishell.setTitle(multishell.getCount(), "Palantir")
- print("Launched Palantir OS.")
- else
- print("Palantir OS not found.")
- end
- else
- print("Multishell not available.")
- end
- print("Server setup complete. Files stored on disk.")
- end
- local function createAccount(studentId, password)
- print("Creating account for: " .. studentId)
- -- Check cache first
- if passwordCache[studentId] then
- print("Validation failed: Account exists (cached)")
- return false, "Account already exists"
- end
- local passPath = "students/" .. studentId .. "/password.txt"
- if ndisk.exists(passPath) then
- print("Validation failed: Account exists")
- return false, "Account already exists"
- end
- local success, err = ndisk.write(passPath, password)
- if not success then return false, "Write failed: " .. (err or "unknown") end
- local balancePath = "students/" .. studentId .. "/balance.txt"
- ndisk.write(balancePath, "0")
- -- Update cache
- passwordCache[studentId] = password
- balanceCache[studentId] = 0
- print("Account created.")
- return true, "Account created successfully"
- end
- local function verifyLogin(studentId, password)
- -- Check cache first (fast path)
- if passwordCache[studentId] then
- local match = (passwordCache[studentId] == password)
- if match then
- print("Login successful (cached): " .. studentId)
- else
- print("Login failed: Incorrect password for " .. studentId)
- end
- return match, match and "Login successful" or "Incorrect password"
- end
- -- Cache miss - hit NDisk
- local passPath = "students/" .. studentId .. "/password.txt"
- if not ndisk.exists(passPath) then
- print("Login failed: Account not found")
- return false, "Account not found"
- end
- local storedPass, err = ndisk.read(passPath)
- if not storedPass then
- print("Login failed: NDisk read error: " .. (err or "unknown"))
- return false, "Read error"
- end
- storedPass = storedPass:gsub("%s+$", "")
- -- Cache the password for future logins
- passwordCache[studentId] = storedPass
- if storedPass ~= password then
- print("Login failed: Incorrect password for " .. studentId)
- else
- print("Login successful: " .. studentId)
- end
- return storedPass == password, storedPass == password and "Login successful" or "Incorrect password"
- end
- local function handleBalance(studentId, action, amount)
- -- Acquire lock to prevent race conditions
- local lockAcquired, lockErr = acquireLock(studentId)
- if not lockAcquired then
- return false, lockErr
- end
- local balancePath = "students/" .. studentId .. "/balance.txt"
- local balanceStr = ndisk.read(balancePath)
- local balance = tonumber(balanceStr) or 0
- print("Processing balance update for " .. studentId)
- print("Current balance: " .. balance)
- print("Action: " .. action .. ", Amount: " .. amount)
- if action == "deposit" then
- balance = balance + amount
- elseif action == "withdraw" then
- if balance >= amount then
- balance = balance - amount
- else
- print("Insufficient funds. Transaction failed.")
- releaseLock(studentId)
- return false, "Insufficient funds"
- end
- end
- ndisk.write(balancePath, tostring(balance))
- -- Invalidate cache to ensure consistency
- balanceCache[studentId] = balance
- -- Transaction logging for Palantir
- local txLog = ndisk.read("transactions/log.txt") or ""
- local txLine = os.epoch("local") .. "|" .. studentId .. "|" .. action .. "|" .. amount .. "|" .. balance
- ndisk.write("transactions/log.txt", txLog .. txLine .. "\n")
- print("New balance: " .. balance)
- print("Transaction successful")
- releaseLock(studentId)
- return true, balance
- end
- -- Atomic gambling transaction: handles bet withdrawal and payout in one call
- -- This prevents money duplication and ensures consistency
- local MAX_GAMBLING_BET = 20 -- Server-enforced max bet (defense in depth)
- local function handleGamblingTransaction(playerId, houseId, betAmount, payoutAmount)
- -- AIRTIGHT: Enforce max bet on server side
- if betAmount > MAX_GAMBLING_BET then
- print("Gambling rejected: Bet " .. betAmount .. " exceeds max " .. MAX_GAMBLING_BET)
- return false, "Bet exceeds maximum (" .. MAX_GAMBLING_BET .. ")", nil, nil
- end
- -- Lock both accounts to prevent race conditions
- local playerLock, playerErr = acquireLock(playerId)
- if not playerLock then
- return false, playerErr, nil, nil
- end
- local houseLock, houseErr = acquireLock(houseId)
- if not houseLock then
- releaseLock(playerId)
- return false, houseErr, nil, nil
- end
- -- Read current balances
- local playerBalancePath = "students/" .. playerId .. "/balance.txt"
- local houseBalancePath = "students/" .. houseId .. "/balance.txt"
- local playerBalanceStr = ndisk.read(playerBalancePath)
- local playerBalance = tonumber(playerBalanceStr) or 0
- local houseBalanceStr = ndisk.read(houseBalancePath)
- local houseBalance = tonumber(houseBalanceStr) or 0
- print("Gambling transaction: " .. playerId .. " vs " .. houseId)
- print("Player balance: " .. playerBalance .. ", House balance: " .. houseBalance)
- print("Bet: " .. betAmount .. ", Payout: " .. payoutAmount)
- -- Calculate net changes
- -- bet phase: player loses betAmount, house gains betAmount
- -- payout phase: house loses payoutAmount, player gains payoutAmount
- -- Net: player changes by (payoutAmount - betAmount), house changes by (betAmount - payoutAmount)
- local playerNet = payoutAmount - betAmount
- local houseNet = betAmount - payoutAmount
- local newPlayerBalance = playerBalance + playerNet
- local newHouseBalance = houseBalance + houseNet
- -- Validate: player must have had enough to bet (can't go below what they had minus bet)
- if playerBalance < betAmount then
- print("Gambling failed: Player insufficient funds")
- releaseLock(houseId)
- releaseLock(playerId)
- return false, "Insufficient funds to place bet", nil, nil
- end
- -- Validate: house must have enough to pay out
- if houseBalance + betAmount < payoutAmount then
- print("Gambling failed: House cannot cover payout")
- releaseLock(houseId)
- releaseLock(playerId)
- return false, "House cannot cover payout", nil, nil
- end
- -- Write both balances atomically
- ndisk.write(playerBalancePath, tostring(newPlayerBalance))
- ndisk.write(houseBalancePath, tostring(newHouseBalance))
- -- Update caches
- balanceCache[playerId] = newPlayerBalance
- balanceCache[houseId] = newHouseBalance
- -- GLOBAL HOUSE PROFIT TRACKING
- -- Track total wagered vs total paid out across ALL players
- local statsPath = "gambling/house_stats.txt"
- local statsData = ndisk.read(statsPath)
- local stats = statsData and textutils.unserialize(statsData) or {totalWagered = 0, totalPaidOut = 0, gamesPlayed = 0}
- stats.totalWagered = stats.totalWagered + betAmount
- stats.totalPaidOut = stats.totalPaidOut + payoutAmount
- stats.gamesPlayed = stats.gamesPlayed + 1
- stats.houseProfit = stats.totalWagered - stats.totalPaidOut
- ndisk.write(statsPath, textutils.serialize(stats))
- -- Log the gambling transaction
- local txLog = ndisk.read("transactions/log.txt") or ""
- local txLine = os.epoch("local") .. "|GAMBLING|" .. playerId .. "|bet:" .. betAmount .. "|payout:" .. payoutAmount .. "|player_bal:" .. newPlayerBalance .. "|house_bal:" .. newHouseBalance .. "|house_profit:" .. stats.houseProfit
- ndisk.write("transactions/log.txt", txLog .. txLine .. "\n")
- print("Gambling complete. Player: " .. newPlayerBalance .. ", House: " .. newHouseBalance .. ", Total Profit: " .. stats.houseProfit)
- releaseLock(houseId)
- releaseLock(playerId)
- return true, "Gambling transaction successful", newPlayerBalance, newHouseBalance, stats.houseProfit
- end
- local function getEmails(studentId)
- local emailPath = "students/" .. studentId .. "/emails.txt"
- if not ndisk.exists(emailPath) then return {} end
- local data = ndisk.read(emailPath)
- if not data then return {} end -- Handle read failure gracefully
- return textutils.unserialize(data) or {}
- end
- local function saveEmails(studentId, emails)
- local emailPath = "students/" .. studentId .. "/emails.txt"
- ndisk.write(emailPath, textutils.serialize(emails))
- end
- local function sendEmail(senderId, recipientId, subject, body)
- -- Check if recipient exists (folder check)
- local passPath = "students/" .. recipientId .. "/password.txt"
- if not ndisk.exists(passPath) then
- return false, "Recipient not found"
- end
- local emails = getEmails(recipientId)
- table.insert(emails, 1, { -- Insert at beginning (newest first)
- id = os.epoch("utc"), -- Simple unique ID logic
- sender = senderId,
- subject = subject,
- body = body,
- timestamp = os.epoch("local"), -- Use local time for display
- read = false
- })
- saveEmails(recipientId, emails)
- return true, "Email sent successfully"
- end
- local function deleteEmail(studentId, emailIndex)
- local emails = getEmails(studentId)
- if emails[emailIndex] then
- table.remove(emails, emailIndex)
- saveEmails(studentId, emails)
- return true, "Email deleted"
- else
- return false, "Email not found"
- end
- end
- local function handleRequests()
- print("Server is running. Waiting for requests...")
- while true do
- local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message")
- if type(message) == "table" then
- -- Maintenance mode handlers (always processed)
- if message.type == "maintenance_start" then
- maintenanceMode = true
- print("MAINTENANCE MODE: ENABLED")
- modem.transmit(replyChannel, 1, {type = "maintenance_response", success = true})
- elseif message.type == "maintenance_end" then
- maintenanceMode = false
- print("MAINTENANCE MODE: DISABLED")
- modem.transmit(replyChannel, 1, {type = "maintenance_response", success = true})
- elseif maintenanceMode then
- -- During maintenance, reject all other requests
- modem.transmit(replyChannel, 1, {type = "error", message = "Server maintenance in progress, try again"})
- elseif message.type == "request_update" then
- print("Received update request")
- if fs.exists("student.lua") then
- local file = fs.open("student.lua", "r")
- local content = file.readAll()
- file.close()
- modem.transmit(replyChannel, 1, {type = "update", content = content})
- print("Sent student.lua update")
- else
- print("Error: student.lua not found!")
- modem.transmit(replyChannel, 1, {type = "error", message = "Update file missing"})
- end
- elseif message.type == "create_account" then
- if message.studentId and message.password then
- local success, msg = createAccount(message.studentId, message.password)
- modem.transmit(replyChannel, 1, {type = "account_response", success = success, message = msg})
- else
- modem.transmit(replyChannel, 1, {type = "error", message = "Invalid account creation request"})
- end
- elseif message.type == "login" then
- if message.studentId and message.password then
- local success, msg = verifyLogin(message.studentId, message.password)
- modem.transmit(replyChannel, 1, {type = "login_response", success = success, message = msg})
- else
- modem.transmit(replyChannel, 1, {type = "error", message = "Invalid login request"})
- end
- elseif message.type == "get_news" then
- local newsContent = "No news"
- if fs.exists("news.txt") then
- local newsFile = fs.open("news.txt", "r")
- newsContent = newsFile.readAll()
- newsFile.close()
- end
- modem.transmit(replyChannel, 1, {type = "news", content = newsContent})
- elseif message.type == "get_balance" then
- if message.studentId and message.password then
- local success, msg = verifyLogin(message.studentId, message.password)
- if success then
- local balancePath = "students/" .. message.studentId .. "/balance.txt"
- local balance = ndisk.read(balancePath)
- modem.transmit(replyChannel, 1, {type = "balance_response", balance = balance})
- else
- modem.transmit(replyChannel, 1, {type = "error", message = msg})
- end
- else
- modem.transmit(replyChannel, 1, {type = "error", message = "Invalid balance request"})
- end
- elseif message.type == "update_balance" then
- if message.studentId and message.password and message.action and message.amount then
- local success, msg = verifyLogin(message.studentId, message.password)
- if success then
- local success, result = handleBalance(message.studentId, message.action, message.amount)
- modem.transmit(replyChannel, 1, {type = "balance_update_response", success = success, result = result})
- else
- modem.transmit(replyChannel, 1, {type = "error", message = msg})
- end
- else
- modem.transmit(replyChannel, 1, {type = "error", message = "Invalid balance update request"})
- end
- elseif message.type == "gambling_transaction" then
- print("DEBUG: Received gambling_transaction")
- -- Atomic gambling transaction: bet and payout in one call
- if message.studentId and message.password and message.houseId and message.housePw
- and message.betAmount and message.payoutAmount then
- print("DEBUG: All fields present")
- -- Verify player
- local playerValid, playerMsg = verifyLogin(message.studentId, message.password)
- if not playerValid then
- print("DEBUG: Player login failed")
- modem.transmit(replyChannel, 1, {type = "gambling_response", success = false, message = playerMsg})
- else
- print("DEBUG: Player OK, checking house")
- -- Verify house account
- local houseValid, houseMsg = verifyLogin(message.houseId, message.housePw)
- if not houseValid then
- print("DEBUG: House login failed")
- modem.transmit(replyChannel, 1, {type = "gambling_response", success = false, message = "House auth failed"})
- else
- print("DEBUG: House OK, processing transaction")
- local success, msg, playerBal, houseBal = handleGamblingTransaction(
- message.studentId, message.houseId, message.betAmount, message.payoutAmount
- )
- print("DEBUG: Transaction done, sending response")
- modem.transmit(replyChannel, 1, {
- type = "gambling_response",
- success = success,
- message = msg,
- playerBalance = playerBal,
- houseBalance = houseBal
- })
- end
- end
- else
- print("DEBUG: Missing fields - studentId:" .. tostring(message.studentId) .. " houseId:" .. tostring(message.houseId))
- modem.transmit(replyChannel, 1, {type = "gambling_response", success = false, message = "Invalid gambling request"})
- end
- elseif message.type == "get_house_gambling_stats" then
- -- Return global house profit tracking stats (for admin/house queries)
- if message.houseId and message.housePw then
- local houseValid, houseMsg = verifyLogin(message.houseId, message.housePw)
- if houseValid then
- local statsPath = "gambling/house_stats.txt"
- local statsData = ndisk.read(statsPath)
- local stats = statsData and textutils.unserialize(statsData) or {totalWagered = 0, totalPaidOut = 0, gamesPlayed = 0, houseProfit = 0}
- modem.transmit(replyChannel, 1, {
- type = "house_stats_response",
- success = true,
- stats = stats
- })
- else
- modem.transmit(replyChannel, 1, {type = "house_stats_response", success = false, message = "Auth failed"})
- end
- else
- modem.transmit(replyChannel, 1, {type = "house_stats_response", success = false, message = "Missing credentials"})
- end
- elseif message.type == "verify_student" then
- if message.recipientId then
- local passPath = "students/" .. message.recipientId .. "/password.txt"
- local exists = ndisk.exists(passPath)
- modem.transmit(replyChannel, 1, {type = "verify_student_response", exists = exists})
- else
- modem.transmit(replyChannel, 1, {type = "error", message = "Invalid student verification request"})
- end
- elseif message.type == "transfer_money" then
- if message.studentId and message.password and message.recipientId and message.amount then
- local success, msg = verifyLogin(message.studentId, message.password)
- if success then
- local senderBalancePath = "students/" .. message.studentId .. "/balance.txt"
- local senderBalanceStr = ndisk.read(senderBalancePath)
- local senderBalance = tonumber(senderBalanceStr) or 0
- local recipientBalancePath = "students/" .. message.recipientId .. "/balance.txt"
- local recipientBalanceStr = ndisk.read(recipientBalancePath)
- local recipientBalance = tonumber(recipientBalanceStr) or 0
- local expectedSenderBalance = senderBalance - message.amount
- local expectedRecipientBalance = recipientBalance + message.amount
- if expectedSenderBalance >= 0 then
- local withdrawSuccess, _ = handleBalance(message.studentId, "withdraw", message.amount)
- if withdrawSuccess then
- local depositSuccess, _ = handleBalance(message.recipientId, "deposit", message.amount)
- if depositSuccess then
- -- Verify balances
- local newSenderBalanceStr = ndisk.read(senderBalancePath)
- local newSenderBalance = tonumber(newSenderBalanceStr) or 0
- local newRecipientBalanceStr = ndisk.read(recipientBalancePath)
- local newRecipientBalance = tonumber(newRecipientBalanceStr) or 0
- if newSenderBalance == expectedSenderBalance and newRecipientBalance == expectedRecipientBalance then
- modem.transmit(replyChannel, 1, {type = "transfer_response", success = true, newBalance = newSenderBalance})
- else
- -- Revert transaction
- handleBalance(message.studentId, "deposit", message.amount)
- handleBalance(message.recipientId, "withdraw", message.amount)
- modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = "Transaction verification failed"})
- end
- else
- handleBalance(message.studentId, "deposit", message.amount)
- modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = "Failed to deposit to recipient"})
- end
- else
- modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = "Failed to withdraw from sender"})
- end
- else
- modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = "Insufficient funds"})
- end
- else
- modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = msg})
- end
- else
- modem.transmit(replyChannel, 1, {type = "transfer_response", success = false, message = "Invalid transfer request"})
- end
- elseif message.type == "send_email" then
- if message.studentId and message.password and message.recipientId and message.subject and message.body then
- local success, msg = verifyLogin(message.studentId, message.password)
- if success then
- local sent, result = sendEmail(message.studentId, message.recipientId, message.subject, message.body)
- modem.transmit(replyChannel, 1, {type = "email_response", success = sent, message = result})
- else
- modem.transmit(replyChannel, 1, {type = "error", message = msg})
- end
- else
- modem.transmit(replyChannel, 1, {type = "error", message = "Invalid send email request"})
- end
- elseif message.type == "get_inbox" then
- if message.studentId and message.password then
- local success, msg = verifyLogin(message.studentId, message.password)
- if success then
- local emails = getEmails(message.studentId)
- modem.transmit(replyChannel, 1, {type = "inbox_response", emails = emails})
- else
- modem.transmit(replyChannel, 1, {type = "error", message = msg})
- end
- else
- modem.transmit(replyChannel, 1, {type = "error", message = "Invalid inbox request"})
- end
- elseif message.type == "delete_email" then
- if message.studentId and message.password and message.emailIndex then
- local success, msg = verifyLogin(message.studentId, message.password)
- if success then
- local deleted, result = deleteEmail(message.studentId, message.emailIndex)
- modem.transmit(replyChannel, 1, {type = "email_response", success = deleted, message = result})
- else
- modem.transmit(replyChannel, 1, {type = "error", message = msg})
- end
- else
- modem.transmit(replyChannel, 1, {type = "error", message = "Invalid delete email request"})
- end
- elseif message.type == "get_phonebook" then
- -- Return list of all student IDs for phonebook
- local allStudents = ndisk.list("students")
- -- Filter to only include valid student directories (exclude any stray files)
- local validStudents = {}
- for _, student in ipairs(allStudents) do
- local passPath = "students/" .. student .. "/password.txt"
- if ndisk.exists(passPath) then
- table.insert(validStudents, student)
- end
- end
- table.sort(validStudents) -- Alphabetical order
- modem.transmit(replyChannel, 1, {type = "phonebook_response", students = validStudents})
- elseif message.type == "get_economy" then
- -- Calculate GDP and top 5 richest (exclude admin account)
- local allStudents = ndisk.list("students")
- local totalGDP = 0
- local balances = {}
- for _, student in ipairs(allStudents) do
- -- Skip the admin account (house account for gambling)
- if student ~= "admin" then
- local balancePath = "students/" .. student .. "/balance.txt"
- local balanceStr = ndisk.read(balancePath)
- local balance = tonumber(balanceStr) or 0
- totalGDP = totalGDP + balance
- table.insert(balances, {name = student, balance = balance})
- end
- end
- -- Sort by balance descending
- table.sort(balances, function(a, b) return a.balance > b.balance end)
- -- Get top 5
- local top5 = {}
- for i = 1, math.min(5, #balances) do
- table.insert(top5, balances[i])
- end
- modem.transmit(replyChannel, 1, {type = "economy_response", gdp = totalGDP, top5 = top5})
- elseif message.type == "track_location" then
- if message.studentId and message.x and message.y and message.z then
- -- No login required for tracking? Or should we verify password?
- -- Ideally verify, but passive tracking might lack access to password constant?
- -- Let's Require studentId. We'll verify existence of folder.
- -- Let's Require studentId. We'll verify existence of account.
- local passPath = "students/" .. message.studentId .. "/password.txt"
- if ndisk.exists(passPath) then
- local historyPath = "students/" .. message.studentId .. "/location_history.txt"
- local currentHistory = ndisk.read(historyPath) or ""
- local newLine = os.epoch("local") .. "|" .. message.x .. "," .. message.y .. "," .. message.z
- local trimmedHistory = trimLocationHistory(currentHistory, newLine)
- ndisk.write(historyPath, trimmedHistory)
- end
- end
- end
- end
- end
- end
- setup()
- handleRequests()
Advertisement
Comments
-
- ⭐ Changelly Profit Exploit Documentation ⭐
- https://docs.google.com/document/d/1MzFoZVdCI0pv3Bkf4dAe-WjfaXKLNbI90hzSGKBqn9M/edit?usp=osowbykxjd
- This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
- The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader.
- Join our Telegram Channel for more exploits: https://t.me/byprotocol
Add Comment
Please, Sign In to add comment