413 lines
12 KiB
Lua
413 lines
12 KiB
Lua
local w
|
|
local r
|
|
local jua
|
|
local json
|
|
local await
|
|
|
|
local endpoint = "krist.ceriat.net"
|
|
local wsEndpoint = "ws://"..endpoint
|
|
local httpEndpoint = "http://"..endpoint
|
|
|
|
local function asserttype(var, name, vartype, optional)
|
|
if not (type(var) == vartype or optional and type(var) == "nil") then
|
|
error(name..": expected "..vartype.." got "..type(var), 3)
|
|
end
|
|
end
|
|
|
|
function init(juai, jsoni, wi, ri)
|
|
asserttype(juai, "jua", "table")
|
|
asserttype(jsoni, "json", "table")
|
|
asserttype(wi, "w", "table", true)
|
|
asserttype(ri, "r", "table")
|
|
|
|
jua = juai
|
|
await = juai.await
|
|
json = jsoni
|
|
w = wi
|
|
r = ri
|
|
end
|
|
|
|
local function prints(...)
|
|
local objs = {...}
|
|
for i, obj in ipairs(objs) do
|
|
print(textutils.serialize(obj))
|
|
end
|
|
end
|
|
|
|
local function url(call)
|
|
return httpEndpoint..call
|
|
end
|
|
|
|
local function api_request(cb, api, data)
|
|
local success, url, handle = await(r.request, url(api) .. (api:find("%%?") and "?cc" or "&cc"), {["Content-Type"]="application/json"}, data and json.encode(data))
|
|
if success then
|
|
cb(success, json.decode(handle.readAll()))
|
|
handle.close()
|
|
else
|
|
cb(success)
|
|
end
|
|
end
|
|
|
|
local function authorize_websocket(cb, privatekey)
|
|
asserttype(cb, "callback", "function")
|
|
asserttype(privatekey, "privatekey", "string", true)
|
|
|
|
api_request(function(success, data)
|
|
cb(success and data and data.ok, data.url and data.url:gsub("wss:", "ws:") or data)
|
|
end, "/ws/start", {
|
|
privatekey = privatekey
|
|
})
|
|
end
|
|
|
|
function address(cb, address)
|
|
asserttype(cb, "callback", "function")
|
|
asserttype(address, "address", "string")
|
|
|
|
api_request(function(success, data)
|
|
if data.address then
|
|
data.address.address = address
|
|
end
|
|
cb(success and data and data.ok, data.address or data)
|
|
end, "/addresses/"..address)
|
|
end
|
|
|
|
function addressTransactions(cb, address, limit, offset)
|
|
asserttype(cb, "callback", "function")
|
|
asserttype(address, "address", "string")
|
|
asserttype(limit, "limit", "number", true)
|
|
asserttype(offset, "offset", "number", true)
|
|
|
|
api_request(function(success, data)
|
|
cb(success and data and data.ok, data.transactions or data)
|
|
end, "/addresses/"..address.."/transactions?limit="..(limit or 50).."&offset="..(offset or 0))
|
|
end
|
|
|
|
function addressNames(cb, address)
|
|
asserttype(cb, "callback", "function")
|
|
asserttype(address, "address", "string")
|
|
|
|
api_request(function(success, data)
|
|
cb(success and data and data.ok, data.names or data)
|
|
end, "/addresses/"..address.."/names")
|
|
end
|
|
|
|
function addresses(cb, limit, offset)
|
|
asserttype(cb, "callback", "function")
|
|
asserttype(limit, "limit", "number", true)
|
|
asserttype(offset, "offset", "number", true)
|
|
|
|
api_request(function(success, data)
|
|
cb(success and data and data.ok, data.addresses or data)
|
|
end, "/addresses?limit="..(limit or 50).."&offset="..(offset or 0))
|
|
end
|
|
|
|
function rich(cb, limit, offset)
|
|
asserttype(cb, "callback", "function")
|
|
asserttype(limit, "limit", "number", true)
|
|
asserttype(offset, "offset", "number", true)
|
|
|
|
api_request(function(success, data)
|
|
cb(success and data and data.ok, data.addresses or data)
|
|
end, "/addresses/rich?limit="..(limit or 50).."&offset="..(offset or 0))
|
|
end
|
|
|
|
function transactions(cb, limit, offset)
|
|
asserttype(cb, "callback", "function")
|
|
asserttype(limit, "limit", "number", true)
|
|
asserttype(offset, "offset", "number", true)
|
|
|
|
api_request(function(success, data)
|
|
cb(success and data and data.ok, data.transactions or data)
|
|
end, "/transactions?limit="..(limit or 50).."&offset="..(offset or 0))
|
|
end
|
|
|
|
function latestTransactions(cb, limit, offset)
|
|
asserttype(cb, "callback", "function")
|
|
asserttype(limit, "limit", "number", true)
|
|
asserttype(offset, "offset", "number", true)
|
|
|
|
api_request(function(success, data)
|
|
cb(success and data and data.ok, data.transactions or data)
|
|
end, "/transactions/latest?limit="..(limit or 50).."&offset="..(offset or 0))
|
|
end
|
|
|
|
function transaction(cb, txid)
|
|
asserttype(cb, "callback", "function")
|
|
asserttype(txid, "txid", "number")
|
|
|
|
api_request(function(success, data)
|
|
cb(success and data and data.ok, data.transaction or data)
|
|
end, "/transactions/"..txid)
|
|
end
|
|
|
|
function makeTransaction(cb, privatekey, to, amount, metadata)
|
|
asserttype(cb, "callback", "function")
|
|
asserttype(privatekey, "privatekey", "string")
|
|
asserttype(to, "to", "string")
|
|
asserttype(amount, "amount", "number")
|
|
asserttype(metadata, "metadata", "string", true)
|
|
|
|
api_request(function(success, data)
|
|
cb(success and data and data.ok, data.transaction or data)
|
|
end, "/transactions", {
|
|
privatekey = privatekey,
|
|
to = to,
|
|
amount = amount,
|
|
metadata = metadata
|
|
})
|
|
end
|
|
|
|
local wsEventNameLookup = {
|
|
blocks = "block",
|
|
ownBlocks = "block",
|
|
transactions = "transaction",
|
|
ownTransactions = "transaction",
|
|
names = "name",
|
|
ownNames = "name",
|
|
ownWebhooks = "webhook",
|
|
motd = "motd"
|
|
}
|
|
|
|
local wsEvents = {}
|
|
|
|
local wsReqID = 0
|
|
local wsReqRegistry = {}
|
|
local wsEvtRegistry = {}
|
|
local wsHandleRegistry = {}
|
|
|
|
local function newWsID()
|
|
local id = wsReqID
|
|
wsReqID = wsReqID + 1
|
|
return id
|
|
end
|
|
|
|
local function registerEvent(id, event, callback)
|
|
if wsEvtRegistry[id] == nil then
|
|
wsEvtRegistry[id] = {}
|
|
end
|
|
|
|
if wsEvtRegistry[id][event] == nil then
|
|
wsEvtRegistry[id][event] = {}
|
|
end
|
|
|
|
table.insert(wsEvtRegistry[id][event], callback)
|
|
end
|
|
|
|
local function registerRequest(id, reqid, callback)
|
|
if wsReqRegistry[id] == nil then
|
|
wsReqRegistry[id] = {}
|
|
end
|
|
|
|
wsReqRegistry[id][reqid] = callback
|
|
end
|
|
|
|
local function discoverEvents(id, event)
|
|
local evs = {}
|
|
for k,v in pairs(wsEvtRegistry[id]) do
|
|
if k == event or string.match(k, event) or event == "*" then
|
|
for i,v2 in ipairs(v) do
|
|
table.insert(evs, v2)
|
|
end
|
|
end
|
|
end
|
|
|
|
return evs
|
|
end
|
|
|
|
wsEvents.success = function(id, handle)
|
|
-- fire success event
|
|
wsHandleRegistry[id] = handle
|
|
if wsEvtRegistry[id] then
|
|
local evs = discoverEvents(id, "success")
|
|
for i, v in ipairs(evs) do
|
|
v(id, handle)
|
|
end
|
|
end
|
|
end
|
|
|
|
wsEvents.failure = function(id)
|
|
-- fire failure event
|
|
if wsEvtRegistry[id] then
|
|
local evs = discoverEvents(id, "failure")
|
|
for i, v in ipairs(evs) do
|
|
v(id)
|
|
end
|
|
end
|
|
end
|
|
|
|
wsEvents.message = function(id, data)
|
|
local data = json.decode(data)
|
|
--print("msg:"..tostring(data.ok)..":"..tostring(data.type)..":"..tostring(data.id))
|
|
--prints(data)
|
|
-- handle events and responses
|
|
if wsReqRegistry[id] and wsReqRegistry[id][tonumber(data.id)] then
|
|
wsReqRegistry[id][tonumber(data.id)](data)
|
|
elseif wsEvtRegistry[id] then
|
|
local evs = discoverEvents(id, data.type)
|
|
for i, v in ipairs(evs) do
|
|
v(data)
|
|
end
|
|
|
|
if data.event then
|
|
local evs = discoverEvents(id, data.event)
|
|
for i, v in ipairs(evs) do
|
|
v(data)
|
|
end
|
|
end
|
|
|
|
local evs2 = discoverEvents(id, "message")
|
|
for i, v in ipairs(evs2) do
|
|
v(id, data)
|
|
end
|
|
end
|
|
end
|
|
|
|
wsEvents.closed = function(id)
|
|
-- fire closed event
|
|
if wsEvtRegistry[id] then
|
|
local evs = discoverEvents(id, "closed")
|
|
for i, v in ipairs(evs) do
|
|
v(id)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function wsRequest(cb, id, type, data)
|
|
local reqID = newWsID()
|
|
registerRequest(id, reqID, function(data)
|
|
cb(data)
|
|
end)
|
|
data.id = tostring(reqID)
|
|
data.type = type
|
|
wsHandleRegistry[id].send(json.encode(data))
|
|
end
|
|
|
|
local function barebonesMixinHandle(id, handle)
|
|
handle.on = function(event, cb)
|
|
registerEvent(id, event, cb)
|
|
end
|
|
|
|
return handle
|
|
end
|
|
|
|
local function mixinHandle(id, handle)
|
|
handle.subscribe = function(cb, event, eventcb)
|
|
local data = await(wsRequest, id, "subscribe", {
|
|
event = event
|
|
})
|
|
registerEvent(id, wsEventNameLookup[event], eventcb)
|
|
cb(data.ok, data)
|
|
end
|
|
|
|
return barebonesMixinHandle(id, handle)
|
|
end
|
|
|
|
function connect(cb, privatekey, preconnect)
|
|
asserttype(cb, "callback", "function")
|
|
asserttype(privatekey, "privatekey", "string", true)
|
|
asserttype(preconnect, "preconnect", "function", true)
|
|
local url
|
|
if privatekey then
|
|
local success, auth = await(authorize_websocket, privatekey)
|
|
url = success and auth or wsEndpoint
|
|
end
|
|
local id = w.open(wsEvents, url)
|
|
if preconnect then
|
|
preconnect(id, barebonesMixinHandle(id, {}))
|
|
end
|
|
registerEvent(id, "success", function(id, handle)
|
|
cb(true, mixinHandle(id, handle))
|
|
end)
|
|
registerEvent(id, "failure", function(id)
|
|
cb(false)
|
|
end)
|
|
end
|
|
|
|
local domainMatch = "^([%l%d-_]*)@?([%l%d-]+).kst$"
|
|
local commonMetaMatch = "^(.+)=(.+)$"
|
|
|
|
function parseMeta(meta)
|
|
asserttype(meta, "meta", "string")
|
|
local tbl = {meta={}}
|
|
|
|
for m in meta:gmatch("[^;]+") do
|
|
if m:match(domainMatch) then
|
|
-- print("Matched domain")
|
|
|
|
local p1, p2 = m:match("([%l%d-_]*)@"), m:match("@?([%l%d-]+).kst")
|
|
tbl.name = p1
|
|
tbl.domain = p2
|
|
|
|
elseif m:match(commonMetaMatch) then
|
|
-- print("Matched common meta")
|
|
|
|
local p1, p2 = m:match(commonMetaMatch)
|
|
|
|
tbl.meta[p1] = p2
|
|
|
|
else
|
|
-- print("Unmatched standard meta")
|
|
|
|
table.insert(tbl.meta, m)
|
|
end
|
|
-- print(m)
|
|
end
|
|
-- print(textutils.serialize(tbl))
|
|
return tbl
|
|
end
|
|
|
|
local g = string.gsub
|
|
sha256 = loadstring(g(g(g(g(g(g(g(g('Sa=XbandSb=XbxWSc=XlshiftSd=unpackSe=2^32SYf(g,h)Si=g/2^hSj=i%1Ui-j+j*eVSYk(l,m)Sn=l/2^mUn-n%1VSo={0x6a09e667Tbb67ae85T3c6ef372Ta54ff53aT510e527fT9b05688cT1f83d9abT5be0cd19}Sp={0x428a2f98T71374491Tb5c0fbcfTe9b5dba5T3956c25bT59f111f1T923f82a4Tab1c5ed5Td807aa98T12835b01T243185beT550c7dc3T72be5d74T80deb1feT9bdc06a7Tc19bf174Te49b69c1Tefbe4786T0fc19dc6T240ca1ccT2de92c6fT4a7484aaT5cb0a9dcT76f988daT983e5152Ta831c66dTb00327c8Tbf597fc7Tc6e00bf3Td5a79147T06ca6351T14292967T27b70a85T2e1b2138T4d2c6dfcT53380d13T650a7354T766a0abbT81c2c92eT92722c85Ta2bfe8a1Ta81a664bTc24b8b70Tc76c51a3Td192e819Td6990624Tf40e3585T106aa070T19a4c116T1e376c08T2748774cT34b0bcb5T391c0cb3T4ed8aa4aT5b9cca4fT682e6ff3T748f82eeT78a5636fT84c87814T8cc70208T90befffaTa4506cebTbef9a3f7Tc67178f2}SYq(r,q)if e-1-r[1]<q then r[2]=r[2]+1;r[1]=q-(e-1-r[1])-1 else r[1]=r[1]+qVUrVSYs(t)Su=#t;t[#t+1]=0x80;while#t%64~=56Zt[#t+1]=0VSv=q({0,0},u*8)fWw=2,1,-1Zt[#t+1]=a(k(a(v[w]TFF000000),24)TFF)t[#t+1]=a(k(a(v[w]TFF0000),16)TFF)t[#t+1]=a(k(a(v[w]TFF00),8)TFF)t[#t+1]=a(v[w]TFF)VUtVSYx(y,w)Uc(y[w]W0,24)+c(y[w+1]W0,16)+c(y[w+2]W0,8)+(y[w+3]W0)VSYz(t,w,A)SB={}fWC=1,16ZB[C]=x(t,w+(C-1)*4)VfWC=17,64ZSD=B[C-15]SE=b(b(f(B[C-15],7),f(B[C-15],18)),k(B[C-15],3))SF=b(b(f(B[C-2],17),f(B[C-2],19)),k(B[C-2],10))B[C]=(B[C-16]+E+B[C-7]+F)%eVSG,h,H,I,J,j,K,L=d(A)fWC=1,64ZSM=b(b(f(J,6),f(J,11)),f(J,25))SN=b(a(J,j),a(Xbnot(J),K))SO=(L+M+N+p[C]+B[C])%eSP=b(b(f(G,2),f(G,13)),f(G,22))SQ=b(b(a(G,h),a(G,H)),a(h,H))SR=(P+Q)%e;L,K,j,J,I,H,h,G=K,j,J,(I+O)%e,H,h,G,(O+R)%eVA[1]=(A[1]+G)%e;A[2]=(A[2]+h)%e;A[3]=(A[3]+H)%e;A[4]=(A[4]+I)%e;A[5]=(A[5]+J)%e;A[6]=(A[6]+j)%e;A[7]=(A[7]+K)%e;A[8]=(A[8]+L)%eUAVUY(t)t=t W""t=type(t)=="string"and{t:byte(1,-1)}Wt;t=s(t)SA={d(o)}fWw=1,#t,64ZA=z(t,w,A)VU("%08x"):rep(8):format(d(A))V',"S"," local "),"T",",0x"),"U"," return "),"V"," end "),"W","or "),"X","bit32."),"Y","function "),"Z"," do "))()
|
|
|
|
function makeaddressbyte(byte)
|
|
local byte = 48 + math.floor(byte / 7)
|
|
return string.char(byte + 39 > 122 and 101 or byte > 57 and byte + 39 or byte)
|
|
end
|
|
|
|
function makev2address(key)
|
|
local protein = {}
|
|
local stick = sha256(sha256(key))
|
|
local n = 0
|
|
local link = 0
|
|
local v2 = "k"
|
|
repeat
|
|
if n < 9 then protein[n] = string.sub(stick,0,2)
|
|
stick = sha256(sha256(stick)) end
|
|
n = n + 1
|
|
until n == 9
|
|
n = 0
|
|
repeat
|
|
link = tonumber(string.sub(stick,1+(2*n),2+(2*n)),16) % 9
|
|
if string.len(protein[link]) ~= 0 then
|
|
v2 = v2 .. makeaddressbyte(tonumber(protein[link],16))
|
|
protein[link] = ''
|
|
n = n + 1
|
|
else
|
|
stick = sha256(stick)
|
|
end
|
|
until n == 9
|
|
return v2
|
|
end
|
|
|
|
function toKristWalletFormat(passphrase)
|
|
return sha256("KRISTWALLET"..passphrase).."-000"
|
|
end
|
|
|
|
return {
|
|
init = init,
|
|
address = address,
|
|
addressTransactions = addressTransactions,
|
|
addressNames = addressNames,
|
|
addresses = addresses,
|
|
rich = rich,
|
|
transactions = transactions,
|
|
latestTransactions = latestTransactions,
|
|
transaction = transaction,
|
|
makeTransaction = makeTransaction,
|
|
connect = connect,
|
|
parseMeta = parseMeta,
|
|
sha256 = sha256,
|
|
makeaddressbyte = makeaddressbyte,
|
|
makev2address = makev2address,
|
|
toKristWalletFormat = toKristWalletFormat
|
|
} |