diff --git a/backend/migrations/20241204071603-add-vp-address-attribute.cjs b/backend/migrations/20241204071603-add-vp-address-attribute.cjs new file mode 100644 index 0000000..9db5bd1 --- /dev/null +++ b/backend/migrations/20241204071603-add-vp-address-attribute.cjs @@ -0,0 +1,45 @@ +require('dotenv').config(); +const { DataTypes, TEXT } = require('sequelize'); + +async function up({ context: queryInterface }) { + await queryInterface.addColumn( + {schema: process.env.DATABASE_SCHEMA_NAME, tableName: 'api_keys'}, + 'VERIFYING_PAYMASTERS', + { + type: DataTypes.TEXT, + allowNull: true + } + ); + + await queryInterface.addColumn( + {schema: process.env.DATABASE_SCHEMA_NAME, tableName: 'api_keys'}, + 'VERIFYING_PAYMASTERS_V2', + { + type: DataTypes.TEXT, + allowNull: true + } + ); +} + +async function down({ context: queryInterface }) { + await queryInterface.removeColumn( + {schema: process.env.DATABASE_SCHEMA_NAME, tableName: 'api_keys'}, + 'VERIFYING_PAYMASTERS', + { + type: DataTypes.TEXT, + allowNull: true + } + ); + + await queryInterface.removeColumn( + {schema: process.env.DATABASE_SCHEMA_NAME, tableName: 'api_keys'}, + 'VERIFYING_PAYMASTERS_V2', + { + type: DataTypes.TEXT, + allowNull: true + } + ); +} + +/** @type {import('sequelize-cli').Migration} */ +module.exports = {up, down}; diff --git a/backend/package.json b/backend/package.json index aa58970..519577e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "arka", - "version": "1.7.0", + "version": "1.8.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/VerifyingPaymasterAbi.ts b/backend/src/abi/VerifyingPaymasterAbi.ts new file mode 100644 index 0000000..8603c92 --- /dev/null +++ b/backend/src/abi/VerifyingPaymasterAbi.ts @@ -0,0 +1,418 @@ +export const abi = [ + { + "inputs": [ + { + "internalType": "contract IEntryPoint", + "name": "_entryPoint", + "type": "address" + }, + { + "internalType": "address", + "name": "_verifyingSigner", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "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": "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": "uint256", + "name": "callGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "verificationGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "preVerificationGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxFeePerGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPriorityFeePerGas", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "paymasterAndData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct UserOperation", + "name": "userOp", + "type": "tuple" + }, + { + "internalType": "uint48", + "name": "validUntil", + "type": "uint48" + }, + { + "internalType": "uint48", + "name": "validAfter", + "type": "uint48" + } + ], + "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": "uint48", + "name": "validUntil", + "type": "uint48" + }, + { + "internalType": "uint48", + "name": "validAfter", + "type": "uint48" + }, + { + "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" + } + ], + "name": "postOp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "senderNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "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": "uint256", + "name": "callGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "verificationGasLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "preVerificationGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxFeePerGas", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPriorityFeePerGas", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "paymasterAndData", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "internalType": "struct UserOperation", + "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 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" + } +]; + +export const byteCode = + '0x60c06040523480156200001157600080fd5b5060405162001703380380620017038339810160408190526200003491620000c2565b81620000403362000059565b6001600160a01b039081166080521660a0525062000101565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6001600160a01b0381168114620000bf57600080fd5b50565b60008060408385031215620000d657600080fd5b8251620000e381620000a9565b6020840151909250620000f681620000a9565b809150509250929050565b60805160a0516115a46200015f6000396000818161013f0152610c820152600081816102880152818161038601528181610450015281816105730152818161063a015281816106ca0152818161077d0152610a0401526115a46000f3fe6080604052600436106100f35760003560e01c8063a9a234091161008a578063c399ec8811610059578063c399ec88146102df578063d0e30db0146102f4578063f2fde38b146102fc578063f465c77e1461031c57600080fd5b8063a9a2340914610256578063b0d691fe14610276578063bb9fe6bf146102aa578063c23a5cea146102bf57600080fd5b80638da5cb5b116100c65780638da5cb5b146101a057806394d4ad60146101cb57806394e1fc19146101fb5780639c90b4431461022957600080fd5b80630396cb60146100f8578063205c28781461010d57806323d9ac9b1461012d578063715018a61461018b575b600080fd5b61010b610106366004611055565b61034a565b005b34801561011957600080fd5b5061010b6101283660046110a4565b6103fc565b34801561013957600080fd5b506101617f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561019757600080fd5b5061010b610494565b3480156101ac57600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610161565b3480156101d757600080fd5b506101eb6101e6366004611112565b6104a8565b6040516101829493929190611154565b34801561020757600080fd5b5061021b6102163660046111f2565b6104e5565b604051908152602001610182565b34801561023557600080fd5b5061021b610244366004611250565b60016020526000908152604090205481565b34801561026257600080fd5b5061010b61027136600461126d565b61054f565b34801561028257600080fd5b506101617f000000000000000000000000000000000000000000000000000000000000000081565b3480156102b657600080fd5b5061010b610569565b3480156102cb57600080fd5b5061010b6102da366004611250565b6105ed565b3480156102eb57600080fd5b5061021b610699565b61010b61074f565b34801561030857600080fd5b5061010b610317366004611250565b6107d7565b34801561032857600080fd5b5061033c6103373660046112cd565b610893565b604051610182929190611386565b6103526108b7565b6040517f0396cb6000000000000000000000000000000000000000000000000000000000815263ffffffff821660048201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1690630396cb609034906024016000604051808303818588803b1580156103e057600080fd5b505af11580156103f4573d6000803e3d6000fd5b505050505050565b6104046108b7565b6040517f205c287800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063205c287890604401600060405180830381600087803b1580156103e057600080fd5b61049c6108b7565b6104a66000610938565b565b60008036816104bb6054601487896113a8565b8101906104c891906113d2565b90945092506104da85605481896113a8565b949793965094505050565b60006104f0846109ad565b73ffffffffffffffffffffffffffffffffffffffff8535166000908152600160209081526040918290205491516105309392469230928991899101611405565b6040516020818303038152906040528051906020012090509392505050565b6105576109ec565b61056384848484610a8b565b50505050565b6105716108b7565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663bb9fe6bf6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156105d957600080fd5b505af1158015610563573d6000803e3d6000fd5b6105f56108b7565b6040517fc23a5cea00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063c23a5cea90602401600060405180830381600087803b15801561067e57600080fd5b505af1158015610692573d6000803e3d6000fd5b5050505050565b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906370a0823190602401602060405180830381865afa158015610726573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061074a9190611462565b905090565b6040517fb760faf90000000000000000000000000000000000000000000000000000000081523060048201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063b760faf99034906024016000604051808303818588803b15801561067e57600080fd5b6107df6108b7565b73ffffffffffffffffffffffffffffffffffffffff8116610887576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b61089081610938565b50565b6060600061089f6109ec565b6108aa858585610aed565b915091505b935093915050565b60005473ffffffffffffffffffffffffffffffffffffffff1633146104a6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161087e565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60603660006109c061012085018561147b565b915091508360208184030360405194506020810185016040528085528082602087013750505050919050565b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146104a6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f53656e646572206e6f7420456e747279506f696e740000000000000000000000604482015260640161087e565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600d60248201527f6d757374206f7665727269646500000000000000000000000000000000000000604482015260640161087e565b6060600080803681610b066101e66101208b018b61147b565b929650909450925090506040811480610b1f5750604181145b610bad57604080517f08c379a00000000000000000000000000000000000000000000000000000000081526020600482015260248101919091527f566572696679696e675061796d61737465723a20696e76616c6964207369676e60448201527f6174757265206c656e67746820696e207061796d6173746572416e6444617461606482015260840161087e565b6000610bf0610bbd8b87876104e5565b7f19457468657265756d205369676e6564204d6573736167653a0a3332000000006000908152601c91909152603c902090565b73ffffffffffffffffffffffffffffffffffffffff8b35166000908152600160205260408120805492935090610c25836114e0565b9190505550610c6a8184848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610d1292505050565b73ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1614610ce757610cc860018686610d36565b60405180602001604052806000815250909650965050505050506108af565b610cf360008686610d36565b6040805160208101909152600081529b909a5098505050505050505050565b6000806000610d218585610d6e565b91509150610d2e81610db3565b509392505050565b600060d08265ffffffffffff16901b60a08465ffffffffffff16901b85610d5e576000610d61565b60015b60ff161717949350505050565b6000808251604103610da45760208301516040840151606085015160001a610d9887828585610f66565b94509450505050610dac565b506000905060025b9250929050565b6000816004811115610dc757610dc761153f565b03610dcf5750565b6001816004811115610de357610de361153f565b03610e4a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604482015260640161087e565b6002816004811115610e5e57610e5e61153f565b03610ec5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e67746800604482015260640161087e565b6003816004811115610ed957610ed961153f565b03610890576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c60448201527f7565000000000000000000000000000000000000000000000000000000000000606482015260840161087e565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115610f9d575060009050600361104c565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015610ff1573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff81166110455760006001925092505061104c565b9150600090505b94509492505050565b60006020828403121561106757600080fd5b813563ffffffff8116811461107b57600080fd5b9392505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461089057600080fd5b600080604083850312156110b757600080fd5b82356110c281611082565b946020939093013593505050565b60008083601f8401126110e257600080fd5b50813567ffffffffffffffff8111156110fa57600080fd5b602083019150836020828501011115610dac57600080fd5b6000806020838503121561112557600080fd5b823567ffffffffffffffff81111561113c57600080fd5b611148858286016110d0565b90969095509350505050565b600065ffffffffffff8087168352808616602084015250606060408301528260608301528284608084013760006080848401015260807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f850116830101905095945050505050565b600061016082840312156111d157600080fd5b50919050565b803565ffffffffffff811681146111ed57600080fd5b919050565b60008060006060848603121561120757600080fd5b833567ffffffffffffffff81111561121e57600080fd5b61122a868287016111be565b935050611239602085016111d7565b9150611247604085016111d7565b90509250925092565b60006020828403121561126257600080fd5b813561107b81611082565b6000806000806060858703121561128357600080fd5b84356003811061129257600080fd5b9350602085013567ffffffffffffffff8111156112ae57600080fd5b6112ba878288016110d0565b9598909750949560400135949350505050565b6000806000606084860312156112e257600080fd5b833567ffffffffffffffff8111156112f957600080fd5b611305868287016111be565b9660208601359650604090950135949350505050565b6000815180845260005b8181101561134157602081850181015186830182015201611325565b81811115611353576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b604081526000611399604083018561131b565b90508260208301529392505050565b600080858511156113b857600080fd5b838611156113c557600080fd5b5050820193919092039150565b600080604083850312156113e557600080fd5b6113ee836111d7565b91506113fc602084016111d7565b90509250929050565b60c08152600061141860c083018961131b565b60208301979097525073ffffffffffffffffffffffffffffffffffffffff949094166040850152606084019290925265ffffffffffff90811660808401521660a090910152919050565b60006020828403121561147457600080fd5b5051919050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18436030181126114b057600080fd5b83018035915067ffffffffffffffff8211156114cb57600080fd5b602001915036819003821315610dac57600080fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611538577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea264697066735822122032b6530a3aa849e53bbced2adebb74c49f731030bc0428bd18e6ba5c8fbf2d3d64736f6c634300080f0033'; diff --git a/backend/src/abi/VerifyingPaymasterAbiV2.ts b/backend/src/abi/VerifyingPaymasterAbiV2.ts new file mode 100644 index 0000000..1c6dd17 --- /dev/null +++ b/backend/src/abi/VerifyingPaymasterAbiV2.ts @@ -0,0 +1,315 @@ +export const abi = [ + { + inputs: [ + { + internalType: 'contract IEntryPoint', + name: '_entryPoint', + type: 'address', + }, + { + internalType: 'address', + name: '_verifyingSigner', + type: 'address', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { 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: [{ internalType: 'address', name: 'owner', type: 'address' }], + name: 'OwnableInvalidOwner', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'account', type: 'address' }], + name: 'OwnableUnauthorizedAccount', + type: 'error', + }, + { + 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: false, + internalType: 'address', + name: 'newVerifyingSigner', + type: 'address', + }, + ], + name: 'VerifyingSignerUpdated', + type: 'event', + }, + { + 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: '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: 'uint48', name: 'validUntil', type: 'uint48' }, + { internalType: 'uint48', name: 'validAfter', type: 'uint48' }, + ], + 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: 'uint48', name: 'validUntil', type: 'uint48' }, + { internalType: 'uint48', name: 'validAfter', type: 'uint48' }, + { 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: 'newOwner', type: 'address' }, + ], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'unlockStake', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: '_newVerifyingSigner', + type: 'address', + }, + ], + name: 'updateVerifyingSigner', + 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 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', + }, +]; + +export const byteCode = + '0x60a06040523480156200001157600080fd5b5060405162001417380380620014178339810160408190526200003491620001ca565b8133806200005d57604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b6200006881620000a1565b506200007481620000f1565b6001600160a01b03908116608052600180546001600160a01b031916929091169190911790555062000234565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6040516301ffc9a760e01b815263122a0e9b60e31b60048201526001600160a01b038216906301ffc9a790602401602060405180830381865afa1580156200013d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000163919062000209565b620001b15760405162461bcd60e51b815260206004820152601e60248201527f49456e747279506f696e7420696e74657266616365206d69736d617463680000604482015260640162000054565b50565b6001600160a01b0381168114620001b157600080fd5b60008060408385031215620001de57600080fd5b8251620001eb81620001b4565b6020840151909250620001fe81620001b4565b809150509250929050565b6000602082840312156200021c57600080fd5b815180151581146200022d57600080fd5b9392505050565b608051611196620002816000396000818161027b01528181610331015281816103c80152818161061e015281816106b801528181610728015281816107b5015261087d01526111966000f3fe6080604052600436106100f35760003560e01c806393b941211161008a578063c23a5cea11610059578063c23a5cea146102b2578063c399ec88146102d2578063d0e30db0146102e7578063f2fde38b146102ef57600080fd5b806393b941211461021957806394d4ad6014610239578063b0d691fe14610269578063bb9fe6bf1461029d57600080fd5b80635829c5f5116100c65780635829c5f514610198578063715018a6146101c65780637c627b21146101db5780638da5cb5b146101fb57600080fd5b80630396cb60146100f8578063205c28781461010d57806323d9ac9b1461012d57806352b7512c1461016a575b600080fd5b61010b610106366004610d3c565b61030f565b005b34801561011957600080fd5b5061010b610128366004610d7e565b61039a565b34801561013957600080fd5b5060015461014d906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561017657600080fd5b5061018a610185366004610dc3565b61040c565b604051610161929190610e11565b3480156101a457600080fd5b506101b86101b3366004610e81565b610430565b604051908152602001610161565b3480156101d257600080fd5b5061010b610540565b3480156101e757600080fd5b5061010b6101f6366004610f28565b610554565b34801561020757600080fd5b506000546001600160a01b031661014d565b34801561022557600080fd5b5061010b610234366004610f93565b610570565b34801561024557600080fd5b50610259610254366004610fb0565b6105cc565b6040516101619493929190610ff2565b34801561027557600080fd5b5061014d7f000000000000000000000000000000000000000000000000000000000000000081565b3480156102a957600080fd5b5061010b610614565b3480156102be57600080fd5b5061010b6102cd366004610f93565b610691565b3480156102de57600080fd5b506101b8610710565b61010b6107a0565b3480156102fb57600080fd5b5061010b61030a366004610f93565b610802565b610317610845565b604051621cb65b60e51b815263ffffffff821660048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690630396cb609034906024016000604051808303818588803b15801561037e57600080fd5b505af1158015610392573d6000803e3d6000fd5b505050505050565b6103a2610845565b60405163040b850f60e31b81526001600160a01b038381166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063205c287890604401600060405180830381600087803b15801561037e57600080fd5b60606000610418610872565b6104238585856108e2565b915091505b935093915050565b60008335806020860135610447604088018861103e565b604051610455929190611085565b60405190819003902061046b606089018961103e565b604051610479929190611085565b604051908190039020608089013561049460e08b018b61103e565b6104a391603491601491611095565b6104ac916110bf565b604080516001600160a01b0390971660208801528601949094526060850192909252608084015260a08084019190915260c08084019290925287013560e0830152860135610100820152466101208201523061014082015265ffffffffffff80861661016083015284166101808201526101a001604051602081830303815290604052805190602001209150509392505050565b610548610845565b6105526000610a77565b565b61055c610872565b6105698585858585610ac7565b5050505050565b610578610845565b600180546001600160a01b0319166001600160a01b0383169081179091556040519081527f51d754ac8f7adf515a023f2c423e01ef97817c1af33cb63b36f1fe12fde2d91a9060200160405180910390a150565b60008036816105de8560348189611095565b8101906105eb91906110dd565b909450925085856105fe60346040611110565b610609928290611095565b949793965094505050565b61061c610845565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663bb9fe6bf6040518163ffffffff1660e01b8152600401600060405180830381600087803b15801561067757600080fd5b505af115801561068b573d6000803e3d6000fd5b50505050565b610699610845565b60405163611d2e7560e11b81526001600160a01b0382811660048301527f0000000000000000000000000000000000000000000000000000000000000000169063c23a5cea90602401600060405180830381600087803b1580156106fc57600080fd5b505af1158015610569573d6000803e3d6000fd5b6040516370a0823160e01b81523060048201526000907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015610777573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061079b9190611131565b905090565b60405163b760faf960e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169063b760faf99034906024016000604051808303818588803b1580156106fc57600080fd5b61080a610845565b6001600160a01b03811661083957604051631e4fbdf760e01b8152600060048201526024015b60405180910390fd5b61084281610a77565b50565b6000546001600160a01b031633146105525760405163118cdaa760e01b8152336004820152602401610830565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146105525760405162461bcd60e51b815260206004820152601560248201527414d95b99195c881b9bdd08115b9d1c9e541bda5b9d605a1b6044820152606401610830565b60606000808036816108fa61025460e08b018b61103e565b9296509094509250905060408114806109135750604181145b610987576040805162461bcd60e51b81526020600482015260248101919091527f566572696679696e675061796d61737465723a20696e76616c6964207369676e60448201527f6174757265206c656e67746820696e207061796d6173746572416e64446174616064820152608401610830565b60006109ca6109978b8787610430565b7f19457468657265756d205369676e6564204d6573736167653a0a3332000000006000908152601c91909152603c902090565b9050610a0c8184848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610aff92505050565b6001546001600160a01b03908116911614610a4c57610a2d60018686610b2b565b6040518060200160405280600081525090965096505050505050610428565b610a5860008686610b2b565b6040805160208101909152600081529b909a5098505050505050505050565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b60405162461bcd60e51b815260206004820152600d60248201526c6d757374206f7665727269646560981b6044820152606401610830565b600080600080610b0f8686610b63565b925092509250610b1f8282610bb0565b50909150505b92915050565b600060d08265ffffffffffff16901b60a08465ffffffffffff16901b85610b53576000610b56565b60015b60ff161717949350505050565b60008060008351604103610b9d5760208401516040850151606086015160001a610b8f88828585610c6d565b955095509550505050610ba9565b50508151600091506002905b9250925092565b6000826003811115610bc457610bc461114a565b03610bcd575050565b6001826003811115610be157610be161114a565b03610bff5760405163f645eedf60e01b815260040160405180910390fd5b6002826003811115610c1357610c1361114a565b03610c345760405163fce698f760e01b815260048101829052602401610830565b6003826003811115610c4857610c4861114a565b03610c69576040516335e2f38360e21b815260048101829052602401610830565b5050565b600080807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115610ca85750600091506003905082610d32565b604080516000808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015610cfc573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116610d2857506000925060019150829050610d32565b9250600091508190505b9450945094915050565b600060208284031215610d4e57600080fd5b813563ffffffff81168114610d6257600080fd5b9392505050565b6001600160a01b038116811461084257600080fd5b60008060408385031215610d9157600080fd5b8235610d9c81610d69565b946020939093013593505050565b60006101208284031215610dbd57600080fd5b50919050565b600080600060608486031215610dd857600080fd5b833567ffffffffffffffff811115610def57600080fd5b610dfb86828701610daa565b9660208601359650604090950135949350505050565b604081526000835180604084015260005b81811015610e3f5760208187018101516060868401015201610e22565b506000606082850101526060601f19601f8301168401019150508260208301529392505050565b803565ffffffffffff81168114610e7c57600080fd5b919050565b600080600060608486031215610e9657600080fd5b833567ffffffffffffffff811115610ead57600080fd5b610eb986828701610daa565b935050610ec860208501610e66565b9150610ed660408501610e66565b90509250925092565b60008083601f840112610ef157600080fd5b50813567ffffffffffffffff811115610f0957600080fd5b602083019150836020828501011115610f2157600080fd5b9250929050565b600080600080600060808688031215610f4057600080fd5b853560038110610f4f57600080fd5b9450602086013567ffffffffffffffff811115610f6b57600080fd5b610f7788828901610edf565b9699909850959660408101359660609091013595509350505050565b600060208284031215610fa557600080fd5b8135610d6281610d69565b60008060208385031215610fc357600080fd5b823567ffffffffffffffff811115610fda57600080fd5b610fe685828601610edf565b90969095509350505050565b600065ffffffffffff808716835280861660208401525060606040830152826060830152828460808401376000608084840101526080601f19601f850116830101905095945050505050565b6000808335601e1984360301811261105557600080fd5b83018035915067ffffffffffffffff82111561107057600080fd5b602001915036819003821315610f2157600080fd5b8183823760009101908152919050565b600080858511156110a557600080fd5b838611156110b257600080fd5b5050820193919092039150565b80356020831015610b2557600019602084900360031b1b1692915050565b600080604083850312156110f057600080fd5b6110f983610e66565b915061110760208401610e66565b90509250929050565b80820180821115610b2557634e487b7160e01b600052601160045260246000fd5b60006020828403121561114357600080fd5b5051919050565b634e487b7160e01b600052602160045260246000fdfea2646970667358221220b3dee56d54c33e3d801b74ef60dfdbf85dc7548b852fda6106bd54223fbdaf2e64736f6c634300081700330000000000000000000000000000000071727de22e5e9d8baf0edac6f37da032000000000000000000000000aeaf09795d8c0e6fa4bb5f89dc9c15ec02021567'; diff --git a/backend/src/constants/ErrorMessage.ts b/backend/src/constants/ErrorMessage.ts index 38f6b09..96aa846 100644 --- a/backend/src/constants/ErrorMessage.ts +++ b/backend/src/constants/ErrorMessage.ts @@ -46,6 +46,12 @@ export default { RECORD_ALREADY_EXISTS_CONTRACT_WHITELIST: 'Record already exists for the chainId, apiKey and contractAddress passed', BALANCE_EXCEEDS_THRESHOLD: 'Balance exceeds threshold to delete key', INVALID_SIGNATURE_OR_TIMESTAMP: 'Invalid signature or timestamp', + VP_NOT_DEPLOYED: 'Verifying paymaster not deployed', + INVALID_EP_VERSION: 'Invalid EntryPoint version', + VP_ALREADY_DEPLOYED: 'Verifying paymaster already deployed', + FAILED_TO_DEPLOY_VP: 'Failed to deploy verifying paymaster', + FAILED_TO_ADD_STAKE: 'Failed to add stake', + INVALID_AMOUNT_TO_STAKE: 'Invalid amount to stake' } export function generateErrorMessage(template: string, values: { [key: string]: string | number }): string { diff --git a/backend/src/models/api-key.ts b/backend/src/models/api-key.ts index 78c74ec..0a778d7 100644 --- a/backend/src/models/api-key.ts +++ b/backend/src/models/api-key.ts @@ -7,6 +7,8 @@ export class APIKey extends Model { public supportedNetworks?: string | null; public erc20Paymasters?: string | null; public erc20PaymastersV2?: string | null; + public verifyingPaymasters?: string | null; + public verifyingPaymastersV2?: string | null; public multiTokenPaymasters?: string | null; public multiTokenOracles?: string | null; public sponsorName?: string | null; @@ -54,6 +56,16 @@ export function initializeAPIKeyModel(sequelize: Sequelize, schema: string) { allowNull: true, field: 'ERC20_PAYMASTERS_V2' }, + verifyingPaymasters: { + type: DataTypes.TEXT, + allowNull: true, + field: 'VERIFYING_PAYMASTERS' + }, + verifyingPaymastersV2: { + type: DataTypes.TEXT, + allowNull: true, + field: 'VERIFYING_PAYMASTERS_V2' + }, bundlerApiKey: { type: DataTypes.TEXT, allowNull: true, diff --git a/backend/src/paymaster/index.ts b/backend/src/paymaster/index.ts index d0ae866..c85704b 100644 --- a/backend/src/paymaster/index.ts +++ b/backend/src/paymaster/index.ts @@ -14,6 +14,8 @@ import ChainlinkOracleAbi from '../abi/ChainlinkOracleAbi.js'; import ERC20PaymasterV07Abi from '../abi/ERC20PaymasterV07Abi.js'; import ERC20Abi from '../abi/ERC20Abi.js'; import EtherspotChainlinkOracleAbi from '../abi/EtherspotChainlinkOracleAbi.js'; +import { abi as verifyingPaymasterAbi, byteCode as verifyingPaymasterByteCode } from '../abi/VerifyingPaymasterAbi.js'; +import { abi as verifyingPaymasterV2Abi, byteCode as verifyingPaymasterV2ByteCode } from '../abi/VerifyingPaymasterAbiV2.js'; export class Paymaster { feeMarkUp: BigNumber; @@ -667,4 +669,102 @@ export class Paymaster { throw new Error(ErrorMessage.ERROR_ON_SUBMITTING_TXN); } } + + async deployVp( + privateKey: string, + bundlerRpcUrl: string, + epAddr: string, + isEp06: boolean, + chainId: number, + log?: FastifyBaseLogger + ) { + try { + const provider = new providers.JsonRpcProvider(bundlerRpcUrl); + const signer = new Wallet(privateKey, provider); + + let contract; + if(isEp06) { + contract = new ethers.ContractFactory(verifyingPaymasterAbi, verifyingPaymasterByteCode, signer); + } else { + contract = new ethers.ContractFactory(verifyingPaymasterV2Abi, verifyingPaymasterV2ByteCode, signer); + } + + const etherscanFeeData = await getEtherscanFee(chainId); + let feeData; + if (etherscanFeeData) { + feeData = etherscanFeeData; + } else { + feeData = await provider.getFeeData(); + feeData.gasPrice = feeData.gasPrice ? feeData.gasPrice.add(this.feeMarkUp) : null; + feeData.maxFeePerGas = feeData.maxFeePerGas ? feeData.maxFeePerGas.add(this.feeMarkUp) : null; + feeData.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ? feeData.maxPriorityFeePerGas.add(this.feeMarkUp) : null; + } + + let tx; + if (!feeData.maxFeePerGas) { + tx = await contract.deploy(epAddr, signer.address, {gasPrice: feeData.gasPrice}); + } else { + tx = await contract.deploy( + epAddr, + signer.address, + { + maxFeePerGas: feeData.maxFeePerGas ?? undefined, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, + type: 2 + } + ); + } + await tx.deployed(); + return { address: tx.address, hash: tx.deployTransaction.hash }; + } catch (error) { + log?.error(`error while deploying verifying paymaster ${error}`); + throw new Error(ErrorMessage.FAILED_TO_DEPLOY_VP); + } + } + + async addStake( + privateKey: string, + bundlerRpcUrl: string, + amount: string, + paymasterAddress: string, + chainId: number, + log?: FastifyBaseLogger + ) { + try { + const provider = new providers.JsonRpcProvider(bundlerRpcUrl); + const signer = new Wallet(privateKey, provider); + + const contract = new ethers.Contract(paymasterAddress, verifyingPaymasterAbi, signer); + + const etherscanFeeData = await getEtherscanFee(chainId); + let feeData; + if (etherscanFeeData) { + feeData = etherscanFeeData; + } else { + feeData = await provider.getFeeData(); + feeData.gasPrice = feeData.gasPrice ? feeData.gasPrice.add(this.feeMarkUp) : null; + feeData.maxFeePerGas = feeData.maxFeePerGas ? feeData.maxFeePerGas.add(this.feeMarkUp) : null; + feeData.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ? feeData.maxPriorityFeePerGas.add(this.feeMarkUp) : null; + } + + let tx; + if (!feeData.maxFeePerGas) { + tx = await contract.addStake("10", {value: ethers.utils.parseEther(amount), gasPrice: feeData.gasPrice}); + } else { + tx = await contract.addStake( + "10", + { + value: ethers.utils.parseEther(amount), + maxFeePerGas: feeData.maxFeePerGas ?? undefined, + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, + type: 2 + } + ); + } + return tx + } catch (error) { + log?.error(`error while adding stake to verifying paymaster ${error}`); + throw new Error(ErrorMessage.FAILED_TO_ADD_STAKE); + } + } } diff --git a/backend/src/repository/api-key-repository.ts b/backend/src/repository/api-key-repository.ts index 027e192..804ff7b 100644 --- a/backend/src/repository/api-key-repository.ts +++ b/backend/src/repository/api-key-repository.ts @@ -59,4 +59,11 @@ export class APIKeyRepository { const result = await this.sequelize.models.APIKey.findOne({ where: { walletAddress: walletAddress } }); return result ? result.get() as APIKey : null; } + + async updateVpAddresses(apiKey: string, vpAddresses: string, isEp06 = true) { + if (isEp06) { + return await this.sequelize.models.APIKey.update({verifyingPaymasters: vpAddresses}, {where: {apiKey}}); + } + return await this.sequelize.models.APIKey.update({verifyingPaymastersV2: vpAddresses}, {where: {apiKey}}); + } } \ No newline at end of file diff --git a/backend/src/repository/whitelist-repository.ts b/backend/src/repository/whitelist-repository.ts index fa86c90..a6b6e0f 100644 --- a/backend/src/repository/whitelist-repository.ts +++ b/backend/src/repository/whitelist-repository.ts @@ -14,7 +14,7 @@ export class WhitelistRepository { const result = await this.sequelize.models.ArkaWhitelist.create({ apiKey: apiKey.apiKey, addresses: apiKey.addresses, - policyId: apiKey.policyId ?? null, + policyId: apiKey.policyId ?? null }) as ArkaWhitelist; diff --git a/backend/src/routes/admin-routes.ts b/backend/src/routes/admin-routes.ts index 11ef7e9..728ca5d 100644 --- a/backend/src/routes/admin-routes.ts +++ b/backend/src/routes/admin-routes.ts @@ -13,8 +13,12 @@ import { CreateSecretCommand, DeleteSecretCommand, GetSecretValueCommand, Secret import EtherspotAbi from "../abi/EtherspotAbi.js"; import { AuthDto } from "../types/auth-dto.js"; import { IncomingHttpHeaders } from "http"; +import { EPVersions } from "../types/sponsorship-policy-dto.js"; +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); const prefixSecretId = 'arka_'; @@ -26,6 +30,11 @@ const adminRoutes: FastifyPluginAsync = async (server) => { client = new SecretsManagerClient(); } + const SUPPORTED_ENTRYPOINTS = { + EPV_06: server.config.EPV_06, + EPV_07: server.config.EPV_07 + } + server.post('/adminLogin', async function (request, reply) { try { @@ -360,6 +369,205 @@ const adminRoutes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }); } }) + + server.post('/deployVerifyingPaymaster', async (request, reply) => { + try { + if (!request.body) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.EMPTY_BODY }); + + const body: any = request.body; + const query: any = request.query; + const chainId = query['chainId'] ?? body.params?.[1]; + const apiKey = query['apiKey'] ?? body.params?.[2]; + const epVersion = body.params?.[0]; + + if (!chainId || isNaN(chainId) || !apiKey) { + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); + } + + if (!epVersion || (epVersion !== EPVersions.EPV_06 && epVersion !== EPVersions.EPV_07)) { + return reply.code(ReturnCode.FAILURE).send({error: ErrorMessage.INVALID_EP_VERSION}); + } + + const isEp06 = epVersion === EPVersions.EPV_06; + + const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(apiKey); + if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }); + + let verifyingPaymasters; + + if(isEp06) { + verifyingPaymasters = apiKeyEntity.verifyingPaymasters ? JSON.parse(apiKeyEntity.verifyingPaymasters) : {}; + } else { + verifyingPaymasters = apiKeyEntity.verifyingPaymastersV2 ? JSON.parse(apiKeyEntity.verifyingPaymastersV2) : {}; + } + if (verifyingPaymasters[chainId]) { + return reply.code(ReturnCode.FAILURE).send( + {error: `${ErrorMessage.VP_ALREADY_DEPLOYED} at ${verifyingPaymasters[chainId]}`} + ); + } + + let privateKey; + let bundlerApiKey = apiKey; + let supportedNetworks; + + if (!unsafeMode) { + const AWSresponse = await client.send( + new GetSecretValueCommand({ + SecretId: prefixSecretId + apiKey, + }) + ); + 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; + } + } + + if (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) { + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); + } + + const networkConfig = getNetworkConfig( + chainId, + supportedNetworks ?? '', + isEp06 ? SUPPORTED_ENTRYPOINTS.EPV_06 : SUPPORTED_ENTRYPOINTS.EPV_07 + ); + + 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}`; + } + + const {address, hash} = await paymaster.deployVp( + privateKey, + bundlerUrl, + networkConfig.entryPoint, + isEp06, + chainId, + server.log + ); + verifyingPaymasters[chainId] = address; + await server.apiKeyRepository.updateVpAddresses(apiKey, JSON.stringify(verifyingPaymasters), isEp06); + + return reply.code(ReturnCode.SUCCESS).send({verifyingPaymaster: address, txHash: hash}); + } catch (error: any) { + request.log.error(error); + return reply.code(ReturnCode.FAILURE).send({ error: error.message ?? ErrorMessage.FAILED_TO_PROCESS }); + } + }); + + server.post('/addStake', async (request, reply) => { + try { + if (!request.body) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.EMPTY_BODY }); + + const body: any = request.body; + const query: any = request.query; + const chainId = query['chainId']; + const apiKey = query['apiKey']; + const epVersion = body.params[0]; + const amount = body.params[1]; + + if (!chainId || isNaN(chainId) || !apiKey) { + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); + } + + if(isNaN(amount)) { + return reply.code(ReturnCode.FAILURE).send({error: ErrorMessage.INVALID_AMOUNT_TO_STAKE}); + } + + if (!epVersion || (epVersion !== EPVersions.EPV_06 && epVersion !== EPVersions.EPV_07)) { + return reply.code(ReturnCode.FAILURE).send({error: ErrorMessage.INVALID_EP_VERSION}); + } + + const isEp06 = epVersion === EPVersions.EPV_06; + + const apiKeyEntity: APIKey | null = await server.apiKeyRepository.findOneByApiKey(apiKey); + if (!apiKeyEntity) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }); + + let verifyingPaymasters; + + if(isEp06) { + verifyingPaymasters = apiKeyEntity.verifyingPaymasters ? JSON.parse(apiKeyEntity.verifyingPaymasters) : {}; + } else { + verifyingPaymasters = apiKeyEntity.verifyingPaymastersV2 ? JSON.parse(apiKeyEntity.verifyingPaymastersV2) : {}; + } + + if (!verifyingPaymasters[chainId]) { + return reply.code(ReturnCode.FAILURE).send( + {error: `${ErrorMessage.VP_NOT_DEPLOYED}`} + ); + } + + let privateKey; + let bundlerApiKey = apiKey; + let supportedNetworks; + + if (!unsafeMode) { + const AWSresponse = await client.send( + new GetSecretValueCommand({ + SecretId: prefixSecretId + apiKey, + }) + ); + 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; + } + } + + if (server.config.SUPPORTED_NETWORKS == '' && !SupportedNetworks) { + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK }); + } + const networkConfig = getNetworkConfig( + chainId, + supportedNetworks ?? '', + isEp06 ? SUPPORTED_ENTRYPOINTS.EPV_06 : SUPPORTED_ENTRYPOINTS.EPV_07 + ); + 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}`; + } + + const tx = await paymaster.addStake( + privateKey, + bundlerUrl, + amount, + verifyingPaymasters[chainId], + chainId, + server.log + ); + return reply.code(ReturnCode.SUCCESS).send({tx}); + } catch (error: any) { + request.log.error(error); + return reply.code(ReturnCode.FAILURE).send({ error: error.message ?? ErrorMessage.FAILED_TO_PROCESS }); + } + }); }; export default adminRoutes; diff --git a/backend/src/routes/deposit-route.ts b/backend/src/routes/deposit-route.ts index f02e382..f987ca5 100644 --- a/backend/src/routes/deposit-route.ts +++ b/backend/src/routes/deposit-route.ts @@ -49,8 +49,10 @@ const depositRoutes: FastifyPluginAsync = async (server) => { const body: any = request.body; const query: any = request.query; const amount = body.params[0]; - const chainId = query['chainId'] ?? body.params[1]; - const api_key = query['apiKey'] ?? body.params[2]; + const ep = query['useEp'] ?? body.params[1] ?? false; + 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 = ''; @@ -92,7 +94,18 @@ 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, true, server.log); + + if(ep) { + return await paymaster.deposit(amount, networkConfig.contracts.etherspotPaymasterAddress, bundlerUrl, privateKey, chainId, true, server.log); + } + const vpAddr = apiKeyEntity.verifyingPaymasters ? + JSON.parse(apiKeyEntity.verifyingPaymasters)[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 83f876b..a25211e 100644 --- a/backend/src/routes/metadata-routes.ts +++ b/backend/src/routes/metadata-routes.ts @@ -10,6 +10,7 @@ import { decode } from "../utils/crypto.js"; import { PAYMASTER_ADDRESS } from "../constants/Pimlico.js"; import { APIKey } from "../models/api-key.js"; import * as EtherspotAbi from "../abi/EtherspotAbi.js"; +import {abi as verifyingPaymasterAbi} from "../abi/VerifyingPaymasterAbi.js"; const metadataRoutes: FastifyPluginAsync = async (server) => { @@ -108,6 +109,13 @@ const metadataRoutes: FastifyPluginAsync = async (server) => { const paymasterContract = new Contract(networkConfig.contracts.etherspotPaymasterAddress, EtherspotAbi.default, provider); const sponsorBalance = await paymasterContract.getSponsorBalance(sponsorAddress); + const verifyingPaymaster = apiKeyEntity.verifyingPaymasters ? JSON.parse(apiKeyEntity.verifyingPaymasters)[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'); @@ -131,7 +139,9 @@ const metadataRoutes: FastifyPluginAsync = async (server) => { chainsSupported: chainsSupported, tokenPaymasters: tokenPaymasterAddresses, multiTokenPaymasters, - sponsorDetails: { name: sponsorName, icon: sponsorImage } + sponsorDetails: { name: sponsorName, icon: sponsorImage }, + verifyingPaymaster: { address: verifyingPaymaster, deposit: verifyingPaymasterDeposit }, + verifyingPaymasters: apiKeyEntity.verifyingPaymasters ? JSON.parse(apiKeyEntity.verifyingPaymasters) : undefined }) } catch (err: any) { request.log.error(err); diff --git a/backend/src/routes/paymaster-routes.ts b/backend/src/routes/paymaster-routes.ts index 8b2e443..af51b15 100644 --- a/backend/src/routes/paymaster-routes.ts +++ b/backend/src/routes/paymaster-routes.ts @@ -230,7 +230,7 @@ const paymasterRoutes: FastifyPluginAsync = async (server) => { ) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.UNSUPPORTED_NETWORK_TOKEN }) switch (mode.toLowerCase()) { - case 'sponsor': { + case 'eps': { const date = new Date(); const provider = new providers.JsonRpcProvider(bundlerUrl); const signer = new Wallet(privateKey, provider) @@ -342,6 +342,85 @@ 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], server.log); break; } + case 'sponsor': { + const date = new Date(); + const provider = new providers.JsonRpcProvider(bundlerUrl); + const signer = new Wallet(privateKey, provider); + + // 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 }); + + const sponsorshipPolicy: SponsorshipPolicy | null = await server.sponsorshipPolicyRepository.findOneByWalletAddressAndSupportedEPVersion(apiKeyData?.walletAddress, getEPVersion(epVersion)); + if (!sponsorshipPolicy) { + const errorMessage: string = generateErrorMessage(ErrorMessage.ACTIVE_SPONSORSHIP_POLICY_NOT_FOUND, { walletAddress: apiKeyData?.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 }); + 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 }); + + if (txnMode) { + const signerAddress = await signer.getAddress(); + 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; + const validAfter = context?.validAfter ? new Date(context.validAfter) : date; + const hex = (Number((validUntil.valueOf() / 1000).toFixed(0)) + 600).toString(16); + const hex1 = (Number((validAfter.valueOf() / 1000).toFixed(0)) - 60).toString(16); + let str = '0x' + let str1 = '0x' + for (let i = 0; i < 14 - hex.length; i++) { + str += '0'; + } + for (let i = 0; i < 14 - hex1.length; i++) { + str1 += '0'; + } + str += hex; + str1 += hex1; + if (contractWhitelistMode) { + const contractWhitelistResult = await checkContractWhitelist(userOp.callData, chainId.chainId, signer.address); + if (!contractWhitelistResult) throw new Error('Contract Method not whitelisted'); + } + + const globalWhitelistRecord = await server.whitelistRepository.findOneByApiKeyAndPolicyId(api_key); + if (!globalWhitelistRecord?.addresses.includes(userOp.sender)) { + const existingWhitelistRecord = await server.whitelistRepository.findOneByApiKeyAndPolicyId(api_key, sponsorshipPolicy.id); + if (!existingWhitelistRecord?.addresses.includes(userOp.sender)) throw new Error('This sender address has not been whitelisted yet'); + } + + if (epVersion === EPVersions.EPV_06) { + 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}); + } + 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}); + } + const paymasterAddr = JSON.parse(apiKeyEntity.verifyingPaymastersV2)[chainId.chainId]; + 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; + } default: { return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_MODE }); } diff --git a/backend/src/routes/whitelist-routes.ts b/backend/src/routes/whitelist-routes.ts index 6f630e7..4181112 100644 --- a/backend/src/routes/whitelist-routes.ts +++ b/backend/src/routes/whitelist-routes.ts @@ -36,8 +36,10 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { const body: any = request.body; const query: any = request.query; const address = body.params[0]; - const chainId = query['chainId'] ?? body.params[1]; - const api_key = query['apiKey'] ?? body.params[2]; + 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]; if (!api_key || typeof(api_key) !== "string") return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) let privateKey = ''; @@ -68,151 +70,59 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { if (!privateKey) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_API_KEY }) if ( !Array.isArray(address) || - address.length > 10 || - !chainId || - isNaN(chainId) + 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 networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_06); - 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}`; const validAddresses = address.every(ethers.utils.isAddress); if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_ADDRESS_PASSSED }); - const result = await paymaster.whitelistAddresses(address, networkConfig.contracts.etherspotPaymasterAddress, bundlerUrl, privateKey, chainId, server.log); - 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", async function (request, reply) { - try { - printRequest("/removeWhitelist", request, server.log); - const body: any = request.body; - const query: any = request.query; - const address = body.params[0]; - 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 }) - 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 }) - 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 ( - !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_06); - 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}`; - const validAddresses = address.every(ethers.utils.isAddress); - if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_ADDRESS_PASSSED }); - const result = await paymaster.removeWhitelistAddress(address, networkConfig.contracts.etherspotPaymasterAddress, bundlerUrl, privateKey, chainId, server.log); - 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", - async function (request, reply) { - try { - printRequest("/checkWhitelist", request, server.log); - const body: any = request.body; - const query: any = request.query; - const accountAddress = body.params[0]; - 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 }) - 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 }) - if (secrets['BUNDLER_API_KEY']) bundlerApiKey = secrets['BUNDLER_API_KEY']; - privateKey = secrets['PRIVATE_KEY']; - supportedNetworks = secrets['SUPPORTED_NETWORKS']; + if(useEp) { + if(!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_06); + 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}`; + const result = await paymaster.whitelistAddresses(address, networkConfig.contracts.etherspotPaymasterAddress, bundlerUrl, privateKey, chainId, server.log); + 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); } 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 ( - !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 }); + if (policyId) { + const signer = new Wallet(privateKey); + 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); } - const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_06); - 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}`; - const response = await paymaster.checkWhitelistAddress(accountAddress, networkConfig.contracts.etherspotPaymasterAddress, bundlerUrl, privateKey, server.log); - server.log.info(response, 'Response sent: '); - if (body.jsonrpc) - return reply.code(ReturnCode.SUCCESS).send({ jsonrpc: body.jsonrpc, id: body.id, result: { message: response === true ? 'Already added' : 'Not added yet' }, error: null }) - return reply.code(ReturnCode.SUCCESS).send({ message: response === true ? 'Already added' : 'Not added yet' }); } catch (err: any) { request.log.error(err); if (err.name == "ResourceNotFoundException") @@ -220,22 +130,24 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }) } } - ) + ); - server.post("/removeWhitelist/v2", + server.post("/removeWhitelist", async function (request, reply) { try { - printRequest("/removeWhitelist/v2", request, server.log); + 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; 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 }) if (!unsafeMode) { @@ -246,51 +158,74 @@ 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 ( !Array.isArray(address) || - address.length > 10 || - !chainId || - isNaN(chainId) + 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 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); + if (!validAddresses) return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_ADDRESS_PASSSED }); + if(useEp) { + if(!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_06); + 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}`; + const result = await paymaster.removeWhitelistAddress(address, networkConfig.contracts.etherspotPaymasterAddress, bundlerUrl, privateKey, chainId, server.log); + 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); } else { - throw new Error(ErrorMessage.NO_WHITELIST_FOUND); + if (policyId) { + const signer = new Wallet(privateKey); + 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 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); } - 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") @@ -298,22 +233,24 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }) } } - ) + ); - server.post("/checkWhitelist/v2", + server.post("/checkWhitelist", async function (request, reply) { try { - printRequest("/checkWhitelist/v2", request, server.log); + 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]; 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 }) if (!unsafeMode) { @@ -324,113 +261,52 @@ 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 ( !accountAddress || - !ethers.utils.isAddress(accountAddress) || - !chainId || - isNaN(chainId) + !ethers.utils.isAddress(accountAddress) ) { 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); + const networkConfig = getNetworkConfig(chainId, supportedNetworks ?? '', SUPPORTED_ENTRYPOINTS.EPV_06); 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("/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); + if(useEp) { + if(!chainId || isNaN(chainId)) { + return reply.code(ReturnCode.FAILURE).send({ error: ErrorMessage.INVALID_DATA }); + } + let bundlerUrl = networkConfig.bundler; + if (networkConfig.bundler.includes('etherspot.io')) bundlerUrl = `${networkConfig.bundler}?api-key=${bundlerApiKey}`; + const response = await paymaster.checkWhitelistAddress(accountAddress, networkConfig.contracts.etherspotPaymasterAddress, bundlerUrl, privateKey, server.log); + server.log.info(response, 'Response sent: '); + if (body.jsonrpc) + return reply.code(ReturnCode.SUCCESS).send({ jsonrpc: body.jsonrpc, id: body.id, result: { message: response === true ? 'Already added' : 'Not added yet' }, error: null }) + return reply.code(ReturnCode.SUCCESS).send({ message: response === true ? 'Already added' : 'Not added yet' }); } else { - const addWhitelistDto = { - apiKey: api_key, - addresses: address, - policyId: policyId ?? null, + const existingWhitelistRecord = await server.whitelistRepository.findOneByApiKeyAndPolicyId( + api_key, + policyId + ); + + if (!existingWhitelistRecord) { + throw new Error(ErrorMessage.NO_WHITELIST_FOUND); } - await server.whitelistRepository.create(addWhitelistDto); - + 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); } - 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") @@ -438,9 +314,9 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { return reply.code(ReturnCode.FAILURE).send({ error: err.message ?? ErrorMessage.FAILED_TO_PROCESS }) } } - ) + ); - server.post("/getAllWhitelist/v2", + server.post("/getAllWhitelist", async function (request, reply) { try { printRequest("/getAllWhitelist/v2", request, server.log); @@ -639,7 +515,6 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { } ) - server.post("/deleteContractWhitelist", async function (request, reply) { try { @@ -709,7 +584,6 @@ const whitelistRoutes: FastifyPluginAsync = async (server) => { } } ) - }; export default whitelistRoutes;