From 1ae919599b6a8aee3dad19841524392f9308b2c9 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Fri, 19 Jul 2024 12:33:57 +1000 Subject: [PATCH] feat: Add initial code drop --- README.md | 10 +- message.js | 75 +++++++ n8n.json | 570 +++++++++++++++++++++++++++++++++++++++++++++++++++++ parser.js | 131 ++++++++++++ 4 files changed, 785 insertions(+), 1 deletion(-) create mode 100644 message.js create mode 100644 n8n.json create mode 100644 parser.js diff --git a/README.md b/README.md index ce56868..01c5766 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# australian_capital_dao +# Australian Capital DAO + +This is a repo to hold the code for the ACD bot. +The bot uses N8N to automate the process of sending messages on any DAO transaction. + +## Files +[n8n.json](/n8n.json) - The n8n workflow file (import this into n8n and add the necessary credentials) +[parser.js](/parser.js) - The script that parses the transaction data to be used to create the message +[message.js](/message.js) - The script that creates the message to send to the telegram and discord bots \ No newline at end of file diff --git a/message.js b/message.js new file mode 100644 index 0000000..16089fb --- /dev/null +++ b/message.js @@ -0,0 +1,75 @@ +function cleanJsonString(jsonString) { + // Remove invisible and non-printable characters (excluding newlines, carriage returns, and tabs) + const cleanedString = jsonString.replace(/[^\x20-\x7E\r\n\t]+/g, ''); + + // Remove leading or trailing whitespace + const trimmedString = cleanedString.trim(); + + // Ensure the string is enclosed within curly braces if it’s a JSON object + if (!trimmedString.startsWith('{') || !trimmedString.endsWith('}')) { + throw new Error('Invalid JSON format: Missing opening or closing brace.'); + } + + return trimmedString; + } + + const addressNames = { + "0x6cb4b39bec23a921c9a20d061bf17d4640b0d39e":"woodburn.au" + }; + function addressName(address) { + if (addressNames.hasOwnProperty(address)){ + return addressNames[address]; + } + return address; + } + + + + var message = "New DAO transaction: "; + const parsed = $('Parser').first().json; + message += parsed.method; + + if (parsed.method == "Submit Vote"){ + message += "\n"+ addressName(parsed.member) + " voted "; + if (parsed.vote == 1){ + message += "for"; + } else { + message += "against"; + } + message += " proposal #"+parsed.proposal; + message += " using " + parsed.votes + " votes"; + } + else if (parsed.method == "Sponsor Proposal"){ + message += "\n"+ addressName(parsed.member) + " sponsored "; + message += "proposal #"+parsed.proposal; + } + else if (parsed.method == "Submit Proposal"){ + message += "\n"+ addressName($('HTTP Request').first().json.result.from); + message += " created proposal #" + parsed.proposal; + try { + const cleanedJsonString = cleanJsonString(parsed.data.details); + const details = JSON.parse(cleanedJsonString); + message += "\nProposal: " + details.title; + message += "\nDescription: " + details.description; + + if (details.contentURI != ""){ + message += "\nURL: " + details.contentURI; + } + } + catch (error){ + message += "\nProposal details failed to parse"; + message += error; + } + } + else if (parsed.method == "Process Proposal"){ + message += "\n"+ addressName($('HTTP Request').first().json.result.from); + message += " executed proposal #" + parsed.proposal; + } + + return { + "message":message, + "explorer":"https://optimistic.etherscan.io/tx/"+$('HTTP Request').first().json.result.transactionHash, + "proposalURL":"https://admin.daohaus.club/#/molochV3/0xa/0xf4604948ad5365840803297bf81cd9a46c36fce7/proposal/"+parsed.proposal + }; + + \ No newline at end of file diff --git a/n8n.json b/n8n.json new file mode 100644 index 0000000..91c8980 --- /dev/null +++ b/n8n.json @@ -0,0 +1,570 @@ +{ + "name": "DAOHaus Alerts", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "530c914a-c732-4cb6-aaf1-2b722f9e7398", + "options": {} + }, + "id": "def14e6f-a3fc-4844-8a94-b2d3a0a9bdb5", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1.1, + "position": [ + 660, + 540 + ], + "webhookId": "530c914a-c732-4cb6-aaf1-2b722f9e7398" + }, + { + "parameters": { + "method": "POST", + "url": "https://opt-mainnet.g.alchemy.com/v2/{alchemy_api_key}", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"id\": 1,\n \"jsonrpc\": \"2.0\",\n \"method\": \"eth_getTransactionReceipt\",\n \"params\": [\n\"{{ $json[\"body\"][\"event\"][\"activity\"][0][\"hash\"] }}\"\n ]\n}", + "options": {} + }, + "id": "b97eaff4-26f2-4230-b3e2-3eb14b5cbbba", + "name": "HTTP Request", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.1, + "position": [ + 1160, + 540 + ] + }, + { + "parameters": { + "authentication": "webhook", + "options": {}, + "embeds": { + "values": [ + { + "description": "={{ $json[\"message\"] }}\n\n{{ $json[\"proposalURL\"] }}", + "author": "Australian Capital DAO", + "color": "#FF00D9", + "timestamp": "={{ ($now.minus({hours:10})).toFormat('yyyy-LL-dd HH:mm:ss') }}\n", + "title": "New DAO TX", + "url": "={{ $json[\"explorer\"] }}" + } + ] + } + }, + "id": "8e49fb62-06a1-4e51-898c-c17e1eb5dae0", + "name": "Discord2", + "type": "n8n-nodes-base.discord", + "typeVersion": 2, + "position": [ + 1960, + 420 + ], + "credentials": { + "discordWebhookApi": { + "id": "XzOnTbxymIRJMNFK", + "name": "N8N Channel" + } + } + }, + { + "parameters": { + "chatId": "-4260162868", + "text": "={{ $json[\"message\"] }}", + "replyMarkup": "inlineKeyboard", + "inlineKeyboard": { + "rows": [ + { + "row": { + "buttons": [ + { + "text": "View tx on Etherscan", + "additionalFields": { + "url": "={{ $json[\"explorer\"] }}" + } + }, + { + "text": "View Proposal", + "additionalFields": { + "url": "={{ $json[\"proposalURL\"] }}" + } + } + ] + } + } + ] + }, + "additionalFields": { + "appendAttribution": false + } + }, + "id": "2577c299-f86e-46d2-8799-e4e10875e3c7", + "name": "Telegram", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1.1, + "position": [ + 1960, + 640 + ], + "credentials": { + "telegramApi": { + "id": "VWQskb1y7DoFEnMh", + "name": "Telegram account" + } + } + }, + { + "parameters": { + "updates": [ + "message" + ], + "additionalFields": {} + }, + "id": "245d297b-8838-441e-b206-1f9cfdff8591", + "name": "Telegram Trigger", + "type": "n8n-nodes-base.telegramTrigger", + "typeVersion": 1.1, + "position": [ + 480, + 940 + ], + "webhookId": "47a00ca7-1eb5-408d-8846-4af7f8b1e36b", + "credentials": { + "telegramApi": { + "id": "VWQskb1y7DoFEnMh", + "name": "Telegram account" + } + } + }, + { + "parameters": { + "authentication": "webhook", + "content": "=New message from: {{ $json[\"message\"][\"from\"][\"first_name\"] }} (username: {{ $json[\"message\"][\"from\"][\"username\"] }}) (ID: {{ $json[\"message\"][\"from\"][\"id\"] }})\nChat Title: {{ $json[\"message\"][\"chat\"][\"title\"] }}\nChat ID: {{ $json[\"message\"][\"chat\"][\"id\"] }}\nChat Type: {{ $json[\"message\"][\"chat\"][\"type\"] }}\nMessage text: {{ $json[\"message\"][\"text\"] }}", + "options": {} + }, + "id": "ce165915-be3e-49f2-b9e8-308f98e37ffd", + "name": "Discord3", + "type": "n8n-nodes-base.discord", + "typeVersion": 2, + "position": [ + 1560, + 900 + ], + "credentials": { + "discordWebhookApi": { + "id": "XzOnTbxymIRJMNFK", + "name": "N8N Channel" + } + } + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "ca511fdf-180b-4236-b474-3bfe439ded0c", + "leftValue": "={{ $json[\"body\"][\"event\"][\"activity\"].length }}", + "rightValue": 1, + "operator": { + "type": "number", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "9536c45d-ae2a-472d-87f5-2b4535150128", + "name": "If1", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [ + 880, + 540 + ] + }, + { + "parameters": { + "jsCode": "function hexToDecimal(hex) {\n return parseInt(hex, 16);\n}\nfunction stripExtraZeros(hexString) {\n // Remove leading zeros while keeping at least \"0x\" if present\n hexString = hexString.replace(/^0x0+/, '');\n return \"0x\"+hexString;\n}\n\n\n\nconst topics = {\n \"0xb9956173924f9c1204bae41dd3737d7ed1161846d13183879cdc03c4b9f8d019\":\"Submit Proposal\",\n \"0x786755545a7e27c12c90cc7f0934514d03fdacfe3684a340b8c4100531e7ecd5\":\"Submit Vote\",\n \"0xb4571f7e4e2c2b6e6185e47ab5caa5fe34087299bd49fbae945a4583101ee3f0\":\"Process Proposal\",\n \"0xd45ad122361f16d6f50d7c4a73638f20ee48eff6186af15224e2a4a1f6d50171\":\"Sponsor Proposal\"\n};\n\nvar Logs = $('HTTP Request').first().json.result.logs;\nvar topicsFromLogs = [];\n\nfor (var i = 0; i < Logs.length; i++) {\n var log = Logs[i];\n topicsFromLogs.push(...log.topics); // Using spread operator to append topics array\n}\n\nvar data = {\n \"method\":\"Unknown method\"\n};\n\nconst opts = {\n \"Submit Vote\":{\n 1:\"member\",\n 2:\"proposal\",\n 3:\"vote\"\n },\n \"Submit Proposal\":{\n 1:\"proposal\",\n 2:\"dataHash\"\n },\n \"Process Proposal\":{\n 1:\"member\",\n 2:\"proposal\",\n 3:\"vote\"\n },\n \"Sponsor Proposal\":{\n 1:\"member\",\n 2:\"proposal\",\n 3:\"votingStarts\"\n }\n}\n\n\n// Check each topic in the logs against the topics object\nfor (let i = 0; i < topicsFromLogs.length; i++) {\n const topic = topicsFromLogs[i];\n if (topics.hasOwnProperty(topic)) {\n data[\"method\"] = topics[topic];\n console.log('Matched Option:', data[\"method\"]);\n } else {\n if (opts.hasOwnProperty(data[\"method\"])){\n var keys = opts[data[\"method\"]];\n if (keys[i] != \"member\"){\n data[keys[i]] = hexToDecimal(topic); \n } else {\n data[keys[i]] = stripExtraZeros(topic);\n }\n }\n }\n}\nfunction hexToUtf8(hexStr) {\n return Buffer.from(hexStr, 'hex').toString('utf8');\n}\n\nif (data[\"method\"]== \"Submit Proposal\"){\n const hexData = $('HTTP Request').first().json.result.logs[0].data;\n // Extracting segments from hexData (assuming it's already defined)\n const votingPeriodHex = hexData.slice(2, 66); // 64 characters (32 bytes) for votingPeriod\n const proposalDataHex = hexData.slice(66, 130); // 64 characters (32 bytes) for proposalData\n const expirationHex = hexData.slice(130, 194); // 64 characters (32 bytes) for expiration\n const baalGasHex = hexData.slice(194, 258); // 64 characters (32 bytes) for baalGas\n const selfSponsorHex = hexData.slice(258, 322); // 64 characters (32 bytes) for selfSponsor\n const timestampHex = hexData.slice(322, 386); // 64 characters (32 bytes) for timestamp\n const detailsHex = hexData.slice(386); // Remaining part for details\n \n // Decode hex segments into decimal or string format\n const votingPeriod = hexToDecimal(votingPeriodHex);\n const proposalData = proposalDataHex;\n const expiration = hexToDecimal(expirationHex);\n const baalGas = hexToDecimal(baalGasHex);\n const selfSponsor = hexToDecimal(selfSponsorHex);\n const timestamp = hexToDecimal(timestampHex);\n \n let detailsObj;\n detailsObj = hexToUtf8(detailsHex);\n const jsonStringStart = detailsObj.indexOf('{\"');\n if (jsonStringStart !== -1) {\n detailsObj = detailsObj.slice(jsonStringStart);\n }\n\n \n // Construct the JSON object\n const jsonObject = {\n votingPeriod: votingPeriod,\n proposalData: proposalData,\n expiration: expiration,\n baalGas: baalGas,\n selfSponsor: selfSponsor,\n timestamp: timestamp,\n details: detailsObj\n };\n\n data['data'] = jsonObject;\n} else if (data[\"method\"]== \"Submit Vote\"){\n const hexData = $('HTTP Request').first().json.result.logs[0].data;\n data[\"votes\"]= hexToDecimal(hexData)/1000000000000000000;\n} else if (data[\"method\"]== \"Process Proposal\"){\n var logData = []\n for (var i = 0; i < Logs.length; i++) {\n if (Logs[i].topics[0]==\"0xb4571f7e4e2c2b6e6185e47ab5caa5fe34087299bd49fbae945a4583101ee3f0\"){\n data[\"proposal\"] = hexToDecimal(Logs[i].topics[1]);\n }\n}\n\n\n}\n\nconsole.log(data);\nreturn data;\n" + }, + "id": "eb7edb1c-a29d-4a55-9e69-3526edb2194a", + "name": "Parser", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1360, + 540 + ] + }, + { + "parameters": { + "jsCode": "function cleanJsonString(jsonString) {\n // Remove invisible and non-printable characters (excluding newlines, carriage returns, and tabs)\n const cleanedString = jsonString.replace(/[^\\x20-\\x7E\\r\\n\\t]+/g, '');\n \n // Remove leading or trailing whitespace\n const trimmedString = cleanedString.trim();\n \n // Ensure the string is enclosed within curly braces if it’s a JSON object\n if (!trimmedString.startsWith('{') || !trimmedString.endsWith('}')) {\n throw new Error('Invalid JSON format: Missing opening or closing brace.');\n }\n\n return trimmedString;\n}\n\nconst addressNames = {\n \"0x6cb4b39bec23a921c9a20d061bf17d4640b0d39e\":\"woodburn.au\" \n};\nfunction addressName(address) {\n if (addressNames.hasOwnProperty(address)){\n return addressNames[address];\n }\n return address;\n}\n\n\n\nvar message = \"New DAO transaction: \";\nconst parsed = $('Parser').first().json;\nmessage += parsed.method;\n\nif (parsed.method == \"Submit Vote\"){\n message += \"\\n\"+ addressName(parsed.member) + \" voted \";\n if (parsed.vote == 1){\n message += \"for\";\n } else {\n message += \"against\";\n }\n message += \" proposal #\"+parsed.proposal;\n message += \" using \" + parsed.votes + \" votes\";\n}\nelse if (parsed.method == \"Sponsor Proposal\"){\n message += \"\\n\"+ addressName(parsed.member) + \" sponsored \";\n message += \"proposal #\"+parsed.proposal;\n}\nelse if (parsed.method == \"Submit Proposal\"){\n message += \"\\n\"+ addressName($('HTTP Request').first().json.result.from);\n message += \" created proposal #\" + parsed.proposal;\n try {\n const cleanedJsonString = cleanJsonString(parsed.data.details);\n const details = JSON.parse(cleanedJsonString);\n message += \"\\nProposal: \" + details.title;\n message += \"\\nDescription: \" + details.description;\n\n if (details.contentURI != \"\"){\n message += \"\\nURL: \" + details.contentURI;\n }\n }\n catch (error){\n message += \"\\nProposal details failed to parse\";\n message += error;\n }\n}\nelse if (parsed.method == \"Process Proposal\"){\n message += \"\\n\"+ addressName($('HTTP Request').first().json.result.from);\n message += \" executed proposal #\" + parsed.proposal;\n}\n\n\n// message += \"\\n\\nhttps://optimistic.etherscan.io/tx/\";\n// message += $('HTTP Request').first().json.result.transactionHash;\n// message += \"\\nhttps://admin.daohaus.club/#/molochV3/0xa/0xf4604948ad5365840803297bf81cd9a46c36fce7/proposal/\"\n// message += parsed.proposal;\n\nreturn {\n \"message\":message,\n \"explorer\":\"https://optimistic.etherscan.io/tx/\"+$('HTTP Request').first().json.result.transactionHash,\n\"proposalURL\":\"https://admin.daohaus.club/#/molochV3/0xa/0xf4604948ad5365840803297bf81cd9a46c36fce7/proposal/\"+parsed.proposal\n};\n\n" + }, + "id": "22c0ce02-9388-4dca-ac32-3fda22a4f280", + "name": "Create message", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 1580, + 500 + ] + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "21456147-b1d5-4cb6-877d-ffe7c567eec6", + "leftValue": "={{ $json[\"message\"][\"entities\"].length }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "notEquals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "id": "cc10be80-8d35-43d0-84a7-78440d3b7801", + "name": "If", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [ + 660, + 1100 + ] + }, + { + "parameters": { + "chatId": "={{ $json[\"message\"][\"chat\"][\"id\"] }}", + "text": "Pong", + "additionalFields": { + "appendAttribution": false, + "reply_to_message_id": "={{ $json[\"message\"][\"message_id\"] }}" + } + }, + "id": "d6d67b9f-279f-4618-a2d4-14737449131f", + "name": "Ping Pong", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1.1, + "position": [ + 1280, + 1000 + ], + "credentials": { + "telegramApi": { + "id": "VWQskb1y7DoFEnMh", + "name": "Telegram account" + } + } + }, + { + "parameters": { + "chatId": "={{ $json[\"message\"][\"chat\"][\"id\"] }}", + "text": "Sure here is the link to the Australian Capital DAO", + "replyMarkup": "inlineKeyboard", + "inlineKeyboard": { + "rows": [ + { + "row": { + "buttons": [ + { + "text": "Go to DAO Page", + "additionalFields": { + "url": "https://admin.daohaus.club/#/molochv3/0xa/0xf4604948ad5365840803297bf81cd9a46c36fce7" + } + } + ] + } + } + ] + }, + "additionalFields": { + "appendAttribution": false, + "reply_to_message_id": "={{ $json[\"message\"][\"message_id\"] }}" + } + }, + "id": "402477d9-701c-4d08-80d9-608c4b702881", + "name": "Dao Link", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1.1, + "position": [ + 1280, + 1160 + ], + "credentials": { + "telegramApi": { + "id": "VWQskb1y7DoFEnMh", + "name": "Telegram account" + } + } + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "leftValue": "={{ $json[\"message\"][\"text\"] }}", + "rightValue": "/ping", + "operator": { + "type": "string", + "operation": "startsWith" + } + } + ], + "combinator": "and" + } + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict" + }, + "conditions": [ + { + "id": "4c2f8464-cf52-467d-a219-e75793559353", + "leftValue": "={{ $json[\"message\"][\"text\"] }}", + "rightValue": "/dao", + "operator": { + "type": "string", + "operation": "startsWith" + } + } + ], + "combinator": "and" + } + } + ] + }, + "options": {} + }, + "id": "eb1090bf-5f39-4c8e-ac85-ed2e7b59e76e", + "name": "Switch", + "type": "n8n-nodes-base.switch", + "typeVersion": 3, + "position": [ + 960, + 1080 + ] + } + ], + "pinData": { + "Webhook": [ + { + "json": { + "headers": { + "host": "n8n.woodburn.au", + "x-forwarded-scheme": "https", + "x-forwarded-proto": "https", + "x-forwarded-for": "118.208.168.176", + "x-real-ip": "118.208.168.176", + "content-length": "845", + "content-type": "application/json", + "user-agent": "insomnia/9.3.1", + "accept": "*/*" + }, + "params": {}, + "query": {}, + "body": { + "webhookId": "wh_mvrhnhyqysvf70qj", + "id": "whevt_rc0mq97pww9a7leo", + "createdAt": "2024-07-18T05:46:57.824Z", + "type": "ADDRESS_ACTIVITY", + "event": { + "network": "OPT_MAINNET", + "activity": [ + { + "fromAddress": "0xf4604948ad5365840803297bf81cd9a46c36fce7", + "toAddress": "0x35d3efffa0e6c63ee5c7e8c7dd7e7d6e961a6689", + "blockNum": "0x752694c", + "hash": "0x02d3f83b6663df1edc04db532190a17ef7a3267ca3104d7c36435eb2aad12b3f", + "value": 0, + "typeTraceAddress": "STATICCALL_0_0", + "asset": "ETH", + "category": "internal", + "rawContract": { + "rawValue": "0x0", + "decimals": 18 + } + } + ] + } + } + } + } + ], + "Telegram Trigger": [ + { + "json": { + "update_id": 328606253, + "message": { + "message_id": 34, + "from": { + "id": 5166524939, + "is_bot": false, + "first_name": "Nathan.Woodburn/", + "username": "nathanwoodburn", + "language_code": "en" + }, + "chat": { + "id": -4260162868, + "title": "Test Bot Group", + "type": "group", + "all_members_are_administrators": true + }, + "date": 1721355038, + "text": "/dao@australian_capital_dao_bot", + "entities": [ + { + "offset": 0, + "length": 31, + "type": "bot_command" + } + ] + } + } + } + ] + }, + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "If1", + "type": "main", + "index": 0 + } + ] + ] + }, + "HTTP Request": { + "main": [ + [ + { + "node": "Parser", + "type": "main", + "index": 0 + } + ] + ] + }, + "Telegram Trigger": { + "main": [ + [ + { + "node": "Discord3", + "type": "main", + "index": 0 + }, + { + "node": "If", + "type": "main", + "index": 0 + } + ] + ] + }, + "If1": { + "main": [ + [ + { + "node": "HTTP Request", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parser": { + "main": [ + [ + { + "node": "Create message", + "type": "main", + "index": 0 + } + ] + ] + }, + "Create message": { + "main": [ + [ + { + "node": "Discord2", + "type": "main", + "index": 0 + }, + { + "node": "Telegram", + "type": "main", + "index": 0 + } + ] + ] + }, + "If": { + "main": [ + [ + { + "node": "Switch", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch": { + "main": [ + [ + { + "node": "Ping Pong", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Dao Link", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": true, + "settings": { + "executionOrder": "v1" + }, + "versionId": "4d31c171-d9e4-47a8-8a76-4a56b6fd3101", + "meta": { + "templateCredsSetupCompleted": true, + "instanceId": "716fbd4310435337bb8df7c59b7984bfb867d59d9129682cbc4d8523a713d40e" + }, + "id": "POAYZQJI8PYGlKWt", + "tags": [] + } \ No newline at end of file diff --git a/parser.js b/parser.js new file mode 100644 index 0000000..60e9375 --- /dev/null +++ b/parser.js @@ -0,0 +1,131 @@ +function hexToDecimal(hex) { + return parseInt(hex, 16); + } + function stripExtraZeros(hexString) { + // Remove leading zeros while keeping at least "0x" if present + hexString = hexString.replace(/^0x0+/, ''); + return "0x"+hexString; + } + + + + const topics = { + "0xb9956173924f9c1204bae41dd3737d7ed1161846d13183879cdc03c4b9f8d019":"Submit Proposal", + "0x786755545a7e27c12c90cc7f0934514d03fdacfe3684a340b8c4100531e7ecd5":"Submit Vote", + "0xb4571f7e4e2c2b6e6185e47ab5caa5fe34087299bd49fbae945a4583101ee3f0":"Process Proposal", + "0xd45ad122361f16d6f50d7c4a73638f20ee48eff6186af15224e2a4a1f6d50171":"Sponsor Proposal" + }; + + var Logs = $('HTTP Request').first().json.result.logs; + var topicsFromLogs = []; + + for (var i = 0; i < Logs.length; i++) { + var log = Logs[i]; + topicsFromLogs.push(...log.topics); // Using spread operator to append topics array + } + + var data = { + "method":"Unknown method" // Default value + }; + + const opts = { + "Submit Vote":{ + 1:"member", + 2:"proposal", + 3:"vote" + }, + "Submit Proposal":{ + 1:"proposal", + 2:"dataHash" + }, + "Process Proposal":{ + 1:"member", + 2:"proposal", + 3:"vote" + }, + "Sponsor Proposal":{ + 1:"member", + 2:"proposal", + 3:"votingStarts" + } + } + + + // Check each topic in the logs against the topics object + for (let i = 0; i < topicsFromLogs.length; i++) { + const topic = topicsFromLogs[i]; + if (topics.hasOwnProperty(topic)) { + data["method"] = topics[topic]; + console.log('Matched Option:', data["method"]); + } else { + if (opts.hasOwnProperty(data["method"])){ + var keys = opts[data["method"]]; + if (keys[i] != "member"){ + data[keys[i]] = hexToDecimal(topic); + } else { + data[keys[i]] = stripExtraZeros(topic); + } + } + } + } + function hexToUtf8(hexStr) { + return Buffer.from(hexStr, 'hex').toString('utf8'); + } + + // Extracting data for specific methods + if (data["method"]== "Submit Proposal"){ + const hexData = $('HTTP Request').first().json.result.logs[0].data; + // Extracting segments from hexData (assuming it's already defined) + const votingPeriodHex = hexData.slice(2, 66); // 64 characters (32 bytes) for votingPeriod + const proposalDataHex = hexData.slice(66, 130); // 64 characters (32 bytes) for proposalData + const expirationHex = hexData.slice(130, 194); // 64 characters (32 bytes) for expiration + const baalGasHex = hexData.slice(194, 258); // 64 characters (32 bytes) for baalGas + const selfSponsorHex = hexData.slice(258, 322); // 64 characters (32 bytes) for selfSponsor + const timestampHex = hexData.slice(322, 386); // 64 characters (32 bytes) for timestamp + const detailsHex = hexData.slice(386); // Remaining part for details + + // Decode hex segments into decimal or string format + const votingPeriod = hexToDecimal(votingPeriodHex); + const proposalData = proposalDataHex; + const expiration = hexToDecimal(expirationHex); + const baalGas = hexToDecimal(baalGasHex); + const selfSponsor = hexToDecimal(selfSponsorHex); + const timestamp = hexToDecimal(timestampHex); + + let detailsObj; + detailsObj = hexToUtf8(detailsHex); + const jsonStringStart = detailsObj.indexOf('{"'); + if (jsonStringStart !== -1) { + detailsObj = detailsObj.slice(jsonStringStart); + } + + + // Construct the JSON object + const jsonObject = { + votingPeriod: votingPeriod, + proposalData: proposalData, + expiration: expiration, + baalGas: baalGas, + selfSponsor: selfSponsor, + timestamp: timestamp, + details: detailsObj + }; + + data['data'] = jsonObject; + } else if (data["method"]== "Submit Vote"){ + const hexData = $('HTTP Request').first().json.result.logs[0].data; + data["votes"]= hexToDecimal(hexData)/1000000000000000000; + } else if (data["method"]== "Process Proposal"){ + var logData = [] + for (var i = 0; i < Logs.length; i++) { + if (Logs[i].topics[0]=="0xb4571f7e4e2c2b6e6185e47ab5caa5fe34087299bd49fbae945a4583101ee3f0"){ + data["proposal"] = hexToDecimal(Logs[i].topics[1]); + } + } + + + } + + console.log(data); + return data; + \ No newline at end of file