ElijahCrafter

Bank Database

Jul 16th, 2026 (edited)
44
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 10.20 KB | Source Code | 0 0
  1. -- PocketOS bank server: stores everyone's money.
  2. --
  3. -- Setup: pick a computer with a wireless modem, set `bank = <its ID>`
  4. -- in /pocketos/config.lua (on every device), and reboot it - it boots
  5. -- into the bank automatically, just like the tracker hub. Accounts are
  6. -- keyed by pocket computer ID and saved in /.bank.
  7. --
  8. -- Deposits come from ATMs (see atm.lua); payments come from the Bank
  9. -- app on pockets. The bank messages people their receipts.
  10.  
  11. -------------------------------------------------- CONFIG - edit me --
  12.  
  13. -- Only these computer IDs may submit ATM deposits, e.g. { 12, 15 }.
  14. -- nil = accept deposits from any computer (fine among friends).
  15. local ATMS = nil
  16.  
  17. -- Balance for brand-new accounts.
  18. local START = 0
  19.  
  20. -----------------------------------------------------------------------
  21.  
  22. local DATA = "/.bank"
  23.  
  24. local accounts = {}
  25. if fs.exists(DATA) then
  26.     local f = fs.open(DATA, "r")
  27.     accounts = textutils.unserialize(f.readAll() or "") or {}
  28.     f.close()
  29. end
  30.  
  31. local function save()
  32.     local f = fs.open(DATA, "w")
  33.     f.write(textutils.serialize(accounts))
  34.     f.close()
  35. end
  36.  
  37. local MDATA = "/.market"
  38. local market = { nextId = 1, items = {} }
  39. if fs.exists(MDATA) then
  40.     local f = fs.open(MDATA, "r")
  41.     local v = textutils.unserialize(f.readAll() or "")
  42.     f.close()
  43.     if type(v) == "table" and type(v.items) == "table" then market = v end
  44. end
  45.  
  46. local function saveMarket()
  47.     local f = fs.open(MDATA, "w")
  48.     f.write(textutils.serialize(market))
  49.     f.close()
  50. end
  51.  
  52. local hasModem = false
  53. for _, side in ipairs(peripheral.getNames()) do
  54.     if peripheral.getType(side) == "modem" then
  55.         if pcall(rednet.open, side) then hasModem = true end
  56.     end
  57. end
  58. if not hasModem then
  59.     print("Attach a wireless modem first!")
  60.     return
  61. end
  62. rednet.host("pocketos.bank", "bank" .. os.getComputerID())
  63.  
  64. -- admin safety: only these IDs may read the transaction ledger
  65. local admins = { 11, 18 }
  66. do
  67.     local ok, cfg = pcall(dofile, "/pocketos/config.lua")
  68.     if ok and type(cfg) == "table" and type(cfg.admins) == "table" then
  69.         admins = cfg.admins
  70.     end
  71. end
  72. local function isAdmin(id)
  73.     for _, a in ipairs(admins) do
  74.         if tonumber(a) == tonumber(id) then return true end
  75.     end
  76.     return false
  77. end
  78.  
  79. local log = {}
  80. local function addLog(s)
  81.     local ok, t = pcall(os.date, "%H:%M ")
  82.     table.insert(log, 1, (ok and t or "") .. s)
  83.     while #log > 12 do table.remove(log) end
  84. end
  85.  
  86. -- persistent transaction history for the developer Ledger view
  87. local TDATA = "/.bank_txns"
  88. local txns = {}
  89. if fs.exists(TDATA) then
  90.     local f = fs.open(TDATA, "r")
  91.     txns = textutils.unserialize(f.readAll() or "") or {}
  92.     f.close()
  93. end
  94.  
  95. local function record(kind, from, to, amount)
  96.     local ok, t = pcall(os.date, "%H:%M")
  97.     table.insert(txns, 1, {
  98.         kind = kind, from = from, to = to, amount = amount,
  99.         time = ok and t or "",
  100.     })
  101.     while #txns > 200 do table.remove(txns) end
  102.     local f = fs.open(TDATA, "w")
  103.     f.write(textutils.serialize(txns))
  104.     f.close()
  105. end
  106.  
  107. local function get(id)
  108.     local key = tostring(id)
  109.     local acc = accounts[key]
  110.     if not acc then
  111.         acc = { bal = START }
  112.         accounts[key] = acc
  113.     end
  114.     return acc
  115. end
  116.  
  117. local function notify(id, text)
  118.     rednet.send(tonumber(id), { text = text, label = "Bank" }, "pocketos")
  119. end
  120.  
  121. local color = term.isColor and term.isColor()
  122. local function tint(c)
  123.     if color then term.setTextColor(c) end
  124. end
  125.  
  126. local function draw()
  127.     term.setBackgroundColor(colors.black)
  128.     term.setTextColor(colors.white)
  129.     term.clear()
  130.     local w, h = term.getSize()
  131.     term.setCursorPos(1, 1)
  132.     tint(colors.yellow)
  133.     write("PocketOS Bank - computer #" .. os.getComputerID())
  134.     local n, total = 0, 0
  135.     for _, acc in pairs(accounts) do
  136.         n = n + 1
  137.         total = total + (acc.bal or 0)
  138.     end
  139.     term.setCursorPos(1, 2)
  140.     tint(colors.lightGray)
  141.     write(n .. " accounts, $" .. total .. " in circulation, "
  142.         .. #market.items .. " listings")
  143.     term.setCursorPos(1, 4)
  144.     write("Recent:")
  145.     tint(colors.white)
  146.     for i, s in ipairs(log) do
  147.         if 4 + i > h - 1 then break end
  148.         term.setCursorPos(1, 4 + i)
  149.         write(s:sub(1, w))
  150.     end
  151.     term.setCursorPos(1, h)
  152.     tint(colors.gray)
  153.     write("Hold Ctrl+T to stop")
  154. end
  155.  
  156. local function allowedATM(id)
  157.     if not ATMS then return true end
  158.     for _, a in ipairs(ATMS) do
  159.         if a == id then return true end
  160.     end
  161.     return false
  162. end
  163.  
  164. addLog("Bank started")
  165. draw()
  166. while true do
  167.     local id, msg = rednet.receive("pocketos.bank")
  168.     if type(msg) == "table" and type(msg.type) == "string" then
  169.         local reply
  170.         if msg.type == "balance" then
  171.             local acc = get(id)
  172.             if type(msg.label) == "string" and #msg.label > 0 then
  173.                 acc.label = msg.label:sub(1, 12)
  174.                 save()
  175.             end
  176.             reply = { ok = true, balance = acc.bal, label = acc.label }
  177.         elseif msg.type == "info" then
  178.             local account = tonumber(msg.account)
  179.             if account then
  180.                 local acc = get(account)
  181.                 reply = { ok = true, balance = acc.bal, label = acc.label }
  182.             else
  183.                 reply = { ok = false, err = "Bad account" }
  184.             end
  185.         elseif msg.type == "deposit" then
  186.             local account = tonumber(msg.account)
  187.             local amount = tonumber(msg.amount)
  188.             amount = amount and math.floor(amount)
  189.             if not allowedATM(id) then
  190.                 reply = { ok = false, err = "ATM not authorised" }
  191.             elseif not account or not amount or amount <= 0
  192.                 or amount > 1000000000 then
  193.                 reply = { ok = false, err = "Bad deposit" }
  194.             else
  195.                 local acc = get(account)
  196.                 acc.bal = (acc.bal or 0) + amount
  197.                 save()
  198.                 addLog("#" .. account .. " deposited $" .. amount)
  199.                 record("deposit", nil, account, amount)
  200.                 notify(account, "Deposited $" .. amount
  201.                     .. ". Balance: $" .. acc.bal)
  202.                 reply = { ok = true, balance = acc.bal, label = acc.label }
  203.             end
  204.         elseif msg.type == "pay" then
  205.             local to = tonumber(msg.to)
  206.             local amount = tonumber(msg.amount)
  207.             amount = amount and math.floor(amount)
  208.             local from = get(id)
  209.             if type(msg.label) == "string" and #msg.label > 0 then
  210.                 from.label = msg.label:sub(1, 12)
  211.             end
  212.             if not to or not amount or amount <= 0 then
  213.                 reply = { ok = false, err = "Bad payment" }
  214.             elseif to == id then
  215.                 reply = { ok = false, err = "That's you!" }
  216.             elseif (from.bal or 0) < amount then
  217.                 reply = { ok = false, err = "Not enough money" }
  218.             else
  219.                 local dest = get(to)
  220.                 from.bal = from.bal - amount
  221.                 dest.bal = (dest.bal or 0) + amount
  222.                 save()
  223.                 addLog("#" .. id .. " paid #" .. to .. " $" .. amount)
  224.                 record("pay", id, to, amount)
  225.                 notify(to, "You got $" .. amount .. " from "
  226.                     .. (from.label and (from.label .. " ") or "")
  227.                     .. "#" .. id .. ". Balance: $" .. dest.bal)
  228.                 reply = { ok = true, balance = from.bal }
  229.             end
  230.         elseif msg.type == "txns" then
  231.             if not isAdmin(id) then
  232.                 reply = { ok = false, err = "Not an admin" }
  233.             else
  234.                 local n = math.min(#txns, tonumber(msg.limit) or 40)
  235.                 local out = {}
  236.                 for i = 1, n do out[i] = txns[i] end
  237.                 reply = { ok = true, txns = out }
  238.             end
  239.         elseif msg.type == "mklist" then
  240.             reply = { ok = true, items = market.items }
  241.         elseif msg.type == "mkpost" then
  242.             local name = type(msg.name) == "string" and msg.name:sub(1, 20) or ""
  243.             local price = tonumber(msg.price)
  244.             price = price and math.floor(price)
  245.             local desc = type(msg.desc) == "string" and msg.desc:sub(1, 60) or ""
  246.             local mine = 0
  247.             for _, it in ipairs(market.items) do
  248.                 if it.seller == id then mine = mine + 1 end
  249.             end
  250.             if name == "" or not price or price <= 0 or price > 1000000000 then
  251.                 reply = { ok = false, err = "Bad listing" }
  252.             elseif mine >= 8 then
  253.                 reply = { ok = false, err = "Max 8 listings" }
  254.             elseif #market.items >= 60 then
  255.                 reply = { ok = false, err = "Market is full" }
  256.             else
  257.                 local acc = get(id)
  258.                 if type(msg.label) == "string" and #msg.label > 0 then
  259.                     acc.label = msg.label:sub(1, 12)
  260.                     save()
  261.                 end
  262.                 market.items[#market.items + 1] = {
  263.                     id = market.nextId,
  264.                     seller = id,
  265.                     label = acc.label,
  266.                     name = name,
  267.                     price = price,
  268.                     desc = desc,
  269.                 }
  270.                 market.nextId = market.nextId + 1
  271.                 saveMarket()
  272.                 addLog("#" .. id .. " listed " .. name .. " $" .. price)
  273.                 reply = { ok = true, id = market.nextId - 1 }
  274.             end
  275.         elseif msg.type == "mkremove" then
  276.             local lid = tonumber(msg.id)
  277.             local removed = false
  278.             for k, it in ipairs(market.items) do
  279.                 if it.id == lid and it.seller == id then
  280.                     table.remove(market.items, k)
  281.                     removed = true
  282.                     break
  283.                 end
  284.             end
  285.             if removed then
  286.                 saveMarket()
  287.                 addLog("#" .. id .. " removed a listing")
  288.                 reply = { ok = true }
  289.             else
  290.                 reply = { ok = false, err = "Not your listing" }
  291.             end
  292.         end
  293.         if reply then
  294.             rednet.send(id, reply, "pocketos.bank")
  295.             draw()
  296.         end
  297.     end
  298. end
  299.  
Advertisement
Add Comment
Please, Sign In to add comment