diff --git a/src/paymasterclient.ts b/src/paymasterclient.ts index fc58ad8..854e5a5 100644 --- a/src/paymasterclient.ts +++ b/src/paymasterclient.ts @@ -9,6 +9,14 @@ export type IsSponsorableResponse = { SponsorWebsite: string } +export type IsSponsorableOptions = { + PrivatePolicyUUID?: string +} + +export type SendRawTransactionOptions = { + PrivatePolicyUUID?: string +} + export enum GaslessTransactionStatus { New = 0, Pending = 1, Confirmed = 2, Failed = 3, Invalid = 4} export type GaslessTransaction = { @@ -48,36 +56,61 @@ export type Bundle = { readonly ChainID: number } -export class PaymasterClient extends ethers.JsonRpcProvider { - constructor(url?: string | FetchRequest, network?: Networkish, options?: JsonRpcApiProviderOptions) { - super(url, network, options) +export class PaymasterClient { + private sponsorClient: ethers.JsonRpcProvider + private userClient: ethers.JsonRpcProvider + + constructor( + userUrl: string | FetchRequest, + sponsorUrl: string | FetchRequest, + network?: Networkish, + options?: JsonRpcApiProviderOptions + ) { + this.userClient = new ethers.JsonRpcProvider(userUrl, network, options) + this.sponsorClient = new ethers.JsonRpcProvider(sponsorUrl, network, options) } async chainID(): Promise { - return await this.send('eth_chainId', []) + return await this.userClient.send('eth_chainId', []) } - async isSponsorable(tx: TransactionRequest): Promise { - return await this.send('pm_isSponsorable', [tx]) + async isSponsorable(tx: TransactionRequest, opts: IsSponsorableOptions = {} ): Promise { + if (opts.PrivatePolicyUUID) { + this.sponsorClient._getConnection().setHeader("X-MegaFuel-Policy-Uuid", opts.PrivatePolicyUUID) + return await this.sponsorClient.send('pm_isSponsorable', [tx]) + } + return await this.userClient.send('pm_isSponsorable', [tx]) } - async sendRawTransaction(signedTx: string): Promise { - return await this.send('eth_sendRawTransaction', [signedTx]) + async sendRawTransaction(signedTx: string, opts: SendRawTransactionOptions= {}): Promise { + if (opts.PrivatePolicyUUID) { + this.sponsorClient._getConnection().setHeader("X-MegaFuel-Policy-Uuid", opts.PrivatePolicyUUID) + return await this.sponsorClient.send('eth_sendRawTransaction', [signedTx]) + } + return await this.userClient.send('eth_sendRawTransaction', [signedTx]) } async getGaslessTransactionByHash(hash: string): Promise { - return await this.send('eth_getGaslessTransactionByHash', [hash]) + return await this.userClient.send('eth_getGaslessTransactionByHash', [hash]) } async getSponsorTxByTxHash(hash: string): Promise { - return await this.send('pm_getSponsorTxByTxHash', [hash]) + return await this.userClient.send('pm_getSponsorTxByTxHash', [hash]) } async getSponsorTxByBundleUuid(bundleUuid: string): Promise { - return await this.send('pm_getSponsorTxByBundleUuid', [bundleUuid]) + return await this.userClient.send('pm_getSponsorTxByBundleUuid', [bundleUuid]) } async getBundleByUuid(bundleUuid: string): Promise { - return await this.send('pm_getBundleByUuid', [bundleUuid]) + return await this.userClient.send('pm_getBundleByUuid', [bundleUuid]) + } + + getSponsorProvider(): ethers.JsonRpcProvider { + return this.sponsorClient + } + + getUserProvider(): ethers.JsonRpcProvider { + return this.userClient } } diff --git a/tests/env.ts b/tests/env.ts index 4b63a3f..acb3036 100644 --- a/tests/env.ts +++ b/tests/env.ts @@ -16,3 +16,4 @@ export const ACCOUNT_ADDRESS = '0xF9A8db17431DD8563747D6FC770297E438Aa12eB' export const CONTRACT_METHOD = '0xa9059cbb' export const TOKEN_CONTRACT_ADDRESS = '0xeD24FC36d5Ee211Ea25A80239Fb8C4Cfd80f12Ee' export const RECIPIENT_ADDRESS = '0xDE08B1Fd79b7016F8DD3Df11f7fa0FbfdF07c941' +export const PRIVATE_POLICY_UUID = "90f1ba4c-1f93-4759-b8a9-da4d59c668b4" diff --git a/tests/paymaster.spec.ts b/tests/paymaster.spec.ts index d1eed25..d32f7b5 100644 --- a/tests/paymaster.spec.ts +++ b/tests/paymaster.spec.ts @@ -7,7 +7,8 @@ import { transformToGaslessTransaction, delay, transformSponsorTxResponse, transformBundleResponse, } from './utils' -import {TOKEN_CONTRACT_ADDRESS, CHAIN_ID, RECIPIENT_ADDRESS} from './env' +import {IsSponsorableOptions, SendRawTransactionOptions} from '../src/paymasterclient' +import {TOKEN_CONTRACT_ADDRESS, CHAIN_ID, RECIPIENT_ADDRESS, PRIVATE_POLICY_UUID} from './env' import {ethers} from 'ethers' let TX_HASH = '' @@ -34,7 +35,7 @@ describe('paymasterQuery', () => { test('should successfully determine if transaction is sponsorable', async () => { const tokenContract = new ethers.Contract(TOKEN_CONTRACT_ADDRESS, tokenAbi, wallet) const tokenAmount = ethers.parseUnits('0', 18) - const nonce = await paymasterClient.getTransactionCount(wallet.address, 'pending') + const nonce = await paymasterClient.getUserProvider().getTransactionCount(wallet.address, 'pending') const transaction = await tokenContract.transfer.populateTransaction(RECIPIENT_ADDRESS.toLowerCase(), tokenAmount) transaction.from = wallet.address @@ -96,4 +97,53 @@ describe('paymasterQuery', () => { expect(sponsorTx.TxHash).toEqual(tx.TxHash) }, 13000) }) + + + /** + * Test for checking if a private policy transaction is sponsorable. + */ + describe('isSponsorable', () => { + test('should successfully determine if transaction is sponsorable', async () => { + const tokenContract = new ethers.Contract(TOKEN_CONTRACT_ADDRESS, tokenAbi, wallet) + const tokenAmount = ethers.parseUnits('0', 18) + const nonce = await paymasterClient.getUserProvider().getTransactionCount(wallet.address, 'pending') + + const transaction = await tokenContract.transfer.populateTransaction(RECIPIENT_ADDRESS.toLowerCase(), tokenAmount) + transaction.from = wallet.address + transaction.nonce = nonce + transaction.gasLimit = BigInt(100000) + transaction.chainId = BigInt(CHAIN_ID) + transaction.gasPrice = BigInt(0) + + const safeTransaction = { + ...transaction, + gasLimit: transaction.gasLimit.toString(), + chainId: transaction.chainId.toString(), + gasPrice: transaction.gasPrice.toString(), + } + + console.log('Prepared transaction:', safeTransaction) + + const opt: IsSponsorableOptions = { + PrivatePolicyUUID: PRIVATE_POLICY_UUID + }; + + const resRaw = await paymasterClient.isSponsorable(safeTransaction, opt) + const res = transformIsSponsorableResponse(resRaw) + expect(res.Sponsorable).toEqual(true) + + const txOpt: SendRawTransactionOptions = { + PrivatePolicyUUID: PRIVATE_POLICY_UUID + }; + + const signedTx = await wallet.signTransaction(safeTransaction) + try { + const tx = await paymasterClient.sendRawTransaction(signedTx,txOpt) + TX_HASH = tx + console.log('Transaction hash received:', TX_HASH) + } catch (error) { + console.error('Transaction failed:', error) + } + }, 100000) // Extends the default timeout as this test involves network calls + }) }) diff --git a/tests/utils.ts b/tests/utils.ts index b18e51b..050c1ca 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -16,7 +16,7 @@ export const sponsorClient = new SponsorClient(SPONSOR_URL, undefined, {staticNe export const assemblyProvider = new ethers.JsonRpcProvider(CHAIN_URL) // Provider for sending the transaction (e.g., could be a different network or provider) -export const paymasterClient = new PaymasterClient(PAYMASTER_URL) +export const paymasterClient = new PaymasterClient(PAYMASTER_URL,SPONSOR_URL+"/"+CHAIN_ID, undefined, {staticNetwork: ethers.Network.from(Number(CHAIN_ID))}) export const wallet = new ethers.Wallet(PRIVATE_KEY, assemblyProvider) // ERC20 token ABI (only including the transfer function)