diff --git a/README.md b/README.md index a5539b9..e1b213b 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,8 @@ The structure should be as follows "137": "https://api.polygonscan.com/api?module=gastracker&action=gasoracle&apikey=YourApiKeyToken", // Note that you need to replace YourApiKeyToken to actual API key from etherscan "1": "https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey=YourApiKeyToken" } which then needs to be converted into `base64` value +- DEFAULT_API_KEY - this is used for checking token paymaster deposits. Please use an apiKey which has the list of token paymasters that needs to be monitored. Please note that it only tracks the token paymasters in `ERC20_PAYMASTERS` and `MULTI_TOKEN_PAYMASTERS` variable specified inside the apiKey. +- WEBHOOK_URL - this is used to notify if the token paymaster deposit comes below 'thresholdValue' specified in backend/config.json and please note that each chain has configured threshold as approx. 50 USD worth of native tokens ## API KEY VALIDATION - In ARKA Admin Frontend, create an API_KEY with the following format - diff --git a/admin_frontend/package-lock.json b/admin_frontend/package-lock.json index 16ffdbe..13fdecc 100644 --- a/admin_frontend/package-lock.json +++ b/admin_frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "admin_frontend", - "version": "1.2.0", + "version": "1.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "admin_frontend", - "version": "1.2.0", + "version": "1.2.1", "dependencies": { "@emotion/react": "11.11.3", "@emotion/styled": "11.11.0", diff --git a/admin_frontend/package.json b/admin_frontend/package.json index d92da44..3d02a22 100644 --- a/admin_frontend/package.json +++ b/admin_frontend/package.json @@ -1,6 +1,6 @@ { "name": "admin_frontend", - "version": "1.2.0", + "version": "1.2.1", "private": true, "dependencies": { "@emotion/react": "11.11.3", diff --git a/backend/config.json.default b/backend/config.json.default index 507a6c9..cae507a 100644 --- a/backend/config.json.default +++ b/backend/config.json.default @@ -4,223 +4,215 @@ "bundler": "https://ethereum-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0x7F690e93CecFca5A31E6e1dF50A33F6d3059048c" - } - }, - { - "chainId": 5, - "bundler": "https://goerli-bundler.etherspot.io", - "contracts": { - "etherspotPaymasterAddress": "0xcaDBADcFeD5530A49762DFc9d1d712CcD6b09b25" - } + }, + "thresholdValue": "0.016" }, { "chainId": 10, "bundler": "https://optimism-bundler.etherspot.io", "contracts": { "etherspotPaymasterAddress": "0x805650ce74561C85baA44a8Bd13E19633Fd0F79d" - } + }, + "thresholdValue": "21.8" }, { "chainId": 14, "bundler": "https://flare-bundler.etherspot.io", "contracts": { "etherspotPaymasterAddress": "0x8A41594e5c6Fe492e437414c24eA6f401186b8d2" - } + }, + "thresholdValue": "1556" + }, + { + "chainId": 30, + "bundler": "https://rootstock-bundler.etherspot.io", + "contracts": { + "etherspotPaymasterAddress": "0xe893A26dD53b325bFFaACdFA224692EFF4C448C4" + }, + "thresholdValue": "0.00079" + }, + { + "chainId": 31, + "bundler": "https://rootstocktestnet-bundler.etherspot.io", + "contracts": { + "etherspotPaymasterAddress": "0x898c530A5fA37720DcF1843AeCC34b6B0cBaEB8a" + }, + "thresholdValue": "0.00079" }, { "chainId": 56, "bundler": "https://bnb-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0xEA5ecE95D3A28f9faB161779d20128b449F9EC9C" - } + }, + "thresholdValue": "0.09" }, { "chainId": 97, "bundler": "https://bnbtestnet-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0x153e26707DF3787183945B88121E4Eb188FDCAAA" - } + }, + "thresholdValue": "0.09" }, { "chainId": 100, "bundler": "https://gnosis-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0x373aBcF1EA9e5802778E32870e7f72C8A6a90349" - } + }, + "thresholdValue": "50" }, { "chainId": 114, "bundler": "https://flaretestnet-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0x2a18C360b525824B3e5656B5a705554f2a5036Be" - } + }, + "thresholdValue": "1556" }, { "chainId": 122, "bundler": "https://fuse-bundler.etherspot.io", "contracts": { "etherspotPaymasterAddress": "0xEC2EE24E79C73DB13Dd9bC782856a5296626b7eb" - } + }, + "thresholdValue": "669" }, { - "chainId": 137, - "bundler": "https://polygon-bundler.etherspot.io", + "chainId": 123, + "bundler": "https://fusetestnet-bundler-bundler.etherspot.io", "contracts": { - "etherspotPaymasterAddress": "0x26FeC24b0D467C9de105217B483931e8f944ff50" - } + "etherspotPaymasterAddress": "0xAF628C207513c5E51d894b3733056B8080634923" + }, + "thresholdValue": "669" }, { - "chainId": 420, - "bundler": "https://optimismgoerli-bundler.etherspot.io", + "chainId": 137, + "bundler": "https://polygon-bundler.etherspot.io", "contracts": { - "etherspotPaymasterAddress": "0x898c530A5fA37720DcF1843AeCC34b6B0cBaEB8a" - } + "etherspotPaymasterAddress": "0x26FeC24b0D467C9de105217B483931e8f944ff50" + }, + "thresholdValue": "69.85" }, { "chainId": 1001, "bundler": "https://klaytntestnet-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0x810FA4C915015b703db0878CF2B9344bEB254a40" - } + }, + "thresholdValue": "275.2" }, { "chainId": 5000, "bundler": "https://mantle-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0x8A41594e5c6Fe492e437414c24eA6f401186b8d2" - } + }, + "thresholdValue": "44.24" }, { "chainId": 5001, "bundler": "https://mantletestnet-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0xb56eC212C60C47fb7385f13b7247886FFa5E9D5C" - } + }, + "thresholdValue": "44.24" }, { "chainId": 8217, "bundler": "https://klaytn-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0x4ebd86AAF89151b5303DB072e0205C668e31E5E7" - } + }, + "thresholdValue": "275.2" }, { "chainId": 8453, "bundler": "https://base-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0x810FA4C915015b703db0878CF2B9344bEB254a40" - } + }, + "thresholdValue": "15.19" }, { "chainId": 42161, "bundler": "https://arbitrum-bundler.etherspot.io", "contracts": { "etherspotPaymasterAddress": "0xEC2EE24E79C73DB13Dd9bC782856a5296626b7eb" - } + }, + "thresholdValue": "43.10" }, { "chainId": 43114, "bundler": "https://avalanche-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0x527569794781671319f20374A050BDbef4181aB3" - } + }, + "thresholdValue": "1.4" }, { "chainId": 59144, "bundler": "https://linea-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0xB3AD9B9B06c6016f81404ee8FcCD0526F018Cf0C" - } + }, + "thresholdValue": "0.016" }, { - "chainId": 80001, - "bundler": "https://mumbai-bundler.etherspot.io", - "contracts": { - "etherspotPaymasterAddress": "0x8350355c08aDAC387b443782124A30A8942BeC2e" - } - }, - { - "chainId": 84531, - "bundler": "https://basegoerli-bundler.etherspot.io", + "chainId": 84532, + "bundler": "https://basesepolia-bundler.etherspot.io", "contracts": { - "etherspotPaymasterAddress": "0x898c530A5fA37720DcF1843AeCC34b6B0cBaEB8a" - } + "etherspotPaymasterAddress": "0xe893A26DD53b325BffAacDfA224692EfF4C448c4" + }, + "thresholdValue": "15.19" }, { - "chainId": 421613, - "bundler": "https://arbitrumgoerli-bundler.etherspot.io", + "chainId": 421614, + "bundler": "https://arbitrumsepolia-bundler.etherspot.io", "contracts": { - "etherspotPaymasterAddress": "0x898c530A5fA37720DcF1843AeCC34b6B0cBaEB8a" - } + "etherspotPaymasterAddress": "0xe893A26DD53b325BffAacDfA224692EfF4C448c4" + }, + "thresholdValue": "43.10" }, { "chainId": 534351, "bundler": "https://scrollsepolia-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0xe893A26DD53b325BffAacDfA224692EfF4C448c4" - } + }, + "thresholdValue": "0.016" }, { - "chainId": 11155111, - "bundler": "https://sepolia-bundler.etherspot.io", - "contracts": { - "etherspotPaymasterAddress": "0xcaDBADcFeD5530A49762DFc9d1d712CcD6b09b25" - } - }, - { - "chainId": 123, - "bundler": "https://fusetestnet-bundler-bundler.etherspot.io", + "chainId": 534352, + "bundler": "https://scroll-bundler.etherspot.io", "contracts": { - "etherspotPaymasterAddress": "0xAF628C207513c5E51d894b3733056B8080634923" - } + "etherspotPaymasterAddress": "0xB3AD9B9B06c6016f81404ee8FcCD0526F018Cf0C" + }, + "thresholdValue": "0.016" }, { - "chainId": 31, - "bundler": "https://rootstocktestnet-bundler.etherspot.io", + "chainId": 11155111, + "bundler": "https://sepolia-bundler.etherspot.io", "contracts": { - "etherspotPaymasterAddress": "0x898c530A5fA37720DcF1843AeCC34b6B0cBaEB8a" - } + "etherspotPaymasterAddress": "0xcaDBADcFeD5530A49762DFc9d1d712CcD6b09b25" + }, + "thresholdValue": "0.016" }, { - "chainId": 421614, - "bundler": "https://arbitrumsepolia-bundler.etherspot.io", + "chainId": 28122024, + "bundler": "https://ancient8testnet-bundler.etherspot.io/", "contracts": { "etherspotPaymasterAddress": "0xe893A26DD53b325BffAacDfA224692EfF4C448c4" - } + }, + "thresholdValue": "0.016" }, { "chainId": 11155420, "bundler": "https://optimismsepolia-bundler.etherspot.io", "contracts": { "etherspotPaymasterAddress": "0xB3AD9B9B06c6016f81404ee8FcCD0526F018Cf0C" - } - }, - { - "chainId": 84532, - "bundler": "https://basesepolia-bundler.etherspot.io", - "contracts": { - "etherspotPaymasterAddress": "0xe893A26DD53b325BffAacDfA224692EfF4C448c4" - } - }, - { - "chainId": 30, - "bundler": "https://rootstock-bundler.etherspot.io", - "contracts": { - "etherspotPaymasterAddress": "0xe893A26dD53b325bFFaACdFA224692EFF4C448C4" - } - }, - { - "chainId": 534352, - "bundler": "https://scroll-bundler.etherspot.io", - "contracts": { - "etherspotPaymasterAddress": "0xB3AD9B9B06c6016f81404ee8FcCD0526F018Cf0C" - } - }, - { - "chainId": 28122024, - "bundler": "https://ancient8testnet-bundler.etherspot.io", - "contracts": { - "etherspotPaymasterAddress": "0xe893A26DD53b325BffAacDfA224692EfF4C448c4" - } + }, + "thresholdValue": "21.8" } ] diff --git a/backend/demo.env b/backend/demo.env index 69b8af6..33c0996 100644 --- a/backend/demo.env +++ b/backend/demo.env @@ -11,3 +11,5 @@ ADMIN_WALLET_ADDRESS= DEFAULT_INDEXER_ENDPOINT=http://localhost:3003 FEE_MARKUP= ETHERSCAN_GAS_ORACLES= +DEFAULT_API_KEY= +WEBHOOK_URL= diff --git a/backend/package.json b/backend/package.json index 5c1c3b9..ae24c02 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "arka", - "version": "1.2.0", + "version": "1.2.1", "description": "ARKA - (Albanian for Cashier's case) is the first open source Paymaster as a service software", "type": "module", "directories": { diff --git a/backend/src/server.ts b/backend/src/server.ts index f07cfc2..246ba04 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -3,6 +3,7 @@ import fastify, { FastifyInstance } from 'fastify'; import cors from '@fastify/cors'; import fastifyCron from 'fastify-cron'; 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 config from './plugins/config.js'; @@ -12,7 +13,8 @@ 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 } from './utils/common.js'; +import { getNetworkConfig, getSQLdata } from './utils/common.js'; +import { checkDeposit } from './utils/monitorTokenPaymaster.js'; let server: FastifyInstance; @@ -148,6 +150,60 @@ const initializeServer = async (): Promise => { server.log.info('no private key found in env') } } + }, + { + // Only these two properties are required, + // the rest is from the node-cron API: + // https://github.com/kelektiv/node-cron#api + cronTime: '0 * * * *', // Every Hour, + name: 'checkTokenPaymasterDeposit', + + // Note: the callbacks (onTick & onComplete) take the server + // as an argument, as opposed to nothing in the node-cron API: + onTick: async () => { + if (process.env.DEFAULT_API_KEY && process.env.WEBHOOK_URL) { + const prefixSecretId = 'arka_'; + let client: SecretsManagerClient; + const unsafeMode: boolean = process.env.UNSAFE_MODE == "true" ? true : false; + const api_key = process.env.DEFAULT_API_KEY; + let customPaymasters = [], multiTokenPaymasters = []; + if (!unsafeMode) { + client = new SecretsManagerClient(); + const AWSresponse = await client.send( + new GetSecretValueCommand({ + SecretId: prefixSecretId + api_key, + }) + ); + client.destroy(); + const secrets = JSON.parse(AWSresponse.SecretString ?? '{}'); + if (secrets['ERC20_PAYMASTERS']) { + const buffer = Buffer.from(secrets['ERC20_PAYMASTERS'], 'base64'); + customPaymasters = JSON.parse(buffer.toString()); + } + if (secrets['MULTI_TOKEN_PAYMASTERS']) { + const buffer = Buffer.from(secrets['MULTI_TOKEN_PAYMASTERS'], 'base64'); + 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'); + customPaymasters = JSON.parse(buffer.toString()); + } + if (record['MULTI_TOKEN_PAYMASTERS']) { + const buffer = Buffer.from(record['MULTI_TOKEN_PAYMASTERS'], 'base64'); + multiTokenPaymasters = JSON.parse(buffer.toString()); + } + } + 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) + } + } + } + } } ] }); diff --git a/backend/src/utils/monitorTokenPaymaster.ts b/backend/src/utils/monitorTokenPaymaster.ts new file mode 100644 index 0000000..b7c4af7 --- /dev/null +++ b/backend/src/utils/monitorTokenPaymaster.ts @@ -0,0 +1,22 @@ +import { FastifyBaseLogger } from "fastify"; +import { ethers, providers } from "ethers"; +import fetch from 'node-fetch'; +import EtherspotAbi from "../abi/EtherspotAbi.js"; + +export async function checkDeposit(paymasterAddress: string, bundlerUrl: string, webhookUrl: string, thresholdValue: string, chainId: number, log: FastifyBaseLogger) { + try { + const provider = new providers.JsonRpcProvider(bundlerUrl); + const contract = new ethers.Contract(paymasterAddress, EtherspotAbi, provider); + const currentDeposit = await contract.getDeposit(); + if (ethers.utils.parseEther(thresholdValue).gte(currentDeposit)) { + const body = { message: `Balance below threshold. Please deposit on tokenPaymasterAddress: ${paymasterAddress} chainId: ${chainId}`, currentDeposit: ethers.utils.formatEther(currentDeposit) } + await fetch(webhookUrl, { + method: 'POST', + body: JSON.stringify(body) + }); + } + } catch (err) { + log.error(`Error on chainId ${chainId} address: ${paymasterAddress} webhookUrl: ${webhookUrl} bunderUrl: ${bundlerUrl}`) + log.error(err); + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 107e643..67efdf6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,8 @@ services: - DEFAULT_INDEXER_ENDPOINT=http://localhost:3003 - FEE_MARKUP= - ETHERSCAN_GAS_ORACLES= + - DEFAULT_API_KEY= + - WEBHOOK_URL= build: context: ./backend dockerfile: Dockerfile diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 040336b..f1429c7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "arka_frontend", - "version": "1.2.0", + "version": "1.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "arka_frontend", - "version": "1.2.0", + "version": "1.2.1", "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 fae57bf..10d6bd8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "arka_frontend", - "version": "1.2.0", + "version": "1.2.1", "private": true, "dependencies": { "@babel/plugin-proposal-private-property-in-object": "7.21.11",