diff --git a/backend/CHANGELOG.md b/backend/CHANGELOG.md index 0167e1d..9a079c8 100644 --- a/backend/CHANGELOG.md +++ b/backend/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog +## [3.1.7] - 2025-04-16 +### New +- Added support for multiTokenPaymaster deployed for EntryPoint v07 + ## [3.1.6] - 2025-04-02 ### Fixes - Fixed bug in paymaster estimation for multiTokenPaymaster getERC20Quotes API diff --git a/backend/migrations/20250416000001-update-apiKey-table.cjs b/backend/migrations/20250416000001-update-apiKey-table.cjs new file mode 100644 index 0000000..653c3ac --- /dev/null +++ b/backend/migrations/20250416000001-update-apiKey-table.cjs @@ -0,0 +1,11 @@ +require('dotenv').config(); + +async function up({ context: queryInterface }) { + await queryInterface.sequelize.query(`ALTER TABLE IF EXISTS "${process.env.DATABASE_SCHEMA_NAME}".api_keys ADD COLUMN "MULTI_TOKEN_PAYMASTERS_V2" text default null`); +} + +async function down({ context: queryInterface }) { + await queryInterface.sequelize.query(`ALTER TABLE IF EXISTS "${process.env.DATABASE_SCHEMA_NAME}".api_keys DROP COLUMN MULTI_TOKEN_PAYMASTERS_V2;`); +} + +module.exports = { up, down } \ No newline at end of file diff --git a/backend/migrations/20250416000002-update-MTP-table.cjs b/backend/migrations/20250416000002-update-MTP-table.cjs new file mode 100644 index 0000000..f5a2322 --- /dev/null +++ b/backend/migrations/20250416000002-update-MTP-table.cjs @@ -0,0 +1,11 @@ +require('dotenv').config(); + +async function up({ context: queryInterface }) { + await queryInterface.sequelize.query(`ALTER TABLE IF EXISTS "${process.env.DATABASE_SCHEMA_NAME}".multi_token_paymaster ADD COLUMN "EP_VERSION" text default 'EPV_06'`); +} + +async function down({ context: queryInterface }) { + await queryInterface.sequelize.query(`ALTER TABLE "${process.env.DATABASE_SCHEMA_NAME}".multi_token_paymaster DROP COLUMN EP_VERSION;`); +} + +module.exports = { up, down } \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index e79a92e..db4f21d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "arka", - "version": "3.1.6", + "version": "3.1.7", "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/PimlicoAbi.ts b/backend/src/abi/ERC20PaymasterAbi.ts similarity index 100% rename from backend/src/abi/PimlicoAbi.ts rename to backend/src/abi/ERC20PaymasterAbi.ts diff --git a/backend/src/abi/MultiTokenPaymasterAbiV2.ts b/backend/src/abi/MultiTokenPaymasterAbiV2.ts new file mode 100644 index 0000000..4cdbbe8 --- /dev/null +++ b/backend/src/abi/MultiTokenPaymasterAbiV2.ts @@ -0,0 +1,878 @@ +export default [ + { + "inputs": [ + { + "internalType": "contract IEntryPoint", + "name": "_entryPoint", + "type": "address" + }, + { + "internalType": "address", + "name": "_verifyingSigner", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CanNotWithdrawToZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "CannotBeUnrealisticValue", + "type": "error" + }, + { + "inputs": [], + "name": "DEXRouterCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "DepositCanNotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "ECDSAInvalidSignature", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "length", + "type": "uint256" + } + ], + "name": "ECDSAInvalidSignatureLength", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "ECDSAInvalidSignatureS", + "type": "error" + }, + { + "inputs": [], + "name": "EntryPointCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "FeeReceiverCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "NativeTokenBalanceZero", + "type": "error" + }, + { + "inputs": [], + "name": "NativeTokensWithdrawalFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [], + "name": "OwnerCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [], + "name": "TokensAndAmountsLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "VerifyingSignerCannotBeZero", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "_oldThresholdCost", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "_newThresholdCost", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "_actor", + "type": "address" + } + ], + "name": "EPGasThresholdChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_oldfeeReceiver", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_newfeeReceiver", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_actor", + "type": "address" + } + ], + "name": "FeeReceiverChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Received", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "totalCharge", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "oracleAggregator", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "priceMarkup", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "userOpHash", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "exchangeRate", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "enum MultiTokenPaymaster.ExchangeRateSource", + "name": "priceSource", + "type": "uint8" + } + ], + "name": "TokenPaymasterOperation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "charge", + "type": "uint256" + } + ], + "name": "TokenPaymentDue", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_oldSigner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_newSigner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_actor", + "type": "address" + } + ], + "name": "VerifyingSignerChanged", + "type": "event" + }, + { + "inputs": [], + "name": "UNACCOUNTED_COST", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "unstakeDelaySec", + "type": "uint32" + } + ], + "name": "addStake", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "entryPoint", + "outputs": [ + { + "internalType": "contract IEntryPoint", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeReceiver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "initCode", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "accountGasLimits", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "preVerificationGas", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "gasFees", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "paymasterAndData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct PackedUserOperation", + "name": "userOp", + "type": "tuple" + }, + { + "internalType": "enum MultiTokenPaymaster.ExchangeRateSource", + "name": "priceSource", + "type": "uint8" + }, + { + "internalType": "uint48", + "name": "validUntil", + "type": "uint48" + }, + { + "internalType": "uint48", + "name": "validAfter", + "type": "uint48" + }, + { + "internalType": "address", + "name": "feeToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleAggregator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exchangeRate", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "priceMarkup", + "type": "uint32" + } + ], + "name": "getHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "paymasterAndData", + "type": "bytes" + } + ], + "name": "parsePaymasterAndData", + "outputs": [ + { + "internalType": "enum MultiTokenPaymaster.ExchangeRateSource", + "name": "priceSource", + "type": "uint8" + }, + { + "internalType": "uint48", + "name": "validUntil", + "type": "uint48" + }, + { + "internalType": "uint48", + "name": "validAfter", + "type": "uint48" + }, + { + "internalType": "address", + "name": "feeToken", + "type": "address" + }, + { + "internalType": "address", + "name": "oracleAggregator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exchangeRate", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "priceMarkup", + "type": "uint32" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IPaymaster.PostOpMode", + "name": "mode", + "type": "uint8" + }, + { + "internalType": "bytes", + "name": "context", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "actualGasCost", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "actualUserOpFeePerGas", + "type": "uint256" + } + ], + "name": "postOp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newFeeReceiver", + "type": "address" + } + ], + "name": "setFeeReceiver", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_newThresholdCost", + "type": "uint256" + } + ], + "name": "setUnaccountedEPGasThreshold", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_newVerifyingSigner", + "type": "address" + } + ], + "name": "setVerifyingSigner", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unlockStake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "initCode", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "accountGasLimits", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "preVerificationGas", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "gasFees", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "paymasterAndData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct PackedUserOperation", + "name": "userOp", + "type": "tuple" + }, + { + "internalType": "bytes32", + "name": "userOpHash", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "maxCost", + "type": "uint256" + } + ], + "name": "validatePaymasterUserOp", + "outputs": [ + { + "internalType": "bytes", + "name": "context", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "validationData", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "verifyingSigner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dest", + "type": "address" + } + ], + "name": "withdrawAllNative", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdrawERC20", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "withdrawERC20Full", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20[]", + "name": "token", + "type": "address[]" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "amount", + "type": "uint256[]" + } + ], + "name": "withdrawMultipleERC20", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20[]", + "name": "token", + "type": "address[]" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "withdrawMultipleERC20Full", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "withdrawAddress", + "type": "address" + } + ], + "name": "withdrawStake", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "withdrawAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdrawTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] as const; \ No newline at end of file diff --git a/backend/src/constants/Pimlico.ts b/backend/src/constants/Token.ts similarity index 100% rename from backend/src/constants/Pimlico.ts rename to backend/src/constants/Token.ts diff --git a/backend/src/models/api-key.ts b/backend/src/models/api-key.ts index 0a778d7..a5805c4 100644 --- a/backend/src/models/api-key.ts +++ b/backend/src/models/api-key.ts @@ -10,6 +10,7 @@ export class APIKey extends Model { public verifyingPaymasters?: string | null; public verifyingPaymastersV2?: string | null; public multiTokenPaymasters?: string | null; + public multiTokenPaymastersV2?: string | null; public multiTokenOracles?: string | null; public sponsorName?: string | null; public logoUrl?: string | null; @@ -77,6 +78,11 @@ export function initializeAPIKeyModel(sequelize: Sequelize, schema: string) { allowNull: true, field: 'MULTI_TOKEN_PAYMASTERS' }, + multiTokenPaymastersV2: { + type: DataTypes.TEXT, + allowNull: true, + field: 'MULTI_TOKEN_PAYMASTERS_V2' + }, multiTokenOracles: { type: DataTypes.TEXT, allowNull: true, diff --git a/backend/src/models/multiTokenPaymaster.ts b/backend/src/models/multiTokenPaymaster.ts index bd4320d..a31bc72 100644 --- a/backend/src/models/multiTokenPaymaster.ts +++ b/backend/src/models/multiTokenPaymaster.ts @@ -7,6 +7,7 @@ export class MultiTokenPaymaster extends Model { public paymasterAddress!: string; public chainId!: number; public decimals!: string; + public epVersion!: string; public readonly createdAt!: Date; public readonly updatedAt!: Date; } @@ -50,6 +51,11 @@ const initializeMTPModel = (sequelize: Sequelize, schema: string) => { allowNull: false, field: 'DECIMALS' }, + epVersion: { + type: DataTypes.STRING, + allowNull: false, + field: 'EP_VERSION' + }, createdAt: { type: DataTypes.DATE, allowNull: false, diff --git a/backend/src/paymaster/index.test.ts b/backend/src/paymaster/index.test.ts index e77770e..a29740c 100644 --- a/backend/src/paymaster/index.test.ts +++ b/backend/src/paymaster/index.test.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ethers, providers, Wallet } from "ethers"; import { Paymaster } from "./index.js"; -import { PAYMASTER_ADDRESS } from "../constants/Pimlico.js"; +import { PAYMASTER_ADDRESS } from "../constants/Token.js"; import MultiTokenPaymasterAbi from "../abi/MultiTokenPaymasterAbi.js"; describe("Validate the Arka Paymaster on Sepolia", () => { diff --git a/backend/src/paymaster/index.ts b/backend/src/paymaster/index.ts index 3fba6fa..2652beb 100644 --- a/backend/src/paymaster/index.ts +++ b/backend/src/paymaster/index.ts @@ -4,9 +4,9 @@ import { arrayify, BytesLike, defaultAbiCoder, hexConcat, hexZeroPad } from 'eth import { FastifyBaseLogger } from 'fastify'; import EtherspotAbiV06 from '../abi/EtherspotAbi.js'; import EtherspotAbiV07 from "../abi/EtherspotVerifyingSignerAbi.js"; -import { PimlicoPaymaster } from './pimlico.js'; +import { TokenPaymaster } from './token.js'; import ErrorMessage from '../constants/ErrorMessage.js'; -import { PAYMASTER_ADDRESS } from '../constants/Pimlico.js'; +import { PAYMASTER_ADDRESS } from '../constants/Token.js'; import { getGasFee } from '../utils/common.js'; import MultiTokenPaymasterAbi from '../abi/MultiTokenPaymasterAbi.js'; import OrochiOracleAbi from '../abi/OrochiOracleAbi.js'; @@ -21,6 +21,7 @@ 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'; +import MultiTokenPaymasterAbiV2 from '../abi/MultiTokenPaymasterAbiV2.js'; const ttl = parseInt(process.env.CACHE_TTL || "600000"); const nativePriceCacheTtl = parseInt(process.env.NATIVE_PRICE_CACHE_TTL || "60000"); @@ -54,13 +55,16 @@ export class Paymaster { EP7_TOKEN_VGL: string; EP7_TOKEN_PGL: string; EP7_PVGL: BigNumber; + MTP_PVGL: string; + MTP_PPGL: 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, sequelize: Sequelize, mtpVglMarkup: string, ep7Pvgl: string) { + constructor(feeMarkUp: string, multiTokenMarkUp: string, ep7TokenVGL: string, ep7TokenPGL: string, sequelize: Sequelize, + mtpVglMarkup: string, ep7Pvgl: string, mtpPvgl: string, mtpPpgl: string) { 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); @@ -69,6 +73,8 @@ export class Paymaster { this.sequelize = sequelize; this.MTP_VGL_MARKUP = mtpVglMarkup; this.EP7_PVGL = BigNumber.from(ep7Pvgl); + this.MTP_PVGL = mtpPvgl; + this.MTP_PPGL = mtpPpgl; } packUint(high128: BigNumberish, low128: BigNumberish): string { @@ -237,6 +243,38 @@ export class Paymaster { } } + async getPaymasterAndDataForMultiTokenPaymasterV07(userOp: any, validUntil: string, validAfter: string, feeToken: string, + ethPrice: string, paymasterContract: Contract, signer: Wallet) { + try { + const priceMarkup = this.multiTokenMarkUp; + const hash = await paymasterContract.getHash( + userOp, + 0, + validUntil, + validAfter, + feeToken, + ethers.constants.AddressZero, + ethPrice, + priceMarkup + ); + + const sig = await signer.signMessage(arrayify(hash)); + + const paymasterData = hexConcat([ + '0x00', + defaultAbiCoder.encode( + ['uint48', 'uint48', 'address', 'address', 'uint256', 'uint32'], + [validUntil, validAfter, feeToken, ethers.constants.AddressZero, ethPrice, priceMarkup] + ), + sig, + ]); + + return paymasterData; + } catch (err: any) { + throw new Error('Failed ' + err.message) + } + } + async getPaymasterAndDataForMultiTokenPaymaster(userOp: any, validUntil: string, validAfter: string, feeToken: string, ethPrice: string, paymasterContract: Contract, signer: Wallet, chainId: number) { const priceMarkup = this.multiTokenMarkUp; @@ -717,7 +755,7 @@ export class Paymaster { userOp.paymasterAndData = paymasterAndData const response = await provider.send('eth_estimateUserOperationGas', [userOp, entryPoint]); if (BigNumber.from(userOp.verificationGasLimit).lt("45000")) userOp.verificationGasLimit = BigNumber.from("45000").toHexString(); // This is to counter the unaccounted cost(45000) - userOp.verificationGasLimit = BigNumber.from(response.verificationGasLimit).add("30000").toHexString(); // This is added just in case the token is proxy + userOp.verificationGasLimit = BigNumber.from(response.verificationGasLimit).add(this.MTP_VGL_MARKUP).toHexString(); // This is added just in case the token is proxy userOp.preVerificationGas = response.preVerificationGas; userOp.callGasLimit = response.callGasLimit; paymasterAndData = await this.getPaymasterAndDataForMultiTokenPaymaster(userOp, validUntil, validAfter, feeToken, ethPrice, paymasterContract, signer, chainId); @@ -738,10 +776,105 @@ export class Paymaster { } } - async pimlico(userOp: any, bundlerRpc: string, entryPoint: string, PaymasterAddress: string, log?: FastifyBaseLogger) { + async signMultiTokenPaymasterV07(userOp: any, validUntil: string, validAfter: string, entryPoint: string, paymasterAddress: string, + feeToken: string, oracleAggregator: string, bundlerRpc: string, signer: Wallet, oracleName: string, nativeOracleAddress: string, + chainId: number, log?: FastifyBaseLogger) { + try { + const provider = new providers.JsonRpcProvider(bundlerRpc); + const paymasterContract = new ethers.Contract(paymasterAddress, MultiTokenPaymasterAbiV2, provider); + let ethPrice; + const isCoingeckoAvailable = this.coingeckoPrice.get(`${chainId}-${feeToken}`); + + if (!oracleAggregator) { + if (!isCoingeckoAvailable) throw new Error('Unable to fetch token price. Please try again later.') + const { latestAnswer, decimals } = await this.getLatestAnswerAndDecimals(provider, nativeOracleAddress, chainId); + const data = await this.getPriceFromCoingecko(chainId, feeToken, latestAnswer, decimals, log); + + ethPrice = data.ethPrice; + } else if (oracleName === "orochi") { + const data = await this.getPriceFromOrochi(oracleAggregator, provider, feeToken, chainId); + ethPrice = data.ethPrice; + } else if (oracleName === "chainlink") { + const { latestAnswer, decimals } = await this.getLatestAnswerAndDecimals(provider, nativeOracleAddress, chainId); + + const data = await this.getPriceFromChainlink( + oracleAggregator, + provider, + feeToken, + latestAnswer, + decimals, + chainId + ); + + ethPrice = data.ethPrice; + } else { + const ecContract = new ethers.Contract(oracleAggregator, EtherspotChainlinkOracleAbi, provider); + const ETHprice = await ecContract.cachedPrice(); + ethPrice = ETHprice + } + if (!userOp.signature) userOp.signature = '0x'; + userOp.paymaster = paymasterAddress; + userOp.paymasterVerificationGasLimit = BigNumber.from(this.MTP_PVGL).toHexString(); // Paymaster specific gas limit + userOp.paymasterPostOpGasLimit = BigNumber.from(this.MTP_PPGL).toHexString(); // Paymaster specific gas limit for token transfer + let accountGasLimits = this.packUint(userOp.verificationGasLimit, userOp.callGasLimit) + const gasFees = this.packUint(userOp.maxPriorityFeePerGas, userOp.maxFeePerGas); + let packedUserOp = { + sender: userOp.sender, + nonce: userOp.nonce, + initCode: userOp.initCode ?? "0x", + callData: userOp.callData, + accountGasLimits: accountGasLimits, + preVerificationGas: userOp.preVerificationGas, + gasFees: gasFees, + paymasterAndData: this.packPaymasterData(paymasterAddress, userOp.paymasterVerificationGasLimit, userOp.paymasterPostOpGasLimit, "0x"), + signature: userOp.signature + } + userOp.paymaster = paymasterAddress; + let paymasterData = await this.getPaymasterAndDataForMultiTokenPaymasterV07(packedUserOp, validUntil, validAfter, feeToken, ethPrice, paymasterContract, signer); + userOp.paymasterData = paymasterData + userOp.paymasterAndData = this.packPaymasterData(paymasterAddress, userOp.paymasterVerificationGasLimit, userOp.paymasterPostOpGasLimit, paymasterData); + const response = await provider.send('eth_estimateUserOperationGas', [userOp, entryPoint]); + if (BigNumber.from(userOp.verificationGasLimit).lt("45000")) userOp.verificationGasLimit = BigNumber.from("45000").toHexString(); // This is to counter the unaccounted cost(45000) + userOp.verificationGasLimit = response.verificationGasLimit; + userOp.preVerificationGas = response.preVerificationGas; + userOp.callGasLimit = response.callGasLimit; + accountGasLimits = this.packUint(userOp.verificationGasLimit, userOp.callGasLimit); + packedUserOp = { + sender: userOp.sender, + nonce: userOp.nonce, + initCode: userOp.initCode ?? "0x", + callData: userOp.callData, + accountGasLimits: accountGasLimits, + preVerificationGas: userOp.preVerificationGas, + gasFees: gasFees, + paymasterAndData: this.packPaymasterData(paymasterAddress, userOp.paymasterVerificationGasLimit, userOp.paymasterPostOpGasLimit, paymasterData), + signature: userOp.signature + } + paymasterData = await this.getPaymasterAndDataForMultiTokenPaymasterV07(packedUserOp, validUntil, validAfter, feeToken, ethPrice, paymasterContract, signer); + userOp.paymasterData = paymasterData + + const returnValue = { + paymaster: paymasterAddress, + paymasterData: paymasterData, + preVerificationGas: BigNumber.from(packedUserOp.preVerificationGas).toHexString(), + verificationGasLimit: BigNumber.from(userOp.verificationGasLimit).toHexString(), + callGasLimit: BigNumber.from(userOp.callGasLimit).toHexString(), + paymasterVerificationGasLimit: userOp.paymasterVerificationGasLimit, + paymasterPostOpGasLimit: userOp.paymasterPostOpGasLimit, + } + return returnValue; + } catch (err: any) { + if (err.message.includes("Quota exceeded")) + throw new Error('Failed to process request to bundler since request Quota exceeded for the current apiKey') + if (log) log.error(err, 'signMultiTokenPaymasterV07'); + throw new Error('Failed to process request to bundler. Please contact support team RawErrorMsg:' + err.message) + } + } + + async erc20Paymaster(userOp: any, bundlerRpc: string, entryPoint: string, PaymasterAddress: string, log?: FastifyBaseLogger) { try { const provider = new providers.JsonRpcProvider(bundlerRpc); - const erc20Paymaster = new PimlicoPaymaster(PaymasterAddress, provider) + const erc20Paymaster = new TokenPaymaster(PaymasterAddress, provider) if (!userOp.signature) userOp.signature = '0x'; // The minimum ABI to get the ERC20 Token balance @@ -785,7 +918,7 @@ export class Paymaster { if (err.message.includes("Quota exceeded")) throw new Error('Failed to process request to bundler since request Quota exceeded for the current apiKey') if (err.message.includes('The required token amount')) throw new Error(err.message); - if (log) log.error(err, 'pimlico'); + if (log) log.error(err, 'erc20Paymaster'); throw new Error('Failed to process request to bundler. Please contact support team RawErrorMsg: ' + err.message) } } @@ -860,13 +993,13 @@ export class Paymaster { } } - async pimlicoAddress(gasToken: string, chainId: number, log?: FastifyBaseLogger) { + async erc20PaymasterAddress(gasToken: string, chainId: number, log?: FastifyBaseLogger) { try { return { message: PAYMASTER_ADDRESS[chainId][gasToken] ?? 'Requested Token Paymaster is not available/deployed', } } catch (err: any) { - if (log) log.error(err, 'pimlicoAddress'); + if (log) log.error(err, 'ERC20PaymasterAddress'); throw new Error(err.message) } } @@ -1192,7 +1325,7 @@ export class Paymaster { const tokenData = this.coingeckoPrice.get(cacheKey)?.data; if (!tokenData) { log?.error('Price fetch error on tokenAddress: ' + tokenAddress, 'CoingeckoError') - throw new Error(tokenAddress + ' ' +ErrorMessage.COINGECKO_PRICE_NOT_FETCHED); + throw new Error(`${tokenAddress} ${ErrorMessage.COINGECKO_PRICE_NOT_FETCHED}`); } } } diff --git a/backend/src/paymaster/pimlico.test.ts b/backend/src/paymaster/token.test.ts similarity index 98% rename from backend/src/paymaster/pimlico.test.ts rename to backend/src/paymaster/token.test.ts index 0e372ea..625c94a 100644 --- a/backend/src/paymaster/pimlico.test.ts +++ b/backend/src/paymaster/token.test.ts @@ -1,13 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { providers, ethers } from "ethers"; -import { PimlicoPaymaster, getERC20Paymaster } from "./pimlico.js"; +import { TokenPaymaster, getERC20Paymaster } from "./token.js"; import { NATIVE_ASSET, ORACLE_ADDRESS, TOKEN_ADDRESS, -} from "../../src/constants/Pimlico.js"; +} from "../constants/Token.js"; -describe("PimlicoPaymaster on Mumbai", () => { +describe("TokenPaymaster on Mumbai", () => { const paymasterAddress = "0x32aCDFeA07a614E52403d2c1feB747aa8079A353"; // Mumbai Etherspot Paymaster Address const entryPointAddress = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; // EntryPoint v0.6 as default const bundlerUrl = "https://mumbai-bundler.etherspot.io"; @@ -39,12 +39,12 @@ describe("PimlicoPaymaster on Mumbai", () => { }; const provider = new providers.JsonRpcProvider(bundlerUrl); - const pimlicoPaymaster = new PimlicoPaymaster(paymasterAddress, provider); + const tokenPaymaster = new TokenPaymaster(paymasterAddress, provider); test.skip("SMOKE: validate the calculateTokenAmount function with valid details", async () => { try { const calculateTokenAmountResponse = - await pimlicoPaymaster.calculateTokenAmount(userOp); + await tokenPaymaster.calculateTokenAmount(userOp); try { expect(calculateTokenAmountResponse).toHaveProperty("_hex"); @@ -63,7 +63,7 @@ describe("PimlicoPaymaster on Mumbai", () => { test.skip("SMOKE: validate the generatePaymasterAndData function with valid details", async () => { try { const generatePaymasterAndDataResponse = - await pimlicoPaymaster.generatePaymasterAndData(userOp); + await tokenPaymaster.generatePaymasterAndData(userOp); try { expect(generatePaymasterAndDataResponse.length.toString()).toMatch( "106" diff --git a/backend/src/paymaster/pimlico.ts b/backend/src/paymaster/token.ts similarity index 96% rename from backend/src/paymaster/pimlico.ts rename to backend/src/paymaster/token.ts index e1ce5f7..47b0237 100644 --- a/backend/src/paymaster/pimlico.ts +++ b/backend/src/paymaster/token.ts @@ -1,8 +1,8 @@ import { Contract, BigNumber, providers, utils, Signer, ethers } from "ethers"; import { UserOperationStruct } from "@account-abstraction/contracts" import { NotPromise } from "@account-abstraction/utils" -import abi from "../abi/PimlicoAbi.js"; -import { NATIVE_ASSET, ORACLE_ADDRESS, TOKEN_ADDRESS, bytecode } from "../constants/Pimlico.js"; +import abi from "../abi/ERC20PaymasterAbi.js"; +import { NATIVE_ASSET, ORACLE_ADDRESS, TOKEN_ADDRESS, bytecode } from "../constants/Token.js"; export interface ERC20PaymasterBuildOptions { entrypoint?: string @@ -14,7 +14,7 @@ export interface ERC20PaymasterBuildOptions { deployer?: Signer } -export class PimlicoPaymaster { +export class TokenPaymaster { private contract: Contract; tokenAddress: Promise; paymasterAddress: string; @@ -52,7 +52,6 @@ export class PimlicoPaymaster { /** * Don't know why but the below calculation is for tokens with 6 decimals such as USDC, USDT - * Pimlico default paymasters uses only USDC * After long testing the below code is neglected for tokens with 18 decimals */ if (ethers.utils.parseUnits('1', 6).eq(tokenDecimals)) { @@ -190,7 +189,7 @@ export async function getERC20Paymaster( erc20: string, entryPoint: string, options?: Omit, "deployer"> -): Promise { +): Promise { let parsedOptions: Required, "deployer">> const chainId = (await provider.getNetwork()).chainId if (options === undefined) { @@ -199,7 +198,7 @@ export async function getERC20Paymaster( nativeAssetOracle: ORACLE_ADDRESS[chainId][NATIVE_ASSET[chainId]], tokenAddress: TOKEN_ADDRESS[chainId][erc20], tokenOracle: ORACLE_ADDRESS[chainId][erc20], - owner: "0x4337000c2828F5260d8921fD25829F606b9E8680" // pimlico address + owner: "0x4337000c2828F5260d8921fD25829F606b9E8680" } } else { parsedOptions = await validatePaymasterOptions(provider, erc20, options) @@ -208,5 +207,5 @@ export async function getERC20Paymaster( if ((await provider.getCode(address)).length <= 2) { throw new Error(`ERC20Paymaster not deployed at ${address}`) } - return new PimlicoPaymaster(address, provider) + return new TokenPaymaster(address, provider) } diff --git a/backend/src/plugins/config.ts b/backend/src/plugins/config.ts index c125234..dc2f7ee 100644 --- a/backend/src/plugins/config.ts +++ b/backend/src/plugins/config.ts @@ -38,6 +38,8 @@ const ConfigSchema = Type.Strict( MTP_VGL_MARKUP: Type.String() || '30000', USE_SKANDHA_FOR_GAS_DATA: Type.Boolean() || true, EP7_PVGL: Type.String(), + MTP_PVGL: Type.String() || undefined, + MTP_PPGL: Type.String() || undefined, }) ); @@ -80,6 +82,8 @@ const configPlugin: FastifyPluginAsync = async (server) => { MTP_VGL_MARKUP: process.env.MTP_VGL_MARKUP, USE_SKANDHA_FOR_GAS_DATA: process.env.USE_SKANDHA_FOR_GAS_DATA, EP7_PVGL: process.env.EP7_PVGL ?? '30000', + MTP_PVGL: process.env.MTP_PVGL ?? '50000', + MTP_PPGL: process.env.MTP_PPGL ?? '70000', } const valid = validate(envVar); @@ -117,7 +121,9 @@ const configPlugin: FastifyPluginAsync = async (server) => { MULTI_TOKEN_ORACLES: process.env.MULTI_TOKEN_ORACLES ?? '', MTP_VGL_MARKUP: process.env.MTP_VGL_MARKUP ?? '30000', USE_SKANDHA_FOR_GAS_DATA: process.env.USE_SKANDHA_FOR_GAS_DATA === 'false' ? false : true, - EP7_PVGL: process.env.EP7_PVGL ?? '30000' + EP7_PVGL: process.env.EP7_PVGL ?? '30000', + MTP_PVGL: process.env.MTP_PVGL ?? '50000', + MTP_PPGL: process.env.MTP_PPGL ?? '70000', } server.log.info(config, "config:"); diff --git a/backend/src/repository/multi-token-paymaster.ts b/backend/src/repository/multi-token-paymaster.ts index e6957a4..4018bea 100644 --- a/backend/src/repository/multi-token-paymaster.ts +++ b/backend/src/repository/multi-token-paymaster.ts @@ -1,5 +1,6 @@ import { Sequelize } from 'sequelize'; import { MultiTokenPaymaster } from '../models/multiTokenPaymaster.js'; +import { EPVersions } from '../types/sponsorship-policy-dto.js'; export class MultiTokenPaymasterRepository { private sequelize: Sequelize; @@ -13,10 +14,10 @@ export class MultiTokenPaymasterRepository { return result.map(id => id.get() as MultiTokenPaymaster); } - async findOneByChainIdAndTokenAddress(chainId: number, tokenAddress: string): Promise { + async findOneByChainIdEPVersionAndTokenAddress(chainId: number, tokenAddress: string, epVersion: EPVersions): Promise { const result = await this.sequelize.models.MultiTokenPaymaster.findOne({ where: { - chainId: chainId, tokenAddress: tokenAddress + chainId: chainId, tokenAddress: tokenAddress, epVersion: epVersion } }) as MultiTokenPaymaster; diff --git a/backend/src/routes/admin-routes.ts b/backend/src/routes/admin-routes.ts index cc86b1e..1ebafe1 100644 --- a/backend/src/routes/admin-routes.ts +++ b/backend/src/routes/admin-routes.ts @@ -18,7 +18,8 @@ import { getNetworkConfig } from "../utils/common.js"; import { Paymaster } from "../paymaster/index.js"; const adminRoutes: 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, server.sequelize, server.config.MTP_VGL_MARKUP, server.config.EP7_PVGL); + 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, + server.config.MTP_VGL_MARKUP, server.config.EP7_PVGL, server.config.MTP_PVGL, server.config.MTP_PPGL); const prefixSecretId = 'arka_'; diff --git a/backend/src/routes/deposit-route.ts b/backend/src/routes/deposit-route.ts index a5ca99f..a6f1aff 100644 --- a/backend/src/routes/deposit-route.ts +++ b/backend/src/routes/deposit-route.ts @@ -11,7 +11,8 @@ 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, server.sequelize, server.config.MTP_VGL_MARKUP, server.config.EP7_PVGL); + 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, + server.config.MTP_VGL_MARKUP, server.config.EP7_PVGL, server.config.MTP_PVGL, server.config.MTP_PPGL); const SUPPORTED_ENTRYPOINTS = { EPV_06: server.config.EPV_06, diff --git a/backend/src/routes/metadata-routes.ts b/backend/src/routes/metadata-routes.ts index b446d46..1b30bdd 100644 --- a/backend/src/routes/metadata-routes.ts +++ b/backend/src/routes/metadata-routes.ts @@ -7,7 +7,7 @@ import { getNetworkConfig, printRequest } from "../utils/common.js"; import ReturnCode from "../constants/ReturnCode.js"; import ErrorMessage from "../constants/ErrorMessage.js"; import { decode } from "../utils/crypto.js"; -import { PAYMASTER_ADDRESS } from "../constants/Pimlico.js"; +import { PAYMASTER_ADDRESS } from "../constants/Token.js"; import { APIKey } from "../models/api-key.js"; import * as EtherspotAbi from "../abi/EtherspotAbi.js"; import {abi as verifyingPaymasterAbi} from "../abi/VerifyingPaymasterAbi.js"; diff --git a/backend/src/routes/paymaster-routes.ts b/backend/src/routes/paymaster-routes.ts index 9166b09..5b97c76 100644 --- a/backend/src/routes/paymaster-routes.ts +++ b/backend/src/routes/paymaster-routes.ts @@ -4,7 +4,7 @@ import { BigNumber, Wallet, ethers, providers } from "ethers"; import { gql, request as GLRequest } from "graphql-request"; import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager"; import SupportedNetworks from "../../config.json" assert { type: "json" }; -import { PAYMASTER_ADDRESS } from "../constants/Pimlico.js"; +import { PAYMASTER_ADDRESS } from "../constants/Token.js"; import ErrorMessage, { generateErrorMessage } from "../constants/ErrorMessage.js"; import ReturnCode from "../constants/ReturnCode.js"; import { decode } from "../utils/crypto.js"; @@ -16,7 +16,7 @@ import { PaymasterRoutesOpts } from "../types/arka-config-dto.js"; const paymasterRoutes: FastifyPluginAsync = async (server, options: PaymasterRoutesOpts) => { - const {paymaster} = options; + const { paymaster } = options; const SUPPORTED_ENTRYPOINTS = { EPV_06: server.config.EPV_06, @@ -102,6 +102,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server, let sponsorName = '', sponsorImage = ''; let contractWhitelistMode = false; let bundlerApiKey = api_key; + let multiTokenPaymastersV2 = []; const apiKeyEntity = await server.apiKeyRepository.findOneByApiKey(api_key); @@ -141,6 +142,11 @@ const paymasterRoutes: FastifyPluginAsync = async (server, multiTokenPaymasters = JSON.parse(buffer.toString()); } + if (apiKeyEntity.multiTokenPaymastersV2) { + const buffer = Buffer.from(apiKeyEntity.multiTokenPaymastersV2, 'base64'); + multiTokenPaymastersV2 = JSON.parse(buffer.toString()); + } + if (apiKeyEntity.multiTokenOracles) { const buffer = Buffer.from(apiKeyEntity.multiTokenOracles, 'base64'); multiTokenOracles = JSON.parse(buffer.toString()); @@ -196,13 +202,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server, else { if (gasToken && ethers.utils.isAddress(gasToken)) gasToken = ethers.utils.getAddress(gasToken) - if (mode.toLowerCase() == 'multitoken' && - !(multiTokenPaymasters[chainId] && multiTokenPaymasters[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') { + if (useVp && mode.toLowerCase() === 'sponsor') { mode = 'vps'; } @@ -275,7 +275,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server, let paymasterAddress: string; if (customPaymasters[chainId] && customPaymasters[chainId][gasToken]) paymasterAddress = customPaymasters[chainId][gasToken]; else paymasterAddress = PAYMASTER_ADDRESS[chainId][gasToken] - result = await paymaster.pimlico(userOp, bundlerUrl, entryPoint, paymasterAddress, server.log); + result = await paymaster.erc20Paymaster(userOp, bundlerUrl, entryPoint, paymasterAddress, server.log); } else if (epVersion === EPVersions.EPV_07) { if ( !(customPaymastersV2[chainId] && customPaymastersV2[chainId][gasToken]) @@ -288,9 +288,17 @@ const paymasterRoutes: FastifyPluginAsync = async (server, break; } case 'multitoken': { - if (epVersion !== EPVersions.EPV_06) - throw new Error('Currently only EPV06 entryPoint address is supported') - if (!(multiTokenPaymasters[chainId] && multiTokenPaymasters[chainId][gasToken])) + if (epVersion !== EPVersions.EPV_06 && epVersion !== EPVersions.EPV_07) + throw new Error(ErrorMessage.MTP_EP_SUPPORT) + let paymasterAddress: string; + if (epVersion === EPVersions.EPV_06) { + paymasterAddress = multiTokenPaymasters[chainId]?.[gasToken]; + } else { + paymasterAddress = multiTokenPaymastersV2[chainId]?.[gasToken]; + } + if (!paymasterAddress) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK_TOKEN }) + if (!(multiTokenOracles[chainId] && multiTokenOracles[chainId][gasToken]) && + !paymaster.coingeckoPrice.get(`${chainId}-${gasToken}`)) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK_TOKEN }) const date = new Date(); const provider = new providers.JsonRpcProvider(bundlerUrl); @@ -314,7 +322,11 @@ const paymasterRoutes: FastifyPluginAsync = async (server, throw new Error("Oracle is not Defined/Invalid"); if (networkConfig.MultiTokenPaymasterOracleUsed == "chainlink" && !NativeOracles[chainId]) throw new Error("Native Oracle address not set for this chainId") - result = await paymaster.signMultiTokenPaymaster(userOp, str, str1, entryPoint, multiTokenPaymasters[chainId][gasToken], gasToken, multiTokenOracles[chainId] ? multiTokenOracles[chainId][gasToken] : '', bundlerUrl, signer, networkConfig.MultiTokenPaymasterOracleUsed, NativeOracles[chainId], chainId, server.log); + if (epVersion == EPVersions.EPV_06) { + result = await paymaster.signMultiTokenPaymaster(userOp, str, str1, entryPoint, paymasterAddress, gasToken, multiTokenOracles[chainId] ? multiTokenOracles[chainId][gasToken] : '', bundlerUrl, signer, networkConfig.MultiTokenPaymasterOracleUsed, NativeOracles[chainId], chainId, server.log); + } else { + result = await paymaster.signMultiTokenPaymasterV07(userOp, str, str1, entryPoint, paymasterAddress, gasToken, multiTokenOracles[chainId] ? multiTokenOracles[chainId][gasToken] : '', bundlerUrl, signer, networkConfig.MultiTokenPaymasterOracleUsed, NativeOracles[chainId], chainId, server.log); + } break; } case 'vps': { @@ -339,7 +351,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server, const errorMessage: string = generateErrorMessage(ErrorMessage.NO_ACTIVE_SPONSORSHIP_POLICY_FOR_CURRENT_TIME, { walletAddress: apiKeyData?.walletAddress, epVersion: epVersion, chainId: chainId.chainId }); return reply.code(ReturnCode.FAILURE).send({ error: errorMessage }); } - + // get supported networks from sponsorshipPolicy const supportedNetworks: number[] | undefined | null = sponsorshipPolicy.enabledChains; if (!supportedNetworks || !supportedNetworks.includes(chainId.chainId)) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); @@ -375,36 +387,36 @@ const paymasterRoutes: FastifyPluginAsync = async (server, } if (epVersion === EPVersions.EPV_06) { - if(!apiKeyEntity.verifyingPaymasters) { - return reply.code(ReturnCode.FAILURE).send({error: ErrorMessage.VP_NOT_DEPLOYED}); + if (!apiKeyEntity.verifyingPaymasters) { + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.VP_NOT_DEPLOYED }); } const paymasterAddr = JSON.parse(apiKeyEntity.verifyingPaymasters)[chainId.chainId]; - if(!paymasterAddr) { - return reply.code(ReturnCode.FAILURE).send({error: ErrorMessage.VP_NOT_DEPLOYED}); + if (!paymasterAddr) { + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.VP_NOT_DEPLOYED }); } result = await paymaster.signV06(userOp, str, str1, entryPoint, paymasterAddr, bundlerUrl, signer, estimate, server.log); } else { - if(!apiKeyEntity.verifyingPaymastersV2) { - return reply.code(ReturnCode.FAILURE).send({error: ErrorMessage.VP_NOT_DEPLOYED}); + if (!apiKeyEntity.verifyingPaymastersV2) { + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.VP_NOT_DEPLOYED }); } const paymasterAddr = JSON.parse(apiKeyEntity.verifyingPaymastersV2)[chainId.chainId]; - if(!paymasterAddr) { - return reply.code(ReturnCode.FAILURE).send({error: ErrorMessage.VP_NOT_DEPLOYED}); + if (!paymasterAddr) { + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.VP_NOT_DEPLOYED }); } result = await paymaster.signV07(userOp, str, str1, entryPoint, paymasterAddr, bundlerUrl, signer, estimate, server.log); } break; } case 'commonerc20': { - if (epVersion !== EPVersions.EPV_06) - throw new Error('Currently only EPV06 entryPoint address is supported') - const multiTokenRec = await server.multiTokenPaymasterRepository.findOneByChainIdAndTokenAddress(chainId, gasToken) + if (epVersion !== EPVersions.EPV_06 && epVersion !== EPVersions.EPV_07) + throw new Error(ErrorMessage.MTP_EP_SUPPORT) + const multiTokenRec = await server.multiTokenPaymasterRepository.findOneByChainIdEPVersionAndTokenAddress(chainId, gasToken, epVersion) if (multiTokenRec) { const date = new Date(); const provider = new providers.JsonRpcProvider(bundlerUrl); const commonPrivateKey = process.env.MTP_PRIVATE_KEY; - if (!commonPrivateKey) return reply.code(ReturnCode.FAILURE).send({error: ErrorMessage.NO_KEY_SET}) + if (!commonPrivateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.NO_KEY_SET }) const signer = new Wallet(commonPrivateKey, provider) const validUntil = context.validUntil ? new Date(context.validUntil) : date; const validAfter = context.validAfter ? new Date(context.validAfter) : date; @@ -425,7 +437,11 @@ const paymasterRoutes: FastifyPluginAsync = async (server, throw new Error("Oracle is not Defined/Invalid"); if (networkConfig.MultiTokenPaymasterOracleUsed == "chainlink" && !NativeOracles[chainId]) throw new Error("Native Oracle address not set for this chainId") - result = await paymaster.signMultiTokenPaymaster(userOp, str, str1, entryPoint, multiTokenRec.paymasterAddress, gasToken, multiTokenRec.oracleAddress ?? '', bundlerUrl, signer, networkConfig.MultiTokenPaymasterOracleUsed, NativeOracles[chainId], chainId, server.log); + if (epVersion == EPVersions.EPV_06) { + result = await paymaster.signMultiTokenPaymaster(userOp, str, str1, entryPoint, multiTokenRec.paymasterAddress, gasToken, multiTokenRec.oracleAddress ?? '', bundlerUrl, signer, networkConfig.MultiTokenPaymasterOracleUsed, NativeOracles[chainId], chainId, server.log); + } else { + result = await paymaster.signMultiTokenPaymasterV07(userOp, str, str1, entryPoint, multiTokenRec.paymasterAddress, gasToken, multiTokenRec.oracleAddress ?? '', bundlerUrl, signer, networkConfig.MultiTokenPaymasterOracleUsed, NativeOracles[chainId], chainId, server.log); + } } else { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_TOKEN }) } @@ -467,10 +483,10 @@ const paymasterRoutes: FastifyPluginAsync = async (server, for (const key of tokenOracleKeys) { tokenCache.push(paymaster.priceAndMetadata.get(key)) } - return reply.code(ReturnCode.SUCCESS).send({coingeckoCache, nativeTokenCache, tokenCache}) + return reply.code(ReturnCode.SUCCESS).send({ coingeckoCache, nativeTokenCache, tokenCache }) } catch (err) { request.log.error(err); - return reply.code(ReturnCode.FAILURE).send({error: err}) + return reply.code(ReturnCode.FAILURE).send({ error: err }) } }) diff --git a/backend/src/routes/pimlico-routes.ts b/backend/src/routes/token-routes.ts similarity index 96% rename from backend/src/routes/pimlico-routes.ts rename to backend/src/routes/token-routes.ts index 7522f53..c2c691c 100644 --- a/backend/src/routes/pimlico-routes.ts +++ b/backend/src/routes/token-routes.ts @@ -3,7 +3,7 @@ import { Type } from "@sinclair/typebox"; import { FastifyPluginAsync } from "fastify"; import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager"; import SupportedNetworks from "../../config.json" assert { type: "json" }; -import { PAYMASTER_ADDRESS } from "../constants/Pimlico.js"; +import { PAYMASTER_ADDRESS } from "../constants/Token.js"; import ErrorMessage from "../constants/ErrorMessage.js"; import ReturnCode from "../constants/ReturnCode.js"; import { decode } from "../utils/crypto.js"; @@ -11,7 +11,7 @@ import { printRequest, getNetworkConfig } from "../utils/common.js"; import { APIKey } from "../models/api-key.js"; import { ethers } from "ethers"; -const pimlicoRoutes: FastifyPluginAsync = async (server) => { +const tokenRoutes: FastifyPluginAsync = async (server) => { const prefixSecretId = 'arka_'; @@ -126,7 +126,8 @@ const pimlicoRoutes: FastifyPluginAsync = async (server) => { paymasterAddress: record.paymasterAddress, gasToken: ethers.utils.getAddress(record.tokenAddress), chainId: record.chainId, - decimals: record.decimals + decimals: record.decimals, + epVersion: record.epVersion, } }); server.log.info(result, 'getAllCommonERC20PaymasterAddress Response sent: '); @@ -144,4 +145,4 @@ const pimlicoRoutes: FastifyPluginAsync = async (server) => { }; -export default pimlicoRoutes; +export default tokenRoutes; diff --git a/backend/src/routes/whitelist-routes.ts b/backend/src/routes/whitelist-routes.ts index e27cd73..2894254 100644 --- a/backend/src/routes/whitelist-routes.ts +++ b/backend/src/routes/whitelist-routes.ts @@ -12,7 +12,8 @@ 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, server.sequelize, server.config.MTP_VGL_MARKUP, server.config.EP7_PVGL); + 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, + server.config.MTP_VGL_MARKUP, server.config.EP7_PVGL, server.config.MTP_PVGL, server.config.MTP_PPGL); const SUPPORTED_ENTRYPOINTS = { EPV_06: server.config.EPV_06, diff --git a/backend/src/server.ts b/backend/src/server.ts index ab00fab..fd87b58 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -9,7 +9,7 @@ import fetch from 'node-fetch'; import sequelizePlugin from './plugins/sequelizePlugin.js'; import config from './plugins/config.js'; import EtherspotChainlinkOracleAbi from './abi/EtherspotChainlinkOracleAbi.js'; -import PimlicoAbi from './abi/PimlicoAbi.js'; +import ERC20PaymasterAbi from './abi/ERC20PaymasterAbi.js'; import PythOracleAbi from './abi/PythOracleAbi.js'; import { getNetworkConfig } from './utils/common.js'; import { checkDeposit } from './utils/monitorTokenPaymaster.js'; @@ -19,7 +19,7 @@ import adminRoutes from './routes/admin-routes.js'; import depositRoutes from './routes/deposit-route.js'; import metadataRoutes from './routes/metadata-routes.js'; import paymasterRoutes from './routes/paymaster-routes.js'; -import pimlicoRoutes from './routes/pimlico-routes.js'; +import tokenRoutes from './routes/token-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" }; @@ -61,7 +61,8 @@ const initializeServer = async (): Promise => { // Register the sequelizePlugin await server.register(sequelizePlugin); - 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, server.config.MTP_VGL_MARKUP, server.config.EP7_PVGL); + 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, + server.config.MTP_VGL_MARKUP, server.config.EP7_PVGL, server.config.MTP_PVGL, server.config.MTP_PPGL); // Synchronize all models await server.sequelize.sync(); @@ -101,7 +102,7 @@ const initializeServer = async (): Promise => { await server.register(depositRoutes); - await server.register(pimlicoRoutes); + await server.register(tokenRoutes); await server.register(whitelistRoutes); @@ -161,7 +162,7 @@ const initializeServer = async (): Promise => { const provider = new providers.JsonRpcProvider(networkConfig.bundler); const signer = new ethers.Wallet(process.env.CRON_PRIVATE_KEY ?? '', provider); deployedPaymasters.forEach(async (deployedPaymaster) => { - const paymasterContract = new ethers.Contract(deployedPaymaster, PimlicoAbi, signer) + const paymasterContract = new ethers.Contract(deployedPaymaster, ERC20PaymasterAbi, signer) const pythMainnetChains = configData?.pythMainnetChainIds?.split(',') ?? []; const pythTestnetChains = configData?.pythTestnetChainIds?.split(',') ?? []; if (pythMainnetChains?.includes(chain) || pythTestnetChains?.includes(chain)) {