diff --git a/backend/package.json b/backend/package.json index b1204ea..f227921 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "arka", - "version": "1.6.2", + "version": "1.6.3", "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/constants/ChainlinkOracles.ts b/backend/src/constants/ChainlinkOracles.ts new file mode 100644 index 0000000..ffe900a --- /dev/null +++ b/backend/src/constants/ChainlinkOracles.ts @@ -0,0 +1,33 @@ +// Chainlink Native tokens/USD addresses +export const NativeOracles: Record = { + 1: "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419", // Ethereum Mainnet + 10: "0x13e3Ee699D1909E989722E753853AE30b17e08c5", // Optimism + 56: "0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE", // BNB + 97: "0x2514895c72f50D8bd4B4F9b1110F0D6bD2c97526", // BNB Testnet + 100: "0x22441d81416430A54336aB28765abd31a792Ad37", // Gnosis + 137: "0xAB594600376Ec9fD91F8e885dADF0CE036862dE0", // Polygon Mainnet + 8453: "0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70", // Base Mainnet + 80002: "0x001382149eBa3441043c1c66972b4772963f5D43", // Polygon Amoy + 84532: "0x4aDC67696bA383F43DD60A9e78F2C97Fbbfc7cb1", // Base Sepolia + 43113: "0x5498BB86BC934c8D34FDA08E81D444153d0D06aD", // Avalanche Testnet + 43114: "0x0A77230d17318075983913bC2145DB16C7366156", // Avalanche Mainnet + 42161: "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612", // Arbitrum Mainnet + 421614: "0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165", // Arbitrum Sepolia + 59144: "0x3c6Cd9Cc7c7a4c2Cf5a82734CD249D7D593354dA", // Linea Mainnet + 534352: "0x6bF14CB0A831078629D993FDeBcB182b21A8774C", // Scroll Mainnet + 534351: "0x59F1ec1f10bD7eD9B938431086bC1D9e233ECf41", // Scroll Sepolia + + // Yet to Support on Arka + 44787: "0x022F9dCC73C5Fb43F2b4eF2EF9ad3eDD1D853946", // Celo Alfajores testnet + 42220: "0x0568fD19986748cEfF3301e55c0eb1E729E0Ab7e", // Celo Mainnet + 1088: "0xD4a5Bb03B5D66d9bf81507379302Ac2C2DFDFa6D", // Metis Mainnet + 1284: "0x4497B606be93e773bbA5eaCFCb2ac5E2214220Eb", // Moonbeam Mainnet + 1285: "0x3f8BFbDc1e79777511c00Ad8591cef888C2113C1", // Moonriver Mainnet + 4002: "0xe04676B9A9A2973BCb0D1478b5E1E9098BBB7f3D", // Fantom Testnet + 250: "0xf4766552D15AE4d256Ad41B6cf2933482B0680dc", // Fantom + 324: "0x6D41d1dc818112880b40e26BD6FD347E41008eDA", // zkSync Mainnet + 300: "0xfEefF7c3fB57d18C5C6Cdd71e45D2D0b4F9377bF", // zkSync Testnet + 1101: "0x97d9F9A00dEE0004BE8ca0A8fa374d486567eE2D", // Polygon zkEVM + 2442: "0xd94522a6feF7779f672f4C88eb672da9222f2eAc", // Polygon zkEVM Cardona Testnet + +} \ No newline at end of file diff --git a/backend/src/paymaster/index.ts b/backend/src/paymaster/index.ts index d4c66a3..e918e68 100644 --- a/backend/src/paymaster/index.ts +++ b/backend/src/paymaster/index.ts @@ -208,7 +208,8 @@ export class Paymaster { return paymasterAndData; } - async getQuotesMultiToken(userOp: any, entryPoint: string, chainId: number, multiTokenPaymasters: any, tokens_list: string[], oracles: any, bundlerRpc: string, oracleName: string, log?: FastifyBaseLogger) { + async getQuotesMultiToken(userOp: any, entryPoint: string, chainId: number, multiTokenPaymasters: any, tokens_list: string[], oracles: any, + bundlerRpc: string, oracleName: string, nativeOracleAddress: string, log?: FastifyBaseLogger) { try { const provider = new providers.JsonRpcProvider(bundlerRpc); const quotes = [], unsupportedTokens = []; @@ -240,6 +241,14 @@ export class Paymaster { const paymasterContract = new ethers.Contract(result.paymasterAddress , MultiTokenPaymasterAbi, provider); result.postOpGas = await paymasterContract.UNACCOUNTED_COST; + let ETHUSDPrice: any, ETHUSDPriceDecimal; + if (oracleName === "chainlink") { + const nativeOracleContract = new ethers.Contract(nativeOracleAddress, ChainlinkOracleAbi, provider); + const ETHprice = await nativeOracleContract.latestRoundData(); + ETHUSDPrice = ETHprice.answer; + ETHUSDPriceDecimal = await nativeOracleContract.decimals(); + result.etherUSDExchangeRate = ETHprice.answer; + } for (let i = 0; i < tokens_list.length; i++) { const gasToken = tokens_list[i]; if (!(multiTokenPaymasters[chainId] && multiTokenPaymasters[chainId][gasToken]) && @@ -258,20 +267,28 @@ export class Paymaster { ethPrice = Number(ethers.utils.formatUnits(ETHprice, 18 - decimals)).toFixed(0); } else if (oracleName === "chainlink") { const chainlinkContract = new ethers.Contract(oracleAddress, ChainlinkOracleAbi, provider); - const ETHprice = await chainlinkContract.latestRoundData(); - ethPrice = ETHprice.answer; + const ETHpriceDecimal = await chainlinkContract.decimals(); + let ETHprice = await chainlinkContract.latestAnswer(); + ETHUSDPrice = ethers.utils.formatUnits(ETHUSDPrice, ETHUSDPriceDecimal); + ETHprice = ethers.utils.formatUnits(ETHprice, ETHpriceDecimal); + ETHUSDPrice = ethers.utils.parseEther(ETHUSDPrice); + ETHprice = ethers.utils.parseEther(ETHprice); + const tokenContract = new ethers.Contract(gasToken, ERC20Abi, provider); + const decimals = Number(await tokenContract.decimals()); + ethPrice = ethers.utils.parseUnits((ETHUSDPrice/ETHprice).toFixed(decimals), decimals).toString() } else { const ecContract = new ethers.Contract(oracleAddress, EtherspotChainlinkOracleAbi, provider); const ETHprice = await ecContract.cachedPrice(); ethPrice = ETHprice } - result.etherUSDExchangeRate = BigNumber.from(ethPrice).toHexString(); + if (result.etherUSDExchangeRate === "0x") + result.etherUSDExchangeRate = BigNumber.from(ethPrice).toHexString(); const symbol = await tokenContract.symbol(); quotes.push({ token: gasToken, symbol: symbol, decimals: decimals, - etherTokenExchangeRate: ethPrice, + etherTokenExchangeRate: BigNumber.from(ethPrice).toHexString(), serviceFeePercent: (this.multiTokenMarkUp/10000 - 100) }) } @@ -288,11 +305,12 @@ export class Paymaster { } async signMultiTokenPaymaster(userOp: any, validUntil: string, validAfter: string, entryPoint: string, paymasterAddress: string, - feeToken: string, oracleAggregator: string, bundlerRpc: string, signer: Wallet, oracleName: string, log?: FastifyBaseLogger) { + feeToken: string, oracleAggregator: string, bundlerRpc: string, signer: Wallet, oracleName: string, nativeOracleAddress: string, log?: FastifyBaseLogger) { try { const provider = new providers.JsonRpcProvider(bundlerRpc); const paymasterContract = new ethers.Contract(paymasterAddress, MultiTokenPaymasterAbi, provider); let ethPrice = ""; + if (oracleName === "orochi") { const oracleContract = new ethers.Contract(oracleAggregator, OrochiOracleAbi, provider); const ETHprice = await oracleContract.getLatestData(1, ethers.utils.hexlify(ethers.utils.toUtf8Bytes('ETH')).padEnd(42, '0')) @@ -302,9 +320,19 @@ export class Paymaster { if (decimals < 18) ethPrice = Number(ethers.utils.formatUnits(ETHprice, 18 - decimals)).toFixed(0); } else if (oracleName === "chainlink") { + const nativeOracleContract = new ethers.Contract(nativeOracleAddress, ChainlinkOracleAbi, provider); + let ETHUSDPrice = await nativeOracleContract.latestAnswer(); const chainlinkContract = new ethers.Contract(oracleAggregator, ChainlinkOracleAbi, provider); - const ETHprice = await chainlinkContract.latestRoundData(); - ethPrice = ETHprice.answer; + const ETHUSDPriceDecimal = await nativeOracleContract.decimals(); + const ETHpriceDecimal = await chainlinkContract.decimals(); + let ETHprice = await chainlinkContract.latestAnswer(); + ETHUSDPrice = ethers.utils.formatUnits(ETHUSDPrice, ETHUSDPriceDecimal); + ETHprice = ethers.utils.formatUnits(ETHprice, ETHpriceDecimal); + ETHUSDPrice = ethers.utils.parseEther(ETHUSDPrice); + ETHprice = ethers.utils.parseEther(ETHprice); + const tokenContract = new ethers.Contract(feeToken, ERC20Abi, provider); + const decimals = Number(await tokenContract.decimals()); + ethPrice = ethers.utils.parseUnits((ETHUSDPrice/ETHprice).toFixed(decimals), decimals).toString() } else { const ecContract = new ethers.Contract(oracleAggregator, EtherspotChainlinkOracleAbi, provider); const ETHprice = await ecContract.cachedPrice(); diff --git a/backend/src/routes/paymaster-routes.ts b/backend/src/routes/paymaster-routes.ts index 08b469d..8b2e443 100644 --- a/backend/src/routes/paymaster-routes.ts +++ b/backend/src/routes/paymaster-routes.ts @@ -12,6 +12,7 @@ import { decode } from "../utils/crypto.js"; import { printRequest, getNetworkConfig } from "../utils/common.js"; import { SponsorshipPolicy } from "../models/sponsorship-policy.js"; import { DEFAULT_EP_VERSION, EPVersions, getEPVersion } from "../types/sponsorship-policy-dto.js"; +import { NativeOracles } from "../constants/ChainlinkOracles.js"; const paymasterRoutes: 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); @@ -216,7 +217,9 @@ const paymasterRoutes: FastifyPluginAsync = async (server) => { !(networkConfig.MultiTokenPaymasterOracleUsed == "orochi" || networkConfig.MultiTokenPaymasterOracleUsed == "chainlink" || networkConfig.MultiTokenPaymasterOracleUsed == "etherspotChainlink")) throw new Error("Oracle is not Defined/Invalid"); if (!multiTokenPaymasters[chainId]) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }) - result = await paymaster.getQuotesMultiToken(userOp, entryPoint, chainId, multiTokenPaymasters, tokens_list, multiTokenOracles, bundlerUrl, networkConfig.MultiTokenPaymasterOracleUsed, server.log); + if (networkConfig.MultiTokenPaymasterOracleUsed == "chainlink" && !NativeOracles[chainId]) + throw new Error("Native Oracle address not set for this chainId") + result = await paymaster.getQuotesMultiToken(userOp, entryPoint, chainId, multiTokenPaymasters, tokens_list, multiTokenOracles, bundlerUrl, networkConfig.MultiTokenPaymasterOracleUsed, NativeOracles[chainId], server.log); } else { if (gasToken && ethers.utils.isAddress(gasToken)) gasToken = ethers.utils.getAddress(gasToken) @@ -334,7 +337,9 @@ const paymasterRoutes: FastifyPluginAsync = async (server) => { if (!networkConfig.MultiTokenPaymasterOracleUsed || !(networkConfig.MultiTokenPaymasterOracleUsed == "orochi" || networkConfig.MultiTokenPaymasterOracleUsed == "chainlink" || networkConfig.MultiTokenPaymasterOracleUsed == "etherspotChainlink")) throw new Error("Oracle is not Defined/Invalid"); - result = await paymaster.signMultiTokenPaymaster(userOp, str, str1, entryPoint, multiTokenPaymasters[chainId][gasToken], gasToken, multiTokenOracles[chainId][gasToken], bundlerUrl, signer, networkConfig.MultiTokenPaymasterOracleUsed, server.log); + 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][gasToken], bundlerUrl, signer, networkConfig.MultiTokenPaymasterOracleUsed, NativeOracles[chainId], server.log); break; } default: {