Skip to content

Commit a78b57f

Browse files
committed
feat: support private policy
1 parent 7b9ce33 commit a78b57f

File tree

4 files changed

+99
-15
lines changed

4 files changed

+99
-15
lines changed

src/paymasterclient.ts

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

12+
export type IsSponsorableOptions = {
13+
PrivatePolicyUUID?: string
14+
}
15+
16+
export type SendRawTransactionOptions = {
17+
PrivatePolicyUUID?: string
18+
}
19+
1220
export enum GaslessTransactionStatus { New = 0, Pending = 1, Confirmed = 2, Failed = 3, Invalid = 4}
1321

1422
export type GaslessTransaction = {
@@ -48,36 +56,61 @@ export type Bundle = {
4856
readonly ChainID: number
4957
}
5058

51-
export class PaymasterClient extends ethers.JsonRpcProvider {
52-
constructor(url?: string | FetchRequest, network?: Networkish, options?: JsonRpcApiProviderOptions) {
53-
super(url, network, options)
59+
export class PaymasterClient {
60+
private sponsorClient: ethers.JsonRpcProvider
61+
private userClient: ethers.JsonRpcProvider
62+
63+
constructor(
64+
userUrl: string | FetchRequest,
65+
sponsorUrl: string | FetchRequest,
66+
network?: Networkish,
67+
options?: JsonRpcApiProviderOptions
68+
) {
69+
this.userClient = new ethers.JsonRpcProvider(userUrl, network, options)
70+
this.sponsorClient = new ethers.JsonRpcProvider(sponsorUrl, network, options)
5471
}
5572

5673
async chainID(): Promise<string> {
57-
return await this.send('eth_chainId', [])
74+
return await this.userClient.send('eth_chainId', [])
5875
}
5976

60-
async isSponsorable(tx: TransactionRequest): Promise<IsSponsorableResponse> {
61-
return await this.send('pm_isSponsorable', [tx])
77+
async isSponsorable(tx: TransactionRequest, opts: IsSponsorableOptions = {} ): Promise<IsSponsorableResponse> {
78+
if (opts.PrivatePolicyUUID) {
79+
this.sponsorClient._getConnection().setHeader("X-MegaFuel-Policy-Uuid", opts.PrivatePolicyUUID)
80+
return await this.sponsorClient.send('pm_isSponsorable', [tx])
81+
}
82+
return await this.userClient.send('pm_isSponsorable', [tx])
6283
}
6384

64-
async sendRawTransaction(signedTx: string): Promise<string> {
65-
return await this.send('eth_sendRawTransaction', [signedTx])
85+
async sendRawTransaction(signedTx: string, opts: SendRawTransactionOptions= {}): Promise<string> {
86+
if (opts.PrivatePolicyUUID) {
87+
this.sponsorClient._getConnection().setHeader("X-MegaFuel-Policy-Uuid", opts.PrivatePolicyUUID)
88+
return await this.sponsorClient.send('eth_sendRawTransaction', [signedTx])
89+
}
90+
return await this.userClient.send('eth_sendRawTransaction', [signedTx])
6691
}
6792

6893
async getGaslessTransactionByHash(hash: string): Promise<GaslessTransaction> {
69-
return await this.send('eth_getGaslessTransactionByHash', [hash])
94+
return await this.userClient.send('eth_getGaslessTransactionByHash', [hash])
7095
}
7196

7297
async getSponsorTxByTxHash(hash: string): Promise<SponsorTx> {
73-
return await this.send('pm_getSponsorTxByTxHash', [hash])
98+
return await this.userClient.send('pm_getSponsorTxByTxHash', [hash])
7499
}
75100

76101
async getSponsorTxByBundleUuid(bundleUuid: string): Promise<SponsorTx> {
77-
return await this.send('pm_getSponsorTxByBundleUuid', [bundleUuid])
102+
return await this.userClient.send('pm_getSponsorTxByBundleUuid', [bundleUuid])
78103
}
79104

80105
async getBundleByUuid(bundleUuid: string): Promise<Bundle> {
81-
return await this.send('pm_getBundleByUuid', [bundleUuid])
106+
return await this.userClient.send('pm_getBundleByUuid', [bundleUuid])
107+
}
108+
109+
getSponsorProvider(): ethers.JsonRpcProvider {
110+
return this.sponsorClient
111+
}
112+
113+
getUserProvider(): ethers.JsonRpcProvider {
114+
return this.userClient
82115
}
83116
}

tests/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export const ACCOUNT_ADDRESS = '0xF9A8db17431DD8563747D6FC770297E438Aa12eB'
1616
export const CONTRACT_METHOD = '0xa9059cbb'
1717
export const TOKEN_CONTRACT_ADDRESS = '0xeD24FC36d5Ee211Ea25A80239Fb8C4Cfd80f12Ee'
1818
export const RECIPIENT_ADDRESS = '0xDE08B1Fd79b7016F8DD3Df11f7fa0FbfdF07c941'
19+
export const PRIVATE_POLICY_UUID = "90f1ba4c-1f93-4759-b8a9-da4d59c668b4"

tests/paymaster.spec.ts

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
transformToGaslessTransaction,
88
delay, transformSponsorTxResponse, transformBundleResponse,
99
} from './utils'
10-
import {TOKEN_CONTRACT_ADDRESS, CHAIN_ID, RECIPIENT_ADDRESS} from './env'
10+
import {IsSponsorableOptions, SendRawTransactionOptions} from '../src/paymasterclient'
11+
import {TOKEN_CONTRACT_ADDRESS, CHAIN_ID, RECIPIENT_ADDRESS, PRIVATE_POLICY_UUID} from './env'
1112
import {ethers} from 'ethers'
1213

1314
let TX_HASH = ''
@@ -34,7 +35,7 @@ describe('paymasterQuery', () => {
3435
test('should successfully determine if transaction is sponsorable', async () => {
3536
const tokenContract = new ethers.Contract(TOKEN_CONTRACT_ADDRESS, tokenAbi, wallet)
3637
const tokenAmount = ethers.parseUnits('0', 18)
37-
const nonce = await paymasterClient.getTransactionCount(wallet.address, 'pending')
38+
const nonce = await paymasterClient.getUserProvider().getTransactionCount(wallet.address, 'pending')
3839

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

tests/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const sponsorClient = new SponsorClient(SPONSOR_URL, undefined, {staticNe
1616
export const assemblyProvider = new ethers.JsonRpcProvider(CHAIN_URL)
1717

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

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

0 commit comments

Comments
 (0)