From 7a2d589df6367c4d8a52fe2f25218e717f220154 Mon Sep 17 00:00:00 2001 From: vignesha22 <82584664+vignesha22@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:29:09 +0530 Subject: [PATCH] PRO-2862 - Coingecko Changes (#161) * coingecko database addition * ft: adding cron jobs to increase cache hit ratio for oracle data (#157) * ft: adding cron jobs to increase cache hit ratio for oracle data * update version * changes to make native oracle calls only for chainlink * Fix/arb token (#158) * fix for arb token in multi token paymaster * update cron expression for erc20 oracle update cron * comment and version update * Pro 2857 (#153) (#159) * Pro 2857 (#153) * ft: adding support for verifying paymaster * update: package.json version * fix: changing names for mode, minor fix for entry point error message * changes: common whitelist endpoints for v1/v2, removed v2 endpoints for whitelist * adding changelog for backend * chnages: version change, minor response changes for metadata and add stake routes * coingecko database addition * merge bug fixes * updated version * added required env vars to demo env * changed to pro key * changes done as requested * changes to retain v2 endpoints, minor changes for change log * updated changelog --------- Co-authored-by: Nikhil Kumar <48001923+nikhilkumar1612@users.noreply.github.com> Co-authored-by: nikhil kumar --- backend/CHANGELOG.md | 12 +- backend/demo.env | 4 + .../2024123000001-create-coingecko-tokens.cjs | 55 +++ ...0241231000001-default_coingecko_tokens.cjs | 76 ++++ ...65640-add-vp7-contracts-to-default-key.cjs | 32 ++ backend/package.json | 2 +- backend/src/abi/VerifyingPaymasterAbiV2.ts | 2 +- backend/src/models/coingecko.ts | 78 +++++ backend/src/paymaster/index.ts | 82 ++++- backend/src/plugins/sequelizePlugin.ts | 5 + .../repository/coingecko-token-repository.ts | 40 +++ backend/src/routes/deposit-route.ts | 49 +-- backend/src/routes/metadata-routes.ts | 147 ++++++-- backend/src/routes/paymaster-routes.ts | 118 +++---- backend/src/routes/pimlico-routes.ts | 24 +- backend/src/routes/whitelist-routes.ts | 329 +++++++++++++++--- backend/src/server.ts | 212 ++++++----- backend/src/services/coingecko.ts | 41 +++ backend/src/services/interface.ts | 53 +++ 19 files changed, 1038 insertions(+), 323 deletions(-) create mode 100644 backend/migrations/2024123000001-create-coingecko-tokens.cjs create mode 100644 backend/migrations/20241231000001-default_coingecko_tokens.cjs create mode 100644 backend/migrations/20250106065640-add-vp7-contracts-to-default-key.cjs create mode 100644 backend/src/models/coingecko.ts create mode 100644 backend/src/repository/coingecko-token-repository.ts create mode 100644 backend/src/services/coingecko.ts create mode 100644 backend/src/services/interface.ts diff --git a/backend/CHANGELOG.md b/backend/CHANGELOG.md index 779368e..75fd477 100644 --- a/backend/CHANGELOG.md +++ b/backend/CHANGELOG.md @@ -1,7 +1,15 @@ # Changelog -## [1.8.0] - 2024-12-25 +## [2.0.1] - 2025-01-08 +### New +- added `/whitelist/v2`, `/removeWhitelist/v2` and `/checkWhitelist/v2` endpoints back. +- `/whitelist`, `/removeWhitelist`, `checkWhitelist`, `/deposit/v2`, `/` endpoints accept `useVp` param which when set to true uses VerifyingPaymaster contract instead of EtherspotPaymaster. +- added coingecko support for token prices alone +- Added fetching of api_key data from db and for hosted setup only private key gets fetched from aws +- added `metadata/v2` api for ep7 config + + +## [2.0.0] - 2024-12-25 ### Breaking changes -- removed `/whitelist/v1`, `/removeWhitelist/v1`, `/checkWhitelist/v1` endpoints. - removed `/whitelist/v2`, `/removeWhitelist/v2`, `/checkWhitelist/v2` endpoints. ### New diff --git a/backend/demo.env b/backend/demo.env index f78986c..6f64628 100644 --- a/backend/demo.env +++ b/backend/demo.env @@ -14,3 +14,7 @@ MULTI_TOKEN_MARKUP=1150000 ETHERSCAN_GAS_ORACLES= DEFAULT_API_KEY= WEBHOOK_URL= + +# coingecko +COINGECKO_URL= +COINGECKO_API_KEY= diff --git a/backend/migrations/2024123000001-create-coingecko-tokens.cjs b/backend/migrations/2024123000001-create-coingecko-tokens.cjs new file mode 100644 index 0000000..a3a708a --- /dev/null +++ b/backend/migrations/2024123000001-create-coingecko-tokens.cjs @@ -0,0 +1,55 @@ +const { Sequelize } = require('sequelize') + +async function up({ context: queryInterface }) { + await queryInterface.createTable('coingecko_tokens', { + "ID": { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + }, + "TOKEN": { + allowNull: false, + primaryKey: false, + type: Sequelize.TEXT + }, + "ADDRESS": { + type: Sequelize.STRING, + allowNull: false + }, + "CHAIN_ID": { + type: Sequelize.BIGINT, + allowNull: false + }, + "COIN_ID": { + type: Sequelize.STRING, + allowNull: false, + primaryKey: true + }, + "DECIMALS": { + type: Sequelize.INTEGER, + allowNull: false, + }, + "CREATED_AT": { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW + }, + "UPDATED_AT": { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.NOW + } + }, { + schema: process.env.DATABASE_SCHEMA_NAME + }); +} + +async function down({ context: queryInterface }) { + await queryInterface.dropTable({ + tableName: 'coingecko_tokens', + schema: process.env.DATABASE_SCHEMA_NAME + }); +} + +module.exports = { up, down } diff --git a/backend/migrations/20241231000001-default_coingecko_tokens.cjs b/backend/migrations/20241231000001-default_coingecko_tokens.cjs new file mode 100644 index 0000000..9f2b645 --- /dev/null +++ b/backend/migrations/20241231000001-default_coingecko_tokens.cjs @@ -0,0 +1,76 @@ +require('dotenv').config(); + +async function up({ context: queryInterface }) { + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('DFYN', '0xc168e40227e4ebd8c1cae80f7a55a4f0e6d66c97', 137, 'dfyn-network', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('BLOK', '0x229b1b6c23ff8953d663c4cbb519717e323a0a84', 137, 'bloktopia', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('BUSD', '0x9c9e5fd8bbc25984b178fdce6117defa39d2db39', 137, 'binance-peg-busd', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('DG', '0xef938b6da8576a896f6e0321ef80996f4890f9c4', 137, 'decentral-games', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('FRM', '0xd99bafe5031cc8b345cb2e8c80135991f12d7130', 137, 'ferrum-network', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('IXT', '0xe06bd4f5aac8d0aa337d13ec88db6defc6eaeefe', 137, 'ix-token', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('jEUR', '0x4e3decbb3645551b8a19f0ea1678079fcb33fb4c', 137, 'jarvis-synthetic-euro', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('KLIMA', '0x4e78011ce80ee02d2c3e649fb657e45898257815', 137, 'klima-dao', 9, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('LDO', '0xc3c7d422809852031b44ab29eec9f1eff2a58756', 137, 'lido-dao', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('MASQ', '0xee9a352f6aac4af1a5b9f467f6a93e0ffbe9dd35', 137, 'masq', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('MCHC', '0xee7666aacaefaa6efeef62ea40176d3eb21953b9', 137, 'mch-coin', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('miMATIC', '0xa3fa99a148fa48d14ed51d610c367c61876997f1', 137, 'mimatic', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('ORBS', '0x614389eaae0a6821dc49062d56bda3d9d45fa2ff', 137, 'orbs', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('PSP', '0x42d61d766b85431666b39b89c43011f24451bff6', 137, 'paraswap', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('RISE', '0xc17c30e98541188614df99239cabd40280810ca3', 137, 'everrise', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('RWB', '0x431cd3c9ac9fc73644bf68bf5691f4b83f9e104f', 137, 'rainbow-token-2', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('SD', '0x1d734a02ef1e1f5886e66b0673b71af5b53ffa94', 137, 'stader', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('SPHERE', '0x62f594339830b90ae4c084ae7d223ffafd9658a7', 137, 'sphere-finance', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('stMATIC', '0x3a58a54c066fdc0f2d55fc9c89f0415c92ebf3c4', 137, 'lido-staked-matic', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('SUPER', '0xa1428174f516f527fafdd146b883bb4428682737', 137, 'superfarm', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('TEL', '0xdf7837de1f2fa4631d716cf2502f8b230f1dcc32', 137, 'telcoin', 2, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('TETU', '0x255707b70bf90aa112006e1b07b9aea6de021424', 137, 'tetu', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('VOXEL', '0xd0258a3fd00f38aa8090dfee343f10a9d4d30d3f', 137, 'voxies', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('WNT', '0x82a0e6c02b91ec9f6ff943c0a933c03dbaa19689', 137, 'wicrypt', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('XSGD', '0xdc3326e71d45186f113a2f448984ca0e8d201995', 137, 'xsgd', 6, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('USDbc', '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA', 8453, 'bridged-usd-coin-base', 6, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('PRIME', '0xfA980cEd6895AC314E7dE34Ef1bFAE90a5AdD21b', 8453, 'echelon-prime', 18, NOW(), NOW());`); + // await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('SDEX', '0xfd4330b0312fdeec6d4225075b82e00493ff2e3f', 8453, 'smardex', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('RETH', '0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c', 8453, 'rocket-pool-eth', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('wstETH', '0x5979d7b546e38e414f7e9822514be443a4800529', 42161, 'wrapped-steth', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('DPX', '0x6c2c06790b3e3e3c38e12ee22f8183b37a13ee55', 42161, 'dopex', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('LIQD', '0x93c15cd7de26f07265f0272e0b831c5d7fab174f', 42161, 'liquid-finance', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('agEUR', '0xfa5ed56a203466cbbc2430a43c66b9d8723528e7', 42161, 'ageur', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('MCB', '0x4e352cf164e64adcbad318c3a1e222e9eba4ce42', 42161, 'mcdex', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('PREMIA', '0x51fc0f6660482ea73330e414efd7808811a57fa2', 42161, 'premia', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('USDS', '0xd74f5255d557944cf7dd0e45ff521520002d5748', 42161, 'sperax-usd', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('VELA', '0x088cd8f5ef3652623c22d48b1605dcfe860cd704', 42161, 'vela-token', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('REUNI', '0x9ed7e4b1bff939ad473da5e7a218c771d1569456', 42161, 'reunit-wallet', 6, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('sUSD', '0x8c6f28f2f1a3c87f0f938b96d27520d9751ec8d9', 10, 'susd-optimism', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('wstETH', '0x1f32b1c2345538c0c6f582fcb022739c4a194ebb', 10, 'wrapped-steth', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('wETH', '0x4200000000000000000000000000000000000006', 10, 'l2-standard-bridged-weth-optimism', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('THALES', '0x217d47011b23bb961eb6d93ca9945b7501a5bb11', 10, 'thales', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('LYRA', '0x50c5725949a6f0c72e6c4a641f24049a917db0cb', 10, 'lyra-finance', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('DOLA', '0x8ae125e8653821e851f12a49f7765db9a9ce7384', 10, 'dola-usd', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('KWENTA', '0x920cf626a271321c151d027030d5d08af699456b', 10, 'kwenta', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('MAI', '0xdfa46478f9e5ea86d57387849598dbfb2e964b02', 10, 'mai-optimism', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('wBNB', '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c', 56, 'wbnb', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('TWT', '0x4b0f1812e5df2a09796481ff14017e6005508003', 56, 'trust-wallet-token', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('MDX', '0x9c65ab58d8d978db963e63f2bfb7121627e3a739', 56, 'mdex-bsc', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('USDD', '0xd17479997f34dd9156deef8f95a52d81d265be9c', 56, 'usdd', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('ZIL', '0xb86abcb37c3a4b64f74f59301aff131a1becc787', 56, 'zilliqa', 12, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('MOBOX', '0x3203c9e46ca618c8c1ce5dc67e7e9d75f5da2377', 56, 'mobox', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('BETH', '0x250632378e573c6be1ac2f97fcdf00515d0aa91b', 56, 'binance-eth', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('ALPHA', '0xa1faa113cbe53436df28ff0aee54275c13b40975', 56, 'alpha-finance', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('REEF', '0xf21768ccbc73ea5b6fd3c687208a7c2def2d966e', 56, 'reef', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('VAI', '0x4bd17003473389a42daf6a0a729f6fdb328bbbd7', 56, 'vai', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('AUTO', '0xa184088a740c695e156f91f5cc086a06bb78b827', 56, 'auto', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('MIM', '0xfe19f0b51438fd612f6fd59c1dbb3ea319f433ba', 56, 'magic-internet-money-bsc', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('BTCB', '0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c', 56, 'binance-bitcoin', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('USTC', '0x23396cf899ca06c4472205fc903bdb4de249d6fc', 56, 'wrapped-ust', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('SFP', '0xd41fdb03ba84762dd66a0af1a6c8540ff1ba5dfb', 56, 'safepal', 18, NOW(), NOW());`); + // await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('ACS', '0x8888888888f004100c0353d657be6300587a6ccd', 56, 'acryptos-2', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('BUNNY', '0xc9849e6fdb743d08faee3e34dd2d1bc69ea11a51', 56, 'pancake-bunny', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('BAKE', '0xe02df9e3e622debdd69fb838bb799e3f168902c5', 56, 'bakerytoken', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('BANANA', '0x603c7f932ed1fc6575303d8fb018fdcbb0f39a95', 56, 'apeswap-finance', 18, NOW(), NOW());`); + await queryInterface.sequelize.query(`INSERT INTO "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens ("TOKEN", "ADDRESS", "CHAIN_ID", "COIN_ID", "DECIMALS", "CREATED_AT", "UPDATED_AT") VALUES ('ANKR', '0xf307910a4c7bbc79691fd374889b36d8531b08e3', 56, 'ankr', 18, NOW(), NOW());`); +} + +async function down({ context: queryInterface }) { + await queryInterface.sequelize.query(`DELETE FROM "${process.env.DATABASE_SCHEMA_NAME}".coingecko_tokens;`); +} + +module.exports = { up, down } \ No newline at end of file diff --git a/backend/migrations/20250106065640-add-vp7-contracts-to-default-key.cjs b/backend/migrations/20250106065640-add-vp7-contracts-to-default-key.cjs new file mode 100644 index 0000000..1787f28 --- /dev/null +++ b/backend/migrations/20250106065640-add-vp7-contracts-to-default-key.cjs @@ -0,0 +1,32 @@ +require('dotenv').config(); + +const vp7Contracts = JSON.stringify({ + 31: "0x805650ce74561C85baA44a8Bd13E19633Fd0F79d", + 50: "0xABA00E929d66119A4A7F4B2E27150fC387ee801c", + 51: "0x2b7cBFA523E0D0546C6d1F706b79dB7B6d910bdA", + 97: "0xD9A97785a91086FDeF17980eDC2f9D290d71153F", + 114: "0x5952653F151e844346825050d7157A9a6b46A23A", + 123: "0xf6E4486156cc2F982eceC15a90B23047F396EcBE", + 5003: "0x42963C58DE382D34CB5a7f77b703e645FcE6DD26", + 80002: "0x9ddB9DC20E904206823184577e9C571c713d2c57", + 84532: "0xD9A97785a91086FDeF17980eDC2f9D290d71153F", + 421614: "0x5FD81CfCAa69F44B6d105795961b3E484ac9e7dB", + 534351: "0xD9A97785a91086FDeF17980eDC2f9D290d71153F", + 11155111: "0x8B57f6b24C7cd85007068Bf0587382804B225DB6", + 11155420: "0x51a62e2B1E295CAe7Db5b91886735f9Ce335AcFB", + 28122024: "0xc95A2Fb019445C9B3459c2C59e7cd6Ad2c8FBb1E" +}); + +async function up({ context: queryInterface }) { + await queryInterface.sequelize.query( + `UPDATE ${process.env.DATABASE_SCHEMA_NAME}."api_keys" SET "VERIFYING_PAYMASTERS_V2"='${vp7Contracts}' WHERE "API_KEY"='arka_public_key'` + ); +} + +async function down({ context: queryInterface }) { + await queryInterface.sequelize.query( + `UPDATE ${process.env.DATABASE_SCHEMA_NAME}."api_keys" SET "VERIFYING_PAYMASTERS_V2"=${null} WHERE "API_KEYS"='arka_public_key'` + ); +} + +module.exports = {up, down}; diff --git a/backend/package.json b/backend/package.json index 07868e5..c0bdc8a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "arka", - "version": "2.0.0", + "version": "2.1.0", "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/abi/VerifyingPaymasterAbiV2.ts b/backend/src/abi/VerifyingPaymasterAbiV2.ts index 1c6dd17..f1c6c71 100644 --- a/backend/src/abi/VerifyingPaymasterAbiV2.ts +++ b/backend/src/abi/VerifyingPaymasterAbiV2.ts @@ -312,4 +312,4 @@ export const abi = [ ]; export const byteCode = - '0x60a06040523480156200001157600080fd5b5060405162001417380380620014178339810160408190526200003491620001ca565b8133806200005d57604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b6200006881620000a1565b506200007481620000f1565b6001600160a01b03908116608052600180546001600160a01b031916929091169190911790555062000234565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6040516301ffc9a760e01b815263122a0e9b60e31b60048201526001600160a01b038216906301ffc9a790602401602060405180830381865afa1580156200013d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000163919062000209565b620001b15760405162461bcd60e51b815260206004820152601e60248201527f49456e747279506f696e7420696e74657266616365206d69736d617463680000604482015260640162000054565b50565b6001600160a01b0381168114620001b157600080fd5b60008060408385031215620001de57600080fd5b8251620001eb81620001b4565b6020840151909250620001fe81620001b4565b809150509250929050565b6000602082840312156200021c57600080fd5b815180151581146200022d57600080fd5b9392505050565b608051611196620002816000396000818161027b01528181610331015281816103c80152818161061e015281816106b801528181610728015281816107b5015261087d01526111966000f3fe6080604052600436106100f35760003560e01c806393b941211161008a578063c23a5cea11610059578063c23a5cea146102b2578063c399ec88146102d2578063d0e30db0146102e7578063f2fde38b146102ef57600080fd5b806393b941211461021957806394d4ad6014610239578063b0d691fe14610269578063bb9fe6bf1461029d57600080fd5b80635829c5f5116100c65780635829c5f514610198578063715018a6146101c65780637c627b21146101db5780638da5cb5b146101fb57600080fd5b80630396cb60146100f8578063205c28781461010d57806323d9ac9b1461012d57806352b7512c1461016a575b600080fd5b61010b610106366004610d3c565b61030f565b005b34801561011957600080fd5b5061010b610128366004610d7e565b61039a565b34801561013957600080fd5b5060015461014d906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561017657600080fd5b5061018a610185366004610dc3565b61040c565b604051610161929190610e11565b3480156101a457600080fd5b506101b86101b3366004610e81565b610430565b604051908152602001610161565b3480156101d257600080fd5b5061010b610540565b3480156101e757600080fd5b5061010b6101f6366004610f28565b610554565b34801561020757600080fd5b506000546001600160a01b031661014d565b34801561022557600080fd5b5061010b610234366004610f93565b610570565b34801561024557600080fd5b50610259610254366004610fb0565b6105cc565b6040516101619493929190610ff2565b34801561027557600080fd5b5061014d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156102a957600080fd5b5061010b610614565b3480156102be57600080fd5b5061010b6102cd366004610f93565b610691565b3480156102de57600080fd5b506101b8610710565b61010b6107a0565b3480156102fb57600080fd5b5061010b61030a366004610f93565b610802565b610317610845565b604051621cb65b60e51b815263ffffffff821660048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690630396cb609034906024016000604051808303818588803b15801561037e57600080fd5b505af1158015610392573d6000803e3d6000fd5b505050505050565b6103a2610845565b60405163040b850f60e31b81526001600160a01b038381166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063205c287890604401600060405180830381600087803b15801561037e57600080fd5b60606000610418610872565b6104238585856108e2565b915091505b935093915050565b60008335806020860135610447604088018861103e565b604051610455929190611085565b60405190819003902061046b606089018961103e565b604051610479929190611085565b604051908190039020608089013561049460e08b018b61103e565b6104a391603491601491611095565b6104ac916110bf565b604080516001600160a01b0390971660208801528601949094526060850192909252608084015260a08084019190915260c08084019290925287013560e0830152860135610100820152466101208201523061014082015265ffffffffffff80861661016083015284166101808201526101a001604051602081830303815290604052805190602001209150509392505050565b610548610845565b6105526000610a77565b565b61055c610872565b6105698585858585610ac7565b5050505050565b610578610845565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f51d754ac8f7adf515a023f2c423e01ef97817c1af33cb63b36f1fe12fde2d91a9060200160405180910390a150565b60008036816105de8560348189611095565b8101906105eb91906110dd565b909450925085856105fe60346040611110565b610609928290611095565b949793965094505050565b61061c610845565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663bb9fe6bf6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561067757600080fd5b505af115801561068b573d6000803e3d6000fd5b50505050565b610699610845565b60405163611d2e7560e11b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063c23a5cea90602401600060405180830381600087803b1580156106fc57600080fd5b505af1158015610569573d6000803e3d6000fd5b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015610777573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061079b9190611131565b905090565b60405163b760faf960e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b760faf99034906024016000604051808303818588803b1580156106fc57600080fd5b61080a610845565b6001600160a01b03811661083957604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b61084281610a77565b50565b6000546001600160a01b031633146105525760405163118cdaa760e01b8152336004820152602401610830565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146105525760405162461bcd60e51b815260206004820152601560248201527414d95b99195c881b9bdd08115b9d1c9e541bda5b9d605a1b6044820152606401610830565b60606000808036816108fa61025460e08b018b61103e565b9296509094509250905060408114806109135750604181145b610987576040805162461bcd60e51b81526020600482015260248101919091527f566572696679696e675061796d61737465723a20696e76616c6964207369676e60448201527f6174757265206c656e67746820696e207061796d6173746572416e64446174616064820152608401610830565b60006109ca6109978b8787610430565b7f19457468657265756d205369676e6564204d6573736167653a0a3332000000006000908152601c91909152603c902090565b9050610a0c8184848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610aff92505050565b6001546001600160a01b03908116911614610a4c57610a2d60018686610b2b565b6040518060200160405280600081525090965096505050505050610428565b610a5860008686610b2b565b6040805160208101909152600081529b909a5098505050505050505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60405162461bcd60e51b815260206004820152600d60248201526c6d757374206f7665727269646560981b6044820152606401610830565b600080600080610b0f8686610b63565b925092509250610b1f8282610bb0565b50909150505b92915050565b600060d08265ffffffffffff16901b60a08465ffffffffffff16901b85610b53576000610b56565b60015b60ff161717949350505050565b60008060008351604103610b9d5760208401516040850151606086015160001a610b8f88828585610c6d565b955095509550505050610ba9565b50508151600091506002905b9250925092565b6000826003811115610bc457610bc461114a565b03610bcd575050565b6001826003811115610be157610be161114a565b03610bff5760405163f645eedf60e01b815260040160405180910390fd5b6002826003811115610c1357610c1361114a565b03610c345760405163fce698f760e01b815260048101829052602401610830565b6003826003811115610c4857610c4861114a565b03610c69576040516335e2f38360e21b815260048101829052602401610830565b5050565b600080807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115610ca85750600091506003905082610d32565b604080516000808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015610cfc573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116610d2857506000925060019150829050610d32565b9250600091508190505b9450945094915050565b600060208284031215610d4e57600080fd5b813563ffffffff81168114610d6257600080fd5b9392505050565b6001600160a01b038116811461084257600080fd5b60008060408385031215610d9157600080fd5b8235610d9c81610d69565b946020939093013593505050565b60006101208284031215610dbd57600080fd5b50919050565b600080600060608486031215610dd857600080fd5b833567ffffffffffffffff811115610def57600080fd5b610dfb86828701610daa565b9660208601359650604090950135949350505050565b604081526000835180604084015260005b81811015610e3f5760208187018101516060868401015201610e22565b506000606082850101526060601f19601f8301168401019150508260208301529392505050565b803565ffffffffffff81168114610e7c57600080fd5b919050565b600080600060608486031215610e9657600080fd5b833567ffffffffffffffff811115610ead57600080fd5b610eb986828701610daa565b935050610ec860208501610e66565b9150610ed660408501610e66565b90509250925092565b60008083601f840112610ef157600080fd5b50813567ffffffffffffffff811115610f0957600080fd5b602083019150836020828501011115610f2157600080fd5b9250929050565b600080600080600060808688031215610f4057600080fd5b853560038110610f4f57600080fd5b9450602086013567ffffffffffffffff811115610f6b57600080fd5b610f7788828901610edf565b9699909850959660408101359660609091013595509350505050565b600060208284031215610fa557600080fd5b8135610d6281610d69565b60008060208385031215610fc357600080fd5b823567ffffffffffffffff811115610fda57600080fd5b610fe685828601610edf565b90969095509350505050565b600065ffffffffffff808716835280861660208401525060606040830152826060830152828460808401376000608084840101526080601f19601f850116830101905095945050505050565b6000808335601e1984360301811261105557600080fd5b83018035915067ffffffffffffffff82111561107057600080fd5b602001915036819003821315610f2157600080fd5b8183823760009101908152919050565b600080858511156110a557600080fd5b838611156110b257600080fd5b5050820193919092039150565b80356020831015610b2557600019602084900360031b1b1692915050565b600080604083850312156110f057600080fd5b6110f983610e66565b915061110760208401610e66565b90509250929050565b80820180821115610b2557634e487b7160e01b600052601160045260246000fd5b60006020828403121561114357600080fd5b5051919050565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220b3dee56d54c33e3d801b74ef60dfdbf85dc7548b852fda6106bd54223fbdaf2e64736f6c634300081700330000000000000000000000000000000071727de22e5e9d8baf0edac6f37da032000000000000000000000000aeaf09795d8c0e6fa4bb5f89dc9c15ec02021567'; + '0x60a06040523480156200001157600080fd5b5060405162001417380380620014178339810160408190526200003491620001ca565b8133806200005d57604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b6200006881620000a1565b506200007481620000f1565b6001600160a01b03908116608052600180546001600160a01b031916929091169190911790555062000234565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6040516301ffc9a760e01b815263122a0e9b60e31b60048201526001600160a01b038216906301ffc9a790602401602060405180830381865afa1580156200013d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000163919062000209565b620001b15760405162461bcd60e51b815260206004820152601e60248201527f49456e747279506f696e7420696e74657266616365206d69736d617463680000604482015260640162000054565b50565b6001600160a01b0381168114620001b157600080fd5b60008060408385031215620001de57600080fd5b8251620001eb81620001b4565b6020840151909250620001fe81620001b4565b809150509250929050565b6000602082840312156200021c57600080fd5b815180151581146200022d57600080fd5b9392505050565b608051611196620002816000396000818161027b01528181610331015281816103c80152818161061e015281816106b801528181610728015281816107b5015261087d01526111966000f3fe6080604052600436106100f35760003560e01c806393b941211161008a578063c23a5cea11610059578063c23a5cea146102b2578063c399ec88146102d2578063d0e30db0146102e7578063f2fde38b146102ef57600080fd5b806393b941211461021957806394d4ad6014610239578063b0d691fe14610269578063bb9fe6bf1461029d57600080fd5b80635829c5f5116100c65780635829c5f514610198578063715018a6146101c65780637c627b21146101db5780638da5cb5b146101fb57600080fd5b80630396cb60146100f8578063205c28781461010d57806323d9ac9b1461012d57806352b7512c1461016a575b600080fd5b61010b610106366004610d3c565b61030f565b005b34801561011957600080fd5b5061010b610128366004610d7e565b61039a565b34801561013957600080fd5b5060015461014d906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561017657600080fd5b5061018a610185366004610dc3565b61040c565b604051610161929190610e11565b3480156101a457600080fd5b506101b86101b3366004610e81565b610430565b604051908152602001610161565b3480156101d257600080fd5b5061010b610540565b3480156101e757600080fd5b5061010b6101f6366004610f28565b610554565b34801561020757600080fd5b506000546001600160a01b031661014d565b34801561022557600080fd5b5061010b610234366004610f93565b610570565b34801561024557600080fd5b50610259610254366004610fb0565b6105cc565b6040516101619493929190610ff2565b34801561027557600080fd5b5061014d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156102a957600080fd5b5061010b610614565b3480156102be57600080fd5b5061010b6102cd366004610f93565b610691565b3480156102de57600080fd5b506101b8610710565b61010b6107a0565b3480156102fb57600080fd5b5061010b61030a366004610f93565b610802565b610317610845565b604051621cb65b60e51b815263ffffffff821660048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690630396cb609034906024016000604051808303818588803b15801561037e57600080fd5b505af1158015610392573d6000803e3d6000fd5b505050505050565b6103a2610845565b60405163040b850f60e31b81526001600160a01b038381166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063205c287890604401600060405180830381600087803b15801561037e57600080fd5b60606000610418610872565b6104238585856108e2565b915091505b935093915050565b60008335806020860135610447604088018861103e565b604051610455929190611085565b60405190819003902061046b606089018961103e565b604051610479929190611085565b604051908190039020608089013561049460e08b018b61103e565b6104a391603491601491611095565b6104ac916110bf565b604080516001600160a01b0390971660208801528601949094526060850192909252608084015260a08084019190915260c08084019290925287013560e0830152860135610100820152466101208201523061014082015265ffffffffffff80861661016083015284166101808201526101a001604051602081830303815290604052805190602001209150509392505050565b610548610845565b6105526000610a77565b565b61055c610872565b6105698585858585610ac7565b5050505050565b610578610845565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f51d754ac8f7adf515a023f2c423e01ef97817c1af33cb63b36f1fe12fde2d91a9060200160405180910390a150565b60008036816105de8560348189611095565b8101906105eb91906110dd565b909450925085856105fe60346040611110565b610609928290611095565b949793965094505050565b61061c610845565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663bb9fe6bf6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561067757600080fd5b505af115801561068b573d6000803e3d6000fd5b50505050565b610699610845565b60405163611d2e7560e11b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063c23a5cea90602401600060405180830381600087803b1580156106fc57600080fd5b505af1158015610569573d6000803e3d6000fd5b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015610777573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061079b9190611131565b905090565b60405163b760faf960e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b760faf99034906024016000604051808303818588803b1580156106fc57600080fd5b61080a610845565b6001600160a01b03811661083957604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b61084281610a77565b50565b6000546001600160a01b031633146105525760405163118cdaa760e01b8152336004820152602401610830565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146105525760405162461bcd60e51b815260206004820152601560248201527414d95b99195c881b9bdd08115b9d1c9e541bda5b9d605a1b6044820152606401610830565b60606000808036816108fa61025460e08b018b61103e565b9296509094509250905060408114806109135750604181145b610987576040805162461bcd60e51b81526020600482015260248101919091527f566572696679696e675061796d61737465723a20696e76616c6964207369676e60448201527f6174757265206c656e67746820696e207061796d6173746572416e64446174616064820152608401610830565b60006109ca6109978b8787610430565b7f19457468657265756d205369676e6564204d6573736167653a0a3332000000006000908152601c91909152603c902090565b9050610a0c8184848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610aff92505050565b6001546001600160a01b03908116911614610a4c57610a2d60018686610b2b565b6040518060200160405280600081525090965096505050505050610428565b610a5860008686610b2b565b6040805160208101909152600081529b909a5098505050505050505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60405162461bcd60e51b815260206004820152600d60248201526c6d757374206f7665727269646560981b6044820152606401610830565b600080600080610b0f8686610b63565b925092509250610b1f8282610bb0565b50909150505b92915050565b600060d08265ffffffffffff16901b60a08465ffffffffffff16901b85610b53576000610b56565b60015b60ff161717949350505050565b60008060008351604103610b9d5760208401516040850151606086015160001a610b8f88828585610c6d565b955095509550505050610ba9565b50508151600091506002905b9250925092565b6000826003811115610bc457610bc461114a565b03610bcd575050565b6001826003811115610be157610be161114a565b03610bff5760405163f645eedf60e01b815260040160405180910390fd5b6002826003811115610c1357610c1361114a565b03610c345760405163fce698f760e01b815260048101829052602401610830565b6003826003811115610c4857610c4861114a565b03610c69576040516335e2f38360e21b815260048101829052602401610830565b5050565b600080807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115610ca85750600091506003905082610d32565b604080516000808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015610cfc573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116610d2857506000925060019150829050610d32565b9250600091508190505b9450945094915050565b600060208284031215610d4e57600080fd5b813563ffffffff81168114610d6257600080fd5b9392505050565b6001600160a01b038116811461084257600080fd5b60008060408385031215610d9157600080fd5b8235610d9c81610d69565b946020939093013593505050565b60006101208284031215610dbd57600080fd5b50919050565b600080600060608486031215610dd857600080fd5b833567ffffffffffffffff811115610def57600080fd5b610dfb86828701610daa565b9660208601359650604090950135949350505050565b604081526000835180604084015260005b81811015610e3f5760208187018101516060868401015201610e22565b506000606082850101526060601f19601f8301168401019150508260208301529392505050565b803565ffffffffffff81168114610e7c57600080fd5b919050565b600080600060608486031215610e9657600080fd5b833567ffffffffffffffff811115610ead57600080fd5b610eb986828701610daa565b935050610ec860208501610e66565b9150610ed660408501610e66565b90509250925092565b60008083601f840112610ef157600080fd5b50813567ffffffffffffffff811115610f0957600080fd5b602083019150836020828501011115610f2157600080fd5b9250929050565b600080600080600060808688031215610f4057600080fd5b853560038110610f4f57600080fd5b9450602086013567ffffffffffffffff811115610f6b57600080fd5b610f7788828901610edf565b9699909850959660408101359660609091013595509350505050565b600060208284031215610fa557600080fd5b8135610d6281610d69565b60008060208385031215610fc357600080fd5b823567ffffffffffffffff811115610fda57600080fd5b610fe685828601610edf565b90969095509350505050565b600065ffffffffffff808716835280861660208401525060606040830152826060830152828460808401376000608084840101526080601f19601f850116830101905095945050505050565b6000808335601e1984360301811261105557600080fd5b83018035915067ffffffffffffffff82111561107057600080fd5b602001915036819003821315610f2157600080fd5b8183823760009101908152919050565b600080858511156110a557600080fd5b838611156110b257600080fd5b5050820193919092039150565b80356020831015610b2557600019602084900360031b1b1692915050565b600080604083850312156110f057600080fd5b6110f983610e66565b915061110760208401610e66565b90509250929050565b80820180821115610b2557634e487b7160e01b600052601160045260246000fd5b60006020828403121561114357600080fd5b5051919050565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220b3dee56d54c33e3d801b74ef60dfdbf85dc7548b852fda6106bd54223fbdaf2e64736f6c63430008170033'; diff --git a/backend/src/models/coingecko.ts b/backend/src/models/coingecko.ts new file mode 100644 index 0000000..b731261 --- /dev/null +++ b/backend/src/models/coingecko.ts @@ -0,0 +1,78 @@ +import { Sequelize, DataTypes, Model } from 'sequelize'; + +export class CoingeckoTokens extends Model { + public id!: number; // Note that the `null assertion` `!` is required in strict mode. + public token!: string; + public address!: string; + public chainId!: number; + public coinId!: string; + public decimals!: string; + public readonly createdAt!: Date; + public readonly updatedAt!: Date; +} + +const initializeCoingeckoModel = (sequelize: Sequelize, schema: string) => { + CoingeckoTokens.init({ + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false, + field: 'ID' + }, + token: { + type: DataTypes.TEXT, + allowNull: false, + primaryKey: true, + field: 'TOKEN' + }, + address: { + type: DataTypes.STRING, + allowNull: false, + field: 'ADDRESS' + }, + chainId: { + type: DataTypes.BIGINT, + allowNull: false, + field: 'CHAIN_ID', + get() { + const value = this.getDataValue('chainId'); + return +value; + } + }, + coinId: { + type: DataTypes.STRING, + allowNull: false, + primaryKey: true, + field: 'COIN_ID' + }, + decimals: { + type: DataTypes.INTEGER, + allowNull: false, + field: 'DECIMALS' + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW, + field: 'CREATED_AT' + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: DataTypes.NOW, + field: 'UPDATED_AT' + }, + }, { + sequelize, + tableName: 'coingecko_tokens', + modelName: 'CoingeckoTokens', + timestamps: true, + createdAt: 'createdAt', + updatedAt: 'updatedAt', + freezeTableName: true, + schema: schema, + }); +}; + +export { initializeCoingeckoModel }; \ No newline at end of file diff --git a/backend/src/paymaster/index.ts b/backend/src/paymaster/index.ts index 3632461..975b6ef 100644 --- a/backend/src/paymaster/index.ts +++ b/backend/src/paymaster/index.ts @@ -16,6 +16,12 @@ import ERC20Abi from '../abi/ERC20Abi.js'; import EtherspotChainlinkOracleAbi from '../abi/EtherspotChainlinkOracleAbi.js'; import { TokenDecimalsAndSymbol, UnaccountedCost } from '../constants/MultitokenPaymaster.js'; import { NativeOracleDecimals } from '../constants/ChainlinkOracles.js'; +import { CoingeckoTokensRepository } from '../repository/coingecko-token-repository.js'; +import { CoingeckoService } from '../services/coingecko.js'; +import { Sequelize } from 'sequelize'; +import { abi as verifyingPaymasterAbi, byteCode as verifyingPaymasterByteCode } from '../abi/VerifyingPaymasterAbi.js'; +import { abi as verifyingPaymasterV2Abi, byteCode as verifyingPaymasterV2ByteCode } from '../abi/VerifyingPaymasterAbiV2.js'; + const ttl = parseInt(process.env.CACHE_TTL || "600000"); const nativePriceCacheTtl = parseInt(process.env.NATIVE_PRICE_CACHE_TTL || "60000"); @@ -36,8 +42,10 @@ interface NativeCurrencyPricyCache { expiry: number; } -import { abi as verifyingPaymasterAbi, byteCode as verifyingPaymasterByteCode } from '../abi/VerifyingPaymasterAbi.js'; -import { abi as verifyingPaymasterV2Abi, byteCode as verifyingPaymasterV2ByteCode } from '../abi/VerifyingPaymasterAbiV2.js'; +interface CoingeckoPriceCache { + data: any; + expiry: number; +} export class Paymaster { feeMarkUp: BigNumber; @@ -46,13 +54,17 @@ export class Paymaster { EP7_TOKEN_PGL: string; priceAndMetadata: Map = new Map(); nativeCurrencyPrice: Map = new Map(); + coingeckoPrice: Map = new Map(); + coingeckoService: CoingeckoService = new CoingeckoService(); + sequelize: Sequelize; - constructor(feeMarkUp: string, multiTokenMarkUp: string, ep7TokenVGL: string, ep7TokenPGL: string) { + constructor(feeMarkUp: string, multiTokenMarkUp: string, ep7TokenVGL: string, ep7TokenPGL: string, sequelize: Sequelize) { this.feeMarkUp = ethers.utils.parseUnits(feeMarkUp, 'gwei'); if (isNaN(Number(multiTokenMarkUp))) this.multiTokenMarkUp = 1150000 // 15% more of the actual cost. Can be anything between 1e6 to 2e6 else this.multiTokenMarkUp = Number(multiTokenMarkUp); this.EP7_TOKEN_PGL = ep7TokenPGL; this.EP7_TOKEN_VGL = ep7TokenVGL; + this.sequelize = sequelize; } packUint(high128: BigNumberish, low128: BigNumberish): string { @@ -569,14 +581,18 @@ export class Paymaster { const promises = []; for(let i=0;i { + const cacheKey = `${chainId}-${tokenAddress}`; + const cache = this.coingeckoPrice.get(cacheKey); + + const nativePrice = ethers.utils.formatUnits(ETHUSDPrice, ETHUSDPriceDecimal); + let ethPrice; + + if(cache && cache.expiry > Date.now()) { + const data = cache.data; + ethPrice = ethers.utils.parseUnits((Number(nativePrice)/data.price).toFixed(data.decimals), data.decimals) + return { + ethPrice, + ...data + } + } + + const coingeckoRepo = new CoingeckoTokensRepository(this.sequelize); + const records = await coingeckoRepo.findAll(); + const tokenIds = records.map((record: { coinId: any; }) => record.coinId); + + const data = await this.coingeckoService.fetchPriceByCoinID(tokenIds); + const tokenPrices: any = []; + records.map(record => { + tokenPrices[ethers.utils.getAddress(record.address)] = { price: Number(data[record.coinId].usd).toFixed(5), decimals: record.decimals, gasToken: tokenAddress, symbol: record.token } + }) + const tokenData = tokenPrices[tokenAddress]; + ethPrice = ethers.utils.parseUnits((Number(nativePrice)/tokenData.price).toFixed(tokenData.decimals), tokenData.decimals) + this.setPricesFromCoingecko(tokenPrices); + + return { + ethPrice, + ...tokenData + } + } + + async setPricesFromCoingecko(coingeckoPrices: any[]) { + for(const tokenAddress in coingeckoPrices) { + const chainId = coingeckoPrices[tokenAddress].chainId; + const cacheKey = `${chainId}-${ethers.utils.getAddress(tokenAddress)}`; + this.coingeckoPrice.set(cacheKey, {data: coingeckoPrices[tokenAddress], expiry: Date.now() + ttl}); + } + } } diff --git a/backend/src/plugins/sequelizePlugin.ts b/backend/src/plugins/sequelizePlugin.ts index 7563416..9b2adc3 100644 --- a/backend/src/plugins/sequelizePlugin.ts +++ b/backend/src/plugins/sequelizePlugin.ts @@ -12,6 +12,8 @@ import { WhitelistRepository } from "../repository/whitelist-repository.js"; import { initializeArkaWhitelistModel } from "../models/whitelist.js"; import { ContractWhitelistRepository } from "../repository/contract-whitelist-repository.js"; import { initializeContractWhitelistModel } from "../models/contract-whitelist.js"; +import { CoingeckoTokensRepository } from "../repository/coingecko-token-repository.js"; +import { initializeCoingeckoModel } from "../models/coingecko.js"; const pg = await import('pg'); const Client = pg.default.Client; @@ -55,6 +57,7 @@ const sequelizePlugin: FastifyPluginAsync = async (server) => { initializeSponsorshipPolicyModel(sequelize, server.config.DATABASE_SCHEMA_NAME); initializeArkaWhitelistModel(sequelize, server.config.DATABASE_SCHEMA_NAME); initializeContractWhitelistModel(sequelize, server.config.DATABASE_SCHEMA_NAME); + initializeCoingeckoModel(sequelize, server.config.DATABASE_SCHEMA_NAME); server.log.info('Initialized SponsorshipPolicy model...'); server.log.info('Initialized all models...'); @@ -71,6 +74,8 @@ const sequelizePlugin: FastifyPluginAsync = async (server) => { server.decorate('whitelistRepository', whitelistRepository); const contractWhitelistRepository: ContractWhitelistRepository = new ContractWhitelistRepository(sequelize); server.decorate('contractWhitelistRepository', contractWhitelistRepository); + const coingeckoRepo: CoingeckoTokensRepository = new CoingeckoTokensRepository(sequelize); + server.decorate('coingeckoRepo', coingeckoRepo); server.log.info('decorated fastify server with models...'); diff --git a/backend/src/repository/coingecko-token-repository.ts b/backend/src/repository/coingecko-token-repository.ts new file mode 100644 index 0000000..8883634 --- /dev/null +++ b/backend/src/repository/coingecko-token-repository.ts @@ -0,0 +1,40 @@ +import { Sequelize } from 'sequelize'; +import { CoingeckoTokens } from 'models/coingecko'; + +export class CoingeckoTokensRepository { + private sequelize: Sequelize; + + constructor(sequelize: Sequelize) { + this.sequelize = sequelize; + } + + async findAll(): Promise { + const result = await this.sequelize.models.CoingeckoTokens.findAll(); + return result.map(id => id.get() as CoingeckoTokens); + } + + async findOneByChainIdAndTokenAddress(chainId: number, tokenAddress: string): Promise { + const result = await this.sequelize.models.CoingeckoTokens.findOne({ + where: { + chainId: chainId, address: tokenAddress + } + }) as CoingeckoTokens; + + if (!result) { + return null; + } + + return result.get() as CoingeckoTokens; + } + + async findOneById(id: number): Promise { + const coingeckoTokens = await this.sequelize.models.CoingeckoTokens.findOne({ where: { id: id } }) as CoingeckoTokens; + if (!coingeckoTokens) { + return null; + } + + const dataValues = coingeckoTokens.get(); + return dataValues as CoingeckoTokens; + } + +} \ No newline at end of file diff --git a/backend/src/routes/deposit-route.ts b/backend/src/routes/deposit-route.ts index f987ca5..d83849c 100644 --- a/backend/src/routes/deposit-route.ts +++ b/backend/src/routes/deposit-route.ts @@ -11,7 +11,7 @@ import { printRequest, getNetworkConfig } from "../utils/common.js"; import { APIKey } from "../models/api-key.js"; const depositRoutes: FastifyPluginAsync = async (server) => { - const paymaster = new Paymaster(server.config.FEE_MARKUP, server.config.MULTI_TOKEN_MARKUP, server.config.EP7_TOKEN_VGL, server.config.EP7_TOKEN_PGL); + const paymaster = new Paymaster(server.config.FEE_MARKUP, server.config.MULTI_TOKEN_MARKUP, server.config.EP7_TOKEN_VGL, server.config.EP7_TOKEN_PGL, server.sequelize); const SUPPORTED_ENTRYPOINTS = { EPV_06: server.config.EPV_06, @@ -49,14 +49,13 @@ const depositRoutes: FastifyPluginAsync = async (server) => { const body: any = request.body; const query: any = request.query; const amount = body.params[0]; - const ep = query['useEp'] ?? body.params[1] ?? false; - const chainId = query['chainId'] ?? body.params[2]; - const api_key = query['apiKey'] ?? body.params[3]; + const useVp = query['useVp'] ?? false; + const chainId = query['chainId'] ?? body.params[1]; + const api_key = query['apiKey'] ?? body.params[2]; if (!api_key || typeof(api_key) !== "string") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) let privateKey = ''; - let supportedNetworks; let bundlerApiKey = api_key; const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) @@ -68,17 +67,13 @@ const depositRoutes: 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['BUNDLER_API_KEY']) { - bundlerApiKey = secrets['BUNDLER_API_KEY']; - } privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - supportedNetworks = apiKeyEntity.supportedNetworks; - if (apiKeyEntity.bundlerApiKey) { - bundlerApiKey = apiKeyEntity.bundlerApiKey; - } + } + const supportedNetworks = apiKeyEntity.supportedNetworks; + if (apiKeyEntity.bundlerApiKey) { + bundlerApiKey = apiKeyEntity.bundlerApiKey; } if ( isNaN(amount) || @@ -95,7 +90,7 @@ const depositRoutes: FastifyPluginAsync = async (server) => { let bundlerUrl = networkConfig.bundler; if (networkConfig.bundler.includes('etherspot.io')) bundlerUrl = `${networkConfig.bundler}?api-key=${bundlerApiKey}`; - if(ep) { + if(!useVp) { return await paymaster.deposit(amount, networkConfig.contracts.etherspotPaymasterAddress, bundlerUrl, privateKey, chainId, true, server.log); } const vpAddr = apiKeyEntity.verifyingPaymasters ? @@ -125,10 +120,10 @@ const depositRoutes: FastifyPluginAsync = async (server) => { const amount = body.params[0]; const chainId = query['chainId'] ?? body.params[1]; const api_key = query['apiKey'] ?? body.params[2]; + const useVp = query['useVp'] ?? false; if (!api_key || typeof(api_key) !== "string") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) let privateKey = ''; - let supportedNetworks; let bundlerApiKey = api_key; const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) @@ -140,17 +135,13 @@ const depositRoutes: 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['BUNDLER_API_KEY']) { - bundlerApiKey = secrets['BUNDLER_API_KEY']; - } privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - supportedNetworks = apiKeyEntity.supportedNetworks; - if (apiKeyEntity.bundlerApiKey) { - bundlerApiKey = apiKeyEntity.bundlerApiKey; - } + } + const supportedNetworks = apiKeyEntity.supportedNetworks; + if (apiKeyEntity.bundlerApiKey) { + bundlerApiKey = apiKeyEntity.bundlerApiKey; } if ( isNaN(amount) || @@ -166,7 +157,17 @@ const depositRoutes: FastifyPluginAsync = async (server) => { if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); let bundlerUrl = networkConfig.bundler; if (networkConfig.bundler.includes('etherspot.io')) bundlerUrl = `${networkConfig.bundler}?api-key=${bundlerApiKey}`; - return await paymaster.deposit(amount, networkConfig.contracts.etherspotPaymasterAddress, bundlerUrl, privateKey, chainId, false, server.log); + + if(!useVp) { + return await paymaster.deposit(amount, networkConfig.contracts.etherspotPaymasterAddress, bundlerUrl, privateKey, chainId, false, server.log); + } + const vpAddr = apiKeyEntity.verifyingPaymastersV2 ? + JSON.parse(apiKeyEntity.verifyingPaymastersV2)[chainId] : + undefined; + if(!vpAddr) { + return reply.code(ReturnCode.FAILURE).send({error: ErrorMessage.VP_NOT_DEPLOYED}) + } + return await paymaster.deposit(amount, vpAddr, bundlerUrl, privateKey, chainId, false, server.log); } catch (err: any) { request.log.error(err); if (err.name == "ResourceNotFoundException") diff --git a/backend/src/routes/metadata-routes.ts b/backend/src/routes/metadata-routes.ts index 8071e23..d99a097 100644 --- a/backend/src/routes/metadata-routes.ts +++ b/backend/src/routes/metadata-routes.ts @@ -42,7 +42,6 @@ const metadataRoutes: FastifyPluginAsync = async (server) => { let customPaymasters = []; let multiTokenPaymasters = []; let privateKey = ''; - let supportedNetworks; let sponsorName = '', sponsorImage = ''; let bundlerApiKey = api_key; const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); @@ -61,38 +60,24 @@ const metadataRoutes: FastifyPluginAsync = async (server) => { 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()); - } - if (secrets['MULTI_TOKEN_PAYMASTERS']) { - const buffer = Buffer.from(secrets['MULTI_TOKEN_PAYMASTERS'], 'base64'); - multiTokenPaymasters = JSON.parse(buffer.toString()); - } - if (secrets['BUNDLER_API_KEY']) { - bundlerApiKey = secrets['BUNDLER_API_KEY']; - } - sponsorName = secrets['SPONSOR_NAME']; - sponsorImage = secrets['LOGO_URL']; privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - if (apiKeyEntity.erc20Paymasters) { - const buffer = Buffer.from(apiKeyEntity.erc20Paymasters, 'base64'); - customPaymasters = JSON.parse(buffer.toString()); - } - if (apiKeyEntity.multiTokenPaymasters) { - const buffer = Buffer.from(apiKeyEntity.multiTokenPaymasters, 'base64'); - multiTokenPaymasters = JSON.parse(buffer.toString()); - } - if (apiKeyEntity.bundlerApiKey) { - bundlerApiKey = apiKeyEntity.bundlerApiKey; - } - sponsorName = apiKeyEntity.sponsorName ? apiKeyEntity.sponsorName : ""; - sponsorImage = apiKeyEntity.logoUrl ? apiKeyEntity.logoUrl : ""; privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - supportedNetworks = apiKeyEntity.supportedNetworks; } + if (apiKeyEntity.erc20Paymasters) { + const buffer = Buffer.from(apiKeyEntity.erc20Paymasters, 'base64'); + customPaymasters = JSON.parse(buffer.toString()); + } + if (apiKeyEntity.multiTokenPaymasters) { + const buffer = Buffer.from(apiKeyEntity.multiTokenPaymasters, 'base64'); + multiTokenPaymasters = JSON.parse(buffer.toString()); + } + if (apiKeyEntity.bundlerApiKey) { + bundlerApiKey = apiKeyEntity.bundlerApiKey; + } + sponsorName = apiKeyEntity.sponsorName ? apiKeyEntity.sponsorName : ""; + sponsorImage = apiKeyEntity.logoUrl ? apiKeyEntity.logoUrl : ""; + const supportedNetworks = apiKeyEntity.supportedNetworks; if (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); } @@ -115,7 +100,111 @@ const metadataRoutes: FastifyPluginAsync = async (server) => { const vpContract = new Contract(verifyingPaymaster, verifyingPaymasterAbi ,provider); verifyingPaymasterDeposit = await vpContract.getDeposit(); } + const chainsSupported: { chainId: number, entryPoint: string }[] = []; + if (supportedNetworks) { + const buffer = Buffer.from(supportedNetworks, 'base64'); + const SUPPORTED_NETWORKS = JSON.parse(buffer.toString()) + SUPPORTED_NETWORKS.map((element: { chainId: number, entryPoint: string }) => { + chainsSupported.push({ chainId: element.chainId, entryPoint: element.entryPoint }); + }) + } else { + SupportedNetworks.map(element => { + chainsSupported.push({ chainId: element.chainId, entryPoint: element.entryPoint }); + }) + } + const tokenPaymasterAddresses = { + ...PAYMASTER_ADDRESS, + ...customPaymasters, + } + return reply.code(ReturnCode.SUCCESS).send({ + sponsorAddress: sponsorAddress, + sponsorWalletBalance: sponsorWalletBalance, + sponsorBalance: sponsorBalance, + chainsSupported: chainsSupported, + tokenPaymasters: tokenPaymasterAddresses, + multiTokenPaymasters, + sponsorDetails: { name: sponsorName, icon: sponsorImage }, + verifyingPaymaster: { address: verifyingPaymaster, deposit: verifyingPaymasterDeposit }, + verifyingPaymasters: apiKeyEntity.verifyingPaymasters ? JSON.parse(apiKeyEntity.verifyingPaymasters) : undefined, + verifyingPaymastersV2: apiKeyEntity.verifyingPaymastersV2 ? JSON.parse(apiKeyEntity.verifyingPaymastersV2) : undefined, + }) + } catch (err: any) { + 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.FAILED_TO_PROCESS }); + } + }) + server.get('/metadata/v2', async function (request, reply) { + try { + printRequest('/metadata/v2', request, server.log); + const query: any = request.query; + const chainId = query['chainId'] ?? 1; + const api_key = query['apiKey']; + if (!api_key || typeof(api_key) !== "string") + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if (!chainId) + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }) + let customPaymasters = []; + let multiTokenPaymasters = []; + let privateKey = ''; + let sponsorName = '', sponsorImage = ''; + let bundlerApiKey = api_key; + const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); + if (!apiKeyEntity) { + server.log.info("Invalid Api Key provided") + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + } + if (!unsafeMode) { + const AWSresponse = await client.send( + new GetSecretValueCommand({ + SecretId: prefixSecretId + api_key, + }) + ); + const secrets = JSON.parse(AWSresponse.SecretString ?? '{}'); + if (!secrets['PRIVATE_KEY']) { + server.log.info("Invalid Api Key provided") + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + } + } else { + privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); + } + if (apiKeyEntity.erc20Paymasters) { + const buffer = Buffer.from(apiKeyEntity.erc20Paymasters, 'base64'); + customPaymasters = JSON.parse(buffer.toString()); + } + if (apiKeyEntity.multiTokenPaymasters) { + const buffer = Buffer.from(apiKeyEntity.multiTokenPaymasters, 'base64'); + multiTokenPaymasters = JSON.parse(buffer.toString()); + } + if (apiKeyEntity.bundlerApiKey) { + bundlerApiKey = apiKeyEntity.bundlerApiKey; + } + sponsorName = apiKeyEntity.sponsorName ? apiKeyEntity.sponsorName : ""; + sponsorImage = apiKeyEntity.logoUrl ? apiKeyEntity.logoUrl : ""; + const supportedNetworks = apiKeyEntity.supportedNetworks; + if (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) { + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); + } + const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_07); + if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); + let bundlerUrl = networkConfig.bundler; + if (bundlerUrl.includes('etherspot.io')) bundlerUrl = `${networkConfig.bundler}?api-key=${bundlerApiKey}`; + const provider = new providers.JsonRpcProvider(bundlerUrl); + const signer = new Wallet(privateKey, provider) + const sponsorWalletBalance = await signer.getBalance(); + const sponsorAddress = await signer.getAddress(); + + //get native balance of the sponsor in the EtherSpotPaymaster-contract + const paymasterContract = new Contract(networkConfig.contracts.etherspotPaymasterAddress, EtherspotAbi.default, provider); + const sponsorBalance = await paymasterContract.getDeposit(); + const verifyingPaymaster = apiKeyEntity.verifyingPaymastersV2 ? JSON.parse(apiKeyEntity.verifyingPaymastersV2)[chainId] : undefined; + let verifyingPaymasterDeposit; + if (verifyingPaymaster) { + const vpContract = new Contract(verifyingPaymaster, verifyingPaymasterAbi ,provider); + verifyingPaymasterDeposit = await vpContract.getDeposit(); + } const chainsSupported: { chainId: number, entryPoint: string }[] = []; if (supportedNetworks) { const buffer = Buffer.from(supportedNetworks, 'base64'); diff --git a/backend/src/routes/paymaster-routes.ts b/backend/src/routes/paymaster-routes.ts index 97f426a..3acd92e 100644 --- a/backend/src/routes/paymaster-routes.ts +++ b/backend/src/routes/paymaster-routes.ts @@ -50,6 +50,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server, let epVersion: EPVersions = DEFAULT_EP_VERSION; let tokens_list: string[] = []; let sponsorDetails = false, estimate = true, tokenQuotes = false; + const useVp = query['useVp'] ?? false; if (body.method) { switch (body.method) { @@ -94,10 +95,6 @@ const paymasterRoutes: FastifyPluginAsync = async (server, let multiTokenPaymasters = []; let multiTokenOracles = []; let privateKey = ''; - let supportedNetworks; - let noOfTxns; - let txnMode; - let indexerEndpoint; let sponsorName = '', sponsorImage = ''; let contractWhitelistMode = false; let bundlerApiKey = api_key; @@ -120,75 +117,43 @@ const paymasterRoutes: FastifyPluginAsync = async (server, 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()); - } - if (secrets['ERC20_PAYMASTERS_V2']) { - const buffer = Buffer.from(secrets['ERC20_PAYMASTERS_V2'], 'base64'); - customPaymastersV2 = JSON.parse(buffer.toString()); - } - if (secrets['MULTI_TOKEN_PAYMASTERS']) { - const buffer = Buffer.from(secrets['MULTI_TOKEN_PAYMASTERS'], 'base64'); - multiTokenPaymasters = JSON.parse(buffer.toString()); - } - if (secrets['MULTI_TOKEN_ORACLES']) { - const buffer = Buffer.from(secrets['MULTI_TOKEN_ORACLES'], 'base64'); - multiTokenOracles = JSON.parse(buffer.toString()); - } - if (secrets['BUNDLER_API_KEY']) { - bundlerApiKey = secrets['BUNDLER_API_KEY']; - } - sponsorName = secrets['SPONSOR_NAME']; - sponsorImage = secrets['LOGO_URL']; 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; - contractWhitelistMode = (secrets['CONTRACT_WHITELIST_MODE'] ?? false) == 'true' ? true : false; } else { + privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); + } - //validate api_key - if (!api_key) { - server.log.error("Invalid Api Key provided") - return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) - } - - if (apiKeyEntity.erc20Paymasters) { - const buffer = Buffer.from(apiKeyEntity.erc20Paymasters, 'base64'); - customPaymasters = JSON.parse(buffer.toString()); - } - - if (apiKeyEntity.erc20PaymastersV2) { - const buffer = Buffer.from(apiKeyEntity.erc20PaymastersV2, 'base64'); - customPaymastersV2 = JSON.parse(buffer.toString()); - } + if (apiKeyEntity.erc20Paymasters) { + const buffer = Buffer.from(apiKeyEntity.erc20Paymasters, 'base64'); + customPaymasters = JSON.parse(buffer.toString()); + } - if (apiKeyEntity.multiTokenPaymasters) { - const buffer = Buffer.from(apiKeyEntity.multiTokenPaymasters, 'base64'); - multiTokenPaymasters = JSON.parse(buffer.toString()); - } + if (apiKeyEntity.erc20PaymastersV2) { + const buffer = Buffer.from(apiKeyEntity.erc20PaymastersV2, 'base64'); + customPaymastersV2 = JSON.parse(buffer.toString()); + } - if (apiKeyEntity.multiTokenOracles) { - const buffer = Buffer.from(apiKeyEntity.multiTokenOracles, 'base64'); - multiTokenOracles = JSON.parse(buffer.toString()); - } + if (apiKeyEntity.multiTokenPaymasters) { + const buffer = Buffer.from(apiKeyEntity.multiTokenPaymasters, 'base64'); + multiTokenPaymasters = JSON.parse(buffer.toString()); + } - if (apiKeyEntity.bundlerApiKey) { - bundlerApiKey = apiKeyEntity.bundlerApiKey; - } + if (apiKeyEntity.multiTokenOracles) { + const buffer = Buffer.from(apiKeyEntity.multiTokenOracles, 'base64'); + multiTokenOracles = JSON.parse(buffer.toString()); + } - sponsorName = apiKeyEntity.sponsorName ? apiKeyEntity.sponsorName : ''; - sponsorImage = apiKeyEntity.logoUrl ? apiKeyEntity.logoUrl : ''; - privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - supportedNetworks = apiKeyEntity.supportedNetworks; - noOfTxns = apiKeyEntity.noOfTransactionsInAMonth; - txnMode = apiKeyEntity.transactionLimit; - indexerEndpoint = apiKeyEntity.indexerEndpoint ?? process.env.DEFAULT_INDEXER_ENDPOINT; - contractWhitelistMode = apiKeyEntity.contractWhitelistMode ?? false; + if (apiKeyEntity.bundlerApiKey) { + bundlerApiKey = apiKeyEntity.bundlerApiKey; } + sponsorName = apiKeyEntity.sponsorName ? apiKeyEntity.sponsorName : ''; + sponsorImage = apiKeyEntity.logoUrl ? apiKeyEntity.logoUrl : ''; + const supportedNetworks = apiKeyEntity.supportedNetworks; + const noOfTxns = apiKeyEntity.noOfTransactionsInAMonth ?? 0; + const txnMode = apiKeyEntity.transactionLimit; + const indexerEndpoint = apiKeyEntity.indexerEndpoint ?? process.env.DEFAULT_INDEXER_ENDPOINT; + contractWhitelistMode = apiKeyEntity.contractWhitelistMode ?? false; + if ( !userOp || !entryPoint || @@ -227,11 +192,16 @@ const paymasterRoutes: FastifyPluginAsync = async (server, if (mode.toLowerCase() == 'multitoken' && !(multiTokenPaymasters[chainId] && multiTokenPaymasters[chainId][gasToken]) && - !(multiTokenOracles[chainId] && multiTokenOracles[chainId][gasToken]) + !(multiTokenOracles[chainId] && multiTokenOracles[chainId][gasToken]) && + !paymaster.coingeckoPrice.get(`${chainId}-${gasToken}`) ) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK_TOKEN }) + if(useVp && mode.toLowerCase() === 'sponsor') { + mode = 'vps'; + } + switch (mode.toLowerCase()) { - case 'eps': { + case 'sponsor': { const date = new Date(); const provider = new providers.JsonRpcProvider(bundlerUrl); const signer = new Wallet(privateKey, provider) @@ -239,19 +209,15 @@ const paymasterRoutes: FastifyPluginAsync = async (server, // get chainid from provider const chainId = await provider.getNetwork(); - // get wallet_address from api_key - const apiKeyData = await server.apiKeyRepository.findOneByApiKey(api_key); - if (!apiKeyData) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.API_KEY_NOT_CONFIGURED_IN_DATABASE }); - // get sponsorshipPolicy for the user from walletAddress and entrypoint version - const sponsorshipPolicy: SponsorshipPolicy | null = await server.sponsorshipPolicyRepository.findOneByWalletAddressAndSupportedEPVersion(apiKeyData?.walletAddress, getEPVersion(epVersion)); + const sponsorshipPolicy: SponsorshipPolicy | null = await server.sponsorshipPolicyRepository.findOneByWalletAddressAndSupportedEPVersion(apiKeyEntity.walletAddress, getEPVersion(epVersion)); if (!sponsorshipPolicy) { - const errorMessage: string = generateErrorMessage(ErrorMessage.ACTIVE_SPONSORSHIP_POLICY_NOT_FOUND, { walletAddress: apiKeyData?.walletAddress, epVersion: epVersion, chainId: chainId.chainId }); + const errorMessage: string = generateErrorMessage(ErrorMessage.ACTIVE_SPONSORSHIP_POLICY_NOT_FOUND, { walletAddress: apiKeyEntity.walletAddress, epVersion: epVersion, chainId: chainId.chainId }); return reply.code(ReturnCode.FAILURE).send({ error: errorMessage }); } if (!Object.assign(new SponsorshipPolicy(), sponsorshipPolicy).isApplicable) { - const errorMessage: string = generateErrorMessage(ErrorMessage.NO_ACTIVE_SPONSORSHIP_POLICY_FOR_CURRENT_TIME, { walletAddress: apiKeyData?.walletAddress, epVersion: epVersion, chainId: chainId.chainId }); + const errorMessage: string = generateErrorMessage(ErrorMessage.NO_ACTIVE_SPONSORSHIP_POLICY_FOR_CURRENT_TIME, { walletAddress: apiKeyEntity.walletAddress, epVersion: epVersion, chainId: chainId.chainId }); return reply.code(ReturnCode.FAILURE).send({ error: errorMessage }); } @@ -261,7 +227,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server, if (txnMode) { const signerAddress = await signer.getAddress(); - const IndexerData = await getIndexerData(signerAddress, userOp.sender, date.getMonth(), date.getFullYear(), noOfTxns, indexerEndpoint); + 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; @@ -343,7 +309,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server, result = await paymaster.signMultiTokenPaymaster(userOp, str, str1, entryPoint, multiTokenPaymasters[chainId][gasToken], gasToken, multiTokenOracles[chainId][gasToken], bundlerUrl, signer, networkConfig.MultiTokenPaymasterOracleUsed, NativeOracles[chainId], chainId, server.log); break; } - case 'sponsor': { + case 'vps': { const date = new Date(); const provider = new providers.JsonRpcProvider(bundlerUrl); const signer = new Wallet(privateKey, provider); @@ -372,7 +338,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server, if (txnMode) { const signerAddress = await signer.getAddress(); - const IndexerData = await getIndexerData(signerAddress, userOp.sender, date.getMonth(), date.getFullYear(), noOfTxns, indexerEndpoint); + 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; diff --git a/backend/src/routes/pimlico-routes.ts b/backend/src/routes/pimlico-routes.ts index 1ec4644..a788bac 100644 --- a/backend/src/routes/pimlico-routes.ts +++ b/backend/src/routes/pimlico-routes.ts @@ -49,9 +49,11 @@ const pimlicoRoutes: FastifyPluginAsync = async (server) => { const api_key = query['apiKey'] ?? body.params[3]; if (!api_key || typeof(api_key) !== "string") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + const apiKeyData = await server.apiKeyRepository.findOneByApiKey(api_key); + if (!apiKeyData) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + const apiKeyEntity: APIKey = apiKeyData as APIKey; let customPaymasters = []; let privateKey = ''; - let supportedNetworks; if (!unsafeMode) { const AWSresponse = await client.send( new GetSecretValueCommand({ @@ -60,25 +62,15 @@ const pimlicoRoutes: 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['ERC20_PAYMASTERS']) { - const buffer = Buffer.from(secrets['ERC20_PAYMASTERS'], 'base64'); - customPaymasters = JSON.parse(buffer.toString()); - } privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - const result = await server.apiKeyRepository.findOneByApiKey(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(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - - supportedNetworks = apiKeyEntity.supportedNetworks; } + if (apiKeyEntity.erc20Paymasters) { + const buffer = Buffer.from(apiKeyEntity.erc20Paymasters, 'base64'); + customPaymasters = JSON.parse(buffer.toString()); + } + const supportedNetworks = apiKeyEntity.supportedNetworks; if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if ( !entryPoint || diff --git a/backend/src/routes/whitelist-routes.ts b/backend/src/routes/whitelist-routes.ts index 4181112..47fd3cd 100644 --- a/backend/src/routes/whitelist-routes.ts +++ b/backend/src/routes/whitelist-routes.ts @@ -12,7 +12,7 @@ import { APIKey } from "../models/api-key.js"; import { ContractWhitelistDto } from "../types/contractWhitelist-dto.js"; const whitelistRoutes: FastifyPluginAsync = async (server) => { - const paymaster = new Paymaster(server.config.FEE_MARKUP, server.config.MULTI_TOKEN_MARKUP, server.config.EP7_TOKEN_VGL, server.config.EP7_TOKEN_PGL); + const paymaster = new Paymaster(server.config.FEE_MARKUP, server.config.MULTI_TOKEN_MARKUP, server.config.EP7_TOKEN_VGL, server.config.EP7_TOKEN_PGL, server.sequelize); const SUPPORTED_ENTRYPOINTS = { EPV_06: server.config.EPV_06, @@ -35,15 +35,20 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { printRequest("/whitelist", request, server.log); const body: any = request.body; const query: any = request.query; - const address = body.params[0]; - const policyId = body.params[1]; - const useEp = query['useEp'] ?? body.params[2] ?? false; - const api_key = query['apiKey'] ?? body.params[3]; - const chainId = query['chainId'] ?? body.params[4]; + let address, policyId, api_key, chainId; + const useVp = query['useVp'] ?? false; + if(!useVp) { + address = body.params[0]; + chainId = query['chainId'] ?? body.params[1]; + api_key = query['apiKey'] ?? body.params[2]; + } else { + address = body.params[0]; + policyId = body.params[1]; + api_key = query['apiKey'] ?? body.params[2]; + } if (!api_key || typeof(api_key) !== "string") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) let privateKey = ''; - let supportedNetworks; let bundlerApiKey = api_key; const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) @@ -55,18 +60,14 @@ const whitelistRoutes: 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['BUNDLER_API_KEY']) { - bundlerApiKey = secrets['BUNDLER_API_KEY']; - } privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - if (apiKeyEntity.bundlerApiKey) { - bundlerApiKey = apiKeyEntity.bundlerApiKey; - } privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - supportedNetworks = apiKeyEntity.supportedNetworks; } + if (apiKeyEntity.bundlerApiKey) { + bundlerApiKey = apiKeyEntity.bundlerApiKey; + } + const supportedNetworks = apiKeyEntity.supportedNetworks; if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if ( !Array.isArray(address) || @@ -76,7 +77,7 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { } const validAddresses = address.every(ethers.utils.isAddress); if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_ADDRESS_PASSSED }); - if(useEp) { + if(!useVp) { if(!chainId || isNaN(chainId)) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); } @@ -138,15 +139,21 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { printRequest("/removeWhitelist", request, server.log); const body: any = request.body; const query: any = request.query; - const address = body.params[0]; - const policyId = body.params[1]; - const chainId = query['chainId'] ?? body.params[2]; - const api_key = query['apiKey'] ?? body.params[3]; - const useEp = query['useEp'] ?? body.params[4] ?? false; + let address, policyId, api_key, chainId; + const useVp = query['useVp'] ?? false; + if(!useVp) { + address = body.params[0]; + chainId = query['chainId'] ?? body.params[1]; + api_key = query['apiKey'] ?? body.params[2]; + } else { + address = body.params[0]; + policyId = body.params[1]; + chainId = query['chainId'] ?? body.params[2]; + api_key = query['apiKey'] ?? body.params[3]; + } if (!api_key || typeof(api_key) !== "string") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) let privateKey = ''; - let supportedNetworks; let bundlerApiKey = api_key; const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) @@ -160,15 +167,17 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { if (!secrets['PRIVATE_KEY']) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if (secrets['BUNDLER_API_KEY']) bundlerApiKey = secrets['BUNDLER_API_KEY']; privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { if (apiKeyEntity.bundlerApiKey) { bundlerApiKey = apiKeyEntity.bundlerApiKey; } privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - supportedNetworks = apiKeyEntity.supportedNetworks; } if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if (apiKeyEntity.bundlerApiKey) { + bundlerApiKey = apiKeyEntity.bundlerApiKey; + } + const supportedNetworks = apiKeyEntity.supportedNetworks; if ( !Array.isArray(address) || address.length > 10 @@ -177,7 +186,7 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { } const validAddresses = address.every(ethers.utils.isAddress); if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_ADDRESS_PASSSED }); - if(useEp) { + if(!useVp) { if(!chainId || isNaN(chainId)) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); } @@ -241,15 +250,21 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { printRequest("/checkWhitelist", request, server.log); const body: any = request.body; const query: any = request.query; - const accountAddress = body.params[0]; - const policyId = body.params[1]; - const chainId = query['chainId'] ?? body.params[2]; - const api_key = query['apiKey'] ?? body.params[3]; - const useEp = query['useEp'] ?? body.params[4]; + let accountAddress, policyId, api_key, chainId; + const useVp = query['useVp'] ?? false; + if(!useVp) { + accountAddress = body.params[0]; + chainId = query['chainId'] ?? body.params[1]; + api_key = query['apiKey'] ?? body.params[2]; + } else { + accountAddress = body.params[0]; + policyId = body.params[1]; + chainId = query['chainId'] ?? body.params[2]; + api_key = query['apiKey'] ?? body.params[3]; + } if (!api_key || typeof(api_key) !== "string") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) let privateKey = ''; - let supportedNetworks; let bundlerApiKey = api_key; const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) @@ -263,12 +278,12 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { if (!secrets['PRIVATE_KEY']) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if (secrets['BUNDLER_API_KEY']) bundlerApiKey = secrets['BUNDLER_API_KEY']; privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { if (apiKeyEntity.bundlerApiKey) bundlerApiKey = apiKeyEntity.bundlerApiKey; privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - supportedNetworks = apiKeyEntity.supportedNetworks; } + if (apiKeyEntity.bundlerApiKey) bundlerApiKey = apiKeyEntity.bundlerApiKey; + const supportedNetworks = apiKeyEntity.supportedNetworks; if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if ( !accountAddress || @@ -281,7 +296,7 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { } const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_06); if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); - if(useEp) { + if(!useVp) { if(!chainId || isNaN(chainId)) { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); } @@ -316,7 +331,220 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { } ); - server.post("/getAllWhitelist", + server.post("/whitelist/v2", + async function (request, reply) { + try { + printRequest("/whitelist/v2", request, server.log); + const body: any = request.body; + const query: any = request.query; + const address = body.params[0]; + const policyId = body.params[1]; + const api_key = query['apiKey'] ?? body.params[2]; + if (!api_key || typeof(api_key) !== "string") + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + let privateKey = ''; + const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); + if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if (!unsafeMode) { + const AWSresponse = await client.send( + new GetSecretValueCommand({ + SecretId: prefixSecretId + api_key, + }) + ); + const secrets = JSON.parse(AWSresponse.SecretString ?? '{}'); + if (!secrets['PRIVATE_KEY']) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + privateKey = secrets['PRIVATE_KEY']; + } else { + privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); + } + if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if ( + !Array.isArray(address) || + address.length > 10 + ) { + 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 }); + } + const validAddresses = address.every(ethers.utils.isAddress); + if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_ADDRESS_PASSSED }); + const signer = new Wallet(privateKey) + if (policyId) { + const policyRecord = await server.sponsorshipPolicyRepository.findOneById(policyId); + if (!policyRecord || (policyRecord?.walletAddress !== signer.address)) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_SPONSORSHIP_POLICY_ID }) + } + const existingWhitelistRecord = await server.whitelistRepository.findOneByApiKeyAndPolicyId(api_key, policyId); + + if (existingWhitelistRecord) { + const toBeAdded: string[] = []; + address.filter(ele => { + if (!existingWhitelistRecord.addresses.includes(ele)) toBeAdded.push(ele); + }); + if (toBeAdded.length < 1) return reply.code(ReturnCode.CONFLICT).send({ error: ErrorMessage.ADDRESS_ALREADY_ADDED }); + const allAddresses = toBeAdded.concat(existingWhitelistRecord.addresses); + existingWhitelistRecord.addresses = allAddresses; + await server.whitelistRepository.updateOneById(existingWhitelistRecord); + } else { + const addWhitelistDto = { + apiKey: api_key, + addresses: address, + policyId: policyId ?? null, + } + await server.whitelistRepository.create(addWhitelistDto); + } + const result = { message: "Successfully whitelisted" } + 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); + } catch (err: any) { + 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.FAILED_TO_PROCESS }) + } + } + ); + + server.post("/removeWhitelist/v2", + async function (request, reply) { + try { + printRequest("/removeWhitelist/v2", request, server.log); + const body: any = request.body; + const query: any = request.query; + const address = body.params[0]; + const policyId = body.params[1]; + const chainId = query['chainId'] ?? body.params[2]; + const api_key = query['apiKey'] ?? body.params[3]; + if (!api_key || typeof(api_key) !== "string") + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + let privateKey = ''; + const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); + if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if (!unsafeMode) { + const AWSresponse = await client.send( + new GetSecretValueCommand({ + SecretId: prefixSecretId + api_key, + }) + ); + const secrets = JSON.parse(AWSresponse.SecretString ?? '{}'); + if (!secrets['PRIVATE_KEY']) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + privateKey = secrets['PRIVATE_KEY']; + } else { + privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); + } + const supportedNetworks = apiKeyEntity.supportedNetworks; + if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if ( + !Array.isArray(address) || + address.length > 10 || + !chainId || + isNaN(chainId) + ) { + 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 }); + } + const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_07); + if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); + const validAddresses = address.every(ethers.utils.isAddress); + if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_ADDRESS_PASSSED }); + const existingWhitelistRecord = await server.whitelistRepository.findOneByApiKeyAndPolicyId(api_key, policyId); + + + if (existingWhitelistRecord) { + const toBeRemoved: string[] = []; + address.filter(ele => { + if (existingWhitelistRecord.addresses.includes(ele)) { + toBeRemoved.push(ele); + existingWhitelistRecord.addresses.splice(existingWhitelistRecord.addresses.indexOf(ele), 1); + } + }); + if (toBeRemoved.length < 1) return reply.code(ReturnCode.CONFLICT).send({ error: ErrorMessage.ADDRESS_NOT_WHITELISTED }); + + if (existingWhitelistRecord.addresses.length < 1) await server.whitelistRepository.deleteById(existingWhitelistRecord.id); + else await server.whitelistRepository.updateOneById(existingWhitelistRecord); + } else { + throw new Error(ErrorMessage.NO_WHITELIST_FOUND); + } + const result = { message: "Successfully removed whitelisted addresses" } + 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); + } catch (err: any) { + 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.FAILED_TO_PROCESS }) + } + } + ); + + server.post("/checkWhitelist/v2", + async function (request, reply) { + try { + printRequest("/checkWhitelist/v2", request, server.log); + const body: any = request.body; + const query: any = request.query; + const accountAddress = body.params[0]; + const policyId = body.params[1]; + const chainId = query['chainId'] ?? body.params[2]; + const api_key = query['apiKey'] ?? body.params[3]; + if (!api_key || typeof(api_key) !== "string") + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + let privateKey = ''; + const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); + if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if (!unsafeMode) { + const AWSresponse = await client.send( + new GetSecretValueCommand({ + SecretId: prefixSecretId + api_key, + }) + ); + const secrets = JSON.parse(AWSresponse.SecretString ?? '{}'); + if (!secrets['PRIVATE_KEY']) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + privateKey = secrets['PRIVATE_KEY']; + } else { + privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); + } + const supportedNetworks = apiKeyEntity.supportedNetworks; + if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if ( + !accountAddress || + !ethers.utils.isAddress(accountAddress) || + !chainId || + isNaN(chainId) + ) { + 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 }); + } + const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_07); + if (!networkConfig) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); + const existingWhitelistRecord = await server.whitelistRepository.findOneByApiKeyAndPolicyId(api_key, policyId); + + if (!existingWhitelistRecord) { + throw new Error(ErrorMessage.NO_WHITELIST_FOUND); + } + const result = { message: existingWhitelistRecord.addresses.includes(accountAddress) ? 'Already added' : 'Not added yet' } + 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); + } catch (err: any) { + 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.FAILED_TO_PROCESS }) + } + } + ); + + server.post("/getAllWhitelist/v2", async function (request, reply) { try { printRequest("/getAllWhitelist/v2", request, server.log); @@ -328,7 +556,6 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { if (!api_key || typeof(api_key) !== "string") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) let privateKey = ''; - let supportedNetworks; const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if (!unsafeMode) { @@ -340,12 +567,11 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { const secrets = JSON.parse(AWSresponse.SecretString ?? '{}'); if (!secrets['PRIVATE_KEY']) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - supportedNetworks = apiKeyEntity.supportedNetworks; } if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + const supportedNetworks = apiKeyEntity.supportedNetworks; if ( !chainId || isNaN(chainId) @@ -388,7 +614,6 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { if (!api_key || typeof(api_key) !== "string") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) let privateKey = ''; - let supportedNetworks; let bundlerApiKey = api_key; const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) @@ -400,15 +625,13 @@ const whitelistRoutes: 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['BUNDLER_API_KEY']) bundlerApiKey = secrets['BUNDLER_API_KEY']; privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - if (apiKeyEntity.bundlerApiKey) bundlerApiKey = apiKeyEntity.bundlerApiKey; privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - supportedNetworks = apiKeyEntity.supportedNetworks; } if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if (apiKeyEntity.bundlerApiKey) bundlerApiKey = apiKeyEntity.bundlerApiKey; + const supportedNetworks = apiKeyEntity.supportedNetworks; if ( !chainId || isNaN(chainId) @@ -456,7 +679,6 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { if (!api_key || typeof(api_key) !== "string") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) let privateKey = ''; - let supportedNetworks; let bundlerApiKey = api_key; const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) @@ -468,15 +690,13 @@ const whitelistRoutes: 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['BUNDLER_API_KEY']) bundlerApiKey = secrets['BUNDLER_API_KEY']; privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - if (apiKeyEntity.bundlerApiKey) bundlerApiKey = apiKeyEntity.bundlerApiKey; privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - supportedNetworks = apiKeyEntity.supportedNetworks; } if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if (apiKeyEntity.bundlerApiKey) bundlerApiKey = apiKeyEntity.bundlerApiKey; + const supportedNetworks = apiKeyEntity.supportedNetworks; if ( !chainId || isNaN(chainId) @@ -526,7 +746,6 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { if (!api_key || typeof(api_key) !== "string") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) let privateKey = ''; - let supportedNetworks; let bundlerApiKey = api_key; const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(api_key); if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) @@ -538,19 +757,15 @@ const whitelistRoutes: 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['BUNDLER_API_KEY']) { - bundlerApiKey = secrets['BUNDLER_API_KEY']; - } privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; } else { - if (apiKeyEntity.bundlerApiKey) { - bundlerApiKey = apiKeyEntity.bundlerApiKey; - } privateKey = decode(apiKeyEntity.privateKey, server.config.HMAC_SECRET); - supportedNetworks = apiKeyEntity.supportedNetworks; } if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) + if (apiKeyEntity.bundlerApiKey) { + bundlerApiKey = apiKeyEntity.bundlerApiKey; + } + const supportedNetworks = apiKeyEntity.supportedNetworks; if ( !chainId || isNaN(chainId) diff --git a/backend/src/server.ts b/backend/src/server.ts index 4a7f03e..9aefd6a 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -13,7 +13,6 @@ import PimlicoAbi from './abi/PimlicoAbi.js'; import PythOracleAbi from './abi/PythOracleAbi.js'; import { getNetworkConfig } from './utils/common.js'; import { checkDeposit } from './utils/monitorTokenPaymaster.js'; -import { APIKey } from './models/api-key.js'; import { APIKeyRepository } from './repository/api-key-repository.js'; import { ArkaConfigRepository } from './repository/arka-config-repository.js'; import adminRoutes from './routes/admin-routes.js'; @@ -24,6 +23,8 @@ import pimlicoRoutes from './routes/pimlico-routes.js'; import whitelistRoutes from './routes/whitelist-routes.js'; import sponsorshipPolicyRoutes from './routes/sponsorship-policy-routes.js'; import SupportedNetworks from "../config.json" assert { type: "json" }; +import { CoingeckoService } from './services/coingecko.js'; +import { CoingeckoTokensRepository } from './repository/coingecko-token-repository.js'; import { Paymaster } from './paymaster/index.js'; import { NativeOracles } from './constants/ChainlinkOracles.js'; @@ -55,10 +56,18 @@ const initializeServer = async (): Promise => { healthcheckUrl: "/healthcheck", logLevel: "warn" }); + + // Register the sequelizePlugin + await server.register(sequelizePlugin); + + // Synchronize all models + await server.sequelize.sync(); + + server.log.info('registered sequelizePlugin...') - const paymaster = new Paymaster(server.config.FEE_MARKUP, server.config.MULTI_TOKEN_MARKUP, server.config.EP7_TOKEN_VGL, server.config.EP7_TOKEN_PGL); + const paymaster = new Paymaster(server.config.FEE_MARKUP, server.config.MULTI_TOKEN_MARKUP, server.config.EP7_TOKEN_VGL, server.config.EP7_TOKEN_PGL, server.sequelize); - await server.register(paymasterRoutes, {paymaster}); + await server.register(paymasterRoutes, { paymaster }); await server.register(adminRoutes); @@ -72,14 +81,6 @@ const initializeServer = async (): Promise => { await server.register(sponsorshipPolicyRoutes); - // Register the sequelizePlugin - await server.register(sequelizePlugin); - - // Synchronize all models - await server.sequelize.sync(); - - server.log.info('registered sequelizePlugin...') - const arkaConfigRepository = new ArkaConfigRepository(server.sequelize); await server.register(fastifyCron, { @@ -97,7 +98,7 @@ const initializeServer = async (): Promise => { let configData: any if (process.env.CRON_PRIVATE_KEY) { const unsafeMode = process.env.UNSAFE_MODE === "true" ? true : false; - if(!unsafeMode) { + if (!unsafeMode) { const client = new SecretsManagerClient(); const api_key = process.env.DEFAULT_API_KEY; const prefixSecretId = "arka_"; @@ -210,114 +211,107 @@ const initializeServer = async (): Promise => { // the rest is from the node-cron API: // https://github.com/kelektiv/node-cron#api cronTime: '0 * * * *', // Every Hour, - name: 'checkTokenPaymasterDeposit', + name: 'checkPaymasterDeposit', // 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 = [], customPaymastersV2 = []; - const apiKeyRepository = new APIKeyRepository(server.sequelize); - - // checking deposit for epv7 native paymasters on db for all apikeys. - const apiKeys = await apiKeyRepository.findAll(); - let defaultBundlerKey = api_key; - - for(const apiKey of apiKeys) { - if(apiKey.apiKey === api_key) { - defaultBundlerKey = apiKey.bundlerApiKey ?? defaultBundlerKey; - } - if(apiKey.supportedNetworks) { - const buffer = Buffer.from(apiKey.supportedNetworks, 'base64'); - const supportedNetworks = JSON.parse(buffer.toString()); - for(const network of supportedNetworks) { - const networkConfig = getNetworkConfig(network.chainId, '', server.config.EPV_07); - if( - network.contracts?.etherspotPaymasterAddress && - networkConfig - ) { - const thresholdValue = network.thresholdValue ?? networkConfig.thresholdValue; - const bundler = network.bundler ?? networkConfig.bundler; - const bundlerUrl = apiKey.bundlerApiKey ? `${bundler}?api-key=${apiKey.bundlerApiKey}` : `${bundler}?api-key=${apiKey.apiKey}`; - checkDeposit(network.contracts.etherspotPaymasterAddress, bundlerUrl, process.env.WEBHOOK_URL, thresholdValue ?? '0.001', Number(network.chainId), server.log); + try { + if (process.env.WEBHOOK_URL) { + let customPaymasters = [], multiTokenPaymasters = [], customPaymastersV2 = []; + const apiKeyRepository = new APIKeyRepository(server.sequelize); + + // checking deposit for epv7 native paymasters on db for all apikeys. + const apiKeys = await apiKeyRepository.findAll(); + + for (const apiKey of apiKeys) { + if (apiKey.supportedNetworks) { + const buffer = Buffer.from(apiKey.supportedNetworks, 'base64'); + const supportedNetworks = JSON.parse(buffer.toString()); + for (const network of supportedNetworks) { + const networkConfig = getNetworkConfig(network.chainId, '', server.config.EPV_07); + if ( + network.contracts?.etherspotPaymasterAddress && + networkConfig + ) { + const thresholdValue = network.thresholdValue ?? networkConfig.thresholdValue; + const bundler = network.bundler ?? networkConfig.bundler; + checkDeposit(network.contracts.etherspotPaymasterAddress, bundler, process.env.WEBHOOK_URL, thresholdValue ?? '0.001', Number(network.chainId), server.log); + } } } - } - } - - // checking deposit for epv6 native paymasters from default config.json. - for(const network of SupportedNetworks) { - const networkConfig = getNetworkConfig(network.chainId, '', server.config.EPV_06); - if(networkConfig) { - const bundlerUrl = `${network.bundler}?api-key=${defaultBundlerKey}`; - checkDeposit(network.contracts.etherspotPaymasterAddress, bundlerUrl, process.env.WEBHOOK_URL, network.thresholdValue ?? '0.001', Number(network.chainId), server.log); - } - } - - 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()); - } - if (secrets['ERC20_PAYMASTERS_V2']) { - const buffer = Buffer.from(secrets['ERC20_PAYMASTERS_V2'], 'base64'); - customPaymastersV2 = JSON.parse(buffer.toString()); - } - } else { - 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 (apiKeyEntity?.multiTokenPaymasters) { - const buffer = Buffer.from(apiKeyEntity.multiTokenPaymasters, 'base64'); - multiTokenPaymasters = JSON.parse(buffer.toString()); - } - if (apiKeyEntity?.erc20PaymastersV2) { - const buffer = Buffer.from(apiKeyEntity.erc20PaymastersV2, 'base64'); - customPaymastersV2 = JSON.parse(buffer.toString()); - } - } - - // checking deposit for epv6 ERC20_PAYMASTERS. - customPaymasters = { ...customPaymasters, ...multiTokenPaymasters }; - for (const chainId in customPaymasters) { - const networkConfig = getNetworkConfig(chainId, '', server.config.EPV_06); - if (networkConfig) { - const bundlerUrl = `${networkConfig.bundler}?api-key=${defaultBundlerKey}`; - for (const symbol in customPaymasters[chainId]) { - checkDeposit(customPaymasters[chainId][symbol], bundlerUrl, process.env.WEBHOOK_URL, networkConfig.thresholdValue ?? '0.001', Number(chainId), server.log) + if (apiKey.erc20Paymasters || apiKey.multiTokenPaymasters) { + // checking deposit for epv6 ERC20_PAYMASTERS and MULTI_TOKEN_PAYMASTERS. + if (apiKey.erc20Paymasters) { + const buffer = Buffer.from(apiKey.erc20Paymasters, 'base64'); + customPaymasters = JSON.parse(buffer.toString()); + } + if (apiKey.multiTokenPaymasters) { + const buffer = Buffer.from(apiKey.multiTokenPaymasters, 'base64'); + multiTokenPaymasters = JSON.parse(buffer.toString()); + customPaymasters = customPaymasters.length > 0 ? { ...customPaymasters, ...multiTokenPaymasters } : multiTokenPaymasters; + } + for (const chainId in customPaymasters) { + const networkConfig = getNetworkConfig(chainId, apiKey.supportedNetworks ?? '', server.config.EPV_06); + if (networkConfig) { + const bundler = networkConfig.bundler; + for (const symbol in customPaymasters[chainId]) { + checkDeposit(customPaymasters[chainId][symbol], bundler, process.env.WEBHOOK_URL, networkConfig.thresholdValue ?? '0.001', Number(chainId), server.log) + } + } + } + } + if (apiKey.erc20PaymastersV2) { + const buffer = Buffer.from(apiKey.erc20PaymastersV2, 'base64'); + customPaymastersV2 = JSON.parse(buffer.toString()); + // checking deposit for epv7 ERC20_PAYMASTERS_V2. + for (const chainId in customPaymastersV2) { + const networkConfig = getNetworkConfig(chainId, apiKey.supportedNetworks ?? '', server.config.EPV_06); + if (networkConfig) { + const bundler = networkConfig.bundler; + for (const symbol in customPaymastersV2[chainId]) { + checkDeposit(customPaymastersV2[chainId][symbol], bundler, process.env.WEBHOOK_URL, networkConfig.thresholdValue ?? '0.001', Number(chainId), server.log); + } + } + } } } - } - // checking deposit for epv7 ERC20_PAYMASTERS_V2. - for(const chainId in customPaymastersV2) { - const networkConfig = getNetworkConfig(chainId, '', server.config.EPV_07); - if(networkConfig) { - const bundlerUrl = `${networkConfig.bundler}?api-key=${defaultBundlerKey}`; - for(const symbol in customPaymastersV2[chainId]) { - checkDeposit(customPaymastersV2[chainId][symbol], bundlerUrl, process.env.WEBHOOK_URL, networkConfig.thresholdValue ?? '0.001', Number(chainId), server.log); - } + // checking deposit for epv6 native paymasters from default config.json. + for (const network of SupportedNetworks) { + checkDeposit(network.contracts.etherspotPaymasterAddress, network.bundler, process.env.WEBHOOK_URL, network.thresholdValue ?? '0.001', Number(network.chainId), server.log); } } + } catch (err) { + server.log.error(err); + } + } + }, + { + // Only these two properties are required, + // the rest is from the node-cron API: + // https://github.com/kelektiv/node-cron#api + cronTime: '*/5 * * * *', // Every 5 Minutes, + name: 'cacheCoingeckoPrices', + + // Note: the callbacks (onTick & onComplete) take the server + // as an argument, as opposed to nothing in the node-cron API: + onTick: async () => { + try { + const coingeckoRepo = new CoingeckoTokensRepository(server.sequelize); + const records = await coingeckoRepo.findAll(); + const tokenIds = records.map(record => record.coinId); + const coingecko = new CoingeckoService(); + + const data = await coingecko.fetchPriceByCoinID(tokenIds); + const tokenPrices: any = []; + records.map(record => { + const address = ethers.utils.getAddress(record.address); + tokenPrices[address] = { price: Number(data[record.coinId].usd).toFixed(5), decimals: record.decimals, chainId: record.chainId, gasToken: address, symbol: record.token } + }) + paymaster.setPricesFromCoingecko(tokenPrices); + } catch (err) { + server.log.error(err); } } }, diff --git a/backend/src/services/coingecko.ts b/backend/src/services/coingecko.ts new file mode 100644 index 0000000..4415c90 --- /dev/null +++ b/backend/src/services/coingecko.ts @@ -0,0 +1,41 @@ +import { FastifyBaseLogger } from 'fastify'; +import { TokenPriceAndMetadataCache, SUPPORTED_CHAINIDS } from './interface.js'; + +const apiUrl = process.env.COINGECKO_URL || ""; +const apiKey = process.env.COINGECKO_API_KEY || ""; + +export class CoingeckoService { + static LOGGER_CONTEXT = 'CoingeckoService'; + priceAndMetadata: Map = new Map(); + + constructor( + // private log: FastifyBaseLogger + ) { + if (apiUrl === "" || apiKey === "") + throw new Error("Coingecko Config not provided properly") + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async fetchPriceByCoinID(tokens: string[], log?: FastifyBaseLogger): Promise { + let price = null; + const url = `${apiUrl}/simple/price?ids=${tokens.join(',')}&vs_currencies=usd`; + const options = { + method: 'GET', + headers: {accept: 'application/json', 'x-cg-pro-api-key': apiKey} + }; + try { + const data = await fetch(url, options); + price = await data.json(); + } catch (err) { + log?.error(err, CoingeckoService.LOGGER_CONTEXT, { + message: 'Failed to fetch native Prices from Coingecko', + url, + }); + } + return price; + } + + isNetworkSupported(chainId: number): boolean { + return SUPPORTED_CHAINIDS.includes(chainId) ? true : false; + } +} \ No newline at end of file diff --git a/backend/src/services/interface.ts b/backend/src/services/interface.ts new file mode 100644 index 0000000..7a2f700 --- /dev/null +++ b/backend/src/services/interface.ts @@ -0,0 +1,53 @@ +export const NETWORK_CHAINID_TO_COINID: Record = { + 1: 'ethereum', + 137: 'matic-network', + 56: 'binancecoin', + 100: 'xdai', + 43114: 'avalanche-2', + 10: 'ethereum', + 42161: 'ethereum', + 204: 'binancecoin', + 8453: 'ethereum', + 59144: 'ethereum', + 534352: 'weth' +}; + +export const NETWORK_CHAINID_TO_COINGECKO_NETWORK: Record = { + 1: 'ethereum', + 137: 'polygon-pos', + 56: 'binance-smart-chain', + 100: 'xdai', + 43114: 'avalanche', + 10: 'optimistic-ethereum', + 42161: 'arbitrum-one', + 204: 'opbnb', + 8453: 'base', + 59144: 'linea', + 534352: 'scroll' +}; + +export const SUPPORTED_CHAINIDS = [ + 1, + 10, + 56, + 100, + 137, + 204, + 8453, + 43114, + 42161, + 59144, + 534352 +]; + +interface TokenPriceAndMetadata { + decimals: number; + symbol: string; + USDPrice: number; + gasToken: string +} + +export interface TokenPriceAndMetadataCache { + data: TokenPriceAndMetadata; + expiry: number +} \ No newline at end of file