diff --git a/README.md b/README.md index 45714e6..181d15b 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,14 @@ There is an option to run the code locally without using AWS and only using loca } } which also needs to be converted into `base64` value +## API KEY VALIDATION +- In ARKA Admin Frontend, create an API_KEY with the following format - +* Min length - 8 Max length - 20 +* contains atleast one Special characters out of these - `@$!%*-_&` +* contains atleast one lowercase alphabet +* contains atleast one uppercase alphabet +* contains atleast one digit 0-9 + ## 🔙 Arka Backend diff --git a/admin_frontend/demo.env b/admin_frontend/demo.env new file mode 100644 index 0000000..842ee73 --- /dev/null +++ b/admin_frontend/demo.env @@ -0,0 +1,2 @@ +REACT_APP_INDEXER_ENDPOINT=http://localhost:3003 +REACT_APP_SERVER_URL=http://localhost:5050 diff --git a/admin_frontend/package-lock.json b/admin_frontend/package-lock.json index fa1ed2d..1a3039e 100644 --- a/admin_frontend/package-lock.json +++ b/admin_frontend/package-lock.json @@ -8,26 +8,26 @@ "name": "admin_frontend", "version": "1.0.0", "dependencies": { - "@emotion/react": "^11.11.3", - "@emotion/styled": "^11.11.0", - "@mui/icons-material": "^5.15.3", - "@mui/lab": "^5.0.0-alpha.159", - "@mui/material": "^5.15.3", - "@testing-library/jest-dom": "^5.17.0", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^13.5.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-hot-toast": "^2.4.1", - "react-router-dom": "^6.21.1", + "@emotion/react": "11.11.3", + "@emotion/styled": "11.11.0", + "@mui/icons-material": "5.15.3", + "@mui/lab": "5.0.0-alpha.159", + "@mui/material": "5.15.3", + "@testing-library/jest-dom": "5.17.0", + "@testing-library/react": "13.4.0", + "@testing-library/user-event": "13.5.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-hot-toast": "2.4.1", + "react-router-dom": "6.21.1", "react-scripts": "5.0.1", - "styled-components": "^6.1.8", - "web-vitals": "^2.1.4" + "styled-components": "6.1.8", + "web-vitals": "2.1.4" }, "devDependencies": { - "autoprefixer": "^10.4.16", - "postcss": "^8.4.33", - "tailwindcss": "^3.4.1" + "autoprefixer": "10.4.16", + "postcss": "8.4.33", + "tailwindcss": "3.4.1" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -7693,14 +7693,6 @@ "tslib": "^2.0.3" } }, - "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "engines": { - "node": ">=10" - } - }, "node_modules/dotenv-expand": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", @@ -15623,6 +15615,14 @@ } } }, + "node_modules/react-scripts/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", diff --git a/admin_frontend/package.json b/admin_frontend/package.json index 5d8e010..e4dadf5 100644 --- a/admin_frontend/package.json +++ b/admin_frontend/package.json @@ -3,21 +3,21 @@ "version": "1.0.0", "private": true, "dependencies": { - "@emotion/react": "^11.11.3", - "@emotion/styled": "^11.11.0", - "@mui/icons-material": "^5.15.3", - "@mui/lab": "^5.0.0-alpha.159", - "@mui/material": "^5.15.3", - "@testing-library/jest-dom": "^5.17.0", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^13.5.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-hot-toast": "^2.4.1", - "react-router-dom": "^6.21.1", + "@emotion/react": "11.11.3", + "@emotion/styled": "11.11.0", + "@mui/icons-material": "5.15.3", + "@mui/lab": "5.0.0-alpha.159", + "@mui/material": "5.15.3", + "@testing-library/jest-dom": "5.17.0", + "@testing-library/react": "13.4.0", + "@testing-library/user-event": "13.5.0", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-hot-toast": "2.4.1", + "react-router-dom": "6.21.1", "react-scripts": "5.0.1", - "styled-components": "^6.1.8", - "web-vitals": "^2.1.4" + "styled-components": "6.1.8", + "web-vitals": "2.1.4" }, "scripts": { "start": "PORT=3002 react-scripts start", @@ -44,8 +44,8 @@ ] }, "devDependencies": { - "autoprefixer": "^10.4.16", - "postcss": "^8.4.33", - "tailwindcss": "^3.4.1" + "autoprefixer": "10.4.16", + "postcss": "8.4.33", + "tailwindcss": "3.4.1" } } diff --git a/admin_frontend/src/components/ApiKeys.jsx b/admin_frontend/src/components/ApiKeys.jsx index 0dd30b8..e5174d2 100644 --- a/admin_frontend/src/components/ApiKeys.jsx +++ b/admin_frontend/src/components/ApiKeys.jsx @@ -1,7 +1,8 @@ import { useEffect, useState } from "react"; import AddCircleIcon from "@mui/icons-material/AddCircle"; -import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; +import RemoveCircleIcon from "@mui/icons-material/RemoveCircle"; import toast from "react-hot-toast"; +// components import { TextField } from "@mui/material"; import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; @@ -17,8 +18,13 @@ import IconButton from "@mui/material/IconButton"; import OutlinedInput from "@mui/material/OutlinedInput"; import FormControl from "@mui/material/FormControl"; import InputLabel from "@mui/material/InputLabel"; +import Checkbox from "@mui/material/Checkbox"; + import Header from "./Header"; +// constants +import { ENDPOINTS } from "../constants/common"; + const ApiKeysPage = () => { const [keys, setKeys] = useState([]); const [loading, setLoading] = useState(false); @@ -26,6 +32,8 @@ const ApiKeysPage = () => { const [privateKey, setPrivateKey] = useState(""); const [supportedNetworks, setSupportedNetworks] = useState(""); const [customErc20Paymaster, setCustomErc20Paymaster] = useState(""); + const [txnMode, setTxnMode] = useState(0); + const [noOfTxn, setNoOfTxn] = useState(10); const [showPassword, setShowPassword] = useState(false); const handleClickShowPassword = () => setShowPassword(!showPassword); @@ -34,21 +42,25 @@ const ApiKeysPage = () => { event.preventDefault(); }; + const handleChange = (event) => { + setTxnMode(event.target.checked ? 1 : 0); + }; + const fetchData = async () => { try { setLoading(true); const data = await ( - await fetch("http://localhost:5050/getKeys", { + await fetch(`${process.env.REACT_APP_SERVER_URL}${ENDPOINTS['getKeys']}`, { method: "GET", }) ).json(); - console.log("data: ", data); setKeys(data); setLoading(false); } catch (err) { - toast.error( - "Check Backend Service for more info" - ); + if (err.message.includes("Falied to fetch")) + toast.error("Failed to connect. Please make sure that the backend is running") + else + toast.error(err.message) } }; @@ -61,6 +73,16 @@ const ApiKeysPage = () => { toast.error("Please input both API_KEY & PRIVATE_KEY field"); return; } + if ( + !/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*-_&])[A-Za-z\d@$!%*-_&]{8,}$/.test( + apiKey + ) + ) { + toast.error( + "Invalid Validation: API_KEY format. Please see the docs for more info" + ); + return; + } try { setLoading(true); const requestData = { @@ -68,14 +90,17 @@ const ApiKeysPage = () => { PRIVATE_KEY: privateKey, SUPPORTED_NETWORKS: supportedNetworks ?? "", ERC20_PAYMASTERS: customErc20Paymaster ?? "", + TRANSACTION_LIMIT: txnMode, + NO_OF_TRANSACTIONS_IN_A_MONTH: noOfTxn, + INDEXER_ENDPOINT: + process.env.REACT_APP_INDEXER_ENDPOINT ?? "http://localhost:3003", }; - const data = await ( - await fetch("http://localhost:5050/saveKey", { - method: "POST", - body: JSON.stringify(requestData), - }) - ).json(); - if (!data.error) { + const data = await fetch(`${process.env.REACT_APP_SERVER_URL}${ENDPOINTS['saveKey']}`, { + method: "POST", + body: JSON.stringify(requestData), + }); + const dataJson = await data.json(); + if (!dataJson.error) { toast.success("Saved Successfully"); setApiKey(""); setPrivateKey(""); @@ -85,9 +110,10 @@ const ApiKeysPage = () => { toast.error("Could not save"); } } catch (err) { - toast.error( - "Check Backend Service for more info" - ); + if (err.message.includes("Falied to fetch")) + toast.error("Failed to connect. Please make sure that the backend is running") + else + toast.error(err.message) setLoading(false); } }; @@ -96,7 +122,7 @@ const ApiKeysPage = () => { try { setLoading(true); const data = await ( - await fetch("http://localhost:5050/deleteKey", { + await fetch(`${process.env.REACT_APP_SERVER_URL}${ENDPOINTS['deleteKey']}`, { method: "POST", body: JSON.stringify({ API_KEY: key }), }) @@ -109,10 +135,10 @@ const ApiKeysPage = () => { toast.error("Could not save"); } } catch (err) { - console.log("err: ", err); - toast.error( - "Check Backend Service for more info" - ); + if (err.message.includes("Falied to fetch")) + toast.error("Failed to connect. Please make sure that the backend is running") + else + toast.error(err.message) setLoading(false); } }; @@ -129,6 +155,8 @@ const ApiKeysPage = () => { Private Key Supported Networks Custom ERC20 Paymasters + Transaction Limit Mode + No of Transactions Allowed Actions Available @@ -211,6 +239,24 @@ const ApiKeysPage = () => { fullWidth /> + + + + + setNoOfTxn(e.target.value)} + value={noOfTxn} + required + fullWidth + /> + { {row.SUPPORTED_NETWORKS} {row.ERC20_PAYMASTERS} + + {row.TRANSACTION_LIMIT === 0 ? "OFF" : "ON"} + + {row.NO_OF_TRANSACTIONS_IN_A_MONTH} =18.14" + } +} diff --git a/backend/indexer/ponder-env.d.ts b/backend/indexer/ponder-env.d.ts new file mode 100644 index 0000000..e5b1f32 --- /dev/null +++ b/backend/indexer/ponder-env.d.ts @@ -0,0 +1,32 @@ +// This file enables type checking and editor autocomplete for this Ponder project. +// After upgrading, you may find that changes have been made to this file. +// If this happens, please commit the changes. Do not manually edit this file. +// See https://ponder.sh/docs/guides/typescript for more information. + +declare module "@/generated" { + import type { + PonderContext, + PonderEvent, + PonderEventNames, + PonderApp, + } from "@ponder/core"; + + type Config = typeof import("./ponder.config.ts").default; + type Schema = typeof import("./ponder.schema.ts").default; + + export const ponder: PonderApp; + export type EventNames = PonderEventNames; + export type Event = PonderEvent< + Config, + name + >; + export type Context = PonderContext< + Config, + Schema, + name + >; + export type IndexingFunctionArgs = { + event: Event; + context: Context; + }; +} diff --git a/backend/indexer/ponder.config.ts b/backend/indexer/ponder.config.ts new file mode 100644 index 0000000..1520984 --- /dev/null +++ b/backend/indexer/ponder.config.ts @@ -0,0 +1,137 @@ +import { createConfig } from "@ponder/core"; +import { http } from "viem"; +import SupportedNetworks from "../config.json" assert { type: "json" }; +import { EtherspotPaymasterAbi } from "./EtherspotAbi"; + +export default createConfig({ + networks: { + mainnet: { chainId: 1, transport: http((SupportedNetworks.find((chain) => chain.chainId == 1))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + goerli: { chainId: 5, transport: http((SupportedNetworks.find((chain) => chain.chainId == 5))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + optimism: { chainId: 10, transport: http((SupportedNetworks.find((chain) => chain.chainId == 10))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + flare: { chainId: 14, transport: http((SupportedNetworks.find((chain) => chain.chainId == 14))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + bnb: { chainId: 56, transport: http((SupportedNetworks.find((chain) => chain.chainId == 56))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + bnbTestnet: { chainId: 97, transport: http((SupportedNetworks.find((chain) => chain.chainId == 97))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + gnosis: { chainId: 100, transport: http((SupportedNetworks.find((chain) => chain.chainId == 100))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + flareTestnet: { chainId: 114, transport: http((SupportedNetworks.find((chain) => chain.chainId == 114))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + fuse: { chainId: 122, transport: http((SupportedNetworks.find((chain) => chain.chainId == 122))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + polygon: { chainId: 137, transport: http((SupportedNetworks.find((chain) => chain.chainId == 137))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + opGoerli: { chainId: 420, transport: http((SupportedNetworks.find((chain) => chain.chainId == 420))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + klaytnTestnet: { chainId: 1001, transport: http((SupportedNetworks.find((chain) => chain.chainId == 1001))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + mantle: { chainId: 5000, transport: http((SupportedNetworks.find((chain) => chain.chainId == 5000))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + mantleTestnet: { chainId: 5001, transport: http((SupportedNetworks.find((chain) => chain.chainId == 5001))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + klaytn: { chainId: 8217, transport: http((SupportedNetworks.find((chain) => chain.chainId == 8217))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + base: { chainId: 8453, transport: http((SupportedNetworks.find((chain) => chain.chainId == 8453))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + arbitrum: { chainId: 42161, transport: http((SupportedNetworks.find((chain) => chain.chainId == 42161))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + avalanche: { chainId: 43114, transport: http((SupportedNetworks.find((chain) => chain.chainId == 43114))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + linea: { chainId: 59144, transport: http((SupportedNetworks.find((chain) => chain.chainId == 59144))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + mumbai: { chainId: 80001, transport: http((SupportedNetworks.find((chain) => chain.chainId == 80001))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 1 }, + baseGoerli: { chainId: 84531, transport: http((SupportedNetworks.find((chain) => chain.chainId == 84531))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + arbitrumGoerli: { chainId: 421613, transport: http((SupportedNetworks.find((chain) => chain.chainId == 421613))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + scrollSepolia: { chainId: 534351, transport: http((SupportedNetworks.find((chain) => chain.chainId == 534351))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + sepolia: { chainId: 11155111, transport: http((SupportedNetworks.find((chain) => chain.chainId == 11155111))?.bundler), maxHistoricalTaskConcurrency: 1, maxRequestsPerSecond: 10 }, + }, + contracts: { + EtherspotPaymaster: { + abi: EtherspotPaymasterAbi, + network: { + mainnet: { + address: '0x7F690e93CecFca5A31E6e1dF50A33F6d3059048c', + startBlock: 18026150 + }, + goerli: { + address: "0xcaDBADcFeD5530A49762DFc9d1d712CcD6b09b25", + startBlock: 9600295 + }, + optimism: { + address: "0x805650ce74561C85baA44a8Bd13E19633Fd0F79d", + startBlock: 108859074 + }, + flare: { + address: "0x8A41594e5c6Fe492e437414c24eA6f401186b8d2", + startBlock: 13893451 + }, + bnb: { + address: "0xEA5ecE95D3A28f9faB161779d20128b449F9EC9C", + startBlock: 31278246 + }, + bnbTestnet: { + address: "0x153e26707DF3787183945B88121E4Eb188FDCAAA", + startBlock: 32874243 + }, + gnosis: { + address: "0x373aBcF1EA9e5802778E32870e7f72C8A6a90349", + startBlock: 29705973 + }, + flareTestnet: { + address: "0x2a18C360b525824B3e5656B5a705554f2a5036Be", + startBlock: 6417126 + }, + fuse: { + address: "0xEC2EE24E79C73DB13Dd9bC782856a5296626b7eb", + startBlock: 25126646 + }, + polygon: { + address: "0x26FeC24b0D467C9de105217B483931e8f944ff50", + startBlock: 46898238 + }, + opGoerli: { + address: "0x898c530A5fA37720DcF1843AeCC34b6B0cBaEB8a", + startBlock: 13936022 + }, + klaytnTestnet: { + address: "0x810FA4C915015b703db0878CF2B9344bEB254a40", + startBlock: 131731991 + }, + mantle: { + address: "0x8A41594e5c6Fe492e437414c24eA6f401186b8d2", + startBlock: 3588793 + }, + mantleTestnet: { + address: "0xb56eC212C60C47fb7385f13b7247886FFa5E9D5C", + startBlock: 19258864 + }, + klaytn: { + address: "0x4ebd86AAF89151b5303DB072e0205C668e31E5E7", + startBlock: 131354386 + }, + base: { + address: "0x810FA4C915015b703db0878CF2B9344bEB254a40", + startBlock: 3265420 + }, + arbitrum: { + address: "0xEC2EE24E79C73DB13Dd9bC782856a5296626b7eb", + startBlock: 126094060 + }, + avalanche: { + address: "0x527569794781671319f20374A050BDbef4181aB3", + startBlock: 34521365 + }, + linea: { + address: "0xB3AD9B9B06c6016f81404ee8FcCD0526F018Cf0C", + startBlock: 303424 + }, + mumbai: { + address: "0x8350355c08aDAC387b443782124A30A8942BeC2e", + startBlock: 41860287 + }, + baseGoerli: { + address: "0x898c530A5fA37720DcF1843AeCC34b6B0cBaEB8a", + startBlock: 9059086 + }, + arbitrumGoerli: { + address: "0x898c530A5fA37720DcF1843AeCC34b6B0cBaEB8a", + }, + scrollSepolia: { + address: "0xe893A26DD53b325BffAacDfA224692EfF4C448c4", + startBlock: 386998 + }, + sepolia: { + address: "0xcaDBADcFeD5530A49762DFc9d1d712CcD6b09b25", + startBlock: 4183498 + } + }, + filter: { event: "SponsorSuccessful" }, + maxBlockRange: 5, + }, + }, +}); diff --git a/backend/indexer/ponder.schema.ts b/backend/indexer/ponder.schema.ts new file mode 100644 index 0000000..dec8e05 --- /dev/null +++ b/backend/indexer/ponder.schema.ts @@ -0,0 +1,14 @@ +import { createSchema } from "@ponder/core"; + +export default createSchema((p) => ({ + PaymasterEvent: p.createTable({ + id: p.string(), + sender: p.string(), + paymaster: p.string(), + transactionHash: p.string(), + timestamp: p.bigint(), + month: p.int(), + year: p.int(), + chainId: p.int(), + }), +})); diff --git a/backend/indexer/src/index.ts b/backend/indexer/src/index.ts new file mode 100644 index 0000000..4da4d33 --- /dev/null +++ b/backend/indexer/src/index.ts @@ -0,0 +1,26 @@ +import { ponder } from "@/generated"; +import { BigNumber } from "ethers"; + +ponder.on("EtherspotPaymaster:SponsorSuccessful", async ({ event, context }) => { + const { db, network } = context; + const { args, block, transaction, log } = event; + const time = Number(BigNumber.from(block.timestamp).toString()); + const timestamp = new Date(time * 1000); + try { + await db.PaymasterEvent.create({ + id: transaction.hash + log.logIndex, + data: { + chainId: network.chainId, + sender: args.sender, + paymaster: args.paymaster, + transactionHash: transaction.hash, + timestamp: block.timestamp, + month: timestamp.getMonth(), + year: timestamp.getFullYear() + } + }) + } catch (err) { + // caught err + console.error(err); + } +}); diff --git a/backend/indexer/tsconfig.json b/backend/indexer/tsconfig.json new file mode 100644 index 0000000..592b9a9 --- /dev/null +++ b/backend/indexer/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + // Type checking + "strict": true, + "noUncheckedIndexedAccess": true, + + // Interop constraints + "verbatimModuleSyntax": false, + "esModuleInterop": true, + "isolatedModules": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + + // Language and environment + "moduleResolution": "bundler", + "module": "ESNext", + "noEmit": true, + "lib": ["ES2022"], + "target": "ES2022", + + // Skip type checking for node modules + "skipLibCheck": true + }, + "include": ["./**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/backend/package.json b/backend/package.json index 0de8d5d..262d3f5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "arka", - "version": "1.0.0", + "version": "1.1.0", "description": "ARKA - (Albanian for Cashier's case) is the first open source Paymaster as a service software", "type": "module", "directories": { @@ -30,6 +30,7 @@ "@account-abstraction/utils": "0.5.0", "@aws-sdk/client-secrets-manager": "3.450.0", "@fastify/cors": "8.4.1", + "@ponder/core": "0.2.7", "@sinclair/typebox": "0.31.28", "ajv": "8.11.2", "crypto": "^1.0.1", @@ -40,9 +41,11 @@ "fastify-cron": "1.3.1", "fastify-plugin": "3.0.1", "getmac": "^6.6.0", + "graphql-request": "6.1.0", "node-fetch": "3.3.2", - "sqlite": "^5.1.1", - "sqlite3": "^5.1.7-rc.0" + "sqlite": "5.1.1", + "sqlite3": "5.1.7-rc.0", + "viem": "2.7.6" }, "devDependencies": { "@babel/core": "7.23.2", @@ -61,7 +64,7 @@ "prettier": "2.8.0", "ts-jest": "29.1.1", "tsx": "3.12.1", - "typescript": "4.9.3", + "typescript": "5.0.4", "vitest": "0.25.8" } } diff --git a/backend/src/abi/EtherspotAbi.ts b/backend/src/abi/EtherspotAbi.ts index 7313be3..059f22e 100644 --- a/backend/src/abi/EtherspotAbi.ts +++ b/backend/src/abi/EtherspotAbi.ts @@ -597,4 +597,4 @@ export default [ "stateMutability": "nonpayable", "type": "function" } - ] \ No newline at end of file + ] as const; \ No newline at end of file diff --git a/backend/src/constants/ErrorMessage.ts b/backend/src/constants/ErrorMessage.ts index 49ee38e..750e81c 100644 --- a/backend/src/constants/ErrorMessage.ts +++ b/backend/src/constants/ErrorMessage.ts @@ -4,7 +4,10 @@ export default { UNSUPPORTED_NETWORK: 'Unsupported network', UNSUPPORTED_NETWORK_TOKEN: 'Unsupported network/token', EMPTY_BODY: 'Empty Body received', - SOMETHING_WENT_WRONG: 'Something went wrong', + FAILED_TO_PROCESS: 'Failed to process the request. Please try again or contact ARKA support team', INVALID_MODE: 'Invalid mode selected', DUPLICATE_RECORD: 'Duplicate record found', -} \ No newline at end of file + QUOTA_EXCEEDED: 'Quota exceeded for this month', + RECORD_NOT_FOUND: 'Api Key provided not found', + API_KEY_VALIDATION_FAILED: 'Api Key is not in the right format as described in readme file', +} diff --git a/backend/src/constants/ReturnCode.ts b/backend/src/constants/ReturnCode.ts index 39e62fa..bc7fc8d 100644 --- a/backend/src/constants/ReturnCode.ts +++ b/backend/src/constants/ReturnCode.ts @@ -1,4 +1,4 @@ export default { SUCCESS: 200, FAILURE: 400, -} \ No newline at end of file +} diff --git a/backend/src/migrations/001.default.sql b/backend/src/migrations/001.default.sql index d4f7994..a0e329e 100644 --- a/backend/src/migrations/001.default.sql +++ b/backend/src/migrations/001.default.sql @@ -39,4 +39,4 @@ INSERT INTO config ( -- Down -------------------------------------------------------------------------------- -DROP TABLE IF EXISTS config \ No newline at end of file +DROP TABLE IF EXISTS config diff --git a/backend/src/migrations/002.apiKeys.sql b/backend/src/migrations/002.apiKeys.sql index f9ad12d..ce07966 100644 --- a/backend/src/migrations/002.apiKeys.sql +++ b/backend/src/migrations/002.apiKeys.sql @@ -7,11 +7,14 @@ CREATE TABLE IF NOT EXISTS api_keys ( WALLET_ADDRESS TEXT NOT NULL, PRIVATE_KEY varchar NOT NULL, SUPPORTED_NETWORKS varchar DEFAULT NULL, - ERC20_PAYMASTERS varchar DEFAULT NULL + ERC20_PAYMASTERS varchar DEFAULT NULL, + TRANSACTION_LIMIT INT NOT NULL, + NO_OF_TRANSACTIONS_IN_A_MONTH int, + INDEXER_ENDPOINT varchar ); -------------------------------------------------------------------------------- -- Down -------------------------------------------------------------------------------- -DROP TABLE IF EXISTS api_keys \ No newline at end of file +DROP TABLE IF EXISTS api_keys diff --git a/backend/src/paymaster/index.ts b/backend/src/paymaster/index.ts index d5ff444..4c55031 100644 --- a/backend/src/paymaster/index.ts +++ b/backend/src/paymaster/index.ts @@ -32,11 +32,10 @@ export class Paymaster { return paymasterAndData; } - async sign(userOp: any, validUntil: string, validAfter: string, entryPoint: string, paymasterAddress: string, bundlerRpc: string, relayerKey: string) { + async sign(userOp: any, validUntil: string, validAfter: string, entryPoint: string, paymasterAddress: string, bundlerRpc: string, signer: Wallet) { try { const provider = new providers.JsonRpcProvider(bundlerRpc); const paymasterContract = new ethers.Contract(paymasterAddress, abi, provider); - const signer = new Wallet(relayerKey, provider) userOp.paymasterAndData = await this.getPaymasterAndData(userOp, validUntil, validAfter, paymasterContract, signer); userOp.signature = '0x'; const response = await provider.send('eth_estimateUserOperationGas', [userOp, entryPoint]); @@ -54,8 +53,8 @@ export class Paymaster { } return returnValue; - } catch (err) { - throw new Error('Transaction Execution reverted') + } catch (err: any) { + throw new Error('Failed to process request to bundler. Please contact support team RawErrorMsg:' + err.message) } } @@ -82,7 +81,7 @@ export class Paymaster { callGasLimit: response.callGasLimit, }; } catch (err: any) { - throw new Error('Transaction Execution reverted ' + err.message) + throw new Error('Failed to process request to bundler. Please contact support team RawErrorMsg: ' + err.message) } } @@ -150,4 +149,4 @@ export class Paymaster { throw new Error('Error while submitting transaction'); } } -} \ No newline at end of file +} diff --git a/backend/src/paymaster/pimlico.ts b/backend/src/paymaster/pimlico.ts index 0bf8023..71eb51c 100644 --- a/backend/src/paymaster/pimlico.ts +++ b/backend/src/paymaster/pimlico.ts @@ -192,4 +192,4 @@ export async function getERC20Paymaster( throw new Error(`ERC20Paymaster not deployed at ${address}`) } return new PimlicoPaymaster(address, provider) -} \ No newline at end of file +} diff --git a/backend/src/plugins/db.ts b/backend/src/plugins/db.ts index d7b89e2..2065a2c 100644 --- a/backend/src/plugins/db.ts +++ b/backend/src/plugins/db.ts @@ -21,4 +21,4 @@ declare module "fastify" { sqlite: Database; } } -export default fp(databasePlugin); \ No newline at end of file +export default fp(databasePlugin); diff --git a/backend/src/routes/admin.ts b/backend/src/routes/admin.ts index ce832e2..f039438 100644 --- a/backend/src/routes/admin.ts +++ b/backend/src/routes/admin.ts @@ -19,7 +19,7 @@ const adminRoutes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.SUCCESS).send(result); } catch (err: any) { request.log.error(err); - return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.SOMETHING_WENT_WRONG }); + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }); } }) @@ -52,17 +52,18 @@ const adminRoutes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.SUCCESS).send({ error: null, message: 'Successfully saved' }); } catch (err: any) { request.log.error(err); - return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.SOMETHING_WENT_WRONG }); + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }); } }); server.post('/saveKey', async function (request, reply) { try { - console.log('body: ', JSON.parse(request.body as string)); const body: any = JSON.parse(request.body as string); if (!body) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.EMPTY_BODY }); if (!body.API_KEY || !body.PRIVATE_KEY) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); + if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*-_&])[A-Za-z\d@$!%*-_&]{8,}$/.test(body.API_KEY)) + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.API_KEY_VALIDATION_FAILED }) const wallet = new ethers.Wallet(body.PRIVATE_KEY); const publicAddress = await wallet.getAddress(); const result: any[] = await new Promise((resolve, reject) => { @@ -81,12 +82,18 @@ const adminRoutes: FastifyPluginAsync = async (server) => { WALLET_ADDRESS, \ PRIVATE_KEY, \ SUPPORTED_NETWORKS, \ - ERC20_PAYMASTERS) VALUES (?, ?, ?, ?, ?)", [ + ERC20_PAYMASTERS, \ + TRANSACTION_LIMIT, \ + NO_OF_TRANSACTIONS_IN_A_MONTH, \ + INDEXER_ENDPOINT) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [ body.API_KEY, publicAddress, hmac, body.SUPPORTED_NETWORKS, body.ERC20_PAYMASTERS, + body.TRANSACTION_LIMIT ?? 0, + body.NO_OF_TRANSACTIONS_IN_A_MONTH ?? 10, + body.INDEXER_ENDPOINT ?? process.env.DEFAULT_INDEXER_ENDPOINT ], (err: any, row: any) => { if (err) reject(err); resolve(row); @@ -95,7 +102,43 @@ const adminRoutes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.SUCCESS).send({ error: null, message: 'Successfully saved' }); } catch (err: any) { request.log.error(err); - return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.SOMETHING_WENT_WRONG }); + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }); + } + }) + + server.post('/updateKey', async function (request, reply) { + try { + const body: any = JSON.parse(request.body as string); + if (!body) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.EMPTY_BODY }); + if (!body.API_KEY) + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); + if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*-_&])[A-Za-z\d@$!%*-_&]{8,}$/.test(body.API_KEY)) + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.API_KEY_VALIDATION_FAILED }) + const result: any[] = await new Promise((resolve, reject) => { + server.sqlite.db.get("SELECT * FROM api_keys WHERE API_KEY=?", [body.API_KEY], (err: any, row: any) => { + if (err) reject(err); + resolve(row); + }) + }); + if (!result || result.length == 0) + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.RECORD_NOT_FOUND }); + await new Promise((resolve, reject) => { + server.sqlite.db.run("UPDATE api_keys SET SUPPORTED_NETWORKS = ?, \ + ERC20_PAYMASTERS = ?, \ + TRANSACTION_LIMIT = ?, \ + NO_OF_TRANSACTIONS_IN_A_MONTH = ?, \ + INDEXER_ENDPOINT = ?, \ + WHERE API_KEY = ?", [body.SUPPORTED_NETWORKS, body.ERC20_PAYMASTERS, body.TRANSACTION_LIMIT ?? 0, body.NO_OF_TRANSACTIONS_IN_A_MONTH ?? 10, + body.INDEXER_ENDPOINT ?? process.env.DEFAULT_INDEXER_ENDPOINT, body.API_KEY + ], (err: any, row: any) => { + if (err) reject(err); + resolve(row); + }) + }); + return reply.code(ReturnCode.SUCCESS).send({ error: null, message: 'Successfully updated' }); + } catch (err: any) { + server.log.error(err); + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }); } }) @@ -113,7 +156,7 @@ const adminRoutes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.SUCCESS).send(result); } catch (err: any) { request.log.error(err); - return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.SOMETHING_WENT_WRONG }); + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }); } }) @@ -123,6 +166,8 @@ const adminRoutes: FastifyPluginAsync = async (server) => { if (!body) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.EMPTY_BODY }); if (!body.API_KEY) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); + if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*-_&])[A-Za-z\d@$!%*-_&]{8,}$/.test(body.API_KEY)) + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.API_KEY_VALIDATION_FAILED }) await new Promise((resolve, reject) => { server.sqlite.db.run("DELETE FROM api_keys WHERE API_KEY=?", [body.API_KEY], (err: any, rows: any) => { if (err) reject(err); @@ -132,7 +177,7 @@ const adminRoutes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.SUCCESS).send({ error: null, message: 'Successfully deleted' }); } catch (err: any) { request.log.error(err); - return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.SOMETHING_WENT_WRONG }); + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }); } }) @@ -159,7 +204,7 @@ const adminRoutes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.SUCCESS).send(supportedNetworks); } catch (err: any) { request.log.error(err); - return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.SOMETHING_WENT_WRONG }); + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }); } }) }; diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index e766d57..7a2fe71 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Type } from "@sinclair/typebox"; import { FastifyPluginAsync } from "fastify"; -import { ethers } from "ethers"; +import { Wallet, ethers, providers } from "ethers"; +import { gql, request as GLRequest } from "graphql-request"; import { Paymaster } from "../paymaster/index.js"; import SupportedNetworks from "../../config.json" assert { type: "json" }; import { TOKEN_ADDRESS } from "../constants/Pimlico.js"; @@ -9,6 +10,7 @@ import ErrorMessage from "../constants/ErrorMessage.js"; import ReturnCode from "../constants/ReturnCode.js"; import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager"; import { decode } from "../utils/crypto.js"; +import { printRequest } from "../utils/common.js"; export function getNetworkConfig(key: any, supportedNetworks: any) { if (supportedNetworks !== '') { @@ -57,6 +59,7 @@ const routes: FastifyPluginAsync = async (server) => { "/", async function (request, reply) { try { + printRequest(request, server.log); const query: any = request.query; const body: any = request.body; if (!body) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.EMPTY_BODY }); @@ -72,6 +75,9 @@ const routes: FastifyPluginAsync = async (server) => { let customPaymasters = []; let privateKey = ''; let supportedNetworks; + let noOfTxns; + let txnMode; + let indexerEndpoint; if (!unsafeMode) { const AWSresponse = await client.send( new GetSecretValueCommand({ @@ -79,23 +85,34 @@ const routes: FastifyPluginAsync = async (server) => { }) ); const secrets = JSON.parse(AWSresponse.SecretString ?? '{}'); - if (!secrets['PRIVATE_KEY']) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if (!secrets['PRIVATE_KEY']) { + server.log.info("Invalid Api Key provided") + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + } if (secrets['ERC20_PAYMASTERS']) { const buffer = Buffer.from(secrets['ERC20_PAYMASTERS'], 'base64'); customPaymasters = JSON.parse(buffer.toString()); } privateKey = secrets['PRIVATE_KEY']; supportedNetworks = secrets['SUPPORTED_NETWORKS']; + noOfTxns = secrets['NO_OF_TRANSACTIONS_IN_A_MONTH'] ?? 10; + txnMode = secrets['TRANSACTION_LIMIT'] ?? 0; + indexerEndpoint = secrets['INDEXER_ENDPOINT'] ?? process.env.DEFAULT_INDEXER_ENDPOINT; } else { const record: any = await getSQLdata(api_key); - console.log(record); - if (!record) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if (!record) { + server.log.info("Invalid Api Key provided") + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + } if (record['ERC20_PAYMASTERS']) { const buffer = Buffer.from(record['ERC20_PAYMASTERS'], 'base64'); customPaymasters = JSON.parse(buffer.toString()); } privateKey = decode(record['PRIVATE_KEY']); supportedNetworks = record['SUPPORTED_NETWORKS']; + noOfTxns = record['NO_OF_TRANSACTIONS_IN_A_MONTH']; + txnMode = record['TRANSACTION_LIMIT']; + indexerEndpoint = record['INDEXER_ENDPOINT'] ?? process.env.DEFAULT_INDEXER_ENDPOINT; } if ( !userOp || @@ -104,9 +121,10 @@ const routes: FastifyPluginAsync = async (server) => { !mode || isNaN(chainId) ) { + server.log.info("Incomplete body data provided") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); } - + if (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); } @@ -121,6 +139,13 @@ const routes: FastifyPluginAsync = async (server) => { switch (mode.toLowerCase()) { case 'sponsor': { const date = new Date(); + const provider = new providers.JsonRpcProvider(networkConfig.bundler); + const signer = new Wallet(privateKey, provider) + if (txnMode) { + const signerAddress = await signer.getAddress(); + const IndexerData = await getIndexerData(signerAddress, userOp.sender, date.getMonth(), date.getFullYear(), noOfTxns, indexerEndpoint); + if (IndexerData.length >= noOfTxns) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.QUOTA_EXCEEDED }) + } const validUntil = context.validUntil ? new Date(context.validUntil) : date; const validAfter = context.validAfter ? new Date(context.validAfter) : date; const hex = (Number((validUntil.valueOf() / 1000).toFixed(0)) + 600).toString(16); @@ -135,7 +160,7 @@ const routes: FastifyPluginAsync = async (server) => { } str += hex; str1 += hex1; - result = await paymaster.sign(userOp, str, str1, entryPoint, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, privateKey); + result = await paymaster.sign(userOp, str, str1, entryPoint, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, signer); break; } case 'erc20': { @@ -146,6 +171,7 @@ const routes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_MODE }); } } + server.log.info(result, 'Response sent: '); if (body.jsonrpc) return reply.code(ReturnCode.SUCCESS).send({ jsonrpc: body.jsonrpc, id: body.id, result, error: null }) return reply.code(ReturnCode.SUCCESS).send(result); @@ -153,7 +179,7 @@ const routes: FastifyPluginAsync = async (server) => { request.log.error(err); if (err.name == "ResourceNotFoundException") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }); - return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.SOMETHING_WENT_WRONG }); + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }); } } ); @@ -163,6 +189,7 @@ const routes: FastifyPluginAsync = async (server) => { whitelistResponseSchema, async function (request, reply) { try { + printRequest(request, server.log); const query: any = request.query; const body: any = request.body; const entryPoint = body.params[0]; @@ -220,6 +247,7 @@ const routes: FastifyPluginAsync = async (server) => { if (!(TOKEN_ADDRESS[chainId] && TOKEN_ADDRESS[chainId][gasToken])) return reply.code(ReturnCode.FAILURE).send({ error: "Invalid network/token" }) result = await paymaster.pimlicoAddress(gasToken, networkConfig.bundler, entryPoint); } + server.log.info(result, 'Response sent: '); if (body.jsonrpc) return reply.code(ReturnCode.SUCCESS).send({ jsonrpc: body.jsonrpc, id: body.id, message: result.message, error: null }) return reply.code(ReturnCode.SUCCESS).send(result); @@ -227,7 +255,7 @@ const routes: FastifyPluginAsync = async (server) => { request.log.error(err); if (err.name == "ResourceNotFoundException") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }); - return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.SOMETHING_WENT_WRONG }); + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }); } } ) @@ -236,6 +264,7 @@ const routes: FastifyPluginAsync = async (server) => { "/whitelist", async function (request, reply) { try { + printRequest(request, server.log); const body: any = request.body; const query: any = request.query; const address = body.params[0]; @@ -257,7 +286,6 @@ const routes: FastifyPluginAsync = async (server) => { supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { const record: any = await getSQLdata(api_key); - console.log(record); if (!record) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) privateKey = decode(record['PRIVATE_KEY']); supportedNetworks = record['SUPPORTED_NETWORKS']; @@ -279,6 +307,7 @@ const routes: FastifyPluginAsync = async (server) => { const validAddresses = address.every(ethers.utils.isAddress); if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: "Invalid Address passed" }); const result = await paymaster.whitelistAddresses(address, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, privateKey); + server.log.info(result, 'Response sent: '); if (body.jsonrpc) return reply.code(ReturnCode.SUCCESS).send({ jsonrpc: body.jsonrpc, id: body.id, result, error: null }) return reply.code(ReturnCode.SUCCESS).send(result); @@ -286,7 +315,7 @@ const routes: FastifyPluginAsync = async (server) => { request.log.error(err); if (err.name == "ResourceNotFoundException") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }); - return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.SOMETHING_WENT_WRONG }) + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }) } } ) @@ -295,6 +324,7 @@ const routes: FastifyPluginAsync = async (server) => { "/checkWhitelist", async function (request, reply) { try { + printRequest(request, server.log); const body: any = request.body; const query: any = request.query; const accountAddress = body.params[0]; @@ -316,7 +346,6 @@ const routes: FastifyPluginAsync = async (server) => { supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { const record: any = await getSQLdata(api_key); - console.log(record); if (!record) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) privateKey = decode(record['PRIVATE_KEY']); supportedNetworks = record['SUPPORTED_NETWORKS']; @@ -336,6 +365,7 @@ const routes: FastifyPluginAsync = async (server) => { const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? ''); if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); const response = await paymaster.checkWhitelistAddress(accountAddress, networkConfig.contracts.etherspotPaymasterAddress, networkConfig.bundler, privateKey); + server.log.info(response, 'Response sent: '); if (body.jsonrpc) return reply.code(ReturnCode.SUCCESS).send({ jsonrpc: body.jsonrpc, id: body.id, result: { message: response === true ? 'Already added' : 'Not added yet' }, error: null }) return reply.code(ReturnCode.SUCCESS).send({ message: response === true ? 'Already added' : 'Not added yet' }); @@ -343,7 +373,7 @@ const routes: FastifyPluginAsync = async (server) => { request.log.error(err); if (err.name == "ResourceNotFoundException") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }); - return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.SOMETHING_WENT_WRONG }) + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }) } } ) @@ -353,6 +383,7 @@ const routes: FastifyPluginAsync = async (server) => { whitelistResponseSchema, async function (request, reply) { try { + printRequest(request, server.log); const body: any = request.body; const query: any = request.query; const amount = body.params[0]; @@ -374,7 +405,6 @@ const routes: FastifyPluginAsync = async (server) => { supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { const record: any = await getSQLdata(api_key); - console.log(record); if (!record) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) privateKey = decode(record['PRIVATE_KEY']); supportedNetworks = record['SUPPORTED_NETWORKS']; @@ -396,19 +426,49 @@ const routes: FastifyPluginAsync = async (server) => { request.log.error(err); if (err.name == "ResourceNotFoundException") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }); - return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.SOMETHING_WENT_WRONG }) + return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }) } } ) async function getSQLdata(apiKey: string) { - const result: any[] = await new Promise((resolve, reject) => { - server.sqlite.db.get("SELECT * FROM api_keys WHERE API_KEY = ?", [apiKey], (err: any, rows: any[]) => { - if (err) reject(err); - resolve(rows); + try { + const result: any[] = await new Promise((resolve, reject) => { + server.sqlite.db.get("SELECT * FROM api_keys WHERE API_KEY = ?", [apiKey], (err: any, rows: any[]) => { + if (err) reject(err); + resolve(rows); + }) }) - }) - return result; + return result; + } catch (err) { + server.log.error(err); + return null; + } + } + + async function getIndexerData(sponsor: string, sender: string, month: number, year: number, noOfTxns: number, endpoint: string): Promise { + try { + const query = gql` + query { + paymasterEvents( + limit: ${noOfTxns} + where: {month: ${month}, year: ${year}, paymaster: "${sponsor}", sender: "${sender}"}) + { + items { + sender + paymaster + transactionHash + year + month + } + } + }`; + const apiResponse: any = await GLRequest(endpoint, query); + return apiResponse.paymasterEvents.items; + } catch (err) { + server.log.error(err); + return []; + } } }; diff --git a/backend/src/utils/common.ts b/backend/src/utils/common.ts new file mode 100644 index 0000000..28e1b68 --- /dev/null +++ b/backend/src/utils/common.ts @@ -0,0 +1,6 @@ +import { FastifyBaseLogger, FastifyRequest } from "fastify"; + +export function printRequest(request: FastifyRequest, log: FastifyBaseLogger) { + log.info(request.query, "query passed: "); + log.info(request.body, "body passed: "); +} diff --git a/backend/src/utils/crypto.ts b/backend/src/utils/crypto.ts index f689757..c834b28 100644 --- a/backend/src/utils/crypto.ts +++ b/backend/src/utils/crypto.ts @@ -28,4 +28,4 @@ export function decode(value: string) { ); if (!digestsEqual) throw new Error('invalid value(s)'); return decodedData; -} \ No newline at end of file +} diff --git a/docker-compose.yml b/docker-compose.yml index 397f82c..471bb10 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: - UNSAFE_MODE=false - SUPPORTED_NETWORKS= - CRON_PRIVATE_KEY= + - REACT_APP_INDEXER_ENDPOINT=http://localhost:3003 build: context: ./backend dockerfile: Dockerfile @@ -27,6 +28,9 @@ services: - "5050:5050" admin_frontend: + environment: + - REACT_APP_INDEXER_ENDPOINT=http://localhost:3003 + - REACT_APP_SERVER_URL=http://localhost:5050 build: context: ./admin_frontend dockerfile: Dockerfile diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f70899f..1286d4a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,8 +28,8 @@ "postcss": "8.4.31", "process": "0.11.10", "react-app-rewired": "2.2.1", - "react-scripts": "^5.0.1", - "tailwindcss": "^3.3.5" + "react-scripts": "5.0.1", + "tailwindcss": "3.3.5" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/frontend/package.json b/frontend/package.json index d5208fa..37b2add 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,8 +36,8 @@ "postcss": "8.4.31", "process": "0.11.10", "react-app-rewired": "2.2.1", - "react-scripts": "^5.0.1", - "tailwindcss": "^3.3.5" + "react-scripts": "5.0.1", + "tailwindcss": "3.3.5" }, "browserslist": { "production": [