diff --git a/admin_frontend/package-lock.json b/admin_frontend/package-lock.json index 13fdecc..7d4bba0 100644 --- a/admin_frontend/package-lock.json +++ b/admin_frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "admin_frontend", - "version": "1.2.1", + "version": "1.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "admin_frontend", - "version": "1.2.1", + "version": "1.2.2", "dependencies": { "@emotion/react": "11.11.3", "@emotion/styled": "11.11.0", diff --git a/admin_frontend/package.json b/admin_frontend/package.json index 3d02a22..8679aa6 100644 --- a/admin_frontend/package.json +++ b/admin_frontend/package.json @@ -1,6 +1,6 @@ { "name": "admin_frontend", - "version": "1.2.1", + "version": "1.2.2", "private": true, "dependencies": { "@emotion/react": "11.11.3", diff --git a/backend/config.json.default b/backend/config.json.default index cae507a..05b1791 100644 --- a/backend/config.json.default +++ b/backend/config.json.default @@ -214,5 +214,13 @@ "etherspotPaymasterAddress": "0xB3AD9B9B06c6016f81404ee8FcCD0526F018Cf0C" }, "thresholdValue": "21.8" + }, + { + "chainId": 888888888, + "bundler": "https://rpc.etherspot.io/ancient8?api-key=eyJvcmciOiI2NTIzZjY5MzUwOTBmNzAwMDFiYjJkZWIiLCJpZCI6ImUxN2VhZWQwZDViNDRjZWI5YjhlN2ZkZjc4YWI0OWRiIiwiaCI6Im11cm11cjEyOCJ9", + "contracts": { + "etherspotPaymasterAddress": "0x810FA4C915015b703db0878CF2B9344bEB254a40" + }, + "thresholdValue": "0.016" } ] diff --git a/backend/package.json b/backend/package.json index ae24c02..966a880 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "arka", - "version": "1.2.1", + "version": "1.2.2", "description": "ARKA - (Albanian for Cashier's case) is the first open source Paymaster as a service software", "type": "module", "directories": { @@ -39,8 +39,9 @@ "ethers": "5.7.2", "fastify": "4.24.3", "fastify-cron": "1.3.1", + "fastify-healthcheck": "4.4.0", "fastify-plugin": "3.0.1", - "getmac": "^6.6.0", + "getmac": "6.6.0", "graphql-request": "6.1.0", "node-fetch": "3.3.2", "sqlite": "5.1.1", diff --git a/backend/src/abi/OrochiOracleAbi.ts b/backend/src/abi/OrochiOracleAbi.ts new file mode 100644 index 0000000..ed3985b --- /dev/null +++ b/backend/src/abi/OrochiOracleAbi.ts @@ -0,0 +1,569 @@ +export default [ + { + "inputs": [ + { + "internalType": "address[]", + "name": "operatorList", + "type": "address[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "DeactivatedUser", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "name": "InvalidDataLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "InvalidOperator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "requiredLen", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxLen", + "type": "uint256" + } + ], + "name": "OutOfRange", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "UnableToPublishData", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "round", + "type": "uint64" + } + ], + "name": "UndefinedRound", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newOperator", + "type": "address" + } + ], + "name": "AddOperator", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "actor", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "name": "Deactivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "actor", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "identifier", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "FulFill", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint32", + "name": "application", + "type": "uint32" + }, + { + "indexed": true, + "internalType": "uint64", + "name": "round", + "type": "uint64" + }, + { + "indexed": true, + "internalType": "bytes20", + "name": "identifier", + "type": "bytes20" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + } + ], + "name": "PublishData", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "OldOperator", + "type": "address" + } + ], + "name": "RemoveOperator", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "actor", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "identifier", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "Request", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOperator", + "type": "address" + } + ], + "name": "addOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "identifier", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "fulfill", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "appId", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "round", + "type": "uint64" + }, + { + "internalType": "bytes20", + "name": "identifier", + "type": "bytes20" + } + ], + "name": "getData", + "outputs": [ + { + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "appId", + "type": "uint32" + }, + { + "internalType": "bytes20", + "name": "identifier", + "type": "bytes20" + } + ], + "name": "getLatestData", + "outputs": [ + { + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "appId", + "type": "uint32" + }, + { + "internalType": "bytes20", + "name": "identifier", + "type": "bytes20" + } + ], + "name": "getLatestRound", + "outputs": [ + { + "internalType": "uint64", + "name": "round", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "lastUpdate", + "type": "uint64" + }, + { + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "appId", + "type": "uint32" + }, + { + "internalType": "bytes20", + "name": "identifier", + "type": "bytes20" + } + ], + "name": "getMetadata", + "outputs": [ + { + "internalType": "uint64", + "name": "round", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "lastUpdate", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "isDeactivated", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "checkAddress", + "type": "address" + } + ], + "name": "isOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "appId", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "packedData", + "type": "bytes" + } + ], + "name": "publishData", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "packedData", + "type": "bytes" + } + ], + "name": "publishPrice", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "oldOperator", + "type": "address" + } + ], + "name": "removeOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "identifier", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "request", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "userAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "status", + "type": "bool" + } + ], + "name": "setDeactivatedStatus", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] as const; \ No newline at end of file diff --git a/backend/src/paymaster/index.ts b/backend/src/paymaster/index.ts index 7ca58cd..a5b978c 100644 --- a/backend/src/paymaster/index.ts +++ b/backend/src/paymaster/index.ts @@ -8,6 +8,7 @@ import ErrorMessage from '../constants/ErrorMessage.js'; import { PAYMASTER_ADDRESS } from '../constants/Pimlico.js'; import { getEtherscanFee } from '../utils/common.js'; import MultiTokenPaymasterAbi from '../abi/MultiTokenPaymasterAbi.js'; +import OrochiOracleAbi from '../abi/OrochiOracleAbi.js'; export class Paymaster { feeMarkUp: BigNumber; @@ -65,19 +66,20 @@ export class Paymaster { } } - async getPaymasterAndDataForMultiTokenPaymaster(userOp: any, validUntil: string, validAfter: string, feeToken: string, oracleAggregator: string, paymasterContract: Contract, signer: Wallet) { - const exchangeRate = 10000000; // This is for setting min tokens required for the txn that gets validated on estimate - const priceMarkup = 1100000; // 10% more of the actual cost. Can be anything between 1e6 to 2e6 + async getPaymasterAndDataForMultiTokenPaymaster(userOp: any, validUntil: string, validAfter: string, feeToken: string, ethPrice: string, paymasterContract: Contract, signer: Wallet) { + const exchangeRate = 1000000; // This is for setting min tokens required for the txn that gets validated on estimate + const rate = ethers.BigNumber.from(exchangeRate).mul(ethPrice); + const priceMarkup = 1000000; // 10% more of the actual cost. Can be anything between 1e6 to 2e6 // actual signing... // priceSource inputs available 0 - for using external exchange price and 1 - for oracle based price const hash = await paymasterContract.getHash( userOp, - 1, + 0, validUntil, validAfter, feeToken, - oracleAggregator, - exchangeRate, + ethers.constants.AddressZero, + rate.toNumber().toFixed(0), priceMarkup, ); @@ -85,10 +87,10 @@ export class Paymaster { const paymasterAndData = hexConcat([ paymasterContract.address, - '0x01', + '0x00', defaultAbiCoder.encode( ['uint48', 'uint48', 'address', 'address', 'uint256', 'uint32'], - [validUntil, validAfter, feeToken, oracleAggregator, exchangeRate, priceMarkup] + [validUntil, validAfter, feeToken, ethers.constants.AddressZero, rate.toNumber().toFixed(0), priceMarkup] ), sig, ]); @@ -100,14 +102,17 @@ export class Paymaster { try { const provider = new providers.JsonRpcProvider(bundlerRpc); const paymasterContract = new ethers.Contract(paymasterAddress, MultiTokenPaymasterAbi, provider); - userOp.paymasterAndData = await this.getPaymasterAndDataForMultiTokenPaymaster(userOp, validUntil, validAfter, feeToken, oracleAggregator, paymasterContract, signer); - + const oracleContract = new ethers.Contract(oracleAggregator, OrochiOracleAbi, provider); + const result = await oracleContract.getLatestData(1, ethers.utils.hexlify(ethers.utils.toUtf8Bytes('ETH')).padEnd(42, '0')) + const ethPrice = Number(ethers.utils.formatEther(result)).toFixed(0); + userOp.paymasterAndData = await this.getPaymasterAndDataForMultiTokenPaymaster(userOp, validUntil, validAfter, feeToken, ethPrice, paymasterContract, signer); + if (!userOp.signature) userOp.signature = '0x'; const response = await provider.send('eth_estimateUserOperationGas', [userOp, entryPoint]); userOp.verificationGasLimit = response.verificationGasLimit; userOp.preVerificationGas = response.preVerificationGas; userOp.callGasLimit = response.callGasLimit; - const paymasterAndData = await this.getPaymasterAndDataForMultiTokenPaymaster(userOp, validUntil, validAfter, feeToken, oracleAggregator, paymasterContract, signer); + const paymasterAndData = await this.getPaymasterAndDataForMultiTokenPaymaster(userOp, validUntil, validAfter, feeToken, ethPrice, paymasterContract, signer); const returnValue = { paymasterAndData, @@ -223,7 +228,8 @@ export class Paymaster { type: 2, }); } - await tx.wait(); + // commented the below line to avoid timeouts for long delays in transaction confirmation. + // await tx.wait(); return { message: `Successfully whitelisted with transaction Hash ${tx.hash}` @@ -275,7 +281,8 @@ export class Paymaster { type: 2, }); } - await tx.wait(); + // commented the below line to avoid timeouts for long delays in transaction confirmation. + // await tx.wait(); return { message: `Successfully removed whitelisted addresses with transaction Hash ${tx.hash}` diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 705e97a..a68d455 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -38,13 +38,6 @@ const routes: FastifyPluginAsync = async (server) => { } } - server.get( - "/healthcheck", - async function (request, reply) { - return reply.code(ReturnCode.SUCCESS).send('Arka Service Running...'); - } - ) - server.post( "/", async function (request, reply) { diff --git a/backend/src/server.ts b/backend/src/server.ts index 246ba04..07e5615 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import fastify, { FastifyInstance } from 'fastify'; +import fastifyHealthcheck from 'fastify-healthcheck'; import cors from '@fastify/cors'; import fastifyCron from 'fastify-cron'; import { providers, ethers } from 'ethers'; @@ -40,6 +41,11 @@ const initializeServer = async (): Promise => { preflightContinue: true }) + await server.register(fastifyHealthcheck, { + healthcheckUrl: "/healthcheck", + logLevel: "warn" + }); + await server.register(routes); await server.register(adminRoutes); @@ -198,8 +204,10 @@ const initializeServer = async (): Promise => { customPaymasters = {...customPaymasters, ...multiTokenPaymasters}; for (const chainId in customPaymasters) { const networkConfig = getNetworkConfig(chainId, ''); - for (const symbol in customPaymasters[chainId]) { - checkDeposit(customPaymasters[chainId][symbol], networkConfig.bundler, process.env.WEBHOOK_URL, networkConfig.thresholdValue ?? '0.001', Number(chainId), server.log) + if (networkConfig) { + for (const symbol in customPaymasters[chainId]) { + checkDeposit(customPaymasters[chainId][symbol], networkConfig.bundler, process.env.WEBHOOK_URL, networkConfig.thresholdValue ?? '0.001', Number(chainId), server.log) + } } } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f1429c7..a1c6693 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "arka_frontend", - "version": "1.2.1", + "version": "1.2.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "arka_frontend", - "version": "1.2.1", + "version": "1.2.2", "dependencies": { "@babel/plugin-proposal-private-property-in-object": "7.21.11", "@emotion/react": "^11.11.1", diff --git a/frontend/package.json b/frontend/package.json index 10d6bd8..5b82775 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "arka_frontend", - "version": "1.2.1", + "version": "1.2.2", "private": true, "dependencies": { "@babel/plugin-proposal-private-property-in-object": "7.21.11",