Skip to content

Commit ca4c90e

Browse files
committed
fix: create new private paymaster client
1 parent babb35e commit ca4c90e

File tree

5 files changed

+119
-111
lines changed

5 files changed

+119
-111
lines changed

src/paymasterclient.ts

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,7 @@ export type IsSponsorableResponse = {
99
SponsorWebsite: string
1010
}
1111

12-
export type IsSponsorableOptions = {
13-
PrivatePolicyUUID?: string
14-
}
15-
1612
export type SendRawTransactionOptions = {
17-
PrivatePolicyUUID?: string
1813
UserAgent?: string
1914
}
2015

@@ -58,19 +53,46 @@ export type Bundle = {
5853
}
5954

6055
export class PaymasterClient extends ethers.JsonRpcProvider {
61-
constructor(url?: string | FetchRequest, network?: Networkish, options?: JsonRpcApiProviderOptions) {
56+
private privatePolicyUUID?: string
57+
58+
private constructor(
59+
url?: string | FetchRequest,
60+
network?: Networkish,
61+
options?: JsonRpcApiProviderOptions,
62+
privatePolicyUUID?: string
63+
) {
6264
super(url, network, options)
65+
this.privatePolicyUUID = privatePolicyUUID
66+
}
67+
68+
// Static method to create a new standard PaymasterClient
69+
static new(
70+
url?: string | FetchRequest,
71+
network?: Networkish,
72+
options?: JsonRpcApiProviderOptions
73+
): PaymasterClient {
74+
return new PaymasterClient(url, network, options)
75+
}
76+
77+
// Static method to create a new PaymasterClient with private policy
78+
static newPrivatePaymaster(
79+
url: string | FetchRequest,
80+
privatePolicyUUID: string,
81+
network?: Networkish,
82+
options?: JsonRpcApiProviderOptions
83+
): PaymasterClient {
84+
return new PaymasterClient(url, network, options, privatePolicyUUID)
6385
}
6486

6587
async chainID(): Promise<string> {
6688
return await this.send('eth_chainId', [])
6789
}
6890

69-
async isSponsorable(tx: TransactionRequest, opts: IsSponsorableOptions = {}): Promise<IsSponsorableResponse> {
70-
if (opts.PrivatePolicyUUID) {
71-
const newConnection = this._getConnection();
72-
newConnection.setHeader("X-MegaFuel-Policy-Uuid", opts.PrivatePolicyUUID);
73-
91+
async isSponsorable(tx: TransactionRequest): Promise<IsSponsorableResponse> {
92+
const policyUUID = this.privatePolicyUUID
93+
if (policyUUID) {
94+
const newConnection = this._getConnection()
95+
newConnection.setHeader("X-MegaFuel-Policy-Uuid", policyUUID)
7496
const sponsorProviderWithHeader = new ethers.JsonRpcProvider(
7597
newConnection,
7698
(this as any)._network,
@@ -79,24 +101,24 @@ export class PaymasterClient extends ethers.JsonRpcProvider {
79101
batchMaxCount: (this as any).batchMaxCount,
80102
polling: (this as any).polling
81103
}
82-
);
83-
84-
return await sponsorProviderWithHeader.send('pm_isSponsorable', [tx]);
104+
)
105+
return await sponsorProviderWithHeader.send('pm_isSponsorable', [tx])
85106
}
86-
return await this.send('pm_isSponsorable', [tx]);
107+
return await this.send('pm_isSponsorable', [tx])
87108
}
88109

89110
async sendRawTransaction(signedTx: string, opts: SendRawTransactionOptions = {}): Promise<string> {
90-
if (opts.UserAgent || opts.PrivatePolicyUUID) {
91-
const newConnection = this._getConnection();
111+
const policyUUID = this.privatePolicyUUID
112+
if (opts.UserAgent || this.privatePolicyUUID) {
113+
const newConnection = this._getConnection()
92114

93115
if (opts.UserAgent) {
94-
newConnection.setHeader("User-Agent", opts.UserAgent);
116+
newConnection.setHeader("User-Agent", opts.UserAgent)
95117
}
96-
if (opts.PrivatePolicyUUID) {
97-
newConnection.setHeader("X-MegaFuel-Policy-Uuid", opts.PrivatePolicyUUID);
118+
if (policyUUID) {
119+
newConnection.setHeader("X-MegaFuel-Policy-Uuid", policyUUID)
98120
}
99-
121+
100122
const sponsorProvider = new ethers.JsonRpcProvider(
101123
newConnection,
102124
(this as any)._network,
@@ -105,13 +127,13 @@ export class PaymasterClient extends ethers.JsonRpcProvider {
105127
batchMaxCount: (this as any).batchMaxCount,
106128
polling: (this as any).polling
107129
}
108-
);
109-
110-
if (opts.PrivatePolicyUUID) {
111-
return await sponsorProvider.send('eth_sendRawTransaction', [signedTx]);
130+
)
131+
132+
if (policyUUID) {
133+
return await sponsorProvider.send('eth_sendRawTransaction', [signedTx])
112134
}
113135
}
114-
return await this.send('eth_sendRawTransaction', [signedTx]);
136+
return await this.send('eth_sendRawTransaction', [signedTx])
115137
}
116138

117139
async getGaslessTransactionByHash(hash: string): Promise<GaslessTransaction> {
@@ -129,4 +151,4 @@ export class PaymasterClient extends ethers.JsonRpcProvider {
129151
async getBundleByUuid(bundleUuid: string): Promise<Bundle> {
130152
return await this.send('pm_getBundleByUuid', [bundleUuid])
131153
}
132-
}
154+
}

src/sponsorclient.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { FetchRequest, JsonRpcApiProviderOptions, Networkish } from "ethers"
2-
import { PaymasterClient } from "./paymasterclient"
3-
import type { AddressLike } from "ethers/src.ts/address"
4-
import type { BigNumberish } from "ethers/src.ts/utils"
1+
import {ethers, FetchRequest, JsonRpcApiProviderOptions, Networkish} from 'ethers'
2+
import type {AddressLike} from 'ethers/src.ts/address'
3+
import type {BigNumberish} from 'ethers/src.ts/utils'
54

65
export enum WhitelistType {
76
FromAccountWhitelist = 'FromAccountWhitelist',
@@ -43,12 +42,8 @@ export type PolicySpendData = {
4342
ChainID: number
4443
}
4544

46-
export class SponsorClient extends PaymasterClient {
47-
constructor(
48-
url?: string | FetchRequest,
49-
network?: Networkish,
50-
options?: JsonRpcApiProviderOptions
51-
) {
45+
export class SponsorClient extends ethers.JsonRpcProvider {
46+
constructor(url?: string | FetchRequest, network?: Networkish, options?: JsonRpcApiProviderOptions) {
5247
super(url, network, options)
5348
}
5449

@@ -68,14 +63,11 @@ export class SponsorClient extends PaymasterClient {
6863
return this.send('pm_getWhitelist', [params])
6964
}
7065

71-
async getUserSpendData(
72-
fromAddress: AddressLike,
73-
policyUUID: string
74-
): Promise<UserSpendData> {
66+
async getUserSpendData(fromAddress: AddressLike, policyUUID: string): Promise<UserSpendData> {
7567
return this.send('pm_getUserSpendData', [fromAddress, policyUUID])
7668
}
7769

7870
async getPolicySpendData(policyUUID: string): Promise<PolicySpendData> {
7971
return this.send('pm_getPolicySpendData', [policyUUID])
8072
}
81-
}
73+
}

tests/paymaster.spec.ts

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ import {
66
transformIsSponsorableResponse,
77
transformToGaslessTransaction,
88
delay, transformSponsorTxResponse, transformBundleResponse,
9+
privatePaymasterClient,
910
} from './utils'
10-
import {TOKEN_CONTRACT_ADDRESS, CHAIN_ID, RECIPIENT_ADDRESS} from './env'
11-
import {ethers} from 'ethers'
11+
import { TOKEN_CONTRACT_ADDRESS, CHAIN_ID, RECIPIENT_ADDRESS } from './env'
12+
import { ethers } from 'ethers'
13+
import { SendRawTransactionOptions } from '../src'
1214

1315
let TX_HASH = ''
1416

@@ -96,4 +98,50 @@ describe('paymasterQuery', () => {
9698
expect(sponsorTx.TxHash).toEqual(tx.TxHash)
9799
}, 13000)
98100
})
101+
102+
103+
104+
/**
105+
* Test for checking if a private policy transaction is sponsorable.
106+
*/
107+
describe('isSponsorable', () => {
108+
test('should successfully determine if transaction is sponsorable', async () => {
109+
const tokenContract = new ethers.Contract(TOKEN_CONTRACT_ADDRESS, tokenAbi, wallet)
110+
const tokenAmount = ethers.parseUnits('0', 18)
111+
const nonce = await privatePaymasterClient.getTransactionCount(wallet.address, 'pending')
112+
113+
const transaction = await tokenContract.transfer.populateTransaction(RECIPIENT_ADDRESS.toLowerCase(), tokenAmount)
114+
transaction.from = wallet.address
115+
transaction.nonce = nonce
116+
transaction.gasLimit = BigInt(100000)
117+
transaction.chainId = BigInt(CHAIN_ID)
118+
transaction.gasPrice = BigInt(0)
119+
120+
const safeTransaction = {
121+
...transaction,
122+
gasLimit: transaction.gasLimit.toString(),
123+
chainId: transaction.chainId.toString(),
124+
gasPrice: transaction.gasPrice.toString(),
125+
}
126+
127+
console.log('Prepared transaction:', safeTransaction)
128+
129+
const resRaw = await privatePaymasterClient.isSponsorable(safeTransaction)
130+
const res = transformIsSponsorableResponse(resRaw)
131+
expect(res.Sponsorable).toEqual(true)
132+
133+
const txOpt: SendRawTransactionOptions = {
134+
UserAgent: "TEST USER AGENT"
135+
};
136+
137+
const signedTx = await wallet.signTransaction(safeTransaction)
138+
try {
139+
const tx = await privatePaymasterClient.sendRawTransaction(signedTx,txOpt)
140+
TX_HASH = tx
141+
console.log('Transaction hash received:', TX_HASH)
142+
} catch (error) {
143+
console.error('Transaction failed:', error)
144+
}
145+
}, 100000) // Extends the default timeout as this test involves network calls
146+
})
99147
})

tests/sponsor.spec.ts

Lines changed: 9 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
1-
import {describe, expect, test} from '@jest/globals'
2-
import {WhitelistType} from '../src'
3-
import {POLICY_UUID, ACCOUNT_ADDRESS, CONTRACT_METHOD, TOKEN_CONTRACT_ADDRESS, CHAIN_ID, RECIPIENT_ADDRESS, PRIVATE_POLICY_UUID} from './env'
4-
import {ethers} from 'ethers'
5-
import {IsSponsorableOptions, SendRawTransactionOptions} from '../src/paymasterclient'
6-
import {
7-
sponsorClient,
8-
wallet,
9-
tokenAbi,
10-
transformIsSponsorableResponse,
11-
} from './utils'
12-
13-
let TX_HASH = ''
1+
import { describe, expect, test } from '@jest/globals'
2+
import { WhitelistType } from '../src'
3+
import { POLICY_UUID, ACCOUNT_ADDRESS, CONTRACT_METHOD } from './env'
4+
import { sponsorClient } from './utils'
145

156
/**
167
* Test suite for Sponsor API methods involving whitelist management and spend data retrieval.
@@ -168,7 +159,10 @@ describe('sponsorQuery', () => {
168159
*/
169160
describe('getUserSpendData', () => {
170161
test('should return not null for user spend data', async () => {
171-
const res = await sponsorClient.getUserSpendData(ACCOUNT_ADDRESS, POLICY_UUID)
162+
const res = await sponsorClient.getUserSpendData(
163+
ACCOUNT_ADDRESS,
164+
POLICY_UUID
165+
)
172166

173167
expect(res).not.toBeNull()
174168
console.log('User spend data:', res)
@@ -201,54 +195,4 @@ describe('sponsorQuery', () => {
201195
console.log('Re-addition to FromAccountWhitelist response:', res)
202196
})
203197
})
204-
205-
206-
/**
207-
* Test for checking if a private policy transaction is sponsorable.
208-
*/
209-
describe('isSponsorable', () => {
210-
test('should successfully determine if transaction is sponsorable', async () => {
211-
const tokenContract = new ethers.Contract(TOKEN_CONTRACT_ADDRESS, tokenAbi, wallet)
212-
const tokenAmount = ethers.parseUnits('0', 18)
213-
const nonce = await sponsorClient.getTransactionCount(wallet.address, 'pending')
214-
215-
const transaction = await tokenContract.transfer.populateTransaction(RECIPIENT_ADDRESS.toLowerCase(), tokenAmount)
216-
transaction.from = wallet.address
217-
transaction.nonce = nonce
218-
transaction.gasLimit = BigInt(100000)
219-
transaction.chainId = BigInt(CHAIN_ID)
220-
transaction.gasPrice = BigInt(0)
221-
222-
const safeTransaction = {
223-
...transaction,
224-
gasLimit: transaction.gasLimit.toString(),
225-
chainId: transaction.chainId.toString(),
226-
gasPrice: transaction.gasPrice.toString(),
227-
}
228-
229-
console.log('Prepared transaction:', safeTransaction)
230-
231-
const opt: IsSponsorableOptions = {
232-
PrivatePolicyUUID: PRIVATE_POLICY_UUID
233-
};
234-
235-
const resRaw = await sponsorClient.isSponsorable(safeTransaction, opt)
236-
const res = transformIsSponsorableResponse(resRaw)
237-
expect(res.Sponsorable).toEqual(true)
238-
239-
const txOpt: SendRawTransactionOptions = {
240-
PrivatePolicyUUID: PRIVATE_POLICY_UUID,
241-
UserAgent: "TEST USER AGENT"
242-
};
243-
244-
const signedTx = await wallet.signTransaction(safeTransaction)
245-
try {
246-
const tx = await sponsorClient.sendRawTransaction(signedTx,txOpt)
247-
TX_HASH = tx
248-
console.log('Transaction hash received:', TX_HASH)
249-
} catch (error) {
250-
console.error('Transaction failed:', error)
251-
}
252-
}, 100000) // Extends the default timeout as this test involves network calls
253-
})
254-
})
198+
})

tests/utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,18 @@ import {
77
Bundle,
88
GaslessTransactionStatus,
99
} from '../src'
10-
import {CHAIN_ID, SPONSOR_URL, CHAIN_URL, PAYMASTER_URL, PRIVATE_KEY, TOKEN_CONTRACT_ADDRESS} from './env'
10+
import {CHAIN_ID, SPONSOR_URL, CHAIN_URL, PAYMASTER_URL, PRIVATE_KEY, TOKEN_CONTRACT_ADDRESS, PRIVATE_POLICY_UUID} from './env'
1111
import {ethers} from 'ethers'
1212

13+
1314
export const sponsorClient = new SponsorClient(SPONSOR_URL+"/"+CHAIN_ID, undefined, {staticNetwork: ethers.Network.from(Number(CHAIN_ID))})
1415

1516
// Provider for assembling the transaction (e.g., testnet)
1617
export const assemblyProvider = new ethers.JsonRpcProvider(CHAIN_URL)
1718

1819
// Provider for sending the transaction (e.g., could be a different network or provider)
19-
export const paymasterClient = new PaymasterClient(PAYMASTER_URL, undefined, {staticNetwork: ethers.Network.from(Number(CHAIN_ID))})
20+
export const paymasterClient = PaymasterClient.new(PAYMASTER_URL, undefined, {staticNetwork: ethers.Network.from(Number(CHAIN_ID))})
21+
export const privatePaymasterClient = PaymasterClient.newPrivatePaymaster(SPONSOR_URL+"/"+CHAIN_ID, PRIVATE_POLICY_UUID, undefined, {staticNetwork: ethers.Network.from(Number(CHAIN_ID))})
2022

2123
export const wallet = new ethers.Wallet(PRIVATE_KEY, assemblyProvider)
2224
// ERC20 token ABI (only including the transfer function)

0 commit comments

Comments
 (0)