@gasfree-kit/evm-4337 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,271 +1,338 @@
1
- # @gasfree-kit/evm-4337
2
-
3
- ERC-4337 USDT transfers for EVM chains using WDK-backed Safe smart accounts.
4
-
5
- This package gives you:
6
-
7
- - standard seed-phrase driven transfers
8
- - sponsored mode, where a paymaster covers gas
9
- - token-paid mode, where gas is charged in USDT
10
- - optional passkey linking and passkey-signed transfers
11
-
12
- ## How It Works
13
-
14
- ```mermaid
15
- flowchart LR
16
- App["Your app"] --> SDK["@gasfree-kit/evm-4337"]
17
- SDK --> WDK["WDK wallet manager"]
18
- WDK --> Safe["Safe smart account"]
19
- Safe --> UserOp["ERC-4337 UserOperation"]
20
- UserOp --> Bundler["Bundler"]
21
- Paymaster["Paymaster"] --> Bundler
22
- Bundler --> Chain["Supported EVM chain"]
23
- ```
24
-
25
- ## Supported Chains
26
-
27
- | Chain | Chain ID | Typical fallback fee estimate |
28
- | -------- | -------- | ----------------------------- |
29
- | Ethereum | 1 | `1.40` USDT |
30
- | Base | 8453 | `0.15` USDT |
31
- | Arbitrum | 42161 | `0.10` USDT |
32
- | Optimism | 10 | `0.10` USDT |
33
- | Polygon | 137 | `0.10` USDT |
34
- | Celo | 42220 | `0.05` USDT |
35
- | Plasma | 9745 | `0.05` USDT |
36
-
37
- ## Installation
38
-
39
- ```bash
40
- npm install @gasfree-kit/evm-4337
41
- ```
42
-
43
- Required peer dependency:
44
-
45
- ```bash
46
- npm install @tetherto/wdk-wallet-evm-erc-4337
47
- ```
48
-
49
- Optional passkey peer dependencies:
50
-
51
- ```bash
52
- npm install @safe-global/protocol-kit @safe-global/relay-kit
53
- ```
54
-
55
- ## Choose Your Gas Mode
56
-
57
- | Mode | When to use it | Required config |
58
- | ---------- | --------------------------------- | ------------------------------------------------ |
59
- | Sponsored | Your paymaster fully covers gas | `isSponsored: true` and `sponsorshipPolicyId` |
60
- | Token-paid | Gas is deducted from USDT balance | `isSponsored: false` and a paymaster/token setup |
61
-
62
- ## Quick Start
63
-
64
- ### 1. Create a config
65
-
66
- Sponsored mode:
67
-
68
- ```ts
69
- import type { EVM4337ClientConfig } from '@gasfree-kit/evm-4337';
70
-
71
- const sponsoredConfig: EVM4337ClientConfig = {
72
- chain: 'base',
73
- rpcUrl: 'https://mainnet.base.org',
74
- bundlerUrl: 'https://your-bundler.example.com',
75
- paymasterUrl: 'https://your-paymaster.example.com',
76
- isSponsored: true,
77
- sponsorshipPolicyId: 'your-policy-id',
78
- };
79
- ```
80
-
81
- Token-paid mode:
82
-
83
- ```ts
84
- const tokenPaidConfig: EVM4337ClientConfig = {
85
- chain: 'base',
86
- rpcUrl: 'https://mainnet.base.org',
87
- bundlerUrl: 'https://your-bundler.example.com',
88
- paymasterUrl: 'https://your-paymaster.example.com',
89
- isSponsored: false,
90
- paymasterAddress: '0xYourPaymasterAddress',
91
- // Optional:
92
- // paymasterTokenAddress: '0xYourUSDTLikeToken'
93
- };
94
- ```
95
-
96
- ### 2. Set up the Safe smart account
97
-
98
- ```ts
99
- import { setupErc4337Wallet } from '@gasfree-kit/evm-4337';
100
-
101
- const seedPhrase = 'your twelve word seed phrase here ...';
102
- const { wallet, account, address } = await setupErc4337Wallet(seedPhrase, sponsoredConfig);
103
-
104
- console.log(address); // Safe address
105
- ```
106
-
107
- ### 3. Check balance
108
-
109
- ```ts
110
- import { EvmTransfer } from '@gasfree-kit/evm-4337';
111
- import { EVM_CHAINS } from '@gasfree-kit/core';
112
-
113
- const balance = await EvmTransfer.checkTokenBalance(
114
- seedPhrase,
115
- sponsoredConfig,
116
- EVM_CHAINS.base.usdtAddress,
117
- );
118
-
119
- console.log(balance.data.usdBalance);
120
- ```
121
-
122
- ### 4. Estimate fees
123
-
124
- ```ts
125
- const estimate = await EvmTransfer.getTransactionEstimateFee(
126
- seedPhrase,
127
- sponsoredConfig,
128
- '0x1111111111111111111111111111111111111111',
129
- );
130
-
131
- console.log(estimate.data.fee);
132
- ```
133
-
134
- ### 5. Send a transfer
135
-
136
- ```ts
137
- const result = await EvmTransfer.sendToken(
138
- seedPhrase,
139
- sponsoredConfig,
140
- EVM_CHAINS.base.usdtAddress,
141
- '50.00',
142
- '0x1111111111111111111111111111111111111111',
143
- );
144
-
145
- console.log(result.transactionHash);
146
- ```
147
-
148
- ### 6. Send a batch transfer
149
-
150
- ```ts
151
- const batch = await EvmTransfer.sendBatchToken(
152
- seedPhrase,
153
- sponsoredConfig,
154
- EVM_CHAINS.base.usdtAddress,
155
- [
156
- { address: '0x1111111111111111111111111111111111111111', amount: '25.00' },
157
- { address: '0x2222222222222222222222222222222222222222', amount: '10.00' },
158
- ],
159
- );
160
- ```
161
-
162
- ## Passkey Flow
163
-
164
- Passkeys are optional. They let you add a WebAuthn signer to an existing Safe so transfers can be approved with biometrics instead of a seed phrase.
165
-
166
- Important:
167
-
168
- - the Safe must already be deployed on-chain before you link a passkey
169
- - `storage: 'persist'` is convenience storage only, not hardened secret storage
170
-
171
- ### Passkey diagram
172
-
173
- ```mermaid
174
- flowchart TD
175
- Seed["Seed phrase owner"] --> Safe["Safe smart account"]
176
- Passkey["Passkey owner"] --> Safe
177
- Safe --> Transfer["Passkey-signed transfer"]
178
- Transfer --> Bundler["Bundler + paymaster"]
179
- Bundler --> Chain["EVM chain"]
180
- ```
181
-
182
- ### Link a passkey to an existing Safe
183
-
184
- ```ts
185
- import { linkPasskeyToSafe } from '@gasfree-kit/evm-4337';
186
-
187
- const passkey = await linkPasskeyToSafe(seedPhrase, sponsoredConfig, {
188
- rpName: 'My App',
189
- userName: 'user@example.com',
190
- storage: 'not_persist',
191
- });
192
-
193
- console.log(passkey.safeAddress);
194
- console.log(passkey.credential.signerAddress);
195
- ```
196
-
197
- ### Check whether a passkey is linked
198
-
199
- ```ts
200
- import { isPasskeyLinked } from '@gasfree-kit/evm-4337';
201
-
202
- const status = await isPasskeyLinked(seedPhrase, sponsoredConfig);
203
-
204
- if (status.linked) {
205
- console.log(status.signerAddress);
206
- }
207
- ```
208
-
209
- ### Send a transfer with a passkey
210
-
211
- ```ts
212
- import { PasskeyTransfer } from '@gasfree-kit/evm-4337';
213
- import { EVM_CHAINS } from '@gasfree-kit/core';
214
-
215
- const tx = await PasskeyTransfer.sendToken(
216
- passkey.credential,
217
- sponsoredConfig,
218
- EVM_CHAINS.base.usdtAddress,
219
- '10.00',
220
- '0x1111111111111111111111111111111111111111',
221
- );
222
- ```
223
-
224
- ### Remove a passkey owner
225
-
226
- ```ts
227
- import { unlinkPasskeyFromSafe } from '@gasfree-kit/evm-4337';
228
-
229
- await unlinkPasskeyFromSafe(seedPhrase, sponsoredConfig, passkey.credential);
230
- ```
231
-
232
- ## Main Exports
233
-
234
- | Export | What it does |
235
- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
236
- | `setupErc4337Wallet` | Creates a WDK-backed ERC-4337 wallet and resolves the Safe address |
237
- | `EvmTransfer` | Estimates, checks balance, sends single transfers, and sends batch transfers |
238
- | `PasskeyTransfer` | Sends passkey-signed transfers after a passkey has been linked |
239
- | `linkPasskeyToSafe` | Registers a passkey and adds its signer to the Safe owner set |
240
- | `isPasskeyLinked` | Checks whether a stored passkey is still an owner on-chain |
241
- | `unlinkPasskeyFromSafe` | Removes a passkey signer from the Safe |
242
- | `getErc4337ConfigForChain` | Normalizes the public client config into runtime config |
243
- | `GAS_FEE_FALLBACKS` and `GAS_FEE_ESTIMATES` | Exposes fallback fee heuristics per chain |
244
- | `toUsdtBaseUnitsEvm`, `formatTokenBalance`, `feeToUsdt`, `encodeErc20Transfer` | Utility helpers for amounts and calldata |
245
-
246
- ## Export Example
247
-
248
- ```ts
249
- import {
250
- setupErc4337Wallet,
251
- EvmTransfer,
252
- PasskeyTransfer,
253
- linkPasskeyToSafe,
254
- isPasskeyLinked,
255
- unlinkPasskeyFromSafe,
256
- getErc4337ConfigForChain,
257
- GAS_FEE_FALLBACKS,
258
- GAS_FEE_ESTIMATES,
259
- } from '@gasfree-kit/evm-4337';
260
- ```
261
-
262
- ## Notes
263
-
264
- - `sponsorshipPolicyId` is required when `isSponsored` is `true`
265
- - self-transfers are blocked
266
- - token-paid mode checks that the USDT balance can cover the transfer and gas reserve
267
- - passkey linking verifies the signer deployment before adding it as an owner
268
-
269
- ## License
270
-
271
- MIT
1
+ # @gasfree-kit/evm-4337
2
+
3
+ ERC-4337 USDT transfers for EVM chains using WDK-backed Safe smart accounts.
4
+
5
+ This package gives you:
6
+
7
+ - standard seed-phrase driven transfers
8
+ - sponsored mode, where a paymaster covers gas
9
+ - token-paid mode, where gas is charged in USDT
10
+ - optional passkey linking and passkey-signed transfers
11
+
12
+ ## How It Works
13
+
14
+ ```
15
+ ┌──────────────────────┐
16
+ Your app
17
+ └──────────┬───────────┘
18
+
19
+ v
20
+ ┌──────────────────────┐
21
+ @gasfree-kit/evm-4337│
22
+ └──────────┬───────────┘
23
+
24
+ v
25
+ ┌──────────────────────┐ ┌──────────────────────┐
26
+ │ WDK wallet manager │──────>│ Safe smart account │
27
+ └──────────────────────┘ └──────────┬───────────┘
28
+
29
+ v
30
+ ┌──────────────────────┐
31
+ ┌──────────────────────┐ │ UserOperation │
32
+ │ Paymaster │·····> │ (ERC-4337) │
33
+ │ │ └──────────┬───────────┘
34
+ │ sponsors gas or │ │
35
+ │ charges in USDT │ v
36
+ └──────────────────────┘ ┌──────────────────────┐
37
+ │ Bundler │
38
+ └──────────┬───────────┘
39
+
40
+ v
41
+ ┌──────────────────────┐
42
+ │ EVM chain │
43
+ └──────────────────────┘
44
+ ```
45
+
46
+ ```mermaid
47
+ flowchart TD
48
+ A["Your app"] --> B["@gasfree-kit/evm-4337"]
49
+ B --> C["WDK wallet manager"]
50
+ C --> D["Safe smart account"]
51
+ D --> E["ERC-4337 UserOperation"]
52
+ F["Paymaster"] -.->|sponsors or charges USDT| E
53
+ E --> G["Bundler"]
54
+ G --> H["EVM chain"]
55
+ ```
56
+
57
+ ## Supported Chains
58
+
59
+ | Chain | Chain ID | Typical fallback fee estimate |
60
+ | -------- | -------- | ----------------------------- |
61
+ | Ethereum | 1 | `1.40` USDT |
62
+ | Base | 8453 | `0.15` USDT |
63
+ | Arbitrum | 42161 | `0.10` USDT |
64
+ | Optimism | 10 | `0.10` USDT |
65
+ | Polygon | 137 | `0.10` USDT |
66
+ | Celo | 42220 | `0.05` USDT |
67
+ | Plasma | 9745 | `0.05` USDT |
68
+
69
+ ## Installation
70
+
71
+ ```bash
72
+ npm install @gasfree-kit/evm-4337
73
+ ```
74
+
75
+ Required peer dependency:
76
+
77
+ ```bash
78
+ npm install @tetherto/wdk-wallet-evm-erc-4337
79
+ ```
80
+
81
+ Optional passkey peer dependencies:
82
+
83
+ ```bash
84
+ npm install @safe-global/protocol-kit @safe-global/relay-kit
85
+ ```
86
+
87
+ ## Choose Your Gas Mode
88
+
89
+ | Mode | When to use it | Required config |
90
+ | ---------- | --------------------------------- | ------------------------------------------------ |
91
+ | Sponsored | Your paymaster fully covers gas | `isSponsored: true` and `sponsorshipPolicyId` |
92
+ | Token-paid | Gas is deducted from USDT balance | `isSponsored: false` and a paymaster/token setup |
93
+
94
+ ## Quick Start
95
+
96
+ ### 1. Create a config
97
+
98
+ Sponsored mode:
99
+
100
+ ```ts
101
+ import type { EVM4337ClientConfig } from '@gasfree-kit/evm-4337';
102
+
103
+ const sponsoredConfig: EVM4337ClientConfig = {
104
+ chain: 'base',
105
+ rpcUrl: 'https://mainnet.base.org',
106
+ bundlerUrl: 'https://your-bundler.example.com',
107
+ paymasterUrl: 'https://your-paymaster.example.com',
108
+ isSponsored: true,
109
+ sponsorshipPolicyId: 'your-policy-id',
110
+ };
111
+ ```
112
+
113
+ Token-paid mode:
114
+
115
+ ```ts
116
+ const tokenPaidConfig: EVM4337ClientConfig = {
117
+ chain: 'base',
118
+ rpcUrl: 'https://mainnet.base.org',
119
+ bundlerUrl: 'https://your-bundler.example.com',
120
+ paymasterUrl: 'https://your-paymaster.example.com',
121
+ isSponsored: false,
122
+ paymasterAddress: '0xYourPaymasterAddress',
123
+ // Optional:
124
+ // paymasterTokenAddress: '0xYourUSDTLikeToken'
125
+ };
126
+ ```
127
+
128
+ ### 2. Generate a seed phrase
129
+
130
+ ```ts
131
+ import { generateSeedPhrase } from '@gasfree-kit/core';
132
+
133
+ const seedPhrase = await generateSeedPhrase();
134
+ ```
135
+
136
+ ### 3. Set up the Safe smart account
137
+
138
+ ```ts
139
+ import { setupErc4337Wallet } from '@gasfree-kit/evm-4337';
140
+
141
+ const { wallet, account, address } = await setupErc4337Wallet(seedPhrase, sponsoredConfig);
142
+
143
+ console.log(address); // Safe address
144
+ ```
145
+
146
+ ### 4. Check balance
147
+
148
+ ```ts
149
+ import { EvmTransfer } from '@gasfree-kit/evm-4337';
150
+ import { EVM_CHAINS } from '@gasfree-kit/core';
151
+
152
+ const balance = await EvmTransfer.checkTokenBalance(
153
+ seedPhrase,
154
+ sponsoredConfig,
155
+ EVM_CHAINS.base.usdtAddress,
156
+ );
157
+
158
+ console.log(balance.data.usdBalance);
159
+ ```
160
+
161
+ ### 5. Estimate fees
162
+
163
+ ```ts
164
+ const estimate = await EvmTransfer.getTransactionEstimateFee(
165
+ seedPhrase,
166
+ sponsoredConfig,
167
+ '0x1111111111111111111111111111111111111111',
168
+ );
169
+
170
+ console.log(estimate.data.fee);
171
+ ```
172
+
173
+ ### 6. Send a transfer
174
+
175
+ ```ts
176
+ const result = await EvmTransfer.sendToken(
177
+ seedPhrase,
178
+ sponsoredConfig,
179
+ EVM_CHAINS.base.usdtAddress,
180
+ '50.00',
181
+ '0x1111111111111111111111111111111111111111',
182
+ );
183
+
184
+ console.log(result.transactionHash);
185
+ ```
186
+
187
+ ### 7. Send a batch transfer
188
+
189
+ ```ts
190
+ const batch = await EvmTransfer.sendBatchToken(
191
+ seedPhrase,
192
+ sponsoredConfig,
193
+ EVM_CHAINS.base.usdtAddress,
194
+ [
195
+ { address: '0x1111111111111111111111111111111111111111', amount: '25.00' },
196
+ { address: '0x2222222222222222222222222222222222222222', amount: '10.00' },
197
+ ],
198
+ );
199
+ ```
200
+
201
+ ## Passkey Flow
202
+
203
+ Passkeys are optional. They let you add a WebAuthn signer to an existing Safe so transfers can be approved with biometrics instead of a seed phrase.
204
+
205
+ Important:
206
+
207
+ - the Safe must already be deployed on-chain before you link a passkey
208
+ - `storage: 'persist'` is convenience storage only, not hardened secret storage
209
+
210
+ ### Passkey diagram
211
+
212
+ ```
213
+ ┌──────────────────────┐
214
+ │ Seed phrase owner │──────┐
215
+ └──────────────────────┘ │
216
+ v
217
+ ┌──────────────────────┐
218
+ Safe smart account │
219
+ └──────────┬───────────┘
220
+ ┌──────────────────────┐ │
221
+ │ Passkey (WebAuthn) │──────┘
222
+ └──────────────────────┘
223
+
224
+ v
225
+ ┌──────────────────────┐
226
+ │ Signed UserOperation │
227
+ └──────────┬───────────┘
228
+
229
+ v
230
+ ┌──────────────────────┐
231
+ │ Bundler + Paymaster │
232
+ └──────────┬───────────┘
233
+
234
+ v
235
+ ┌──────────────────────┐
236
+ │ EVM chain │
237
+ └──────────────────────┘
238
+ ```
239
+
240
+ ```mermaid
241
+ flowchart TD
242
+ A["Seed phrase owner"] --> C["Safe smart account"]
243
+ B["Passkey (WebAuthn)"] --> C
244
+ C --> D["Signed UserOperation"]
245
+ D --> E["Bundler + Paymaster"]
246
+ E --> F["EVM chain"]
247
+ ```
248
+
249
+ ### Link a passkey to an existing Safe
250
+
251
+ ```ts
252
+ import { linkPasskeyToSafe } from '@gasfree-kit/evm-4337';
253
+
254
+ const passkey = await linkPasskeyToSafe(seedPhrase, sponsoredConfig, {
255
+ rpName: 'My App',
256
+ userName: 'user@example.com',
257
+ storage: 'not_persist',
258
+ });
259
+
260
+ console.log(passkey.safeAddress);
261
+ console.log(passkey.credential.signerAddress);
262
+ ```
263
+
264
+ ### Check whether a passkey is linked
265
+
266
+ ```ts
267
+ import { isPasskeyLinked } from '@gasfree-kit/evm-4337';
268
+
269
+ const status = await isPasskeyLinked(seedPhrase, sponsoredConfig);
270
+
271
+ if (status.linked) {
272
+ console.log(status.signerAddress);
273
+ }
274
+ ```
275
+
276
+ ### Send a transfer with a passkey
277
+
278
+ ```ts
279
+ import { PasskeyTransfer } from '@gasfree-kit/evm-4337';
280
+ import { EVM_CHAINS } from '@gasfree-kit/core';
281
+
282
+ const tx = await PasskeyTransfer.sendToken(
283
+ passkey.credential,
284
+ sponsoredConfig,
285
+ EVM_CHAINS.base.usdtAddress,
286
+ '10.00',
287
+ '0x1111111111111111111111111111111111111111',
288
+ );
289
+ ```
290
+
291
+ ### Remove a passkey owner
292
+
293
+ ```ts
294
+ import { unlinkPasskeyFromSafe } from '@gasfree-kit/evm-4337';
295
+
296
+ await unlinkPasskeyFromSafe(seedPhrase, sponsoredConfig, passkey.credential);
297
+ ```
298
+
299
+ ## Main Exports
300
+
301
+ | Export | What it does |
302
+ | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
303
+ | `setupErc4337Wallet` | Creates a WDK-backed ERC-4337 wallet and resolves the Safe address |
304
+ | `EvmTransfer` | Estimates, checks balance, sends single transfers, and sends batch transfers |
305
+ | `PasskeyTransfer` | Sends passkey-signed transfers after a passkey has been linked |
306
+ | `linkPasskeyToSafe` | Registers a passkey and adds its signer to the Safe owner set |
307
+ | `isPasskeyLinked` | Checks whether a stored passkey is still an owner on-chain |
308
+ | `unlinkPasskeyFromSafe` | Removes a passkey signer from the Safe |
309
+ | `getErc4337ConfigForChain` | Normalizes the public client config into runtime config |
310
+ | `GAS_FEE_FALLBACKS` and `GAS_FEE_ESTIMATES` | Exposes fallback fee heuristics per chain |
311
+ | `toUsdtBaseUnitsEvm`, `formatTokenBalance`, `feeToUsdt`, `encodeErc20Transfer` | Utility helpers for amounts and calldata |
312
+
313
+ ## Export Example
314
+
315
+ ```ts
316
+ import {
317
+ setupErc4337Wallet,
318
+ EvmTransfer,
319
+ PasskeyTransfer,
320
+ linkPasskeyToSafe,
321
+ isPasskeyLinked,
322
+ unlinkPasskeyFromSafe,
323
+ getErc4337ConfigForChain,
324
+ GAS_FEE_FALLBACKS,
325
+ GAS_FEE_ESTIMATES,
326
+ } from '@gasfree-kit/evm-4337';
327
+ ```
328
+
329
+ ## Notes
330
+
331
+ - `sponsorshipPolicyId` is required when `isSponsored` is `true`
332
+ - self-transfers are blocked
333
+ - token-paid mode checks that the USDT balance can cover the transfer and gas reserve
334
+ - passkey linking verifies the signer deployment before adding it as an owner
335
+
336
+ ## License
337
+
338
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gasfree-kit/evm-4337",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "ERC-4337 gasless transactions for EVM chains — powered by WDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "@noble/hashes": "^2.0.1",
23
23
  "abstractionkit": "^0.2.30",
