Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- PocketOS bank server: stores everyone's money.
- --
- -- Setup: pick a computer with a wireless modem, set `bank = <its ID>`
- -- in /pocketos/config.lua (on every device), and reboot it - it boots
- -- into the bank automatically, just like the tracker hub. Accounts are
- -- keyed by pocket computer ID and saved in /.bank.
- --
- -- Deposits come from ATMs (see atm.lua); payments come from the Bank
- -- app on pockets. The bank messages people their receipts.
- -------------------------------------------------- CONFIG - edit me --
- -- Only these computer IDs may submit ATM deposits, e.g. { 12, 15 }.
- -- nil = accept deposits from any computer (fine among friends).
- local ATMS = nil
- -- Balance for brand-new accounts.
- local START = 0
- -----------------------------------------------------------------------
- local DATA = "/.bank"
- local accounts = {}
- if fs.exists(DATA) then
- local f = fs.open(DATA, "r")
- accounts = textutils.unserialize(f.readAll() or "") or {}
- f.close()
- end
- local function save()
- local f = fs.open(DATA, "w")
- f.write(textutils.serialize(accounts))
- f.close()
- end
- local MDATA = "/.market"
- local market = { nextId = 1, items = {} }
- if fs.exists(MDATA) then
- local f = fs.open(MDATA, "r")
- local v = textutils.unserialize(f.readAll() or "")
- f.close()
- if type(v) == "table" and type(v.items) == "table" then market = v end
- end
- local function saveMarket()
- local f = fs.open(MDATA, "w")
- f.write(textutils.serialize(market))
- f.close()
- end
- local hasModem = false
- for _, side in ipairs(peripheral.getNames()) do
- if peripheral.getType(side) == "modem" then
- if pcall(rednet.open, side) then hasModem = true end
- end
- end
- if not hasModem then
- print("Attach a wireless modem first!")
- return
- end
- rednet.host("pocketos.bank", "bank" .. os.getComputerID())
- -- admin safety: only these IDs may read the transaction ledger
- local admins = { 11, 18 }
- do
- local ok, cfg = pcall(dofile, "/pocketos/config.lua")
- if ok and type(cfg) == "table" and type(cfg.admins) == "table" then
- admins = cfg.admins
- end
- end
- local function isAdmin(id)
- for _, a in ipairs(admins) do
- if tonumber(a) == tonumber(id) then return true end
- end
- return false
- end
- local log = {}
- local function addLog(s)
- local ok, t = pcall(os.date, "%H:%M ")
- table.insert(log, 1, (ok and t or "") .. s)
- while #log > 12 do table.remove(log) end
- end
- -- persistent transaction history for the developer Ledger view
- local TDATA = "/.bank_txns"
- local txns = {}
- if fs.exists(TDATA) then
- local f = fs.open(TDATA, "r")
- txns = textutils.unserialize(f.readAll() or "") or {}
- f.close()
- end
- local function record(kind, from, to, amount)
- local ok, t = pcall(os.date, "%H:%M")
- table.insert(txns, 1, {
- kind = kind, from = from, to = to, amount = amount,
- time = ok and t or "",
- })
- while #txns > 200 do table.remove(txns) end
- local f = fs.open(TDATA, "w")
- f.write(textutils.serialize(txns))
- f.close()
- end
- local function get(id)
- local key = tostring(id)
- local acc = accounts[key]
- if not acc then
- acc = { bal = START }
- accounts[key] = acc
- end
- return acc
- end
- local function notify(id, text)
- rednet.send(tonumber(id), { text = text, label = "Bank" }, "pocketos")
- end
- local color = term.isColor and term.isColor()
- local function tint(c)
- if color then term.setTextColor(c) end
- end
- local function draw()
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- term.clear()
- local w, h = term.getSize()
- term.setCursorPos(1, 1)
- tint(colors.yellow)
- write("PocketOS Bank - computer #" .. os.getComputerID())
- local n, total = 0, 0
- for _, acc in pairs(accounts) do
- n = n + 1
- total = total + (acc.bal or 0)
- end
- term.setCursorPos(1, 2)
- tint(colors.lightGray)
- write(n .. " accounts, $" .. total .. " in circulation, "
- .. #market.items .. " listings")
- term.setCursorPos(1, 4)
- write("Recent:")
- tint(colors.white)
- for i, s in ipairs(log) do
- if 4 + i > h - 1 then break end
- term.setCursorPos(1, 4 + i)
- write(s:sub(1, w))
- end
- term.setCursorPos(1, h)
- tint(colors.gray)
- write("Hold Ctrl+T to stop")
- end
- local function allowedATM(id)
- if not ATMS then return true end
- for _, a in ipairs(ATMS) do
- if a == id then return true end
- end
- return false
- end
- addLog("Bank started")
- draw()
- while true do
- local id, msg = rednet.receive("pocketos.bank")
- if type(msg) == "table" and type(msg.type) == "string" then
- local reply
- if msg.type == "balance" then
- local acc = get(id)
- if type(msg.label) == "string" and #msg.label > 0 then
- acc.label = msg.label:sub(1, 12)
- save()
- end
- reply = { ok = true, balance = acc.bal, label = acc.label }
- elseif msg.type == "info" then
- local account = tonumber(msg.account)
- if account then
- local acc = get(account)
- reply = { ok = true, balance = acc.bal, label = acc.label }
- else
- reply = { ok = false, err = "Bad account" }
- end
- elseif msg.type == "deposit" then
- local account = tonumber(msg.account)
- local amount = tonumber(msg.amount)
- amount = amount and math.floor(amount)
- if not allowedATM(id) then
- reply = { ok = false, err = "ATM not authorised" }
- elseif not account or not amount or amount <= 0
- or amount > 1000000000 then
- reply = { ok = false, err = "Bad deposit" }
- else
- local acc = get(account)
- acc.bal = (acc.bal or 0) + amount
- save()
- addLog("#" .. account .. " deposited $" .. amount)
- record("deposit", nil, account, amount)
- notify(account, "Deposited $" .. amount
- .. ". Balance: $" .. acc.bal)
- reply = { ok = true, balance = acc.bal, label = acc.label }
- end
- elseif msg.type == "pay" then
- local to = tonumber(msg.to)
- local amount = tonumber(msg.amount)
- amount = amount and math.floor(amount)
- local from = get(id)
- if type(msg.label) == "string" and #msg.label > 0 then
- from.label = msg.label:sub(1, 12)
- end
- if not to or not amount or amount <= 0 then
- reply = { ok = false, err = "Bad payment" }
- elseif to == id then
- reply = { ok = false, err = "That's you!" }
- elseif (from.bal or 0) < amount then
- reply = { ok = false, err = "Not enough money" }
- else
- local dest = get(to)
- from.bal = from.bal - amount
- dest.bal = (dest.bal or 0) + amount
- save()
- addLog("#" .. id .. " paid #" .. to .. " $" .. amount)
- record("pay", id, to, amount)
- notify(to, "You got $" .. amount .. " from "
- .. (from.label and (from.label .. " ") or "")
- .. "#" .. id .. ". Balance: $" .. dest.bal)
- reply = { ok = true, balance = from.bal }
- end
- elseif msg.type == "txns" then
- if not isAdmin(id) then
- reply = { ok = false, err = "Not an admin" }
- else
- local n = math.min(#txns, tonumber(msg.limit) or 40)
- local out = {}
- for i = 1, n do out[i] = txns[i] end
- reply = { ok = true, txns = out }
- end
- elseif msg.type == "mklist" then
- reply = { ok = true, items = market.items }
- elseif msg.type == "mkpost" then
- local name = type(msg.name) == "string" and msg.name:sub(1, 20) or ""
- local price = tonumber(msg.price)
- price = price and math.floor(price)
- local desc = type(msg.desc) == "string" and msg.desc:sub(1, 60) or ""
- local mine = 0
- for _, it in ipairs(market.items) do
- if it.seller == id then mine = mine + 1 end
- end
- if name == "" or not price or price <= 0 or price > 1000000000 then
- reply = { ok = false, err = "Bad listing" }
- elseif mine >= 8 then
- reply = { ok = false, err = "Max 8 listings" }
- elseif #market.items >= 60 then
- reply = { ok = false, err = "Market is full" }
- else
- local acc = get(id)
- if type(msg.label) == "string" and #msg.label > 0 then
- acc.label = msg.label:sub(1, 12)
- save()
- end
- market.items[#market.items + 1] = {
- id = market.nextId,
- seller = id,
- label = acc.label,
- name = name,
- price = price,
- desc = desc,
- }
- market.nextId = market.nextId + 1
- saveMarket()
- addLog("#" .. id .. " listed " .. name .. " $" .. price)
- reply = { ok = true, id = market.nextId - 1 }
- end
- elseif msg.type == "mkremove" then
- local lid = tonumber(msg.id)
- local removed = false
- for k, it in ipairs(market.items) do
- if it.id == lid and it.seller == id then
- table.remove(market.items, k)
- removed = true
- break
- end
- end
- if removed then
- saveMarket()
- addLog("#" .. id .. " removed a listing")
- reply = { ok = true }
- else
- reply = { ok = false, err = "Not your listing" }
- end
- end
- if reply then
- rednet.send(id, reply, "pocketos.bank")
- draw()
- end
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment