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": [