diff --git a/src/paymasterclient.ts b/src/paymasterclient.ts index c21fa24..0e120dc 100644 --- a/src/paymasterclient.ts +++ b/src/paymasterclient.ts @@ -9,12 +9,7 @@ export type IsSponsorableResponse = { SponsorWebsite: string } -export type IsSponsorableOptions = { - PrivatePolicyUUID?: string -} - export type SendRawTransactionOptions = { - PrivatePolicyUUID?: string UserAgent?: string } @@ -58,19 +53,46 @@ export type Bundle = { } export class PaymasterClient extends ethers.JsonRpcProvider { - constructor(url?: string | FetchRequest, network?: Networkish, options?: JsonRpcApiProviderOptions) { - super(url, network, options) + private privatePolicyUUID?: string; + + private constructor( + url?: string | FetchRequest, + network?: Networkish, + options?: JsonRpcApiProviderOptions, + privatePolicyUUID?: string + ) { + super(url, network, options); + this.privatePolicyUUID = privatePolicyUUID; + } + + // Static method to create a new standard PaymasterClient + static new( + url?: string | FetchRequest, + network?: Networkish, + options?: JsonRpcApiProviderOptions + ): PaymasterClient { + return new PaymasterClient(url, network, options); + } + + // Static method to create a new PaymasterClient with private policy + static newPrivatePaymaster( + url: string | FetchRequest, + privatePolicyUUID: string, + network?: Networkish, + options?: JsonRpcApiProviderOptions + ): PaymasterClient { + return new PaymasterClient(url, network, options, privatePolicyUUID); } async chainID(): Promise { - return await this.send('eth_chainId', []) + return await this.send('eth_chainId', []); } - async isSponsorable(tx: TransactionRequest, opts: IsSponsorableOptions = {}): Promise { - if (opts.PrivatePolicyUUID) { + async isSponsorable(tx: TransactionRequest): Promise { + const policyUUID = this.privatePolicyUUID; + if (policyUUID) { const newConnection = this._getConnection(); - newConnection.setHeader("X-MegaFuel-Policy-Uuid", opts.PrivatePolicyUUID); - + newConnection.setHeader("X-MegaFuel-Policy-Uuid", policyUUID); const sponsorProviderWithHeader = new ethers.JsonRpcProvider( newConnection, (this as any)._network, @@ -80,23 +102,24 @@ export class PaymasterClient extends ethers.JsonRpcProvider { polling: (this as any).polling } ); - return await sponsorProviderWithHeader.send('pm_isSponsorable', [tx]); } return await this.send('pm_isSponsorable', [tx]); } async sendRawTransaction(signedTx: string, opts: SendRawTransactionOptions = {}): Promise { - if (opts.UserAgent || opts.PrivatePolicyUUID) { + const policyUUID = this.privatePolicyUUID; + if (opts.UserAgent || this.privatePolicyUUID) { const newConnection = this._getConnection(); if (opts.UserAgent) { newConnection.setHeader("User-Agent", opts.UserAgent); } - if (opts.PrivatePolicyUUID) { - newConnection.setHeader("X-MegaFuel-Policy-Uuid", opts.PrivatePolicyUUID); + if (policyUUID) { + console.log("X-MegaFuel-Policy-Uuid", policyUUID) + newConnection.setHeader("X-MegaFuel-Policy-Uuid", policyUUID); } - + const sponsorProvider = new ethers.JsonRpcProvider( newConnection, (this as any)._network, @@ -106,8 +129,8 @@ export class PaymasterClient extends ethers.JsonRpcProvider { polling: (this as any).polling } ); - - if (opts.PrivatePolicyUUID) { + + if (policyUUID) { return await sponsorProvider.send('eth_sendRawTransaction', [signedTx]); } } @@ -129,4 +152,4 @@ export class PaymasterClient extends ethers.JsonRpcProvider { async getBundleByUuid(bundleUuid: string): Promise { return await this.send('pm_getBundleByUuid', [bundleUuid]) } -} +} \ No newline at end of file diff --git a/src/sponsorclient.ts b/src/sponsorclient.ts index 27aa531..ebd64f1 100644 --- a/src/sponsorclient.ts +++ b/src/sponsorclient.ts @@ -1,7 +1,6 @@ -import { FetchRequest, JsonRpcApiProviderOptions, Networkish } from "ethers" -import { PaymasterClient } from "./paymasterclient" -import type { AddressLike } from "ethers/src.ts/address" -import type { BigNumberish } from "ethers/src.ts/utils" +import {ethers, FetchRequest, JsonRpcApiProviderOptions, Networkish} from 'ethers' +import type {AddressLike} from 'ethers/src.ts/address' +import type {BigNumberish} from 'ethers/src.ts/utils' export enum WhitelistType { FromAccountWhitelist = 'FromAccountWhitelist', @@ -43,12 +42,8 @@ export type PolicySpendData = { ChainID: number } -export class SponsorClient extends PaymasterClient { - constructor( - url?: string | FetchRequest, - network?: Networkish, - options?: JsonRpcApiProviderOptions - ) { +export class SponsorClient extends ethers.JsonRpcProvider { + constructor(url?: string | FetchRequest, network?: Networkish, options?: JsonRpcApiProviderOptions) { super(url, network, options) } @@ -68,14 +63,11 @@ export class SponsorClient extends PaymasterClient { return this.send('pm_getWhitelist', [params]) } - async getUserSpendData( - fromAddress: AddressLike, - policyUUID: string - ): Promise { + async getUserSpendData(fromAddress: AddressLike, policyUUID: string): Promise { return this.send('pm_getUserSpendData', [fromAddress, policyUUID]) } async getPolicySpendData(policyUUID: string): Promise { return this.send('pm_getPolicySpendData', [policyUUID]) } -} +} \ No newline at end of file diff --git a/tests/paymaster.spec.ts b/tests/paymaster.spec.ts index d1eed25..ff2f652 100644 --- a/tests/paymaster.spec.ts +++ b/tests/paymaster.spec.ts @@ -6,9 +6,11 @@ import { transformIsSponsorableResponse, transformToGaslessTransaction, delay, transformSponsorTxResponse, transformBundleResponse, + privatePaymasterClient, } from './utils' -import {TOKEN_CONTRACT_ADDRESS, CHAIN_ID, RECIPIENT_ADDRESS} from './env' -import {ethers} from 'ethers' +import { TOKEN_CONTRACT_ADDRESS, CHAIN_ID, RECIPIENT_ADDRESS } from './env' +import { ethers } from 'ethers' +import { SendRawTransactionOptions } from '../src' let TX_HASH = '' @@ -96,4 +98,50 @@ 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 privatePaymasterClient.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 resRaw = await privatePaymasterClient.isSponsorable(safeTransaction) + const res = transformIsSponsorableResponse(resRaw) + expect(res.Sponsorable).toEqual(true) + + const txOpt: SendRawTransactionOptions = { + UserAgent: "TEST USER AGENT" + }; + + const signedTx = await wallet.signTransaction(safeTransaction) + try { + const tx = await privatePaymasterClient.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/sponsor.spec.ts b/tests/sponsor.spec.ts index 506994d..df70355 100644 --- a/tests/sponsor.spec.ts +++ b/tests/sponsor.spec.ts @@ -1,254 +1,198 @@ -import {describe, expect, test} from '@jest/globals' -import {WhitelistType} from '../src' -import {POLICY_UUID, ACCOUNT_ADDRESS, CONTRACT_METHOD, TOKEN_CONTRACT_ADDRESS, CHAIN_ID, RECIPIENT_ADDRESS, PRIVATE_POLICY_UUID} from './env' -import {ethers} from 'ethers' -import {IsSponsorableOptions, SendRawTransactionOptions} from '../src/paymasterclient' -import { - sponsorClient, - wallet, - tokenAbi, - transformIsSponsorableResponse, -} from './utils' - -let TX_HASH = '' +import { describe, expect, test } from "@jest/globals"; +import { WhitelistType } from "../src"; +import { POLICY_UUID, ACCOUNT_ADDRESS, CONTRACT_METHOD } from "./env"; +import { sponsorClient } from "./utils"; /** * Test suite for Sponsor API methods involving whitelist management and spend data retrieval. */ -describe('sponsorQuery', () => { +describe("sponsorQuery", () => { /** * Tests adding an account address to the 'From Account' whitelist. */ - describe('addToWhitelist FromAccountWhitelist', () => { - test('should add an account address to FromAccountWhitelist successfully', async () => { + describe("addToWhitelist FromAccountWhitelist", () => { + test("should add an account address to FromAccountWhitelist successfully", async () => { const res = await sponsorClient.addToWhitelist({ PolicyUUID: POLICY_UUID, WhitelistType: WhitelistType.FromAccountWhitelist, Values: [ACCOUNT_ADDRESS], - }) + }); - expect(res).toEqual(true) - console.log('FromAccountWhitelist addition response:', res) - }) - }) + expect(res).toEqual(true); + console.log("FromAccountWhitelist addition response:", res); + }); + }); /** * Tests adding an account address to the 'To Account' whitelist. */ - describe('addToWhitelist ToAccountWhitelist', () => { - test('should add an account address to ToAccountWhitelist successfully', async () => { + describe("addToWhitelist ToAccountWhitelist", () => { + test("should add an account address to ToAccountWhitelist successfully", async () => { const res = await sponsorClient.addToWhitelist({ PolicyUUID: POLICY_UUID, WhitelistType: WhitelistType.ToAccountWhitelist, Values: [ACCOUNT_ADDRESS], - }) + }); - expect(res).toEqual(true) - console.log('ToAccountWhitelist addition response:', res) - }) - }) + expect(res).toEqual(true); + console.log("ToAccountWhitelist addition response:", res); + }); + }); /** * Tests adding an account address to the BEP20 receiver whitelist. */ - describe('addToWhitelist BEP20ReceiverWhiteList', () => { - test('should add an account address to BEP20ReceiverWhiteList successfully', async () => { + describe("addToWhitelist BEP20ReceiverWhiteList", () => { + test("should add an account address to BEP20ReceiverWhiteList successfully", async () => { const res = await sponsorClient.addToWhitelist({ PolicyUUID: POLICY_UUID, WhitelistType: WhitelistType.BEP20ReceiverWhiteList, Values: [ACCOUNT_ADDRESS], - }) + }); - expect(res).toEqual(true) - console.log('BEP20ReceiverWhiteList addition response:', res) - }) - }) + expect(res).toEqual(true); + console.log("BEP20ReceiverWhiteList addition response:", res); + }); + }); /** * Tests adding a contract method signature to the whitelist. */ - describe('addToWhitelist ContractMethodSigWhitelist', () => { - test('should add a contract method signature to ContractMethodSigWhitelist successfully', async () => { + describe("addToWhitelist ContractMethodSigWhitelist", () => { + test("should add a contract method signature to ContractMethodSigWhitelist successfully", async () => { const res = await sponsorClient.addToWhitelist({ PolicyUUID: POLICY_UUID, WhitelistType: WhitelistType.ContractMethodSigWhitelist, Values: [CONTRACT_METHOD], - }) + }); - expect(res).toEqual(true) - console.log('ContractMethodSigWhitelist addition response:', res) - }) - }) + expect(res).toEqual(true); + console.log("ContractMethodSigWhitelist addition response:", res); + }); + }); /** * Tests retrieving whitelists of contract method signatures. */ - describe('getWhitelist', () => { - test('should retrieve contract method signatures successfully', async () => { + describe("getWhitelist", () => { + test("should retrieve contract method signatures successfully", async () => { const res = await sponsorClient.getWhitelist({ PolicyUUID: POLICY_UUID, WhitelistType: WhitelistType.ContractMethodSigWhitelist, Offset: 0, Limit: 10, - }) + }); - expect(res[0]).toEqual(CONTRACT_METHOD) - console.log('Retrieved ContractMethodSigWhitelist:', res) - }) - }) + expect(res[0]).toEqual(CONTRACT_METHOD); + console.log("Retrieved ContractMethodSigWhitelist:", res); + }); + }); /** * Tests removing an account address from a whitelist. */ - describe('removeFromWhitelist', () => { - test('should remove an account address from FromAccountWhitelist successfully', async () => { + describe("removeFromWhitelist", () => { + test("should remove an account address from FromAccountWhitelist successfully", async () => { const res = await sponsorClient.removeFromWhitelist({ PolicyUUID: POLICY_UUID, WhitelistType: WhitelistType.FromAccountWhitelist, Values: [ACCOUNT_ADDRESS], - }) + }); - expect(res).toEqual(true) - console.log('FromAccountWhitelist removal response:', res) - }) - }) + expect(res).toEqual(true); + console.log("FromAccountWhitelist removal response:", res); + }); + }); /** * Tests verifying the removal of an account address from a whitelist. */ - describe('getWhitelist', () => { - test('should not contain account address post-removal', async () => { + describe("getWhitelist", () => { + test("should not contain account address post-removal", async () => { const res = await sponsorClient.getWhitelist({ PolicyUUID: POLICY_UUID, WhitelistType: WhitelistType.FromAccountWhitelist, Offset: 0, Limit: 10, - }) + }); if (res !== null && res !== undefined) { - expect(res).not.toContain(ACCOUNT_ADDRESS) + expect(res).not.toContain(ACCOUNT_ADDRESS); } - console.log('FromAccountWhitelist post-removal check:', res) - }) - }) + console.log("FromAccountWhitelist post-removal check:", res); + }); + }); /** * Tests clearing all entries from a specific whitelist type. */ - describe('emptyWhitelist', () => { - test('should clear all entries from BEP20ReceiverWhiteList successfully', async () => { + describe("emptyWhitelist", () => { + test("should clear all entries from BEP20ReceiverWhiteList successfully", async () => { const res = await sponsorClient.emptyWhitelist({ PolicyUUID: POLICY_UUID, WhitelistType: WhitelistType.BEP20ReceiverWhiteList, - }) + }); - expect(res).toEqual(true) - console.log('BEP20ReceiverWhiteList clearance response:', res) - }) - }) + expect(res).toEqual(true); + console.log("BEP20ReceiverWhiteList clearance response:", res); + }); + }); /** * Tests verifying the emptiness of a whitelist. */ - describe('getWhitelist', () => { - test('should confirm the whitelist is empty', async () => { + describe("getWhitelist", () => { + test("should confirm the whitelist is empty", async () => { const res = await sponsorClient.getWhitelist({ PolicyUUID: POLICY_UUID, WhitelistType: WhitelistType.BEP20ReceiverWhiteList, Offset: 0, Limit: 10, - }) + }); - expect(res).toBeNull() - console.log('BEP20ReceiverWhiteList emptiness check:', res) - }) - }) + expect(res).toBeNull(); + console.log("BEP20ReceiverWhiteList emptiness check:", res); + }); + }); /** * Tests retrieving user spend data. */ - describe('getUserSpendData', () => { - test('should return not null for user spend data', async () => { - const res = await sponsorClient.getUserSpendData(ACCOUNT_ADDRESS, POLICY_UUID) - - expect(res).not.toBeNull() - console.log('User spend data:', res) - }) - }) + describe("getUserSpendData", () => { + test("should return not null for user spend data", async () => { + const res = await sponsorClient.getUserSpendData( + ACCOUNT_ADDRESS, + POLICY_UUID + ); + + expect(res).not.toBeNull(); + console.log("User spend data:", res); + }); + }); /** * Tests retrieving policy spend data. */ - describe('getPolicySpendData', () => { - test('should retrieve policy spend data successfully', async () => { - const res = await sponsorClient.getPolicySpendData(POLICY_UUID) - expect(res.ChainID).not.toBeNull() - console.log('Policy spend data:', res) - }) - }) + describe("getPolicySpendData", () => { + test("should retrieve policy spend data successfully", async () => { + const res = await sponsorClient.getPolicySpendData(POLICY_UUID); + expect(res.ChainID).not.toBeNull(); + console.log("Policy spend data:", res); + }); + }); /** * Tests re-adding an account address to the 'From Account' whitelist after previous tests. */ - describe('addToWhitelist FromAccountWhitelist', () => { - test('should re-add an account address to FromAccountWhitelist successfully after removal', async () => { + describe("addToWhitelist FromAccountWhitelist", () => { + test("should re-add an account address to FromAccountWhitelist successfully after removal", async () => { const res = await sponsorClient.addToWhitelist({ PolicyUUID: POLICY_UUID, WhitelistType: WhitelistType.FromAccountWhitelist, Values: [ACCOUNT_ADDRESS], - }) - - expect(res).toEqual(true) - console.log('Re-addition to FromAccountWhitelist response:', res) - }) - }) + }); - - /** - * 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 sponsorClient.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 sponsorClient.isSponsorable(safeTransaction, opt) - const res = transformIsSponsorableResponse(resRaw) - expect(res.Sponsorable).toEqual(true) - - const txOpt: SendRawTransactionOptions = { - PrivatePolicyUUID: PRIVATE_POLICY_UUID, - UserAgent: "TEST USER AGENT" - }; - - const signedTx = await wallet.signTransaction(safeTransaction) - try { - const tx = await sponsorClient.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 - }) -}) + expect(res).toEqual(true); + console.log("Re-addition to FromAccountWhitelist response:", res); + }); + }); +}); diff --git a/tests/utils.ts b/tests/utils.ts index d9dfae1..4619567 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -7,16 +7,18 @@ import { Bundle, GaslessTransactionStatus, } from '../src' -import {CHAIN_ID, SPONSOR_URL, CHAIN_URL, PAYMASTER_URL, PRIVATE_KEY, TOKEN_CONTRACT_ADDRESS} from './env' +import {CHAIN_ID, SPONSOR_URL, CHAIN_URL, PAYMASTER_URL, PRIVATE_KEY, TOKEN_CONTRACT_ADDRESS, PRIVATE_POLICY_UUID} from './env' import {ethers} from 'ethers' + export const sponsorClient = new SponsorClient(SPONSOR_URL+"/"+CHAIN_ID, undefined, {staticNetwork: ethers.Network.from(Number(CHAIN_ID))}) // Provider for assembling the transaction (e.g., testnet) 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, undefined, {staticNetwork: ethers.Network.from(Number(CHAIN_ID))}) +export const paymasterClient = PaymasterClient.new(PAYMASTER_URL, undefined, {staticNetwork: ethers.Network.from(Number(CHAIN_ID))}) +export const privatePaymasterClient = PaymasterClient.newPrivatePaymaster(SPONSOR_URL+"/"+CHAIN_ID, PRIVATE_POLICY_UUID, 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)