@gasfree-kit/evm-4337 0.1.1 → 0.3.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 +329 -271
- package/package.json +2 -2
- package/patches/@tetherto__wdk-safe-relay-kit@4.1.5.patch +68 -68
- package/scripts/postinstall.js +101 -101
package/README.md
CHANGED
|
@@ -1,271 +1,329 @@
|
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
###
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
+
## Supported Chains
|
|
47
|
+
|
|
48
|
+
| Chain | Chain ID | Typical fallback fee estimate |
|
|
49
|
+
| -------- | -------- | ----------------------------- |
|
|
50
|
+
| Ethereum | 1 | `1.40` USDT |
|
|
51
|
+
| Base | 8453 | `0.15` USDT |
|
|
52
|
+
| Arbitrum | 42161 | `0.10` USDT |
|
|
53
|
+
| Optimism | 10 | `0.10` USDT |
|
|
54
|
+
| Polygon | 137 | `0.10` USDT |
|
|
55
|
+
| Celo | 42220 | `0.05` USDT |
|
|
56
|
+
| Plasma | 9745 | `0.05` USDT |
|
|
57
|
+
|
|
58
|
+
## Prerequisites
|
|
59
|
+
|
|
60
|
+
This package depends on [`@gasfree-kit/core`](../core/README.md) for:
|
|
61
|
+
|
|
62
|
+
- **Seed phrase generation** — `generateSeedPhrase()` creates the mnemonic used to set up wallets
|
|
63
|
+
- **Chain metadata** — `EVM_CHAINS` provides chain IDs, USDT addresses, and explorer URLs
|
|
64
|
+
- **Address validation** — `validateEvmAddress()` catches malformed addresses before sending
|
|
65
|
+
- **Error classes** — `GasfreeError`, `InsufficientBalanceError`, etc. for consistent error handling
|
|
66
|
+
|
|
67
|
+
`@gasfree-kit/core` is installed automatically as a dependency.
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install @gasfree-kit/evm-4337 @gasfree-kit/core
|
|
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
|
+
### Link a passkey to an existing Safe
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { linkPasskeyToSafe } from '@gasfree-kit/evm-4337';
|
|
244
|
+
|
|
245
|
+
const passkey = await linkPasskeyToSafe(seedPhrase, sponsoredConfig, {
|
|
246
|
+
rpName: 'My App',
|
|
247
|
+
userName: 'user@example.com',
|
|
248
|
+
storage: 'not_persist',
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
console.log(passkey.safeAddress);
|
|
252
|
+
console.log(passkey.credential.signerAddress);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Check whether a passkey is linked
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { isPasskeyLinked } from '@gasfree-kit/evm-4337';
|
|
259
|
+
|
|
260
|
+
const status = await isPasskeyLinked(seedPhrase, sponsoredConfig);
|
|
261
|
+
|
|
262
|
+
if (status.linked) {
|
|
263
|
+
console.log(status.signerAddress);
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Send a transfer with a passkey
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
import { PasskeyTransfer } from '@gasfree-kit/evm-4337';
|
|
271
|
+
import { EVM_CHAINS } from '@gasfree-kit/core';
|
|
272
|
+
|
|
273
|
+
const tx = await PasskeyTransfer.sendToken(
|
|
274
|
+
passkey.credential,
|
|
275
|
+
sponsoredConfig,
|
|
276
|
+
EVM_CHAINS.base.usdtAddress,
|
|
277
|
+
'10.00',
|
|
278
|
+
'0x1111111111111111111111111111111111111111',
|
|
279
|
+
);
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Remove a passkey owner
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { unlinkPasskeyFromSafe } from '@gasfree-kit/evm-4337';
|
|
286
|
+
|
|
287
|
+
await unlinkPasskeyFromSafe(seedPhrase, sponsoredConfig, passkey.credential);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Main Exports
|
|
291
|
+
|
|
292
|
+
| Export | What it does |
|
|
293
|
+
| ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
|
|
294
|
+
| `setupErc4337Wallet` | Creates a WDK-backed ERC-4337 wallet and resolves the Safe address |
|
|
295
|
+
| `EvmTransfer` | Estimates, checks balance, sends single transfers, and sends batch transfers |
|
|
296
|
+
| `PasskeyTransfer` | Sends passkey-signed transfers after a passkey has been linked |
|
|
297
|
+
| `linkPasskeyToSafe` | Registers a passkey and adds its signer to the Safe owner set |
|
|
298
|
+
| `isPasskeyLinked` | Checks whether a stored passkey is still an owner on-chain |
|
|
299
|
+
| `unlinkPasskeyFromSafe` | Removes a passkey signer from the Safe |
|
|
300
|
+
| `getErc4337ConfigForChain` | Normalizes the public client config into runtime config |
|
|
301
|
+
| `GAS_FEE_FALLBACKS` and `GAS_FEE_ESTIMATES` | Exposes fallback fee heuristics per chain |
|
|
302
|
+
| `toUsdtBaseUnitsEvm`, `formatTokenBalance`, `feeToUsdt`, `encodeErc20Transfer` | Utility helpers for amounts and calldata |
|
|
303
|
+
|
|
304
|
+
## Export Example
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import {
|
|
308
|
+
setupErc4337Wallet,
|
|
309
|
+
EvmTransfer,
|
|
310
|
+
PasskeyTransfer,
|
|
311
|
+
linkPasskeyToSafe,
|
|
312
|
+
isPasskeyLinked,
|
|
313
|
+
unlinkPasskeyFromSafe,
|
|
314
|
+
getErc4337ConfigForChain,
|
|
315
|
+
GAS_FEE_FALLBACKS,
|
|
316
|
+
GAS_FEE_ESTIMATES,
|
|
317
|
+
} from '@gasfree-kit/evm-4337';
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Notes
|
|
321
|
+
|
|
322
|
+
- `sponsorshipPolicyId` is required when `isSponsored` is `true`
|
|
323
|
+
- self-transfers are blocked
|
|
324
|
+
- token-paid mode checks that the USDT balance can cover the transfer and gas reserve
|
|
325
|
+
- passkey linking verifies the signer deployment before adding it as an owner
|
|
326
|
+
|
|
327
|
+
## License
|
|
328
|
+
|
|
329
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gasfree-kit/evm-4337",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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.
|
|
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({
|
package/scripts/postinstall.js
CHANGED
|
@@ -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
|
+
}
|