From 22e2877e72f60afa4a0ab667842c98d8f69af8cd Mon Sep 17 00:00:00 2001 From: Asia Date: Fri, 22 Nov 2024 16:31:28 +0100 Subject: [PATCH 1/6] hollow --- lua/hollow/hollow-db.lua | 70 +++++++++ lua/hollow/hollow-utils.lua | 44 ++++++ lua/hollow/hollow.lua | 282 ++++++++++++++++++++++++++++++++++++ package.json | 3 +- tests/hollow.test.js | 177 ++++++++++++++++++++++ 5 files changed, 575 insertions(+), 1 deletion(-) create mode 100644 lua/hollow/hollow-db.lua create mode 100644 lua/hollow/hollow-utils.lua create mode 100644 lua/hollow/hollow.lua create mode 100644 tests/hollow.test.js diff --git a/lua/hollow/hollow-db.lua b/lua/hollow/hollow-db.lua new file mode 100644 index 00000000..bb9b49bc --- /dev/null +++ b/lua/hollow/hollow-db.lua @@ -0,0 +1,70 @@ +local sqlite3 = require("lsqlite3") + +function ExecuteStatement(stmt, name) + local step_result, step_err = stmt:step() + if step_result ~= sqlite3.DONE then + stmt:finalize() + error(name .. " error executing statement: " .. (step_err or "Unknown error") .. " step_result: " .. step_result) + end +end + +function InsertOrder(wallet_address, timestamp, item_type, item_qty, payment) + local stmt = db:prepare([[ + insert into orders(wallet_address, timestamp, item_type, item_qty, payment) + values (?, ?, ?, ?, ?); + ]]) + if not stmt then + error("Preparing insert order failure: " .. db:errcode() .. " " .. db:errmsg()) + end + local stmtBind = stmt:bind_values(wallet_address, timestamp, item_type, item_qty, payment) + if not stmtBind then + error("Binding insert order failure: " .. db:errcode() .. " " .. db:errmsg()) + end + + ExecuteStatement(stmt, "Inserting order") +end + +function ReadOrders(limit, offset, wallet_address) + local readOrdersResult = {} + + local sql = [[ + SELECT * + FROM orders + ]] + + local valuesToBind = {} + + if wallet_address then + sql = sql .. ' WHERE wallet_address = ?' + table.insert(valuesToBind, wallet_address) + end + + sql = sql .. ' LIMIT ? OFFSET ?;' + table.insert(valuesToBind, limit) + table.insert(valuesToBind, offset) + local stmt, err_parse = db:prepare(sql) + if not stmt then + error("Error parsing SQL statement: " .. (err_parse or "Unknown error")) + end + + local ok, err_bind = pcall(function () + stmt:bind_values(table.unpack(valuesToBind)) + end) + if not ok then + stmt:finalize() + error("Error binding values: " .. (err_bind or "Unknown error")) + end + + local exec_ok, err_exec = pcall(function () + for row in stmt:nrows("") do + table.insert(readOrdersResult, row) + end + end) + if not exec_ok then + stmt:finalize() + error("Error executing query: " .. (err_exec or "Unknown error")) + end + + stmt:finalize() + return readOrdersResult +end \ No newline at end of file diff --git a/lua/hollow/hollow-utils.lua b/lua/hollow/hollow-utils.lua new file mode 100644 index 00000000..3de9f079 --- /dev/null +++ b/lua/hollow/hollow-utils.lua @@ -0,0 +1,44 @@ +local bint = require('.bint')(256) + +function CheckValidAddress(address) + if not address or type(address) ~= 'string' then + return false + end + + return string.match(address, '^[%w%-_]+$') ~= nil and #address == 43 +end + +function CheckValidAmount(data) + return bint(data) > bint(0) +end + +function Add(a,b) + return tostring(bint(a) + bint(b)) +end + +function Multiply(a,b) + return tostring(bint(a) * bint(b)) +end + +function Subtract(a,b) + return tostring(bint(a) - bint(b)) +end + +function ToNumber(a) + return tonumber(a) + end + + function HandleError(args) -- Target, TransferToken, Quantity + -- If there is a valid quantity then return the funds + if args.TransferToken and args.Quantity and CheckValidAmount(args.Quantity) then + ao.send({ + Target = args.TransferToken, + Action = 'Transfer', + Tags = { + Recipient = args.Target, + Quantity = tostring(args.Quantity) + } + }) + end + ao.send({ Target = args.Target, Action = args.Action, Tags = { Status = 'Error', Message = args.Message } }) +end diff --git a/lua/hollow/hollow.lua b/lua/hollow/hollow.lua new file mode 100644 index 00000000..b4f8e74f --- /dev/null +++ b/lua/hollow/hollow.lua @@ -0,0 +1,282 @@ +local bint = require('.bint')(256) +local sqlite3 = require("lsqlite3") + +Owner = Owner or ao.env.Process.Owner + +Items = Items or {} +Balances = Balances or {} + +ao.authorities = {"fcoN_xJeisVsPXA-trzVAuIiqO3ydLQxM-L4XbrQKzY", "jmGGoJaDYDTx4OCM7MP-7l-VLIM4ZEGCS0cHPsSmiNE"} + +-- +-- WRITE +-- 1. Add-Item - add new item to the hollow or change price/qty +-- 3. Credit-Notice - create order of the items +-- READ +-- 1. Read-Item - read item with specific name +-- 2. Read-Hollow - read all the items in the hollow +-- 3. Read-Balance - read player's items balance +-- 4. Read-Orders - read orders from db +-- + +-- Open the database +db = db or sqlite3.open_memory() + +-- init db +db:exec([[ +create table if not exists orders( + id INTEGER PRIMARY KEY AUTOINCREMENT, + wallet_address TEXT NOT NULL, + timestamp INTEGER NOT NULL, + item_type TEXT NOT NULL, + item_qty INTEGER NOT NULL, + payment INTEGER NOT NULL +); +]]) + +Handlers.add('Credit-Notice', Handlers.utils.hasMatchingTag('Action', 'Credit-Notice'), function(msg) + local data = { + Sender = msg.Tags.Sender, + Quantity = msg.Tags.Quantity, + } + + -- If msg.From is different than msg.Owner - authorities are taken into account + if msg.From == msg.Owner and msg.Owner ~= Owner then + HandleError({ + Target = msg.From, + Action = 'Create-Order-Error', + Message = msg.Owner .. ' unauthorized.', + Quantity = data.Quantity, + TransferToken = data.Sender, + }) + return + end + + -- Check if all transfer fields are present + if not data.Sender or not data.Quantity then + HandleError({ + Target = msg.From, + Action = 'Input-Error', + Message = 'Invalid arguments, required { Sender, Quantity }.', + Quantity = nil, + TransferToken = nil, + }) + return + end + + -- Check if sender is a valid address + if not CheckValidAddress(data.Sender) then + HandleError({ + Target = msg.From, + Action = 'Validation-Error', + Message = 'Sender must be a valid address.', + Quantity = nil, + TransferToken = nil, + }) + return + end + + -- Check if quantity is a valid integer greater than zero + if not CheckValidAmount(data.Quantity) then + HandleError({ + Target = msg.From, + Action = 'Validation-Error', + Message = 'Quantity must be an integer greater than zero.', + Quantity = nil, + TransferToken = nil, + }) + return + end + + -- If Order-Action then create the order + if (Handlers.utils.hasMatchingTag('Action', 'X-Order-Action') and msg.Tags['X-Order-Action'] == 'Create-Order') then + local orderArgs = { + orderId = msg.Id, + token = msg.From, + item = msg.Tags.Item, + itemQuantity = msg.Tags['Item-Quantity'], + sender = data.Sender, + quantity = data.Quantity, + timestamp = msg.Timestamp, + blockheight = msg['Block-Height'] + } + + -- check if item is passed + if not orderArgs.item or not orderArgs.itemQuantity then + HandleError({ + Target = orderArgs.sender, + Action = 'Input-Error', + Message = 'Invalid arguments, required { Item, Item-Quantity }.', + Quantity = orderArgs.quantity, + TransferToken = msg.From, + }) + return + end + + -- check if item is present in the hollow + if not Items[orderArgs.item] then + HandleError({ + Target = orderArgs.sender, + Action = 'Create-Order-Error', + Message = 'Item not found ' .. orderArgs.item, + Quantity = orderArgs.quantity, + TransferToken = msg.From, + }) + return + end + + -- check if there are enough items in the hollow + if ToNumber(Items[orderArgs.item].Quantity) < ToNumber(orderArgs.itemQuantity) then + HandleError({ + Target = orderArgs.sender, + Action = 'Create-Order-Error', + Message = 'Not enough items in the hollow, required: ' .. orderArgs.itemQuantity .. ' had: ' .. Items[orderArgs.item].Quantity, + Quantity = orderArgs.quantity, + TransferToken = msg.From, + }) + return + end + + -- check if price of ordered items alligns with quantity of the Credit-Notice + local totalItemPrice = Multiply(orderArgs.itemQuantity, Items[orderArgs.item].Price) + + if ToNumber(totalItemPrice) <= ToNumber(orderArgs.quantity) then + if not Balances[orderArgs.sender] then Balances[orderArgs.sender] = {} end + if not Balances[orderArgs.sender][orderArgs.item] then + Balances[orderArgs.sender][orderArgs.item] = '0' + end + + Items[orderArgs.item].Quantity = Subtract(Items[orderArgs.item].Quantity, orderArgs.itemQuantity) + Balances[orderArgs.sender][orderArgs.item] = Add(Balances[orderArgs.sender][orderArgs.item], orderArgs.itemQuantity) + ao.send({ + Target = msg.From, + Action = 'Create-Order-Success', + Data = 'Successfully created order for item: ' .. orderArgs.item .. ' Qty: ' .. orderArgs.itemQuantity .. ' for price: ' .. totalItemPrice + }) + + + -- return remaining token quantity to the sender + if ToNumber(totalItemPrice) < ToNumber(orderArgs.quantity) then + ao.send({ + Target = msg.From, + Action = 'Transfer', + Tags = { + Recipient = orderArgs.Sender, + Quantity = tostring(Subtract(orderArgs.quantity, totalItemPrice)) + } + }) + end + InsertOrder(orderArgs.sender, msg.Timestamp, orderArgs.item, orderArgs.itemQuantity, totalItemPrice) + return + else + HandleError({ + Target = msg.From, + Action = 'Create-Order-Error', + Message = 'Not enough tokens transferred, required: ' .. totalItemPrice .. ' had: ' .. orderArgs.quantity, + Quantity = orderArgs.quantity, + TransferToken = msg.From, + }) + return + end + end +end) + +Handlers.add('Add-Item', Handlers.utils.hasMatchingTag('Action', 'Add-Item'), function(msg) + assert(type(msg.Tags.Quantity) == 'string', 'Quantity is required.') + assert(type(msg.Tags.Item) == 'string', 'Item is required.') + + if not Items[msg.Tags.Item] then + assert(type(msg.Tags.Price) == 'string', 'Price is required for new item.') + end + + if msg.Owner ~= Owner then + HandleError({ + Target = msg.From, + Action = 'Add-Item-Error', + Message = 'Only the Owner can add new items..', + Quantity = nil, + TransferToken = nil, + }) + return + end + + if not Items[msg.Tags.Item] then + Items[msg.Tags.Item] = { + Price = "0", + Quantity = "0" + } + end + + Items[msg.Tags.Item].Quantity = Add(Items[msg.Tags.Item].Quantity, bint(msg.Tags.Quantity)) + if msg.Tags.Price then + Items[msg.Tags.Item].Price = msg.Tags.Price + end + + ao.send({ + Target = msg.From, + Action = 'Add-Item-Success', + Data = 'Successfully added item ' .. msg.Tags.Item .. ' Qty ' .. Items[msg.Item].Quantity .. ' Price ' .. Items[msg.Item].Price + }) +end) + +Handlers.add('Read-Item', Handlers.utils.hasMatchingTag('Action', 'Read-Item'), function(msg) + assert(type(msg.Tags.Item) == 'string', 'Item is required.') + + if not Items[msg.Tags.Item] then + HandleError({ + Target = msg.From, + Action = 'Item-Info-Error', + Message = 'Item not found.', + Quantity = nil, + TransferToken = nil, + }) + return + end + + ao.send({ + Target = msg.From, + Action = 'Item-Info', + Data = Items[msg.Tags.Item], + }) +end) + +Handlers.add('Read-Balance', Handlers.utils.hasMatchingTag('Action', 'Read-Balance'), function(msg) + assert(type(msg.Tags['Wallet-Address']) == 'string', 'Wallet address is required.') + + if not Balances[msg.Tags['Wallet-Address']] then + HandleError({ + Target = msg.From, + Action = 'Read-Balance-Error', + Message = 'Wallet address not found in balances.', + Quantity = nil, + TransferToken = nil, + }) + return + end + + ao.send({ + Target = msg.From, + Action = 'Read-Balance-Success', + Data = Balances[msg.Tags['Wallet-Address']], + }) +end) + +Handlers.add('Read-Hollow', Handlers.utils.hasMatchingTag('Action', 'Read-Hollow'), function(msg) + ao.send({ + Target = msg.From, + Action = 'Read-Hollow-Success', + Data = Items, + }) +end) + +Handlers.add('Read-Orders', Handlers.utils.hasMatchingTag('Action', 'Read-Orders'), function(msg) + local limit = msg.Tags.Limit or 10 + local offset = msg.Tags.Offset or 0 + local wallet_address = msg.Tags['Wallet-Address'] or nil + local result = ReadOrders(limit, offset, wallet_address) + ao.send({ + Target = msg.From, + Action = 'Read-Orders-Success', + Data = result, + }) +end) diff --git a/package.json b/package.json index dba13e7f..12809421 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "deploy-session-game:prod:eu": "yarn build && node tools/deploy/deploy-spawn-session-game.js --env prod --config open-eu --time $1", "deploy-session-game:prod:asia": "yarn build && node tools/deploy/deploy-spawn-session-game.js --env prod --config open-asia --time $1", "display-session-schedule": "yarn build && node tools/deploy/display-session-config-schedule.js", - "test": "node build.js && vitest", + "test": "node build.js && yarn test:flags", + "test:flags": "node --experimental-wasm-memory64 node_modules/vitest/vitest.mjs", "build": "node build.js", "format": "npx prettier --write .", "prepare": "husky install" diff --git a/tests/hollow.test.js b/tests/hollow.test.js new file mode 100644 index 00000000..e3f60d46 --- /dev/null +++ b/tests/hollow.test.js @@ -0,0 +1,177 @@ +import { Send } from './aos.helper.js'; +import { describe, test, expect } from 'vitest'; +import fs from 'fs'; + +global.ao = {}; +ao.send = function (di) { + console.log(`got me some data`, di); + ao.di = di; +}; + +describe('HollowTestJs', () => { + test('Eval hollow contract', async () => { + const dbCode = fs.readFileSync('./lua/hollow/hollow-db.lua', 'utf-8'); + await Send({ Action: 'Eval', Data: dbCode }); + const utilsCode = fs.readFileSync('./lua/hollow/hollow-utils.lua', 'utf-8'); + await Send({ Action: 'Eval', Data: utilsCode }); + const code = fs.readFileSync('./lua/hollow/hollow.lua', 'utf-8'); + const result = await Send({ Action: 'Eval', Data: code }); + + expect(result.Output.data).eq(''); + expect(result.Messages).toEqual([]); + }); + + test('Add item', async () => { + expect((await Send(addSingleItem('hp', '5', '100'))).Output.data).not.toContain('Error'); + expect((await Send(addSingleItem('ap', '10', '200'))).Output.data).not.toContain('Error'); + }); + + test('Add item - unathorized', async () => { + const action = addSingleItem('hp', '5', '100'); + action.Owner = 'DIFFERENT_OWNER'; + action.From = 'DIFFERENT_OWNER'; + expect((await Send(action)).Messages[0].Tags.find((m) => m.name == 'Status')?.value).toEqual('Error'); + }); + + test(`Change item's price`, async () => { + expect((await Send(addSingleItem('hp', '0', '500'))).Output.data).not.toContain('Error'); + }); + + test(`Read item's info`, async () => { + const result = await Send({ Action: 'Read-Item', Item: 'hp' }); + const item = JSON.parse(result.Messages[0].Data); + const result2 = await Send({ Action: 'Read-Item', Item: 'ap' }); + const item2 = JSON.parse(result2.Messages[0].Data); + expect(item.Price).toBe('500'); + expect(item.Quantity).toBe('5'); + expect(item2.Price).toBe('200'); + expect(item2.Quantity).toBe('10'); + }); + + test(`Read item - not existing`, async () => { + expect( + (await Send({ Action: 'Read-Item', Item: 'random' })).Messages[0].Tags.find((t) => t.name == 'Status')?.value + ).toEqual('Error'); + }); + + test(`Create order for item`, async () => { + expect((await Send(createSingleOrder('1000', '2', 'hp'))).Output.data).not.toContain('Error'); + }); + + test('Create order - unathorized', async () => { + const action = createSingleOrder('1000', '2', 'hp'); + action.Owner = 'DIFFERENT_OWNER'; + action.From = 'DIFFERENT_OWNER'; + const result = await Send(action); + const message = findMessageBasedOnTag(result, 'Action', 'Create-Order-Error'); + const transferMessage = findMessageBasedOnTag(result, 'Action', 'Transfer'); + expect(getTagValue(message.Tags, 'Status')).toEqual('Error'); + expect(getTagValue(transferMessage.Tags, 'Quantity')).toEqual('1000'); + }); + + test(`Create order - item not in the hollow`, async () => { + const result = await Send(createSingleOrder('1000', '2', 'random')); + const message = findMessageBasedOnTag(result, 'Action', 'Create-Order-Error'); + const transferMessage = findMessageBasedOnTag(result, 'Action', 'Transfer'); + expect(getTagValue(message.Tags, 'Status')).toEqual('Error'); + expect(getTagValue(transferMessage.Tags, 'Quantity')).toEqual('1000'); + }); + + test(`Create order - not enough items in the hollow`, async () => { + const result = await Send(createSingleOrder('1000', '10', 'hp')); + const message = findMessageBasedOnTag(result, 'Action', 'Create-Order-Error'); + const transferMessage = findMessageBasedOnTag(result, 'Action', 'Transfer'); + expect(getTagValue(message.Tags, 'Status')).toEqual('Error'); + expect(getTagValue(transferMessage.Tags, 'Quantity')).toEqual('1000'); + }); + + test(`Create order - not enough tokens for an item`, async () => { + const result = await Send(createSingleOrder('1000', '3', 'hp')); + const message = findMessageBasedOnTag(result, 'Action', 'Create-Order-Error'); + const transferMessage = findMessageBasedOnTag(result, 'Action', 'Transfer'); + expect(getTagValue(message.Tags, 'Status')).toEqual('Error'); + expect(getTagValue(transferMessage.Tags, 'Quantity')).toEqual('1000'); + }); + + test(`Create order - should transfer remaining quantity if it's too much`, async () => { + const result = await Send(createSingleOrder('500', '2', 'ap')); + console.dir(result); + const transferMessage = findMessageBasedOnTag(result, 'Action', 'Transfer'); + expect(getTagValue(transferMessage.Tags, 'Quantity')).toEqual('100'); + }); + + test(`Read balance`, async () => { + const result = await Send({ + Action: 'Read-Balance', + ['Wallet-Address']: 'jmGGoJaDYDTx4OCM7MP-7l-VLIM4ZEGCS0cHPsSmiNE', + }); + const balance = JSON.parse(result.Messages[0].Data); + expect(balance.ap).toEqual('2'); + expect(balance.hp).toEqual('2'); + }); + + test(`Read hollow`, async () => { + const result = JSON.parse( + ( + await Send({ + Action: 'Read-Hollow', + }) + ).Messages[0].Data + ); + expect(result['ap'].Price).toBe('200'); + expect(result['hp'].Price).toBe('500'); + expect(result['ap'].Quantity).toBe('8'); + expect(result['hp'].Quantity).toBe('3'); + }); + + test(`Read orders`, async () => { + const result = JSON.parse( + ( + await Send({ + Action: 'Read-Orders', + }) + ).Messages[0].Data + ); + expect(result.length).toBe(2); + }); + + test(`Read orders - specific address`, async () => { + const result = JSON.parse( + ( + await Send({ + Action: 'Read-Orders', + ['Wallet-Address']: '123', + }) + ).Messages[0].Data + ); + expect(result.length).toBe(0); + }); +}); + +function addSingleItem(type, qty, price) { + return { + Action: 'Add-Item', + Item: type, + Quantity: qty, + Price: price, + }; +} + +function createSingleOrder(qty, itemQty, item) { + return { + ['Action']: 'Credit-Notice', + ['X-Order-Action']: 'Create-Order', + Sender: 'jmGGoJaDYDTx4OCM7MP-7l-VLIM4ZEGCS0cHPsSmiNE', + Quantity: qty, + ['Item-Quantity']: itemQty, + Item: item, + }; +} + +function findMessageBasedOnTag(result, name, value) { + return result.Messages.find((m) => m.Tags.find((t) => t.name == name && t.value == value)); +} + +function getTagValue(tags, name) { + return tags.find((m) => m.name == name)?.value; +} From 8d626f62541fb6be9037cb26309914023feb0d07 Mon Sep 17 00:00:00 2001 From: Asia Date: Mon, 25 Nov 2024 10:31:10 +0100 Subject: [PATCH 2/6] base hollow - review fixes --- lua/hollow/hollow-db.lua | 2 +- lua/hollow/hollow-utils.lua | 8 ++------ lua/hollow/hollow.lua | 20 +++++++++++++------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lua/hollow/hollow-db.lua b/lua/hollow/hollow-db.lua index bb9b49bc..fa8d8272 100644 --- a/lua/hollow/hollow-db.lua +++ b/lua/hollow/hollow-db.lua @@ -39,7 +39,7 @@ function ReadOrders(limit, offset, wallet_address) table.insert(valuesToBind, wallet_address) end - sql = sql .. ' LIMIT ? OFFSET ?;' + sql = sql .. ' ORDER BY timestamp DESC LIMIT ? OFFSET ?;' table.insert(valuesToBind, limit) table.insert(valuesToBind, offset) local stmt, err_parse = db:prepare(sql) diff --git a/lua/hollow/hollow-utils.lua b/lua/hollow/hollow-utils.lua index 3de9f079..84d2a6d3 100644 --- a/lua/hollow/hollow-utils.lua +++ b/lua/hollow/hollow-utils.lua @@ -5,7 +5,7 @@ function CheckValidAddress(address) return false end - return string.match(address, '^[%w%-_]+$') ~= nil and #address == 43 + return string.match(address, '^[%w%-_]+$') ~= nil and (#address == 43 or #address == 42) end function CheckValidAmount(data) @@ -24,11 +24,7 @@ function Subtract(a,b) return tostring(bint(a) - bint(b)) end -function ToNumber(a) - return tonumber(a) - end - - function HandleError(args) -- Target, TransferToken, Quantity +function HandleError(args) -- Target, TransferToken, Quantity -- If there is a valid quantity then return the funds if args.TransferToken and args.Quantity and CheckValidAmount(args.Quantity) then ao.send({ diff --git a/lua/hollow/hollow.lua b/lua/hollow/hollow.lua index b4f8e74f..306e5b77 100644 --- a/lua/hollow/hollow.lua +++ b/lua/hollow/hollow.lua @@ -1,5 +1,6 @@ local bint = require('.bint')(256) local sqlite3 = require("lsqlite3") +local json = require('json') Owner = Owner or ao.env.Process.Owner @@ -23,6 +24,7 @@ ao.authorities = {"fcoN_xJeisVsPXA-trzVAuIiqO3ydLQxM-L4XbrQKzY", "jmGGoJaDYDTx4O db = db or sqlite3.open_memory() -- init db +db:exec("BEGIN TRANSACTION;") db:exec([[ create table if not exists orders( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -33,6 +35,10 @@ create table if not exists orders( payment INTEGER NOT NULL ); ]]) +db:exec([[ + CREATE INDEX IF NOT EXISTS idx_wallet_address_timestamp ON orders (wallet_address, timestamp DESC); +]]) +db:exec("COMMIT;") Handlers.add('Credit-Notice', Handlers.utils.hasMatchingTag('Action', 'Credit-Notice'), function(msg) local data = { @@ -126,7 +132,7 @@ Handlers.add('Credit-Notice', Handlers.utils.hasMatchingTag('Action', 'Credit-No end -- check if there are enough items in the hollow - if ToNumber(Items[orderArgs.item].Quantity) < ToNumber(orderArgs.itemQuantity) then + if tonumber(Items[orderArgs.item].Quantity) < tonumber(orderArgs.itemQuantity) then HandleError({ Target = orderArgs.sender, Action = 'Create-Order-Error', @@ -140,7 +146,7 @@ Handlers.add('Credit-Notice', Handlers.utils.hasMatchingTag('Action', 'Credit-No -- check if price of ordered items alligns with quantity of the Credit-Notice local totalItemPrice = Multiply(orderArgs.itemQuantity, Items[orderArgs.item].Price) - if ToNumber(totalItemPrice) <= ToNumber(orderArgs.quantity) then + if tonumber(totalItemPrice) <= tonumber(orderArgs.quantity) then if not Balances[orderArgs.sender] then Balances[orderArgs.sender] = {} end if not Balances[orderArgs.sender][orderArgs.item] then Balances[orderArgs.sender][orderArgs.item] = '0' @@ -156,7 +162,7 @@ Handlers.add('Credit-Notice', Handlers.utils.hasMatchingTag('Action', 'Credit-No -- return remaining token quantity to the sender - if ToNumber(totalItemPrice) < ToNumber(orderArgs.quantity) then + if tonumber(totalItemPrice) < tonumber(orderArgs.quantity) then ao.send({ Target = msg.From, Action = 'Transfer', @@ -236,7 +242,7 @@ Handlers.add('Read-Item', Handlers.utils.hasMatchingTag('Action', 'Read-Item'), ao.send({ Target = msg.From, Action = 'Item-Info', - Data = Items[msg.Tags.Item], + Data = json.encode(Items[msg.Tags.Item]), }) end) @@ -257,7 +263,7 @@ Handlers.add('Read-Balance', Handlers.utils.hasMatchingTag('Action', 'Read-Balan ao.send({ Target = msg.From, Action = 'Read-Balance-Success', - Data = Balances[msg.Tags['Wallet-Address']], + Data = json.encode(Balances[msg.Tags['Wallet-Address']]), }) end) @@ -265,7 +271,7 @@ Handlers.add('Read-Hollow', Handlers.utils.hasMatchingTag('Action', 'Read-Hollow ao.send({ Target = msg.From, Action = 'Read-Hollow-Success', - Data = Items, + Data = json.encode(Items), }) end) @@ -277,6 +283,6 @@ Handlers.add('Read-Orders', Handlers.utils.hasMatchingTag('Action', 'Read-Orders ao.send({ Target = msg.From, Action = 'Read-Orders-Success', - Data = result, + Data = json.encode(result), }) end) From e386301eca6c3bc3963c2a3b794e3b13d16ffd84 Mon Sep 17 00:00:00 2001 From: Asia Date: Wed, 27 Nov 2024 14:12:43 +0100 Subject: [PATCH 3/6] hollow - scripts --- lua/hollow/hollow.lua | 15 +++++++- src/game/config/warp-ao-ids.js | 44 +++++++++++----------- src/game/process/game.mjs | 4 +- src/game/process/hub.mjs | 1 - tools/deploy/config/session-local-ao.json | 28 +------------- tools/hollow/add-item.js | 33 ++++++++++++++++ tools/hollow/create-order.js | 37 ++++++++++++++++++ tools/hollow/hollow-id.txt | 1 + tools/hollow/read-balance.js | 19 ++++++++++ tools/hollow/read-hollow.js | 16 ++++++++ tools/hollow/read-orders.js | 19 ++++++++++ tools/hollow/send-interaction-to-game.js | 46 +++++++++++++++++++++++ tools/hollow/spawn-hollow.js | 45 ++++++++++++++++++++++ tools/hollow/utils.js | 9 +++++ 14 files changed, 264 insertions(+), 53 deletions(-) create mode 100644 tools/hollow/add-item.js create mode 100644 tools/hollow/create-order.js create mode 100644 tools/hollow/hollow-id.txt create mode 100644 tools/hollow/read-balance.js create mode 100644 tools/hollow/read-hollow.js create mode 100644 tools/hollow/read-orders.js create mode 100644 tools/hollow/send-interaction-to-game.js create mode 100644 tools/hollow/spawn-hollow.js create mode 100644 tools/hollow/utils.js diff --git a/lua/hollow/hollow.lua b/lua/hollow/hollow.lua index 306e5b77..38fcb1eb 100644 --- a/lua/hollow/hollow.lua +++ b/lua/hollow/hollow.lua @@ -99,8 +99,8 @@ Handlers.add('Credit-Notice', Handlers.utils.hasMatchingTag('Action', 'Credit-No local orderArgs = { orderId = msg.Id, token = msg.From, - item = msg.Tags.Item, - itemQuantity = msg.Tags['Item-Quantity'], + item = msg.Tags['X-Item'], + itemQuantity = msg.Tags['X-Item-Quantity'], sender = data.Sender, quantity = data.Quantity, timestamp = msg.Timestamp, @@ -172,6 +172,17 @@ Handlers.add('Credit-Notice', Handlers.utils.hasMatchingTag('Action', 'Credit-No } }) end + + -- send tokens to the token proces + ao.send({ + Target = msg.From, + Action = 'Transfer', + Tags = { + Recipient = msg.From, + Quantity = tostring(totalItemPrice) + } + }) + InsertOrder(orderArgs.sender, msg.Timestamp, orderArgs.item, orderArgs.itemQuantity, totalItemPrice) return else diff --git a/src/game/config/warp-ao-ids.js b/src/game/config/warp-ao-ids.js index a3901809..f89c8de7 100644 --- a/src/game/config/warp-ao-ids.js +++ b/src/game/config/warp-ao-ids.js @@ -1,23 +1,23 @@ export default { - hub_moduleId_prod: '7DIoC6Ctz09MAwtBs3NX25-BQZWtqnQqpBU09lKmb2I', - hub_processId_prod: '-7x_Zi_75dA9id7eCTiT2TisnuTFN9cyPJbscvu1yMw', - hub_moduleId_local: 'lqWpkoOURkAn_eFEFzeGlQn-SsbPNJc8IyQR0TQJQdY', - hub_processId_local: 'vQzsHcGgOrrLSMJsjnITQS-D17NOumDRCr8Hfs1eUuw', - hub_moduleId_dev: 'aaabbb', - hub_processId_dev: 'aaabbb', - token_processId_prod: 'rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8', - processId_prod: 'Olq489PnNOCaGYl7W59cuqHXVLtLnZ7GZzHh2A8OvBk', - moduleId_prod: '7m6fKm42USDFRi8sLg_fPszkZ73Bqn9pUr5oWHCZiC8', - mapId_prod: 'RZrD8U6MsN48I5Z2lkBM4biSiVehB0eVbOA3pcXI_9U', - leaderboard_processId_prod: '5qzT9T2xH7sfnE66gtICnI_71c4dc9mSl0uOqYnX2o0', - leaderboard_processId_local: 'lCviigwaPHbR8pUr8H6tbOHP_Syyu1cP4mY9ayrodF0', - leaderboard_processId_dev: 'lCviigwaPHbR8pUr8H6tbOHP_Syyu1cP4mY9ayrodF0', - token_processId_local: 'rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8', - processId_local: 'oz3dEfpoAUN-kkxOKvtSPaUSiwCZgpMgKrV1C3WPJDg', - moduleId_local: 'wx-Pl9QvvTTHGrWCNSmGOVn80b1DO75q2YlY55mGdtg', - mapId_local: 'RZrD8U6MsN48I5Z2lkBM4biSiVehB0eVbOA3pcXI_9U', - token_processId_dev: 'rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8', - processId_dev: 'N8b2aPBXFhtZXbygq3wveXukUCTKKSFEu5qxB4CL-zU', - moduleId_dev: 'ZGU4Q6U_XBOVRxkTz7cxYKC7-iWdYg7TVHWihel7z9I', - mapId_dev: 'RZrD8U6MsN48I5Z2lkBM4biSiVehB0eVbOA3pcXI_9U', -}; + "hub_moduleId_prod": "7DIoC6Ctz09MAwtBs3NX25-BQZWtqnQqpBU09lKmb2I", + "hub_processId_prod": "-7x_Zi_75dA9id7eCTiT2TisnuTFN9cyPJbscvu1yMw", + "hub_moduleId_local": "R5LMaFWBcA6mGthG57n5ZLyRdvtTyZnWgK8UFcn1qi8", + "hub_processId_local": "sK24pd-XiM9TlB_1rS2r8DCB-66hzMSBCggCcKkjY6I", + "hub_moduleId_dev": "aaabbb", + "hub_processId_dev": "aaabbb", + "token_processId_prod": "rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8", + "processId_prod": "Olq489PnNOCaGYl7W59cuqHXVLtLnZ7GZzHh2A8OvBk", + "moduleId_prod": "7m6fKm42USDFRi8sLg_fPszkZ73Bqn9pUr5oWHCZiC8", + "mapId_prod": "RZrD8U6MsN48I5Z2lkBM4biSiVehB0eVbOA3pcXI_9U", + "leaderboard_processId_prod": "5qzT9T2xH7sfnE66gtICnI_71c4dc9mSl0uOqYnX2o0", + "leaderboard_processId_local": "lCviigwaPHbR8pUr8H6tbOHP_Syyu1cP4mY9ayrodF0", + "leaderboard_processId_dev": "lCviigwaPHbR8pUr8H6tbOHP_Syyu1cP4mY9ayrodF0", + "token_processId_local": "rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8", + "processId_local": "oz3dEfpoAUN-kkxOKvtSPaUSiwCZgpMgKrV1C3WPJDg", + "moduleId_local": "wx-Pl9QvvTTHGrWCNSmGOVn80b1DO75q2YlY55mGdtg", + "mapId_local": "RZrD8U6MsN48I5Z2lkBM4biSiVehB0eVbOA3pcXI_9U", + "token_processId_dev": "rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8", + "processId_dev": "N8b2aPBXFhtZXbygq3wveXukUCTKKSFEu5qxB4CL-zU", + "moduleId_dev": "ZGU4Q6U_XBOVRxkTz7cxYKC7-iWdYg7TVHWihel7z9I", + "mapId_dev": "RZrD8U6MsN48I5Z2lkBM4biSiVehB0eVbOA3pcXI_9U" +}; \ No newline at end of file diff --git a/src/game/process/game.mjs b/src/game/process/game.mjs index 203ba2d9..42de99cb 100644 --- a/src/game/process/game.mjs +++ b/src/game/process/game.mjs @@ -33,7 +33,9 @@ function addToLastTxs(message, state) { } } -export function handle(state, message) { +export async function handle(state, message) { + const test = await readExternal('iWM-odlyQHopPECpyz465p7ED8lm5d3hyyWtijKhie4', 'Read-Hollow'); + console.log(test); state.randomCounter = 0; if (state.hasOwnProperty('rawMap')) { __init(state, message); diff --git a/src/game/process/hub.mjs b/src/game/process/hub.mjs index 01c2692e..e181348f 100644 --- a/src/game/process/hub.mjs +++ b/src/game/process/hub.mjs @@ -2,7 +2,6 @@ import Const from '../common/const.mjs'; export function handle(state, message) { console.log('-- Game Hub, message from ', message.Owner); - const actionTagValue = message.Tags?.find((t) => t.name === 'Action')?.value; const gameProcess = message.Tags?.find((t) => t.name === 'From-Process')?.value; const module = message.Tags?.find((t) => t.name === 'From-Module')?.value; diff --git a/tools/deploy/config/session-local-ao.json b/tools/deploy/config/session-local-ao.json index 9cee72c9..e3e5dc5a 100644 --- a/tools/deploy/config/session-local-ao.json +++ b/tools/deploy/config/session-local-ao.json @@ -28,37 +28,11 @@ "start": { "secondsFromNow": -60 }, - "gameDuration": 420000, + "gameDuration": 10800000, "gameDuration_comment": "1 minute", "gameBreak": 10000, "gameBreak_comment": "10 seconds", "games": [{ "map": "b2m3" }] - }, - { - "start": { - "secondsFromNow": 20 - }, - "gameDuration": 420000, - "gameDuration_comment": "1 minute", - "gameBreak": 10000, - "gameBreak_comment": "10 seconds", - "commonSetup": { - "playersLimit": 20 - }, - "games": [{ "map": "b2m1" }] - }, - { - "start": { - "secondsFromNow": 20 - }, - "gameDuration": 420000, - "gameDuration_comment": "1 minute", - "gameBreak": 10000, - "gameBreak_comment": "10 seconds", - "commonSetup": { - "playersLimit": 20 - }, - "games": [{ "map": "b2m2" }] } ], "tokensShipment": { diff --git a/tools/hollow/add-item.js b/tools/hollow/add-item.js new file mode 100644 index 00000000..cf558489 --- /dev/null +++ b/tools/hollow/add-item.js @@ -0,0 +1,33 @@ +import { connect, createDataItemSigner } from '@permaweb/aoconnect'; +import fs from 'node:fs'; +import { backOff } from 'exponential-backoff'; +import { getHollowId } from './utils.js'; + +const { message } = connect(); + +const WALLET = JSON.parse(fs.readFileSync('.secrets/general/jwk.json', 'utf-8')); +const hollowId = getHollowId(); +async function addItem() { + const signer = createDataItemSigner(WALLET); + try { + const r = await backOff(() => + message({ + process: hollowId, + data: '1234', + tags: [ + { name: 'Action', value: 'Add-Item' }, + { name: 'Item', value: 'ap' }, + { name: 'Quantity', value: '50' }, + { name: 'Price', value: '100' }, + ], + signer, + }) + ); + console.log(`Successfully sent 'Add-Item' action for process '${hollowId}'.`); + console.log(`https://www.ao.link/#/message/${r}`); + } catch (e) { + console.error(e); + } +} + +addItem().then(() => console.log(`Message sent.`)); diff --git a/tools/hollow/create-order.js b/tools/hollow/create-order.js new file mode 100644 index 00000000..ac3ce022 --- /dev/null +++ b/tools/hollow/create-order.js @@ -0,0 +1,37 @@ +import { connect, createDataItemSigner } from '@permaweb/aoconnect'; +import fs from 'node:fs'; +import { backOff } from 'exponential-backoff'; +import { getHollowId } from './utils.js'; + +const { message } = connect(); + +const WALLET = JSON.parse(fs.readFileSync('.secrets/general/jwk.json', 'utf-8')); +const hollowId = getHollowId(); +const tokenId = 'rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8'; + +async function createOrder() { + const signer = createDataItemSigner(WALLET); + try { + const transferId = await backOff(() => + message({ + process: tokenId, + data: '1234', + tags: [ + { name: 'Action', value: 'Transfer' }, + { name: 'Quantity', value: '100' }, + { name: 'Recipient', value: hollowId }, + { name: 'X-Order-Action', value: 'Create-Order' }, + { name: 'X-Item-Quantity', value: '1' }, + { name: 'X-Item', value: 'ap' }, + ], + signer, + }) + ); + console.log(`Successfully sent 'Transfer' action for process '${tokenId}'.`); + console.log(`https://www.ao.link/#/message/${transferId}`); + } catch (e) { + console.error(e); + } +} + +createOrder().then(() => console.log(`Order created.`)); diff --git a/tools/hollow/hollow-id.txt b/tools/hollow/hollow-id.txt new file mode 100644 index 00000000..7e1204e8 --- /dev/null +++ b/tools/hollow/hollow-id.txt @@ -0,0 +1 @@ +iWM-odlyQHopPECpyz465p7ED8lm5d3hyyWtijKhie4 \ No newline at end of file diff --git a/tools/hollow/read-balance.js b/tools/hollow/read-balance.js new file mode 100644 index 00000000..a3961f93 --- /dev/null +++ b/tools/hollow/read-balance.js @@ -0,0 +1,19 @@ +import { dryrun } from '@permaweb/aoconnect'; +import { getHollowId } from './utils.js'; + +const hollowId = getHollowId(); + +async function readBalance() { + const readRes = await dryrun({ + process: hollowId, + tags: [ + { name: 'Action', value: 'Read-Balance' }, + { name: 'Wallet-Address', value: 'jmGGoJaDYDTx4OCM7MP-7l-VLIM4ZEGCS0cHPsSmiNE' }, + ], + data: '1234', + }); + const res = JSON.parse(readRes.Messages[0].Data); + console.log(res); +} + +readBalance().then(() => console.log(`THE END`)); diff --git a/tools/hollow/read-hollow.js b/tools/hollow/read-hollow.js new file mode 100644 index 00000000..e48e15c8 --- /dev/null +++ b/tools/hollow/read-hollow.js @@ -0,0 +1,16 @@ +import { dryrun } from '@permaweb/aoconnect'; +import { getHollowId } from './utils.js'; + +const hollowId = getHollowId(); + +async function readHollow() { + const readRes = await dryrun({ + process: hollowId, + tags: [{ name: 'Action', value: 'Read-Hollow' }], + data: '1234', + }); + const res = JSON.parse(readRes.Messages[0].Data); + console.log(res); +} + +readHollow().then(() => console.log(`THE END`)); diff --git a/tools/hollow/read-orders.js b/tools/hollow/read-orders.js new file mode 100644 index 00000000..cd2650ba --- /dev/null +++ b/tools/hollow/read-orders.js @@ -0,0 +1,19 @@ +import { dryrun } from '@permaweb/aoconnect'; +import { getHollowId } from './utils.js'; + +const hollowId = getHollowId(); + +async function readOrders() { + const readRes = await dryrun({ + process: hollowId, + tags: [ + { name: 'Action', value: 'Read-Orders' }, + { name: 'Wallet-Address', value: 'jmGGoJaDYDTx4OCM7MP-7l-VLIM4ZEGCS0cHPsSmiNE' }, + ], + data: '1234', + }); + const res = JSON.parse(readRes.Messages[0].Data); + console.log(res); +} + +readOrders().then(() => console.log(`THE END`)); diff --git a/tools/hollow/send-interaction-to-game.js b/tools/hollow/send-interaction-to-game.js new file mode 100644 index 00000000..ee485a54 --- /dev/null +++ b/tools/hollow/send-interaction-to-game.js @@ -0,0 +1,46 @@ +import { connect, createDataItemSigner } from '@permaweb/aoconnect'; +import fs from 'node:fs'; +import { createData } from 'warp-arbundles'; +import { Tag } from 'warp-contracts'; +import { ArweaveSigner } from 'warp-contracts-plugin-deploy'; + +const { message } = connect(); + +const WALLET = JSON.parse(fs.readFileSync('.secrets/general/jwk.json', 'utf-8')); +const signer = new ArweaveSigner(WALLET); + +async function createOrder() { + try { + const processTags = [ + new Tag('Data-Protocol', 'ao'), + new Tag('Variant', 'ao.TN.1'), + new Tag('Type', 'Message'), + new Tag('From-Process', '8rIkbRxf64wSTpm5zJ91d4qneJZ4cdBzqGXXuDkClV0'), + new Tag('From-Module', 'xvI2Mak44Wf_OtNJFh11ZUopPXVqxg-g6A075nMcdTw'), + new Tag('Action', JSON.stringify({ cmd: 'pick' })), + new Tag('SDK', 'ao'), + new Tag('Name', 'pick'), + ]; + + const data = JSON.stringify('{}'); + const processDataItem = createData(data, signer, { + tags: processTags, + target: '8rIkbRxf64wSTpm5zJ91d4qneJZ4cdBzqGXXuDkClV0', + }); + await processDataItem.sign(signer); + + const test = await fetch('http://localhost:8081', { + method: 'POST', + headers: { + 'Content-Type': 'application/octet-stream', + Accept: 'application/json', + }, + body: processDataItem.getRaw(), + }).then((res) => res.json()); + console.log(test); + } catch (e) { + console.error(e); + } +} + +createOrder().then(() => console.log(`Order created.`)); diff --git a/tools/hollow/spawn-hollow.js b/tools/hollow/spawn-hollow.js new file mode 100644 index 00000000..2efb583d --- /dev/null +++ b/tools/hollow/spawn-hollow.js @@ -0,0 +1,45 @@ +import { connect, createDataItemSigner } from '@permaweb/aoconnect'; +import fs from 'node:fs'; +import { backOff } from 'exponential-backoff'; + +const AOS_MODULE_SQLITE = 'CJ-iZL7RKNA43UZr3l6J5M8JegMP9RldoCoVge_vRuI'; +const AO_TESTNET_SU = '_GQ33BkPtZrqxA84vM8Zk-N2aO0toNNu_C-l-rawrBA'; + +const { spawn, message } = connect(); + +const WALLET = JSON.parse(fs.readFileSync('.secrets/general/jwk.json', 'utf-8')); + +async function doSpawn(fileName) { + const CODE = fs.readFileSync(`./lua/hollow/${fileName}`, 'utf-8'); + const signer = createDataItemSigner(WALLET); + + console.info(`Spawning AOS ${fileName} Lua process`); + const processId = 'iWM-odlyQHopPECpyz465p7ED8lm5d3hyyWtijKhie4'; + // const processId = await spawn({ + // module: AOS_MODULE_SQLITE, + // scheduler: AO_TESTNET_SU, + // signer, + // data: '1984', + // }); + + try { + const r = await backOff(() => + message({ + process: processId, + data: CODE, + tags: [{ name: 'Action', value: 'Eval' }], + signer, + }) + ); + console.log(`Successfully sent 'eval' action for process '${processId}'.`); + console.log(`https://www.ao.link/#/message/${r}`); + + fs.writeFileSync(`./tools/hollow/hollow-id.txt`, processId, 'utf-8'); + } catch (e) { + console.error(e); + } + + return processId; +} + +doSpawn('hollow.lua').then((processId) => console.log(`https://www.ao.link/#/entity/${processId}`)); diff --git a/tools/hollow/utils.js b/tools/hollow/utils.js new file mode 100644 index 00000000..08c00ab0 --- /dev/null +++ b/tools/hollow/utils.js @@ -0,0 +1,9 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +export function getHollowId() { + const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file + const __dirname = path.dirname(__filename); // get the name of the directory + return fs.readFileSync(path.join(__dirname, 'hollow-id.txt'), 'utf-8'); +} From 40a8e703a02e3294dc13644296e8c7b522fa4e3b Mon Sep 17 00:00:00 2001 From: Asia Date: Wed, 11 Dec 2024 12:58:02 +0100 Subject: [PATCH 4/6] hollow - ui --- index.html | 10 +- package.json | 2 +- public/css/mithril/app.css | 18 +++ public/css/mithril/market-page.css | 203 +++++++++++++++++++++++++++++ src/game/config/warp-ao-ids.js | 44 +++---- src/game/gui/ConnectWalletGui.js | 17 ++- src/game/gui/pages/GamePage.js | 13 ++ src/game/gui/pages/MarketPage.js | 102 +++++++++++++++ src/main.js | 8 +- src/routes.js | 8 ++ yarn.lock | 126 +++++++++++------- 11 files changed, 470 insertions(+), 81 deletions(-) create mode 100644 public/css/mithril/market-page.css create mode 100644 src/game/gui/pages/GamePage.js create mode 100644 src/game/gui/pages/MarketPage.js create mode 100644 src/routes.js diff --git a/index.html b/index.html index 6bf62141..54a3eebc 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,7 @@ + Cyberpunk Beavers @@ -33,6 +34,10 @@ padding: 0px; overflow: hidden; height: 100%; + + @media (max-width: 600px) { + overflow: auto; + } } #mithril-gui { @@ -61,10 +66,9 @@ -
+
- - + diff --git a/package.json b/package.json index 12809421..38172ef7 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "mrpas": "^2.0.0", "phaser": "^3.80.1", "warp-contracts": "^1.4.45", - "warp-contracts-plugin-quickjs": "^1.1.10", + "warp-contracts-plugin-quickjs": "^1.1.13", "warp-contracts-plugin-signature": "^1.0.20", "webfontloader": "^1.6.28", "ws": "^8.16.0" diff --git a/public/css/mithril/app.css b/public/css/mithril/app.css index dde1efab..b4d49a89 100644 --- a/public/css/mithril/app.css +++ b/public/css/mithril/app.css @@ -1,6 +1,8 @@ :root { /* colors */ --yellow: #fcee09; + --golden: #ffd700; + --dark-yellow: #ffcc66; --white: #ffffff; --green: #00ff00; --darkgreen: darkgreen; @@ -11,8 +13,15 @@ --blue: rgba(0, 140, 255, 0.83); --ap: #25a31f; --boxblack: #050a0e; + --black: #000000; + --violet: #8000ff; + --grey: #ccc; + --light-violet: #9a33ff; + --neon-blue: #00bfff; + --turquoise: #00ffff; /* font sizes */ + --fs-16: 16px; --regular: 22px; --big: 25px; --big-title: 50px; @@ -22,6 +31,11 @@ /* font families */ --ff-main: 'Press Start 2P'; + --ff-poppins: 'Poppins ', sans-serif; + + /* screen */ + --mobile: 600px; + --tablet: 1200px; } body { @@ -30,6 +44,10 @@ body { background-color: #000000; font-family: 'Courier New', Courier, monospace; font-size: 16px; + + @media (max-width: 600px) { + overflow: auto; + } } .d-flex { diff --git a/public/css/mithril/market-page.css b/public/css/mithril/market-page.css new file mode 100644 index 00000000..a4aea82e --- /dev/null +++ b/public/css/mithril/market-page.css @@ -0,0 +1,203 @@ +.marketplace { + font-size: var(--regular); + font-weight: bold; + width: 100%; + height: 100%; + background: linear-gradient(135deg, #1a1d30, #2a2d47 40%, #803399 60%, #d1267a 80%); + background-repeat: no-repeat; + background-size: cover; + background-position: center; + font-family: 'Bebas Neue', sans-serif; + text-transform: uppercase; + margin-bottom: 5%; + color: transparent; + text-shadow: none; + letter-spacing: 3px; + line-height: 1.2; + position: absolute; + + .container { + position: absolute; + left: 10%; + top: 5%; + right: 10%; + bottom: 10%; + + @media (max-width: var(--mobile)) { + right: 0%; + left: 0%; + } + + .products { + display: flex; + flex-wrap: wrap; + margin-left: -16px; + margin-right: -16px; + + @media (max-width: var(--mobile)) { + justify-content: center; + } + } + + .product-tile { + flex: 1 1 calc(25% - 32px); + margin: 16px; + box-sizing: border-box; + min-width: 200px; + max-width: 300px; + padding: 16px; + align-items: center; + display: flex; + flex-direction: column; + + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 8px; + max-width: 300px; + flex: 1 1 calc(25% - 32px); + box-sizing: border-box; + background: rgba(0, 0, 0, 0.5); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + transition: + transform 0.3s, + background-color 0.3s; + } + + .product-tile:hover { + transform: scale(1.05); + background: rgba(255, 255, 255, 0.1); + cursor: pointer; + } + + @media (min-width: 601px) { + .products::after { + content: ''; + flex: auto; + margin-left: 16px; + } + } + + .title { + text-align: center; + font-size: 60px; + margin-bottom: 5%; + color: transparent; + -webkit-text-stroke: 2px var(--turquoise); + text-shadow: none; + letter-spacing: 3px; + line-height: 1.2; + + @media (max-width: var(--tablet)) { + font-size: 40px; + margin-top: 10%; + } + } + + img { + transition: 0.3s; + width: 100px; + } + + img:hover { + transform: scale(1.2); + cursor: pointer; + } + } + + .product-info { + width: 100%; + margin-top: 12px; + } + + .name, + .price, + .available { + -webkit-text-stroke: 2px var(--dark-yellow); + font-size: 20px; + margin: 4px 0; + } + + .quantity-control { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + gap: 8px; + } + + .styled-input { + flex: 1; + text-align: center; + padding: 6px; + border: 1px solid var(--grey); + border-radius: 4px; + font-size: 16px; + } + + button { + padding: 8px 12px; + border: none; + border-radius: 4px; + background-color: var(--neon-blue); + color: var(--white); + font-size: 16px; + cursor: pointer; + transition: background-color 0.3s; + } + + button:hover { + background-color: var(--light-violet); + } + + button:disabled { + background-color: var(--grey); + cursor: not-allowed; + } + + .buy-button { + background-color: var(--violet); + color: var(--black); + border: none; + padding: 12px 16px; + border-radius: 4px; + font-size: 18px; + font-weight: bold; + cursor: pointer; + transition: background-color 0.3s; + margin-top: 12px; + width: 100%; + } +} + +.market-connect-wallet { + position: absolute; + top: 20px; + right: 20px; + background: transparent; + border: 2px solid var(--golden); + color: var(--golden); + font-family: var(--ff-poppins); + font-size: var(--fs-16); + font-weight: bold; + padding: 12px 24px; + border-radius: 8px; + cursor: pointer; + text-transform: uppercase; + letter-spacing: 2px; + transition: all 0.3s ease; + z-index: 10; + width: 200px; + height: auto; + text-align: center; +} + +.market-connect-wallet:hover { + background: var(--golden); + color: var(--black); + box-shadow: + 0 0 10px var(--golden), + 0 0 20px var(--golden); +} + +.market-connect-wallet:active { + transform: scale(0.95); +} diff --git a/src/game/config/warp-ao-ids.js b/src/game/config/warp-ao-ids.js index f89c8de7..6d55c31c 100644 --- a/src/game/config/warp-ao-ids.js +++ b/src/game/config/warp-ao-ids.js @@ -1,23 +1,23 @@ export default { - "hub_moduleId_prod": "7DIoC6Ctz09MAwtBs3NX25-BQZWtqnQqpBU09lKmb2I", - "hub_processId_prod": "-7x_Zi_75dA9id7eCTiT2TisnuTFN9cyPJbscvu1yMw", - "hub_moduleId_local": "R5LMaFWBcA6mGthG57n5ZLyRdvtTyZnWgK8UFcn1qi8", - "hub_processId_local": "sK24pd-XiM9TlB_1rS2r8DCB-66hzMSBCggCcKkjY6I", - "hub_moduleId_dev": "aaabbb", - "hub_processId_dev": "aaabbb", - "token_processId_prod": "rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8", - "processId_prod": "Olq489PnNOCaGYl7W59cuqHXVLtLnZ7GZzHh2A8OvBk", - "moduleId_prod": "7m6fKm42USDFRi8sLg_fPszkZ73Bqn9pUr5oWHCZiC8", - "mapId_prod": "RZrD8U6MsN48I5Z2lkBM4biSiVehB0eVbOA3pcXI_9U", - "leaderboard_processId_prod": "5qzT9T2xH7sfnE66gtICnI_71c4dc9mSl0uOqYnX2o0", - "leaderboard_processId_local": "lCviigwaPHbR8pUr8H6tbOHP_Syyu1cP4mY9ayrodF0", - "leaderboard_processId_dev": "lCviigwaPHbR8pUr8H6tbOHP_Syyu1cP4mY9ayrodF0", - "token_processId_local": "rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8", - "processId_local": "oz3dEfpoAUN-kkxOKvtSPaUSiwCZgpMgKrV1C3WPJDg", - "moduleId_local": "wx-Pl9QvvTTHGrWCNSmGOVn80b1DO75q2YlY55mGdtg", - "mapId_local": "RZrD8U6MsN48I5Z2lkBM4biSiVehB0eVbOA3pcXI_9U", - "token_processId_dev": "rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8", - "processId_dev": "N8b2aPBXFhtZXbygq3wveXukUCTKKSFEu5qxB4CL-zU", - "moduleId_dev": "ZGU4Q6U_XBOVRxkTz7cxYKC7-iWdYg7TVHWihel7z9I", - "mapId_dev": "RZrD8U6MsN48I5Z2lkBM4biSiVehB0eVbOA3pcXI_9U" -}; \ No newline at end of file + hub_moduleId_prod: '7DIoC6Ctz09MAwtBs3NX25-BQZWtqnQqpBU09lKmb2I', + hub_processId_prod: '-7x_Zi_75dA9id7eCTiT2TisnuTFN9cyPJbscvu1yMw', + hub_moduleId_local: 'R5LMaFWBcA6mGthG57n5ZLyRdvtTyZnWgK8UFcn1qi8', + hub_processId_local: 'sK24pd-XiM9TlB_1rS2r8DCB-66hzMSBCggCcKkjY6I', + hub_moduleId_dev: 'aaabbb', + hub_processId_dev: 'aaabbb', + token_processId_prod: 'rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8', + processId_prod: 'Olq489PnNOCaGYl7W59cuqHXVLtLnZ7GZzHh2A8OvBk', + moduleId_prod: '7m6fKm42USDFRi8sLg_fPszkZ73Bqn9pUr5oWHCZiC8', + mapId_prod: 'RZrD8U6MsN48I5Z2lkBM4biSiVehB0eVbOA3pcXI_9U', + leaderboard_processId_prod: '5qzT9T2xH7sfnE66gtICnI_71c4dc9mSl0uOqYnX2o0', + leaderboard_processId_local: 'lCviigwaPHbR8pUr8H6tbOHP_Syyu1cP4mY9ayrodF0', + leaderboard_processId_dev: 'lCviigwaPHbR8pUr8H6tbOHP_Syyu1cP4mY9ayrodF0', + token_processId_local: 'rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8', + processId_local: 'oz3dEfpoAUN-kkxOKvtSPaUSiwCZgpMgKrV1C3WPJDg', + moduleId_local: 'wx-Pl9QvvTTHGrWCNSmGOVn80b1DO75q2YlY55mGdtg', + mapId_local: 'RZrD8U6MsN48I5Z2lkBM4biSiVehB0eVbOA3pcXI_9U', + token_processId_dev: 'rH_-7vT_IgfFWiDsrcTghIhb9aRclz7lXcK7RCOV2h8', + processId_dev: 'N8b2aPBXFhtZXbygq3wveXukUCTKKSFEu5qxB4CL-zU', + moduleId_dev: 'ZGU4Q6U_XBOVRxkTz7cxYKC7-iWdYg7TVHWihel7z9I', + mapId_dev: '4zykod9G8typt6OjaX_Lfd_eSM0LC5O5Be3LZMfoHto', +}; diff --git a/src/game/gui/ConnectWalletGui.js b/src/game/gui/ConnectWalletGui.js index d3efcd02..d98b1f23 100644 --- a/src/game/gui/ConnectWalletGui.js +++ b/src/game/gui/ConnectWalletGui.js @@ -6,7 +6,6 @@ import { getAddress } from '@ethersproject/address'; import m from 'mithril'; const CHANGE_SCENE_TIMEOUT_MS = window.warpAO.config.env === 'dev' ? 0 : 2000; - export function ConnectWalletSceneGui(initialVnode) { let walletConnectionText; let walletErrorText = null; @@ -141,11 +140,25 @@ export function ConnectWalletSceneGui(initialVnode) { }, style: { width: '150px', - marginBottom: '40px', + marginBottom: '20px', }, }, 'Read manual' ), + m( + '.button yellow', + { + onclick: async () => { + playClick(); + m.route.set('/market'); + }, + style: { + width: '150px', + marginBottom: '40px', + }, + }, + 'Market' + ), m( '.button green', { diff --git a/src/game/gui/pages/GamePage.js b/src/game/gui/pages/GamePage.js new file mode 100644 index 00000000..c197975a --- /dev/null +++ b/src/game/gui/pages/GamePage.js @@ -0,0 +1,13 @@ +import '../../config/warp-ao.js'; +import { config } from '../../../main.js'; + +export function GamePage() { + return { + oncreate: () => { + window.game = new Phaser.Game(config); + }, + view: () => { + console.log(`Game loaded.`); + }, + }; +} diff --git a/src/game/gui/pages/MarketPage.js b/src/game/gui/pages/MarketPage.js new file mode 100644 index 00000000..9dfe76bb --- /dev/null +++ b/src/game/gui/pages/MarketPage.js @@ -0,0 +1,102 @@ +import m from 'mithril'; +import { playClick } from '../../utils/mithril'; +import { hideGui } from '../../utils/mithril'; + +export function MarketPage() { + return { + oninit: function (vnode) { + hideGui(); + + if (window.game) { + window.game.destroy(true, false); + } + vnode.state.products = [ + { id: 1, name: 'Product A', price: 20, available: 0, imageUrl: '/assets/images/equipment/landmine.png' }, + { id: 2, name: 'Product B', price: 30, available: 30, imageUrl: '/assets/images/equipment/scanner_device.png' }, + { id: 2, name: 'Product B', price: 30, available: 30, imageUrl: '/assets/images/equipment/scanner_device.png' }, + { id: 2, name: 'Product B', price: 30, available: 30, imageUrl: '/assets/images/equipment/scanner_device.png' }, + { id: 2, name: 'Product B', price: 30, available: 30, imageUrl: '/assets/images/equipment/scanner_device.png' }, + { id: 2, name: 'Product B', price: 30, available: 30, imageUrl: '/assets/images/equipment/scanner_device.png' }, + { id: 2, name: 'Product B', price: 30, available: 30, imageUrl: '/assets/images/equipment/scanner_device.png' }, + // Add more products as needed + ]; + }, + + view: function (vnode) { + return m('.marketplace', [ + m('.market-connect-wallet', 'Connect wallet'), + m('.container', [ + m('.title', 'Marketplace'), + m('.products', [vnode.state.products.map((product) => m(ProductTile, { product }))]), + ]), + ]); + }, + }; +} + +function ProductTile() { + return { + view: function (vnode) { + const { product } = vnode.attrs; + + return m('.product-tile', [ + m('img', { src: product.imageUrl, alt: product.name }), + m('.product-info', [ + m('.name', product.name), + m('.price', `Price: $${product.price}`), + m('.available', `Available: ${product.available}`), + m('.quantity-control', { class: 'full-width' }, [ + m( + 'button.decrement', + { + onclick: () => { + product.selectedQuantity = Math.max((product.selectedQuantity || 0) - 1, 0); + m.redraw(); + }, + disabled: (product.selectedQuantity || 0) <= 0, + }, + '-' + ), + m('input.styled-input', { + type: 'number', + min: 1, + max: product.available, + value: product.selectedQuantity || 0, + oninput: (e) => { + const value = parseInt(e.target.value, 10); + if (!isNaN(value)) { + product.selectedQuantity = Math.min(Math.max(value, 0), product.available); + } + }, + }), + m( + 'button.increment', + { + onclick: () => { + product.selectedQuantity = Math.min((product.selectedQuantity || 0) + 1, product.available); + m.redraw(); + }, + disabled: (product.selectedQuantity || 0) >= product.available, + }, + '+' + ), + ]), + m( + 'button.buy-button', + { + onclick: () => buyProduct(product), + disabled: (product.selectedQuantity || 0) <= 0, // Disable if quantity is 0 + }, + 'Buy' + ), + ]), + ]); + }, + }; +} + +function buyProduct(product) { + console.log('Buying', product); + // Implement the buy logic here + playClick(); +} diff --git a/src/main.js b/src/main.js index aef5161a..510ffbd0 100644 --- a/src/main.js +++ b/src/main.js @@ -30,7 +30,7 @@ export const loadingSceneKey = scenes.loadingScene.key; export const loungeAreaSceneKey = scenes.loungeAreaScene.key; export const characterPickSceneKey = scenes.characterPickScene.key; -const config = { +export const config = { title: 'CyberBeavers', type: Phaser.WEBGL, parent: 'game', @@ -64,12 +64,10 @@ const config = { }; window.sizeChanged = () => { - if (window.game.isBooted) { + if (window.game?.isBooted) { setTimeout(() => { - window.game.scale.resize(window.innerWidth, window.innerHeight); + window.game?.scale.resize(window.innerWidth, window.innerHeight); }, 100); } }; window.onresize = () => window.sizeChanged(); - -window.game = new Phaser.Game(config); diff --git a/src/routes.js b/src/routes.js new file mode 100644 index 00000000..054e7274 --- /dev/null +++ b/src/routes.js @@ -0,0 +1,8 @@ +import m from 'mithril'; +import { GamePage } from './game/gui/pages/GamePage'; +import { MarketPage } from './game/gui/pages/MarketPage'; + +m.route(document.getElementById('app'), '/', { + '/': GamePage, + '/market': MarketPage, +}); diff --git a/yarn.lock b/yarn.lock index 46acfac6..55107609 100644 --- a/yarn.lock +++ b/yarn.lock @@ -733,38 +733,38 @@ dependencies: "@sinclair/typebox" "^0.27.8" -"@jitl/quickjs-ffi-types@0.29.2": - version "0.29.2" - resolved "https://registry.npmjs.org/@jitl/quickjs-ffi-types/-/quickjs-ffi-types-0.29.2.tgz" - integrity sha512-069uQTiEla2PphXg6UpyyJ4QXHkTj3S9TeXgaMCd8NDYz3ODBw5U/rkg6fhuU8SMpoDrWjEzybmV5Mi2Pafb5w== +"@jitl/quickjs-ffi-types@0.31.0": + version "0.31.0" + resolved "https://registry.yarnpkg.com/@jitl/quickjs-ffi-types/-/quickjs-ffi-types-0.31.0.tgz#1f5cbec1c1ec03eba860842ea59914db3cf3c69a" + integrity sha512-1yrgvXlmXH2oNj3eFTrkwacGJbmM0crwipA3ohCrjv52gBeDaD7PsTvFYinlAnqU8iPME3LGP437yk05a2oejw== -"@jitl/quickjs-wasmfile-debug-asyncify@0.29.2": - version "0.29.2" - resolved "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-asyncify/-/quickjs-wasmfile-debug-asyncify-0.29.2.tgz" - integrity sha512-YdRw2414pFkxzyyoJGv81Grbo9THp/5athDMKipaSBNNQvFE9FGRrgE9tt2DT2mhNnBx1kamtOGj0dX84Yy9bg== +"@jitl/quickjs-wasmfile-debug-asyncify@0.31.0": + version "0.31.0" + resolved "https://registry.yarnpkg.com/@jitl/quickjs-wasmfile-debug-asyncify/-/quickjs-wasmfile-debug-asyncify-0.31.0.tgz#0a707fe7ed25e0d5986c8346ae02788a648c9c63" + integrity sha512-YkdzQdr1uaftFhgEnTRjTTZHk2SFZdpWO7XhOmRVbi6CEVsH9g5oNF8Ta1q3OuSJHRwwT8YsuR1YzEiEIJEk6w== dependencies: - "@jitl/quickjs-ffi-types" "0.29.2" + "@jitl/quickjs-ffi-types" "0.31.0" -"@jitl/quickjs-wasmfile-debug-sync@0.29.2": - version "0.29.2" - resolved "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-sync/-/quickjs-wasmfile-debug-sync-0.29.2.tgz" - integrity sha512-VgisubjyPMWEr44g+OU0QWGyIxu7VkApkLHMxdORX351cw22aLTJ+Z79DJ8IVrTWc7jh4CBPsaK71RBQDuVB7w== +"@jitl/quickjs-wasmfile-debug-sync@0.31.0": + version "0.31.0" + resolved "https://registry.yarnpkg.com/@jitl/quickjs-wasmfile-debug-sync/-/quickjs-wasmfile-debug-sync-0.31.0.tgz#aac7f901c4df7fdf3aea456c66dbf38f48b0564e" + integrity sha512-8XvloaaWBONqcHXYs5tWOjdhQVxzULilIfB2hvZfS6S+fI4m2+lFiwQy7xeP8ExHmiZ7D8gZGChNkdLgjGfknw== dependencies: - "@jitl/quickjs-ffi-types" "0.29.2" + "@jitl/quickjs-ffi-types" "0.31.0" -"@jitl/quickjs-wasmfile-release-asyncify@0.29.2": - version "0.29.2" - resolved "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-asyncify/-/quickjs-wasmfile-release-asyncify-0.29.2.tgz" - integrity sha512-sf3luCPr8wBVmGV6UV8Set+ie8wcO6mz5wMvDVO0b90UVCKfgnx65A1JfeA+zaSGoaFyTZ3sEpXSGJU+6qJmLw== +"@jitl/quickjs-wasmfile-release-asyncify@0.31.0": + version "0.31.0" + resolved "https://registry.yarnpkg.com/@jitl/quickjs-wasmfile-release-asyncify/-/quickjs-wasmfile-release-asyncify-0.31.0.tgz#00b11f65b540e7c9472bd08fb499adac77f4a227" + integrity sha512-uz0BbQYTxNsFkvkurd7vk2dOg57ElTBLCuvNtRl4rgrtbC++NIndD5qv2+AXb6yXDD3Uy1O2PCwmoaH0eXgEOg== dependencies: - "@jitl/quickjs-ffi-types" "0.29.2" + "@jitl/quickjs-ffi-types" "0.31.0" -"@jitl/quickjs-wasmfile-release-sync@0.29.2": - version "0.29.2" - resolved "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-sync/-/quickjs-wasmfile-release-sync-0.29.2.tgz" - integrity sha512-UFIcbY3LxBRUjEqCHq3Oa6bgX5znt51V5NQck8L2US4u989ErasiMLUjmhq6UPC837Sjqu37letEK/ZpqlJ7aA== +"@jitl/quickjs-wasmfile-release-sync@0.31.0": + version "0.31.0" + resolved "https://registry.yarnpkg.com/@jitl/quickjs-wasmfile-release-sync/-/quickjs-wasmfile-release-sync-0.31.0.tgz#66939a090f4a5132e25e15b76e76d09c25f92ea6" + integrity sha512-hYduecOByj9AsAfsJhZh5nA6exokmuFC8cls39+lYmTCGY51bgjJJJwReEu7Ff7vBWaQCL6TeDdVlnp2WYz0jw== dependencies: - "@jitl/quickjs-ffi-types" "0.29.2" + "@jitl/quickjs-ffi-types" "0.31.0" "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" @@ -1024,6 +1024,15 @@ ramda "^0.30.0" zod "^3.23.5" +"@permaweb/ao-scheduler-utils@~0.0.25": + version "0.0.25" + resolved "https://registry.yarnpkg.com/@permaweb/ao-scheduler-utils/-/ao-scheduler-utils-0.0.25.tgz#50c96a411d59f58010ace322d6abd3cd5e11e4cd" + integrity sha512-b0UYSTgnLMIYLScrfNBgcqK7ZMmd78L3J0Jz4RIsIq2P5PtkdRqQ7fYqLlltg7bD1f3dvl4TkO1925ED4ei7LA== + dependencies: + lru-cache "^10.2.2" + ramda "^0.30.0" + zod "^3.23.5" + "@permaweb/aoconnect@^0.0.55": version "0.0.55" resolved "https://registry.npmjs.org/@permaweb/aoconnect/-/aoconnect-0.0.55.tgz" @@ -1038,6 +1047,26 @@ warp-arbundles "^1.0.4" zod "^3.22.4" +"@permaweb/aoconnect@^0.0.62": + version "0.0.62" + resolved "https://registry.yarnpkg.com/@permaweb/aoconnect/-/aoconnect-0.0.62.tgz#404d9f506b70385027447a9bd7d3d51b93104ce2" + integrity sha512-e42yASru6ze09rugKzi2yD3E57OK0xSbjDM5TI7gKnDVMo8JHweiLCntglItJ44vuNUA7Sdool83v4bmEohaZw== + dependencies: + "@permaweb/ao-scheduler-utils" "~0.0.25" + "@permaweb/protocol-tag-utils" "~0.0.2" + buffer "^6.0.3" + debug "^4.3.7" + hyper-async "^1.1.2" + mnemonist "^0.39.8" + ramda "^0.30.1" + warp-arbundles "^1.0.4" + zod "^3.23.8" + +"@permaweb/protocol-tag-utils@~0.0.2": + version "0.0.2" + resolved "https://registry.yarnpkg.com/@permaweb/protocol-tag-utils/-/protocol-tag-utils-0.0.2.tgz#c8a2eddf7da15d70a6e60aecff839730cb59aee3" + integrity sha512-2IiKu71W7pkHKIzxabCGQ5q8DSppZaE/sPcPF2hn+OWwfe04M7b5X5LHRXQNPRuxHWuioieGdPQb3F7apOlffQ== + "@randlabs/communication-bridge@1.0.1": version "1.0.1" resolved "https://registry.npmjs.org/@randlabs/communication-bridge/-/communication-bridge-1.0.1.tgz" @@ -2281,7 +2310,7 @@ debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@^4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7, debug@~4.3.1, debug@~4.3.2: version "4.3.7" resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -4246,32 +4275,32 @@ queue-microtask@^1.2.2, queue-microtask@^1.2.3: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -quickjs-emscripten-core@0.29.2: - version "0.29.2" - resolved "https://registry.npmjs.org/quickjs-emscripten-core/-/quickjs-emscripten-core-0.29.2.tgz" - integrity sha512-jEAiURW4jGqwO/fW01VwlWqa2G0AJxnN5FBy1xnVu8VIVhVhiaxUfCe+bHqS6zWzfjFm86HoO40lzpteusvyJA== +quickjs-emscripten-core@0.31.0: + version "0.31.0" + resolved "https://registry.yarnpkg.com/quickjs-emscripten-core/-/quickjs-emscripten-core-0.31.0.tgz#22cbccec207adb6ce486ec1e90730499815e3e15" + integrity sha512-oQz8p0SiKDBc1TC7ZBK2fr0GoSHZKA0jZIeXxsnCyCs4y32FStzCW4d1h6E1sE0uHDMbGITbk2zhNaytaoJwXQ== dependencies: - "@jitl/quickjs-ffi-types" "0.29.2" + "@jitl/quickjs-ffi-types" "0.31.0" -quickjs-emscripten@^0.29.2: - version "0.29.2" - resolved "https://registry.npmjs.org/quickjs-emscripten/-/quickjs-emscripten-0.29.2.tgz" - integrity sha512-SlvkvyZgarReu2nr4rkf+xz1vN0YDUz7sx4WHz8LFtK6RNg4/vzAGcFjE7nfHYBEbKrzfIWvKnMnxZkctQ898w== +quickjs-emscripten@^0.31.0: + version "0.31.0" + resolved "https://registry.yarnpkg.com/quickjs-emscripten/-/quickjs-emscripten-0.31.0.tgz#6dbd7dbb5ddf84cf3eb28c3d1411a8300affe3a9" + integrity sha512-K7Yt78aRPLjPcqv3fIuLW1jW3pvwO21B9pmFOolsjM/57ZhdVXBr51GqJpalgBlkPu9foAvhEAuuQPnvIGvLvQ== dependencies: - "@jitl/quickjs-wasmfile-debug-asyncify" "0.29.2" - "@jitl/quickjs-wasmfile-debug-sync" "0.29.2" - "@jitl/quickjs-wasmfile-release-asyncify" "0.29.2" - "@jitl/quickjs-wasmfile-release-sync" "0.29.2" - quickjs-emscripten-core "0.29.2" + "@jitl/quickjs-wasmfile-debug-asyncify" "0.31.0" + "@jitl/quickjs-wasmfile-debug-sync" "0.31.0" + "@jitl/quickjs-wasmfile-release-asyncify" "0.31.0" + "@jitl/quickjs-wasmfile-release-sync" "0.31.0" + quickjs-emscripten-core "0.31.0" ramda@^0.29.1: version "0.29.1" resolved "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz" integrity sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA== -ramda@^0.30.0: +ramda@^0.30.0, ramda@^0.30.1: version "0.30.1" - resolved "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.30.1.tgz#7108ac95673062b060025052cd5143ae8fc605bf" integrity sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw== randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: @@ -5085,15 +5114,16 @@ warp-contracts-plugin-deploy@^1.0.13: node-pre-gyp "^0.17.0" node-stdlib-browser "^1.2.0" -warp-contracts-plugin-quickjs@^1.1.10: - version "1.1.13" - resolved "https://registry.npmjs.org/warp-contracts-plugin-quickjs/-/warp-contracts-plugin-quickjs-1.1.13.tgz" - integrity sha512-5nAlaKuZkutq1E1FQ0v8HDvsb7pPgEx/pgXfaQHqhptVPToFw228i0FlVz60Q0wVlHG0L5R3JGDmo7wLSoYwew== +warp-contracts-plugin-quickjs@^1.1.13: + version "1.1.14" + resolved "https://registry.yarnpkg.com/warp-contracts-plugin-quickjs/-/warp-contracts-plugin-quickjs-1.1.14.tgz#24058057b32d7aa74839423e8c4db64fd483680d" + integrity sha512-6u5tFIQQhE1OdvyCTPYRIMCHTO/dRk82IzfczxfRseAzyttSRUl1tRlC/zGTVag+YTLGa+nWDDRlqE+iCX5tPg== dependencies: + "@permaweb/aoconnect" "^0.0.62" "@redstone-finance/protocol" "0.5.4" fast-copy "^3.0.2" pngjs "7.0.0" - quickjs-emscripten "^0.29.2" + quickjs-emscripten "^0.31.0" seedrandom "^3.0.5" warp-contracts "1.4.40" @@ -5353,7 +5383,7 @@ zip-stream@^4.1.0: compress-commons "^4.1.2" readable-stream "^3.6.0" -zod@^3.22.4, zod@^3.23.5: +zod@^3.22.4, zod@^3.23.5, zod@^3.23.8: version "3.23.8" - resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== From 2cdd2d93bb529084a8df6e6674e4633b2e1c253c Mon Sep 17 00:00:00 2001 From: Asia Date: Mon, 16 Dec 2024 11:37:28 +0100 Subject: [PATCH 5/6] hollow - connection --- public/css/mithril/market-page.css | 80 ++++++++++++++++++++++-- src/game/gui/GameHubGui.js | 19 +++++- src/game/gui/pages/MarketPage.js | 85 +++++++++++++++++++++++++- src/game/gui/state.js | 1 + src/game/scenes/GameHubScene.js | 1 + tools/leaderboard/check-game-scores.js | 10 +-- 6 files changed, 182 insertions(+), 14 deletions(-) create mode 100644 src/game/gui/state.js diff --git a/public/css/mithril/market-page.css b/public/css/mithril/market-page.css index a4aea82e..005cd029 100644 --- a/public/css/mithril/market-page.css +++ b/public/css/mithril/market-page.css @@ -43,8 +43,7 @@ flex: 1 1 calc(25% - 32px); margin: 16px; box-sizing: border-box; - min-width: 200px; - max-width: 300px; + min-width: 300px; padding: 16px; align-items: center; display: flex; @@ -169,9 +168,15 @@ } .market-connect-wallet { + display: flex; position: absolute; - top: 20px; right: 20px; + top: 20px; + flex-direction: column; + align-items: center; +} + +.market-connect-wallet-button { background: transparent; border: 2px solid var(--golden); color: var(--golden); @@ -190,7 +195,7 @@ text-align: center; } -.market-connect-wallet:hover { +.market-connect-wallet-button { background: var(--golden); color: var(--black); box-shadow: @@ -198,6 +203,71 @@ 0 0 20px var(--golden); } -.market-connect-wallet:active { +.market-connect-wallet-button { transform: scale(0.95); } + +.market-connect-wallet-address { + color: black; + font-size: var(--fs-16); + margin-top: 10px; +} + +.modal-background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); /* Semi-transparent background */ + display: flex; + align-items: flex-start; + justify-content: center; + z-index: 10; +} + +.modal { + background: rgba(0, 0, 0, 0.8); + padding: 20px; + border-radius: 12px; + width: 90%; + max-width: 450px; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); + transition: transform 0.3s ease-out; + transform: scale(1.1); + margin-top: 35px; +} + +.modal-content { + text-align: center; + color: white; +} + +.modal-close { + float: right; + border: none; + background: none; + color: white; + font-size: 24px; + cursor: pointer; +} + +.wallet-button { + width: 100%; + padding: 10px; + margin-top: 20px; + background-color: #1a1d30; /* Match button color to site theme */ + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 16px; +} + +.wallet-button:hover { + background-color: #2a2d47; +} + +.modal-title { + text-align: left; +} diff --git a/src/game/gui/GameHubGui.js b/src/game/gui/GameHubGui.js index edc96513..564883d7 100644 --- a/src/game/gui/GameHubGui.js +++ b/src/game/gui/GameHubGui.js @@ -2,11 +2,14 @@ import { trimString } from '../utils/utils.js'; import { playClick } from '../utils/mithril.js'; import { GAMEPLAY_MODE_LABEL } from '../common/const.mjs'; import m from 'mithril'; +import { state } from './state.js'; export function GameHubGui() { return { view: function (vnode) { - const { games, gameError, joinGame, spectateGame, enterGlobalLeaderboard } = vnode.attrs; + const { games, gameError, joinGame, spectateGame, enterGlobalLeaderboard, walletAddress } = vnode.attrs; + state.walletAddress = walletAddress; + console.log(state); return [ m('.mithril-component', { id: 'game-hub' }, [ m('.games-list-wrapper', [ @@ -24,6 +27,20 @@ export function GameHubGui() { }, 'GLOBAL LEADERBOARD' ), + m( + '.button yellow', + { + onclick: async () => { + playClick(); + m.route.set('/market'); + }, + style: { + marginTop: '40px', + textTransform: 'uppercase', + }, + }, + 'Market' + ), ]), ]), ]; diff --git a/src/game/gui/pages/MarketPage.js b/src/game/gui/pages/MarketPage.js index 9dfe76bb..dcf96e6a 100644 --- a/src/game/gui/pages/MarketPage.js +++ b/src/game/gui/pages/MarketPage.js @@ -1,8 +1,18 @@ import m from 'mithril'; import { playClick } from '../../utils/mithril'; import { hideGui } from '../../utils/mithril'; +import { state } from '../state'; +import { trimString } from '../../utils/utils'; export function MarketPage() { + const { walletAddress } = state; + let showModal = false; // State to control the visibility of the modal + + function toggleModal() { + console.log('toggle'); + showModal = !showModal; + m.redraw(); + } return { oninit: function (vnode) { hideGui(); @@ -24,7 +34,22 @@ export function MarketPage() { view: function (vnode) { return m('.marketplace', [ - m('.market-connect-wallet', 'Connect wallet'), + m('.market-connect-wallet', [ + m( + '.market-connect-wallet-button', + { + onclick: () => { + playClick(); + toggleModal(); + }, + disabled: walletAddress, + }, + walletAddress ? 'Disconnect' : 'Connect' + ), + walletAddress && m('.market-connect-wallet-address', trimString(walletAddress, 3, 3, 3)), + ]), + showModal && m(Modal, { onClose: toggleModal }), // Show modal based on state + m('.container', [ m('.title', 'Marketplace'), m('.products', [vnode.state.products.map((product) => m(ProductTile, { product }))]), @@ -34,6 +59,61 @@ export function MarketPage() { }; } +function Modal() { + return { + view: function (vnode) { + return m('.modal-background', [ + m('.modal', [ + m('.modal-content', [ + m( + 'button.modal-close', + { + onclick: () => { + console.log('closing'); + vnode.attrs.onClose(); + }, + }, + '×' + ), + m('h1.modal-title', 'Connect Wallet'), + m( + 'button.wallet-button', + { + onclick: async () => { + console.log('clicked'); + await handleArconnect(); + }, + }, + 'Arconnect' + ), + m('button.wallet-button', 'Metamask'), + ]), + ]), + ]); + }, + }; +} + +async function handleArconnect() { + console.log(window.arweaveWallet); + if (!window.arweaveWallet) return; + try { + console.log('connecting'); + await window.arweaveWallet.connect([ + 'ACCESS_ADDRESS', + 'DISPATCH', + 'SIGN_TRANSACTION', + 'ACCESS_PUBLIC_KEY', + 'SIGNATURE', + ]); + } catch (e) { + window.alert( + `Problem with loading ArConnect.\nPlease refresh page and try again. Our best tech beavers are working on solving this problem.` + ); + } + state.walletAddress = await window.arweaveWallet.getActiveAddress(); +} + function ProductTile() { return { view: function (vnode) { @@ -85,7 +165,7 @@ function ProductTile() { 'button.buy-button', { onclick: () => buyProduct(product), - disabled: (product.selectedQuantity || 0) <= 0, // Disable if quantity is 0 + disabled: (product.selectedQuantity || 0) <= 0, }, 'Buy' ), @@ -97,6 +177,5 @@ function ProductTile() { function buyProduct(product) { console.log('Buying', product); - // Implement the buy logic here playClick(); } diff --git a/src/game/gui/state.js b/src/game/gui/state.js new file mode 100644 index 00000000..1979e545 --- /dev/null +++ b/src/game/gui/state.js @@ -0,0 +1 @@ +export const state = {}; diff --git a/src/game/scenes/GameHubScene.js b/src/game/scenes/GameHubScene.js index 7bd5bbd6..89ab8452 100644 --- a/src/game/scenes/GameHubScene.js +++ b/src/game/scenes/GameHubScene.js @@ -49,6 +49,7 @@ export default class GameHubScene extends Phaser.Scene { return m(GameHubGui, { gameError: self.gameError, games: self.games, + walletAddress: self.walletAddress, joinGame: async (processId, game) => { console.log(`picked ${processId}`); await serverConnection.initGame(game.module, processId, game.mapTxId); diff --git a/tools/leaderboard/check-game-scores.js b/tools/leaderboard/check-game-scores.js index 0a4f64c2..6dc74aab 100644 --- a/tools/leaderboard/check-game-scores.js +++ b/tools/leaderboard/check-game-scores.js @@ -1,7 +1,7 @@ import { createDataItemSigner, dryrun, message, result } from '@permaweb/aoconnect'; import fs from 'node:fs'; -const WALLET = JSON.parse(fs.readFileSync('../.secrets/general/jwk.json', 'utf-8')); +const WALLET = JSON.parse(fs.readFileSync('.secrets/general/jwk.json', 'utf-8')); const leaderboardProcessId = '4X2o4Kw9_-lc8I8SaMQaUOIMEooNyTvlMdjZ5Bv6tO8'; @@ -9,15 +9,15 @@ async function checkGlobalScores() { const signer = createDataItemSigner(WALLET); const scoresRes = await dryrun({ - process: leaderboardProcessId, + process: 'hehYPGeX8rD77MErYexQAtHeOzY6QasGsSuBC5YCmWY', tags: [ - { name: 'Action', value: 'GameScores' }, - { name: 'GameProcessId', value: 'GohbVx7iMV6n2H96JA30gj6BVc8yWvWspSCNQr5FQbE' }, + { name: 'Action', value: 'Info' }, + // { name: 'GameProcessId', value: 'GohbVx7iMV6n2H96JA30gj6BVc8yWvWspSCNQr5FQbE' }, ], signer, data: '1984', }); - console.log(JSON.parse(scoresRes.Output.data)); + console.log(scoresRes.Messages[0].Tags); return scoresRes; } From 38c710683cb4e7a7574446e6fb09f1a9125163dc Mon Sep 17 00:00:00 2001 From: Tadeuchi Date: Thu, 19 Dec 2024 15:28:39 +0100 Subject: [PATCH 6/6] test fixes --- lua/hollow/hollow.lua | 2 +- tests/hollow.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/hollow/hollow.lua b/lua/hollow/hollow.lua index 38fcb1eb..bdd87530 100644 --- a/lua/hollow/hollow.lua +++ b/lua/hollow/hollow.lua @@ -112,7 +112,7 @@ Handlers.add('Credit-Notice', Handlers.utils.hasMatchingTag('Action', 'Credit-No HandleError({ Target = orderArgs.sender, Action = 'Input-Error', - Message = 'Invalid arguments, required { Item, Item-Quantity }.', + Message = 'Invalid arguments, required { X-Item, X-Item-Quantity }.', Quantity = orderArgs.quantity, TransferToken = msg.From, }) diff --git a/tests/hollow.test.js b/tests/hollow.test.js index e3f60d46..3f71d41c 100644 --- a/tests/hollow.test.js +++ b/tests/hollow.test.js @@ -163,8 +163,8 @@ function createSingleOrder(qty, itemQty, item) { ['X-Order-Action']: 'Create-Order', Sender: 'jmGGoJaDYDTx4OCM7MP-7l-VLIM4ZEGCS0cHPsSmiNE', Quantity: qty, - ['Item-Quantity']: itemQty, - Item: item, + ['X-Item-Quantity']: itemQty, + ['X-Item']: item, }; }