24
- "@gasfree-kit/core": "0.1.1"
24
+ "@gasfree-kit/core": "0.2.0"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "@safe-global/protocol-kit": "^5.0.0",
@@ -1,68 +1,68 @@
1
- diff --git a/dist/cjs/src/index.cjs b/dist/cjs/src/index.cjs
2
- --- a/dist/cjs/src/index.cjs
3
- +++ b/dist/cjs/src/index.cjs
4
- @@ -1946,6 +1946,9 @@ var GenericFeeEstimator = class {
5
- const context = "paymasterTokenAddress" in paymasterOptions ? {
6
- token: paymasterOptions.paymasterTokenAddress
7
- } : paymasterOptions.paymasterContext ?? {};
8
- + if (paymasterOptions.sponsorshipPolicyId) {
9
- + context.sponsorshipPolicyId = paymasterOptions.sponsorshipPolicyId;
10
- + }
11
- const [feeData, paymasterStubData] = await Promise.all([
12
- this.#getUserOperationGasPrices(this.rpcUrl),
13
- paymasterClient.request({
14
- @@ -2017,7 +2020,8 @@ var GenericFeeEstimator = class {
15
- const sponsoredData = await paymasterClient.request({
16
- method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
17
- params
18
- });
19
- - return sponsoredData;
20
- + sponsoredData.callGasLimit = userOperation.callGasLimit.toString();
21
- + return sponsoredData;
22
- }
23
- const erc20PaymasterData = await paymasterClient.request({
24
- method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
25
- @@ -2032,7 +2036,8 @@ var GenericFeeEstimator = class {
26
- const threshold = await protocolKit.getThreshold();
27
- erc20PaymasterData.verificationGasLimit = (BigInt(erc20PaymasterData.verificationGasLimit) + BigInt(threshold) * this.defaultVerificationGasLimitOverhead).toString();
28
- }
29
- - return erc20PaymasterData;
30
- + erc20PaymasterData.callGasLimit = userOperation.callGasLimit.toString();
31
- + return erc20PaymasterData;
32
- }
33
- async #getUserOperationGasPrices(rpcUrl) {
34
- const client = (0, import_viem11.createPublicClient)({
35
- diff --git a/dist/esm/src/index.mjs b/dist/esm/src/index.mjs
36
- --- a/dist/esm/src/index.mjs
37
- +++ b/dist/esm/src/index.mjs
38
- @@ -1923,6 +1923,9 @@ var GenericFeeEstimator = class {
39
- const context = "paymasterTokenAddress" in paymasterOptions ? {
40
- token: paymasterOptions.paymasterTokenAddress
41
- } : paymasterOptions.paymasterContext ?? {};
42
- + if (paymasterOptions.sponsorshipPolicyId) {
43
- + context.sponsorshipPolicyId = paymasterOptions.sponsorshipPolicyId;
44
- + }
45
- const [feeData, paymasterStubData] = await Promise.all([
46
- this.#getUserOperationGasPrices(this.rpcUrl),
47
- paymasterClient.request({
48
- @@ -1994,7 +1997,8 @@ var GenericFeeEstimator = class {
49
- const sponsoredData = await paymasterClient.request({
50
- method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
51
- params
52
- });
53
- - return sponsoredData;
54
- + sponsoredData.callGasLimit = userOperation.callGasLimit.toString();
55
- + return sponsoredData;
56
- }
57
- const erc20PaymasterData = await paymasterClient.request({
58
- method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
59
- @@ -2009,7 +2013,8 @@ var GenericFeeEstimator = class {
60
- const threshold = await protocolKit.getThreshold();
61
- erc20PaymasterData.verificationGasLimit = (BigInt(erc20PaymasterData.verificationGasLimit) + BigInt(threshold) * this.defaultVerificationGasLimitOverhead).toString();
62
- }
63
- - return erc20PaymasterData;
64
- + erc20PaymasterData.callGasLimit = userOperation.callGasLimit.toString();
65
- + return erc20PaymasterData;
66
- }
67
- async #getUserOperationGasPrices(rpcUrl) {
68
- const client = createPublicClient2({
1
+ diff --git a/dist/cjs/src/index.cjs b/dist/cjs/src/index.cjs
2
+ --- a/dist/cjs/src/index.cjs
3
+ +++ b/dist/cjs/src/index.cjs
4
+ @@ -1946,6 +1946,9 @@ var GenericFeeEstimator = class {
5
+ const context = "paymasterTokenAddress" in paymasterOptions ? {
6
+ token: paymasterOptions.paymasterTokenAddress
7
+ } : paymasterOptions.paymasterContext ?? {};
8
+ + if (paymasterOptions.sponsorshipPolicyId) {
9
+ + context.sponsorshipPolicyId = paymasterOptions.sponsorshipPolicyId;
10
+ + }
11
+ const [feeData, paymasterStubData] = await Promise.all([
12
+ this.#getUserOperationGasPrices(this.rpcUrl),
13
+ paymasterClient.request({
14
+ @@ -2017,7 +2020,8 @@ var GenericFeeEstimator = class {
15
+ const sponsoredData = await paymasterClient.request({
16
+ method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
17
+ params
18
+ });
19
+ - return sponsoredData;
20
+ + sponsoredData.callGasLimit = userOperation.callGasLimit.toString();
21
+ + return sponsoredData;
22
+ }
23
+ const erc20PaymasterData = await paymasterClient.request({
24
+ method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
25
+ @@ -2032,7 +2036,8 @@ var GenericFeeEstimator = class {
26
+ const threshold = await protocolKit.getThreshold();
27
+ erc20PaymasterData.verificationGasLimit = (BigInt(erc20PaymasterData.verificationGasLimit) + BigInt(threshold) * this.defaultVerificationGasLimitOverhead).toString();
28
+ }
29
+ - return erc20PaymasterData;
30
+ + erc20PaymasterData.callGasLimit = userOperation.callGasLimit.toString();
31
+ + return erc20PaymasterData;
32
+ }
33
+ async #getUserOperationGasPrices(rpcUrl) {
34
+ const client = (0, import_viem11.createPublicClient)({
35
+ diff --git a/dist/esm/src/index.mjs b/dist/esm/src/index.mjs
36
+ --- a/dist/esm/src/index.mjs
37
+ +++ b/dist/esm/src/index.mjs
38
+ @@ -1923,6 +1923,9 @@ var GenericFeeEstimator = class {
39
+ const context = "paymasterTokenAddress" in paymasterOptions ? {
40
+ token: paymasterOptions.paymasterTokenAddress
41
+ } : paymasterOptions.paymasterContext ?? {};
42
+ + if (paymasterOptions.sponsorshipPolicyId) {
43
+ + context.sponsorshipPolicyId = paymasterOptions.sponsorshipPolicyId;
44
+ + }
45
+ const [feeData, paymasterStubData] = await Promise.all([
46
+ this.#getUserOperationGasPrices(this.rpcUrl),
47
+ paymasterClient.request({
48
+ @@ -1994,7 +1997,8 @@ var GenericFeeEstimator = class {
49
+ const sponsoredData = await paymasterClient.request({
50
+ method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
51
+ params
52
+ });
53
+ - return sponsoredData;
54
+ + sponsoredData.callGasLimit = userOperation.callGasLimit.toString();
55
+ + return sponsoredData;
56
+ }
57
+ const erc20PaymasterData = await paymasterClient.request({
58
+ method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
59
+ @@ -2009,7 +2013,8 @@ var GenericFeeEstimator = class {
60
+ const threshold = await protocolKit.getThreshold();
61
+ erc20PaymasterData.verificationGasLimit = (BigInt(erc20PaymasterData.verificationGasLimit) + BigInt(threshold) * this.defaultVerificationGasLimitOverhead).toString();
62
+ }
63
+ - return erc20PaymasterData;
64
+ + erc20PaymasterData.callGasLimit = userOperation.callGasLimit.toString();
65
+ + return erc20PaymasterData;
66
+ }
67
+ async #getUserOperationGasPrices(rpcUrl) {
68
+ const client = createPublicClient2({
@@ -1,101 +1,101 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Postinstall script for @gasfree-kit/evm-4337.
5
- *
6
- * Applies a critical patch to @tetherto/wdk-safe-relay-kit that fixes:
7
- * 1. sponsorshipPolicyId not being passed to Candide paymaster context
8
- * 2. callGasLimit being dropped by paymaster response (both sponsored & ERC-20)
9
- *
10
- * Without this patch, gas estimation fails on Candide-sponsored transactions.
11
- *
12
- * Works with npm, pnpm, and yarn.
13
- * pnpm users can also use patchedDependencies in package.json instead.
14
- */
15
-
16
- const { spawnSync } = require('child_process');
17
- const fs = require('fs');
18
- const path = require('path');
19
- const packageRoot = path.resolve(__dirname, '..');
20
-
21
- function findProjectRoot(startDir) {
22
- let current = startDir;
23
- while (true) {
24
- const parent = path.dirname(current);
25
- if (parent === current) {
26
- return startDir;
27
- }
28
- if (fs.existsSync(path.join(parent, 'package.json'))) {
29
- return parent;
30
- }
31
- current = parent;
32
- }
33
- }
34
-
35
- function runOrThrow(command, args, options) {
36
- const result = spawnSync(command, args, {
37
- stdio: 'pipe',
38
- windowsHide: true,
39
- ...options,
40
- });
41
-
42
- if (result.error) {
43
- throw result.error;
44
- }
45
-
46
- if (result.status !== 0) {
47
- const stderr = result.stderr ? result.stderr.toString().trim() : '';
48
- throw new Error(stderr || `${command} exited with code ${result.status}`);
49
- }
50
- }
51
-
52
- const patchFile = path.join(
53
- __dirname,
54
- '..',
55
- 'patches',
56
- '@tetherto__wdk-safe-relay-kit@4.1.5.patch',
57
- );
58
-
59
- if (!fs.existsSync(patchFile)) {
60
- // Patch file not found — skip (might be in a CI environment or already applied)
61
- process.exit(0);
62
- }
63
-
64
- // Find the target package
65
- const projectRoot = findProjectRoot(packageRoot);
66
- const possiblePaths = [
67
- path.join(projectRoot, 'node_modules', '@tetherto', 'wdk-safe-relay-kit'),
68
- path.join(packageRoot, 'node_modules', '@tetherto', 'wdk-safe-relay-kit'),
69
- ];
70
-
71
- const targetDir = possiblePaths.find((p) => fs.existsSync(p));
72
-
73
- if (!targetDir) {
74
- // @tetherto/wdk-safe-relay-kit not installed yet — skip
75
- // The user's package manager will install peer deps and this runs again
76
- process.exit(0);
77
- }
78
-
79
- // Check if patch is already applied by looking for our marker
80
- const cjsFile = path.join(targetDir, 'dist', 'cjs', 'src', 'index.cjs');
81
- if (fs.existsSync(cjsFile)) {
82
- const content = fs.readFileSync(cjsFile, 'utf8');
83
- if (content.includes('context.sponsorshipPolicyId')) {
84
- // Patch already applied
85
- process.exit(0);
86
- }
87
- }
88
-
89
- // Apply the patch using git apply without shell interpolation.
90
- try {
91
- runOrThrow('git', ['apply', '--unsafe-paths', `--directory=${targetDir}`, patchFile], {
92
- cwd: projectRoot,
93
- });
94
- console.log('@gasfree-kit/evm-4337: Applied wdk-safe-relay-kit patch (Candide paymaster fix)');
95
- } catch {
96
- console.warn(
97
- '@gasfree-kit/evm-4337: Could not auto-apply wdk-safe-relay-kit patch.\n' +
98
- 'Please apply manually: patches/@tetherto__wdk-safe-relay-kit@4.1.5.patch\n' +
99
- 'See: https://github.com/gasfree-kit/evm-4337#patch',
100
- );
101
- }
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Postinstall script for @gasfree-kit/evm-4337.
5
+ *
6
+ * Applies a critical patch to @tetherto/wdk-safe-relay-kit that fixes:
7
+ * 1. sponsorshipPolicyId not being passed to Candide paymaster context
8
+ * 2. callGasLimit being dropped by paymaster response (both sponsored & ERC-20)
9
+ *
10
+ * Without this patch, gas estimation fails on Candide-sponsored transactions.
11
+ *
12
+ * Works with npm, pnpm, and yarn.
13
+ * pnpm users can also use patchedDependencies in package.json instead.
14
+ */
15
+
16
+ const { spawnSync } = require('child_process');
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const packageRoot = path.resolve(__dirname, '..');
20
+
21
+ function findProjectRoot(startDir) {
22
+ let current = startDir;
23
+ while (true) {
24
+ const parent = path.dirname(current);
25
+ if (parent === current) {
26
+ return startDir;
27
+ }
28
+ if (fs.existsSync(path.join(parent, 'package.json'))) {
29
+ return parent;
30
+ }
31
+ current = parent;
32
+ }
33
+ }
34
+
35
+ function runOrThrow(command, args, options) {
36
+ const result = spawnSync(command, args, {
37
+ stdio: 'pipe',
38
+ windowsHide: true,
39
+ ...options,
40
+ });
41
+
42
+ if (result.error) {
43
+ throw result.error;
44
+ }
45
+
46
+ if (result.status !== 0) {
47
+ const stderr = result.stderr ? result.stderr.toString().trim() : '';
48
+ throw new Error(stderr || `${command} exited with code ${result.status}`);
49
+ }
50
+ }
51
+
52
+ const patchFile = path.join(
53
+ __dirname,
54
+ '..',
55
+ 'patches',
56
+ '@tetherto__wdk-safe-relay-kit@4.1.5.patch',
57
+ );
58
+
59
+ if (!fs.existsSync(patchFile)) {
60
+ // Patch file not found — skip (might be in a CI environment or already applied)
61
+ process.exit(0);
62
+ }
63
+
64
+ // Find the target package
65
+ const projectRoot = findProjectRoot(packageRoot);
66
+ const possiblePaths = [
67
+ path.join(projectRoot, 'node_modules', '@tetherto', 'wdk-safe-relay-kit'),
68
+ path.join(packageRoot, 'node_modules', '@tetherto', 'wdk-safe-relay-kit'),
69
+ ];
70
+
71
+ const targetDir = possiblePaths.find((p) => fs.existsSync(p));
72
+
73
+ if (!targetDir) {
74
+ // @tetherto/wdk-safe-relay-kit not installed yet — skip
75
+ // The user's package manager will install peer deps and this runs again
76
+ process.exit(0);
77
+ }
78
+
79
+ // Check if patch is already applied by looking for our marker
80
+ const cjsFile = path.join(targetDir, 'dist', 'cjs', 'src', 'index.cjs');
81
+ if (fs.existsSync(cjsFile)) {
82
+ const content = fs.readFileSync(cjsFile, 'utf8');
83
+ if (content.includes('context.sponsorshipPolicyId')) {
84
+ // Patch already applied
85
+ process.exit(0);
86
+ }
87
+ }
88
+
89
+ // Apply the patch using git apply without shell interpolation.
90
+ try {
91
+ runOrThrow('git', ['apply', '--unsafe-paths', `--directory=${targetDir}`, patchFile], {
92
+ cwd: projectRoot,
93
+ });
94
+ console.log('@gasfree-kit/evm-4337: Applied wdk-safe-relay-kit patch (Candide paymaster fix)');
95
+ } catch {
96
+ console.warn(
97
+ '@gasfree-kit/evm-4337: Could not auto-apply wdk-safe-relay-kit patch.\n' +
98
+ 'Please apply manually: patches/@tetherto__wdk-safe-relay-kit@4.1.5.patch\n' +
99
+ 'See: https://github.com/gasfree-kit/evm-4337#patch',
100
+ );
101
+ }