diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f7dcdc --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# build +/build/ + +# misc +.DS_Store +.env.production + +# debug +npm-debug.log* + +.nyc_output +coverage + +.env +config.json +database.sqlite + +package-lock.json +pnpm-lock.yaml + +# Ponder +/indexer/.ponder +/indexer/generated +yarn.lock \ No newline at end of file diff --git a/admin_frontend/.gitignore b/admin_frontend/.gitignore index 4d29575..960b87d 100644 --- a/admin_frontend/.gitignore +++ b/admin_frontend/.gitignore @@ -21,3 +21,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +yarn.lock \ No newline at end of file diff --git a/admin_frontend/src/components/ApiKeys.jsx b/admin_frontend/src/components/ApiKeys.jsx index 2bfd197..9bb1fd1 100644 --- a/admin_frontend/src/components/ApiKeys.jsx +++ b/admin_frontend/src/components/ApiKeys.jsx @@ -135,12 +135,12 @@ const ApiKeysPage = () => { JSON.stringify(customErc20Paymaster) ).toString("base64"); const requestData = { - API_KEY: apiKey, - PRIVATE_KEY: privateKey, - SUPPORTED_NETWORKS: + apiKey: apiKey, + privateKey: privateKey, + supportedNetworks: Buffer.from(JSON.stringify(supportedNetworks)).toString("base64") ?? "", - ERC20_PAYMASTERS: base64Erc20 ?? "", + erc20Paymasters: base64Erc20 ?? "", }; const data = await fetch( `${process.env.REACT_APP_SERVER_URL}${ENDPOINTS["saveKey"]}`, @@ -280,12 +280,12 @@ const ApiKeysPage = () => { {keys.map((row, index) => ( - - {row.WALLET_ADDRESS} - {row.API_KEY} + + {row.walletAddress} + {row.apiKey}
-
{showPassword ? row.PRIVATE_KEY : "*****"}
+
{showPassword ? row.privateKey : "*****"}
{ @@ -324,7 +324,7 @@ const ApiKeysPage = () => { startIcon={} variant="contained" onClick={() => { - handleDelete(row.API_KEY); + handleDelete(row.apiKey); }} > Delete Row diff --git a/backend/.gitignore b/backend/.gitignore index 6b999ff..0b86d72 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -25,4 +25,6 @@ pnpm-lock.yaml # Ponder /indexer/.ponder -/indexer/generated \ No newline at end of file +/indexer/generated + +yarn.lock \ No newline at end of file diff --git a/backend/src/models/APIKey.ts b/backend/src/models/APIKey.ts new file mode 100644 index 0000000..0a181d1 --- /dev/null +++ b/backend/src/models/APIKey.ts @@ -0,0 +1,86 @@ +import { Sequelize, DataTypes, Model } from 'sequelize'; + +export class APIKey extends Model { + declare apiKey: string; + declare walletAddress: string; + declare privateKey: string; + declare supportedNetworks: string | null; + declare erc20Paymasters: string | null; + declare multiTokenPaymasters: string | null; + declare multiTokenOracles: string | null; + declare sponsorName: string | null; + declare logoUrl: string | null; + declare transactionLimit: number; + declare noOfTransactionsInAMonth: number | null; + declare indexerEndpoint: string | null; +} + +export function initializeAPIKeyModel(sequelize: Sequelize) { + APIKey.init({ + apiKey: { + type: DataTypes.TEXT, + allowNull: false, + primaryKey: true, + field: 'API_KEY' + }, + walletAddress: { + type: DataTypes.TEXT, + allowNull: false, + field: 'WALLET_ADDRESS' + }, + privateKey: { + type: DataTypes.STRING, + allowNull: false, + field: 'PRIVATE_KEY' + }, + supportedNetworks: { + type: DataTypes.STRING, + allowNull: true, + field: 'SUPPORTED_NETWORKS' + }, + erc20Paymasters: { + type: DataTypes.STRING, + allowNull: true, + field: 'ERC20_PAYMASTERS' + }, + multiTokenPaymasters: { + type: DataTypes.STRING, + allowNull: true, + field: 'MULTI_TOKEN_PAYMASTERS' + }, + multiTokenOracles: { + type: DataTypes.STRING, + allowNull: true, + field: 'MULTI_TOKEN_ORACLES' + }, + sponsorName: { + type: DataTypes.STRING, + allowNull: true, + field: 'SPONSOR_NAME' + }, + logoUrl: { + type: DataTypes.STRING, + allowNull: true, + field: 'LOGO_URL' + }, + transactionLimit: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'TRANSACTION_LIMIT' + }, + noOfTransactionsInAMonth: { + type: DataTypes.INTEGER, + allowNull: true, + field: 'NO_OF_TRANSACTIONS_IN_A_MONTH' + }, + indexerEndpoint: { + type: DataTypes.STRING, + allowNull: true, + field: 'INDEXER_ENDPOINT' + }, + }, { + tableName: 'api_keys', + sequelize, // passing the `sequelize` instance is required + timestamps: false, // this will deactivate the `createdAt` and `updatedAt` columns + }); +} \ No newline at end of file diff --git a/backend/src/plugins/sequelizePlugin.ts b/backend/src/plugins/sequelizePlugin.ts new file mode 100644 index 0000000..349a09f --- /dev/null +++ b/backend/src/plugins/sequelizePlugin.ts @@ -0,0 +1,26 @@ +import fp from "fastify-plugin"; +import { FastifyPluginAsync } from "fastify"; +import { Sequelize } from 'sequelize'; +import sqlite3 from 'sqlite3'; + +const sequelizePlugin: FastifyPluginAsync = async (server) => { + const sequelize = new Sequelize({ + dialect: 'sqlite', + storage: './database.sqlite', + dialectModule: sqlite3 + }); + + server.decorate('sequelize', sequelize); + + server.addHook('onClose', (instance, done) => { + instance.sequelize.close().then(() => done(), done); + }); +}; + +declare module "fastify" { + interface FastifyInstance { + sequelize: Sequelize; + } +} + +export default fp(sequelizePlugin); \ No newline at end of file diff --git a/backend/src/repository/APIKeyRepository.ts b/backend/src/repository/APIKeyRepository.ts new file mode 100644 index 0000000..131085c --- /dev/null +++ b/backend/src/repository/APIKeyRepository.ts @@ -0,0 +1,25 @@ +import { Sequelize } from 'sequelize'; +import { APIKey } from '../models/APIKey'; + +export class APIKeyRepository { + private sequelize: Sequelize; + + constructor(sequelize: Sequelize) { + this.sequelize = sequelize; + } + + async findAll(): Promise { + const result = await this.sequelize.models.APIKey.findAll(); + return result.map(apiKey => apiKey.get() as APIKey); + } + + async findOneByApiKey(apiKey: string): Promise { + const result = await this.sequelize.models.APIKey.findOne({ where: { apiKey: apiKey } }); + return result ? result.get() as APIKey : null; + } + + async findOneByWalletAddress(walletAddress: string): Promise { + const result = await this.sequelize.models.APIKey.findOne({ where: { walletAddress: walletAddress } }); + return result ? result.get() as APIKey : null; + } +} \ No newline at end of file diff --git a/backend/src/routes/admin.ts b/backend/src/routes/admin.ts index 5c851b9..0e1aa5e 100644 --- a/backend/src/routes/admin.ts +++ b/backend/src/routes/admin.ts @@ -6,16 +6,18 @@ import ErrorMessage from "../constants/ErrorMessage.js"; import ReturnCode from "../constants/ReturnCode.js"; import { encode, decode } from "../utils/crypto.js"; import SupportedNetworks from "../../config.json" assert { type: "json" }; +import { Op } from 'sequelize'; +import { APIKey } from "models/APIKey.js"; +import { APIKeyRepository } from "repository/APIKeyRepository.js"; const adminRoutes: FastifyPluginAsync = async (server) => { - server.post('/adminLogin', 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.WALLET_ADDRESS) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); console.log(body, server.config.ADMIN_WALLET_ADDRESS) - if (ethers.utils.getAddress(body.WALLET_ADDRESS) === server.config.ADMIN_WALLET_ADDRESS) return reply.code(ReturnCode.SUCCESS).send({error: null, message: "Successfully Logged in"}); + if (ethers.utils.getAddress(body.WALLET_ADDRESS) === server.config.ADMIN_WALLET_ADDRESS) return reply.code(ReturnCode.SUCCESS).send({ error: null, message: "Successfully Logged in" }); return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_USER }); } catch (err: any) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_USER }); @@ -74,53 +76,43 @@ const adminRoutes: FastifyPluginAsync = async (server) => { 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 || !body.PRIVATE_KEY) + if (!body.apiKey || !body.privateKey) 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)) + if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*-_&])[A-Za-z\d@$!%*-_&]{8,}$/.test(body.apiKey)) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.API_KEY_VALIDATION_FAILED }) - const wallet = new ethers.Wallet(body.PRIVATE_KEY); + const wallet = new ethers.Wallet(body.privateKey); const publicAddress = await wallet.getAddress(); - const result: any[] = await new Promise((resolve, reject) => { - server.sqlite.db.get("SELECT * FROM api_keys WHERE WALLET_ADDRESS=?", [publicAddress], (err: any, row: any) => { - if (err) reject(err); - resolve(row); - }) - }) - if (result && result.length > 0) + + // Use Sequelize to find the API key + const result = await server.sequelize.models.APIKey.findOne({ where: { walletAddress: publicAddress } }); + if (result) { + request.log.error('Duplicate record found'); return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.DUPLICATE_RECORD }); - const privateKey = body.PRIVATE_KEY; + } + + const privateKey = body.privateKey; const hmac = encode(privateKey); - await new Promise((resolve, reject) => { - server.sqlite.db.run("INSERT INTO api_keys ( \ - API_KEY, \ - WALLET_ADDRESS, \ - PRIVATE_KEY, \ - SUPPORTED_NETWORKS, \ - ERC20_PAYMASTERS, \ - MULTI_TOKEN_PAYMASTERS, \ - MULTI_TOKEN_ORACLES, \ - SPONSOR_NAME, \ - LOGO_URL, \ - TRANSACTION_LIMIT, \ - NO_OF_TRANSACTIONS_IN_A_MONTH, \ - INDEXER_ENDPOINT) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", [ - body.API_KEY, - publicAddress, - hmac, - body.SUPPORTED_NETWORKS, - body.ERC20_PAYMASTERS, - body.MULTI_TOKEN_PAYMASTERS ?? null, - body.MULTI_TOKEN_ORACLES ?? null, - body.SPONSOR_NAME ?? null, - body.LOGO_URL ?? null, - 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); - }); + + // console.log(`support network on request.body is: ${body.supportedNetworks}`); + // console.log(`erc20 paymasters on request.body is: ${body.erc20Paymasters}`); + // console.log(`request body is: ${JSON.stringify(body)}`); + + // Use Sequelize to insert the new API key + await server.sequelize.models.APIKey.create({ + apiKey: body.apiKey, + walletAddress: publicAddress, + privateKey: hmac, + supportedNetworks: body.supportedNetworks, + erc20Paymasters: body.erc20Paymasters, + multiTokenPaymasters: body.multiTokenPaymasters ?? null, + multiTokenOracles: body.multiTokenOracles ?? null, + sponsorName: body.sponsorName ?? null, + logoUrl: body.logoUrl ?? null, + transactionLimit: body.transactionLimit ?? 0, + noOfTransactionsInAMonth: body.noOfTransactionsInAMonth ?? 10, + indexerEndpoint: body.indexerEndpoint ?? process.env.DEFAULT_INDEXER_ENDPOINT }); + return reply.code(ReturnCode.SUCCESS).send({ error: null, message: 'Successfully saved' }); } catch (err: any) { request.log.error(err); @@ -128,6 +120,7 @@ const adminRoutes: FastifyPluginAsync = async (server) => { } }) + server.post('/updateKey', async function (request, reply) { try { const body: any = JSON.parse(request.body as string); @@ -135,28 +128,20 @@ const adminRoutes: FastifyPluginAsync = async (server) => { 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.API_KEY_VALIDATION_FAILED }); + + const apiKeyInstance = await server.sequelize.models.APIKey.findOne({ where: { apiKey: body.API_KEY } }); + if (!apiKeyInstance) 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); - }) + + await apiKeyInstance.update({ + supportedNetworks: body.SUPPORTED_NETWORKS, + erc20Paymasters: body.ERC20_PAYMASTERS, + transactionLimit: body.TRANSACTION_LIMIT ?? 0, + noOfTransactionsInAMonth: body.NO_OF_TRANSACTIONS_IN_A_MONTH ?? 10, + indexerEndpoint: body.INDEXER_ENDPOINT ?? process.env.DEFAULT_INDEXER_ENDPOINT }); + return reply.code(ReturnCode.SUCCESS).send({ error: null, message: 'Successfully updated' }); } catch (err: any) { server.log.error(err); @@ -166,16 +151,13 @@ const adminRoutes: FastifyPluginAsync = async (server) => { server.get('/getKeys', async function (request, reply) { try { - const result: any[] = await new Promise((resolve, reject) => { - server.sqlite.db.all("SELECT * FROM api_keys", (err: any, rows: any[]) => { - if (err) reject(err); - resolve(rows); - }) - }) - result.map((value) => { - value.PRIVATE_KEY = decode(value.PRIVATE_KEY) + if(!server.sequelize) throw new Error('Sequelize instance is not available'); + const apiKeyRepository = new APIKeyRepository(server.sequelize); + const apiKeys: APIKey[] = await apiKeyRepository.findAll(); + apiKeys.forEach((apiKeyEntity: APIKey) => { + apiKeyEntity.privateKey = decode(apiKeyEntity.privateKey); }); - return reply.code(ReturnCode.SUCCESS).send(result); + return reply.code(ReturnCode.SUCCESS).send(apiKeys); } catch (err: any) { request.log.error(err); return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }); @@ -189,13 +171,14 @@ const adminRoutes: FastifyPluginAsync = async (server) => { 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); - resolve(rows); - }) - }) + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.API_KEY_VALIDATION_FAILED }); + + const apiKeyInstance = await server.sequelize.models.APIKey.findOne({ where: { apiKey: body.API_KEY } }); + if (!apiKeyInstance) + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.RECORD_NOT_FOUND }); + + await apiKeyInstance.destroy(); + return reply.code(ReturnCode.SUCCESS).send({ error: null, message: 'Successfully deleted' }); } catch (err: any) { request.log.error(err); diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 824358f..a3d683b 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -10,7 +10,9 @@ import { PAYMASTER_ADDRESS } from "../constants/Pimlico.js"; import ErrorMessage from "../constants/ErrorMessage.js"; import ReturnCode from "../constants/ReturnCode.js"; import { decode } from "../utils/crypto.js"; -import { printRequest, getNetworkConfig, getSQLdata } from "../utils/common.js"; +import { printRequest, getNetworkConfig } from "../utils/common.js"; +import { APIKeyRepository } from "repository/APIKeyRepository.js"; +import { APIKey } from "models/APIKey.js"; const SUPPORTED_ENTRYPOINTS = { 'EPV_06' : "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", @@ -126,30 +128,33 @@ const routes: FastifyPluginAsync = async (server) => { txnMode = secrets['TRANSACTION_LIMIT'] ?? 0; indexerEndpoint = secrets['INDEXER_ENDPOINT'] ?? process.env.DEFAULT_INDEXER_ENDPOINT; } else { - const record: any = await getSQLdata(api_key, server.sqlite.db, server.log); - if (!record) { + const apiKeyEntity = await new APIKeyRepository(server.sequelize).findOneByApiKey(api_key); + + if (!apiKeyEntity) { 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'); + + if (apiKeyEntity.erc20Paymasters) { + const buffer = Buffer.from(apiKeyEntity.erc20Paymasters, 'base64'); customPaymasters = JSON.parse(buffer.toString()); } - if (record['MULTI_TOKEN_PAYMASTERS']) { - const buffer = Buffer.from(record['MULTI_TOKEN_PAYMASTERS'], 'base64'); + + if (apiKeyEntity.multiTokenPaymasters) { + const buffer = Buffer.from(apiKeyEntity.multiTokenPaymasters, 'base64'); multiTokenPaymasters = JSON.parse(buffer.toString()); } - if (record['MULTI_TOKEN_ORACLES']) { - const buffer = Buffer.from(record['MULTI_TOKEN_ORACLES'], 'base64'); + if (apiKeyEntity.multiTokenOracles) { + const buffer = Buffer.from(apiKeyEntity.multiTokenOracles, 'base64'); multiTokenOracles = JSON.parse(buffer.toString()); } - sponsorName = record['SPONSOR_NAME']; - sponsorImage = record['LOGO_URL']; - 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; + sponsorName = apiKeyEntity.sponsorName ? apiKeyEntity.sponsorName : ''; + sponsorImage = apiKeyEntity.logoUrl ? apiKeyEntity.logoUrl : ''; + privateKey = decode(apiKeyEntity.privateKey); + supportedNetworks = apiKeyEntity.supportedNetworks; + noOfTxns = apiKeyEntity.noOfTransactionsInAMonth; + txnMode = apiKeyEntity.transactionLimit; + indexerEndpoint = apiKeyEntity.indexerEndpoint ?? process.env.DEFAULT_INDEXER_ENDPOINT; } if ( @@ -299,14 +304,17 @@ const routes: FastifyPluginAsync = async (server) => { privateKey = secrets['PRIVATE_KEY']; supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - const record: any = await getSQLdata(api_key, server.sqlite.db, server.log); - if (!record) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - if (record['ERC20_PAYMASTERS']) { - const buffer = Buffer.from(record['ERC20_PAYMASTERS'], 'base64'); + const result = await server.sequelize.models.APIKey.findOne({ where: { apiKey: api_key } }); + if (!result) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + const apiKeyEntity: APIKey = result as APIKey; + if (apiKeyEntity.erc20Paymasters) { + const buffer = Buffer.from(apiKeyEntity.erc20Paymasters, 'base64'); customPaymasters = JSON.parse(buffer.toString()); } - privateKey = decode(record['PRIVATE_KEY']); - supportedNetworks = record['SUPPORTED_NETWORKS']; + + privateKey = decode(apiKeyEntity.privateKey); + + supportedNetworks = apiKeyEntity.supportedNetworks; } if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if ( @@ -366,10 +374,10 @@ const routes: FastifyPluginAsync = async (server) => { privateKey = secrets['PRIVATE_KEY']; supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - const record: any = await getSQLdata(api_key, server.sqlite.db, server.log); - if (!record) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - privateKey = decode(record['PRIVATE_KEY']); - supportedNetworks = record['SUPPORTED_NETWORKS']; + const apiKeyEntity: APIKey | null = await new APIKeyRepository(server.sequelize).findOneByApiKey(api_key); + if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + privateKey = decode(apiKeyEntity.privateKey); + supportedNetworks = apiKeyEntity.supportedNetworks; } if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if ( @@ -424,10 +432,10 @@ const routes: FastifyPluginAsync = async (server) => { privateKey = secrets['PRIVATE_KEY']; supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - const record: any = await getSQLdata(api_key, server.sqlite.db, server.log); - if (!record) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - privateKey = decode(record['PRIVATE_KEY']); - supportedNetworks = record['SUPPORTED_NETWORKS']; + const apiKeyEntity: APIKey | null = await new APIKeyRepository(server.sequelize).findOneByApiKey(api_key); + if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + privateKey = decode(apiKeyEntity.apiKey); + supportedNetworks = apiKeyEntity.supportedNetworks; } if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if ( @@ -482,10 +490,10 @@ const routes: FastifyPluginAsync = async (server) => { privateKey = secrets['PRIVATE_KEY']; supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - const record: any = await getSQLdata(api_key, server.sqlite.db, server.log); - if (!record) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - privateKey = decode(record['PRIVATE_KEY']); - supportedNetworks = record['SUPPORTED_NETWORKS']; + const apiKeyEntity: APIKey | null = await new APIKeyRepository(server.sequelize).findOneByApiKey(api_key); + if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + privateKey = decode(apiKeyEntity.privateKey); + supportedNetworks = apiKeyEntity.supportedNetworks; } if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if ( @@ -541,10 +549,10 @@ const routes: FastifyPluginAsync = async (server) => { privateKey = secrets['PRIVATE_KEY']; supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - const record: any = await getSQLdata(api_key, server.sqlite.db, server.log); - if (!record) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - privateKey = decode(record['PRIVATE_KEY']); - supportedNetworks = record['SUPPORTED_NETWORKS']; + const apiKeyEntity: APIKey | null = await new APIKeyRepository(server.sequelize).findOneByApiKey(api_key); + if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + privateKey = decode(apiKeyEntity.apiKey); + supportedNetworks = apiKeyEntity.supportedNetworks; } if ( isNaN(amount) || @@ -594,10 +602,10 @@ const routes: FastifyPluginAsync = async (server) => { privateKey = secrets['PRIVATE_KEY']; supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - const record: any = await getSQLdata(api_key, server.sqlite.db, server.log); - if (!record) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - privateKey = decode(record['PRIVATE_KEY']); - supportedNetworks = record['SUPPORTED_NETWORKS']; + const apiKeyEntity: APIKey | null = await new APIKeyRepository(server.sequelize).findOneByApiKey(api_key); + if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + privateKey = decode(apiKeyEntity.privateKey); + supportedNetworks = apiKeyEntity.supportedNetworks; } if ( isNaN(amount) || diff --git a/backend/src/routes/metadata.ts b/backend/src/routes/metadata.ts index 4007af3..500d613 100644 --- a/backend/src/routes/metadata.ts +++ b/backend/src/routes/metadata.ts @@ -2,12 +2,13 @@ import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-sec import { FastifyPluginAsync } from "fastify"; import { Wallet, providers } from "ethers"; import SupportedNetworks from "../../config.json" assert { type: "json" }; -import { getNetworkConfig, printRequest, getSQLdata } from "../utils/common.js"; +import { getNetworkConfig, printRequest } from "../utils/common.js"; import ReturnCode from "../constants/ReturnCode.js"; import ErrorMessage from "../constants/ErrorMessage.js"; import { decode } from "../utils/crypto.js"; import { PAYMASTER_ADDRESS } from "../constants/Pimlico.js"; - +import { APIKey } from "models/APIKey"; +import { APIKeyRepository } from "repository/APIKeyRepository"; const metadataRoutes: FastifyPluginAsync = async (server) => { @@ -62,23 +63,23 @@ const metadataRoutes: FastifyPluginAsync = async (server) => { privateKey = secrets['PRIVATE_KEY']; supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - const record: any = await getSQLdata(api_key, server.sqlite.db, server.log); - if (!record) { + const apiKeyEntity: APIKey | null = await new APIKeyRepository(server.sequelize).findOneByApiKey(api_key); + if (!apiKeyEntity) { 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'); + if (apiKeyEntity.erc20Paymasters) { + const buffer = Buffer.from(apiKeyEntity.erc20Paymasters, 'base64'); customPaymasters = JSON.parse(buffer.toString()); } - if (record['MULTI_TOKEN_PAYMASTERS']) { - const buffer = Buffer.from(record['MULTI_TOKEN_PAYMASTERS'], 'base64'); + if (apiKeyEntity.multiTokenPaymasters) { + const buffer = Buffer.from(apiKeyEntity.multiTokenPaymasters, 'base64'); multiTokenPaymasters = JSON.parse(buffer.toString()); } - sponsorName = record['SPONSOR_NAME']; - sponsorImage = record['LOGO_URL']; - privateKey = decode(record['PRIVATE_KEY']); - supportedNetworks = record['SUPPORTED_NETWORKS']; + sponsorName = apiKeyEntity.sponsorName ? apiKeyEntity.sponsorName : ""; + sponsorImage = apiKeyEntity.logoUrl ? apiKeyEntity.logoUrl : ""; + privateKey = decode(apiKeyEntity.privateKey); + supportedNetworks = apiKeyEntity.supportedNetworks; } if (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); diff --git a/backend/src/server.ts b/backend/src/server.ts index 07e5615..07f6639 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -7,6 +7,7 @@ import { providers, ethers } from 'ethers'; import { GetSecretValueCommand, SecretsManagerClient } from '@aws-sdk/client-secrets-manager'; import fetch from 'node-fetch'; import database from './plugins/db.js'; +import sequelizePlugin from './plugins/sequelizePlugin.js'; import config from './plugins/config.js'; import routes from './routes/index.js'; import adminRoutes from './routes/admin.js'; @@ -14,8 +15,10 @@ import metadataRoutes from './routes/metadata.js'; import EtherspotChainlinkOracleAbi from './abi/EtherspotChainlinkOracleAbi.js'; import PimlicoAbi from './abi/PimlicoAbi.js'; import PythOracleAbi from './abi/PythOracleAbi.js'; -import { getNetworkConfig, getSQLdata } from './utils/common.js'; +import { getNetworkConfig } from './utils/common.js'; import { checkDeposit } from './utils/monitorTokenPaymaster.js'; +import { APIKey, initializeAPIKeyModel } from 'models/APIKey.js'; +import { APIKeyRepository } from 'repository/APIKeyRepository.js'; let server: FastifyInstance; @@ -55,6 +58,12 @@ const initializeServer = async (): Promise => { // Database await server.register(database); + // Register the sequelizePlugin + await server.register(sequelizePlugin); + + // Initialize the APIKey model + initializeAPIKeyModel(server.sequelize); + const ConfigData: any = await new Promise(resolve => { server.sqlite.db.get("SELECT * FROM config", (err, row) => { if (err) resolve(null); @@ -191,17 +200,19 @@ const initializeServer = async (): Promise => { multiTokenPaymasters = JSON.parse(buffer.toString()); } } else { - const record: any = await getSQLdata(api_key, server.sqlite.db, server.log); - if (record['ERC20_PAYMASTERS']) { - const buffer = Buffer.from(record['ERC20_PAYMASTERS'], 'base64'); + const apiKeyRepository = new APIKeyRepository(server.sequelize); + const apiKeyEntity: APIKey | null = await apiKeyRepository.findOneByApiKey(api_key); + + if (apiKeyEntity?.erc20Paymasters) { + const buffer = Buffer.from(apiKeyEntity.erc20Paymasters, 'base64'); customPaymasters = JSON.parse(buffer.toString()); } - if (record['MULTI_TOKEN_PAYMASTERS']) { - const buffer = Buffer.from(record['MULTI_TOKEN_PAYMASTERS'], 'base64'); - multiTokenPaymasters = JSON.parse(buffer.toString()); + if (apiKeyEntity?.multiTokenPaymasters) { + const buffer = Buffer.from(apiKeyEntity.multiTokenPaymasters, 'base64'); + multiTokenPaymasters = JSON.parse(buffer.toString()); } } - customPaymasters = {...customPaymasters, ...multiTokenPaymasters}; + customPaymasters = { ...customPaymasters, ...multiTokenPaymasters }; for (const chainId in customPaymasters) { const networkConfig = getNetworkConfig(chainId, ''); if (networkConfig) { diff --git a/backend/src/utils/common.ts b/backend/src/utils/common.ts index bfc2624..6fbea0e 100644 --- a/backend/src/utils/common.ts +++ b/backend/src/utils/common.ts @@ -3,6 +3,7 @@ import { BigNumber, ethers } from "ethers"; import { Database } from "sqlite3"; import SupportedNetworks from "../../config.json" assert { type: "json" }; import { EtherscanResponse, getEtherscanFeeResponse } from "./interface.js"; +import { APIKey } from "models/APIKey"; export function printRequest(methodName: string, request: FastifyRequest, log: FastifyBaseLogger) { log.info(methodName, "called: "); @@ -19,21 +20,6 @@ export function getNetworkConfig(key: any, supportedNetworks: any, entryPoint: s return SupportedNetworks.find((chain) => chain.chainId == key && chain.entryPoint == entryPoint); } -export async function getSQLdata(apiKey: string, db: Database, log: FastifyBaseLogger) { - try { - const result: any[] = await new Promise((resolve, reject) => { - db.get("SELECT * FROM api_keys WHERE API_KEY = ?", [apiKey], (err: any, rows: any[]) => { - if (err) reject(err); - resolve(rows); - }) - }) - return result; - } catch (err) { - log.error(err); - return null; - } -} - export async function getEtherscanFee(chainId: number, log?: FastifyBaseLogger): Promise { try { const etherscanUrlsBase64 = process.env.ETHERSCAN_GAS_ORACLES; @@ -50,7 +36,7 @@ export async function getEtherscanFee(chainId: number, log?: FastifyBaseLogger): console.log('setting maxFeePerGas and maxPriorityFeePerGas as received') const maxFeePerGas = ethers.utils.parseUnits(response.result.suggestBaseFee, 'gwei') const fastGasPrice = ethers.utils.parseUnits(response.result.FastGasPrice, 'gwei') - return { + return { maxPriorityFeePerGas: fastGasPrice.sub(maxFeePerGas), maxFeePerGas, gasPrice: maxFeePerGas, diff --git a/frontend/.gitignore b/frontend/.gitignore index 2c69316..076645b 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -16,3 +16,4 @@ chrome-user-data *.swo .env.local +yarn.lock \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c7ab87d --- /dev/null +++ b/package.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "sequelize": "^6.37.3" + }, + "devDependencies": { + "@types/sequelize": "^4.28.20", + "@types/sqlite3": "^3.1.11" + } +}