@t402/wdk-gasless 1.0.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/dist/cjs/index.d.ts +409 -0
- package/dist/cjs/index.js +727 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.d.mts +409 -0
- package/dist/esm/index.mjs +701 -0
- package/dist/esm/index.mjs.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
CHAIN_IDS: () => CHAIN_IDS,
|
|
24
|
+
DEFAULT_BUNDLER_URLS: () => DEFAULT_BUNDLER_URLS,
|
|
25
|
+
SAFE_4337_ADDRESSES: () => SAFE_4337_ADDRESSES,
|
|
26
|
+
USDC_ADDRESSES: () => USDC_ADDRESSES,
|
|
27
|
+
USDT0_ADDRESSES: () => USDT0_ADDRESSES,
|
|
28
|
+
WdkGaslessClient: () => WdkGaslessClient,
|
|
29
|
+
WdkSmartAccount: () => WdkSmartAccount,
|
|
30
|
+
createWdkGaslessClient: () => createWdkGaslessClient,
|
|
31
|
+
createWdkSmartAccount: () => createWdkSmartAccount,
|
|
32
|
+
getChainName: () => getChainName,
|
|
33
|
+
getTokenAddress: () => getTokenAddress
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/client.ts
|
|
38
|
+
var import_viem2 = require("viem");
|
|
39
|
+
var import_evm = require("@t402/evm");
|
|
40
|
+
|
|
41
|
+
// src/account.ts
|
|
42
|
+
var import_viem = require("viem");
|
|
43
|
+
var SAFE_4337_ADDRESSES = {
|
|
44
|
+
/** Safe 4337 Module */
|
|
45
|
+
module: "0xa581c4A4DB7175302464fF3C06380BC3270b4037",
|
|
46
|
+
/** Safe Module Setup */
|
|
47
|
+
moduleSetup: "0x2dd68b007B46fBe91B9A7c3EDa5A7a1063cB5b47",
|
|
48
|
+
/** Safe Singleton */
|
|
49
|
+
singleton: "0x29fcB43b46531BcA003ddC8FCB67FFE91900C762",
|
|
50
|
+
/** Safe Proxy Factory */
|
|
51
|
+
proxyFactory: "0x4e1DCf7AD4e460CfD30791CCC4F9c8a4f820ec67",
|
|
52
|
+
/** Safe Fallback Handler */
|
|
53
|
+
fallbackHandler: "0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99",
|
|
54
|
+
/** Add Modules Lib */
|
|
55
|
+
addModulesLib: "0x8EcD4ec46D4D2a6B64fE960B3D64e8B94B2234eb"
|
|
56
|
+
};
|
|
57
|
+
var PROXY_FACTORY_ABI = [
|
|
58
|
+
{
|
|
59
|
+
inputs: [
|
|
60
|
+
{ name: "singleton", type: "address" },
|
|
61
|
+
{ name: "initializer", type: "bytes" },
|
|
62
|
+
{ name: "saltNonce", type: "uint256" }
|
|
63
|
+
],
|
|
64
|
+
name: "createProxyWithNonce",
|
|
65
|
+
outputs: [{ name: "proxy", type: "address" }],
|
|
66
|
+
stateMutability: "nonpayable",
|
|
67
|
+
type: "function"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
inputs: [
|
|
71
|
+
{ name: "singleton", type: "address" },
|
|
72
|
+
{ name: "initializer", type: "bytes" },
|
|
73
|
+
{ name: "saltNonce", type: "uint256" }
|
|
74
|
+
],
|
|
75
|
+
name: "proxyCreationCode",
|
|
76
|
+
outputs: [{ name: "", type: "bytes" }],
|
|
77
|
+
stateMutability: "view",
|
|
78
|
+
type: "function"
|
|
79
|
+
}
|
|
80
|
+
];
|
|
81
|
+
var SAFE_ABI = [
|
|
82
|
+
{
|
|
83
|
+
inputs: [
|
|
84
|
+
{ name: "owners", type: "address[]" },
|
|
85
|
+
{ name: "threshold", type: "uint256" },
|
|
86
|
+
{ name: "to", type: "address" },
|
|
87
|
+
{ name: "data", type: "bytes" },
|
|
88
|
+
{ name: "fallbackHandler", type: "address" },
|
|
89
|
+
{ name: "paymentToken", type: "address" },
|
|
90
|
+
{ name: "payment", type: "uint256" },
|
|
91
|
+
{ name: "paymentReceiver", type: "address" }
|
|
92
|
+
],
|
|
93
|
+
name: "setup",
|
|
94
|
+
outputs: [],
|
|
95
|
+
stateMutability: "nonpayable",
|
|
96
|
+
type: "function"
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
var ADD_MODULES_LIB_ABI = [
|
|
100
|
+
{
|
|
101
|
+
inputs: [{ name: "modules", type: "address[]" }],
|
|
102
|
+
name: "enableModules",
|
|
103
|
+
outputs: [],
|
|
104
|
+
stateMutability: "nonpayable",
|
|
105
|
+
type: "function"
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
var SAFE_4337_MODULE_ABI = [
|
|
109
|
+
{
|
|
110
|
+
inputs: [
|
|
111
|
+
{ name: "to", type: "address" },
|
|
112
|
+
{ name: "value", type: "uint256" },
|
|
113
|
+
{ name: "data", type: "bytes" },
|
|
114
|
+
{ name: "operation", type: "uint8" }
|
|
115
|
+
],
|
|
116
|
+
name: "executeUserOp",
|
|
117
|
+
outputs: [],
|
|
118
|
+
stateMutability: "nonpayable",
|
|
119
|
+
type: "function"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
inputs: [
|
|
123
|
+
{ name: "tos", type: "address[]" },
|
|
124
|
+
{ name: "values", type: "uint256[]" },
|
|
125
|
+
{ name: "datas", type: "bytes[]" },
|
|
126
|
+
{ name: "operations", type: "uint8[]" }
|
|
127
|
+
],
|
|
128
|
+
name: "executeUserOpBatch",
|
|
129
|
+
outputs: [],
|
|
130
|
+
stateMutability: "nonpayable",
|
|
131
|
+
type: "function"
|
|
132
|
+
}
|
|
133
|
+
];
|
|
134
|
+
var WdkSmartAccount = class {
|
|
135
|
+
wdkAccount;
|
|
136
|
+
publicClient;
|
|
137
|
+
chainId;
|
|
138
|
+
owners;
|
|
139
|
+
threshold;
|
|
140
|
+
saltNonce;
|
|
141
|
+
cachedAddress;
|
|
142
|
+
cachedInitCode;
|
|
143
|
+
cachedOwnerAddress;
|
|
144
|
+
deploymentChecked = false;
|
|
145
|
+
isAccountDeployed = false;
|
|
146
|
+
constructor(config) {
|
|
147
|
+
this.wdkAccount = config.wdkAccount;
|
|
148
|
+
this.publicClient = config.publicClient;
|
|
149
|
+
this.chainId = config.chainId;
|
|
150
|
+
this.threshold = config.threshold ?? 1;
|
|
151
|
+
this.saltNonce = config.saltNonce ?? 0n;
|
|
152
|
+
this.owners = config.additionalOwners ?? [];
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Initialize the account (fetch WDK address)
|
|
156
|
+
* Call this before using the account
|
|
157
|
+
*/
|
|
158
|
+
async initialize() {
|
|
159
|
+
if (!this.cachedOwnerAddress) {
|
|
160
|
+
const address = await this.wdkAccount.getAddress();
|
|
161
|
+
this.cachedOwnerAddress = address;
|
|
162
|
+
if (!this.owners.includes(this.cachedOwnerAddress)) {
|
|
163
|
+
this.owners.unshift(this.cachedOwnerAddress);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get the WDK account's EOA address
|
|
169
|
+
*/
|
|
170
|
+
async getOwnerAddress() {
|
|
171
|
+
await this.initialize();
|
|
172
|
+
return this.cachedOwnerAddress;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get the smart account address (counterfactual)
|
|
176
|
+
*/
|
|
177
|
+
async getAddress() {
|
|
178
|
+
await this.initialize();
|
|
179
|
+
if (this.cachedAddress) {
|
|
180
|
+
return this.cachedAddress;
|
|
181
|
+
}
|
|
182
|
+
const initCode = await this.getInitCode();
|
|
183
|
+
if (initCode === "0x") {
|
|
184
|
+
const initializerData = await this.buildInitializer();
|
|
185
|
+
const salt = (0, import_viem.keccak256)(
|
|
186
|
+
(0, import_viem.encodeAbiParameters)(
|
|
187
|
+
[{ type: "bytes32" }, { type: "uint256" }],
|
|
188
|
+
[(0, import_viem.keccak256)(initializerData), this.saltNonce]
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
const proxyCreationCode = await this.publicClient.readContract({
|
|
192
|
+
address: SAFE_4337_ADDRESSES.proxyFactory,
|
|
193
|
+
abi: PROXY_FACTORY_ABI,
|
|
194
|
+
functionName: "proxyCreationCode",
|
|
195
|
+
args: [SAFE_4337_ADDRESSES.singleton, initializerData, this.saltNonce]
|
|
196
|
+
});
|
|
197
|
+
this.cachedAddress = (0, import_viem.getContractAddress)({
|
|
198
|
+
bytecode: proxyCreationCode,
|
|
199
|
+
from: SAFE_4337_ADDRESSES.proxyFactory,
|
|
200
|
+
opcode: "CREATE2",
|
|
201
|
+
salt
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
const initializerData = `0x${initCode.slice(2 + 40 * 2)}`;
|
|
205
|
+
const salt = (0, import_viem.keccak256)(
|
|
206
|
+
(0, import_viem.encodeAbiParameters)(
|
|
207
|
+
[{ type: "bytes32" }, { type: "uint256" }],
|
|
208
|
+
[(0, import_viem.keccak256)(initializerData), this.saltNonce]
|
|
209
|
+
)
|
|
210
|
+
);
|
|
211
|
+
const proxyCreationCode = await this.publicClient.readContract({
|
|
212
|
+
address: SAFE_4337_ADDRESSES.proxyFactory,
|
|
213
|
+
abi: PROXY_FACTORY_ABI,
|
|
214
|
+
functionName: "proxyCreationCode",
|
|
215
|
+
args: [SAFE_4337_ADDRESSES.singleton, initializerData, this.saltNonce]
|
|
216
|
+
});
|
|
217
|
+
this.cachedAddress = (0, import_viem.getContractAddress)({
|
|
218
|
+
bytecode: proxyCreationCode,
|
|
219
|
+
from: SAFE_4337_ADDRESSES.proxyFactory,
|
|
220
|
+
opcode: "CREATE2",
|
|
221
|
+
salt
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return this.cachedAddress;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Sign a UserOperation hash using the WDK account
|
|
228
|
+
*/
|
|
229
|
+
async signUserOpHash(userOpHash) {
|
|
230
|
+
await this.initialize();
|
|
231
|
+
const signature = await this.wdkAccount.signMessage(userOpHash);
|
|
232
|
+
return (0, import_viem.concat)([signature, "0x00"]);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Get the account's init code for deployment
|
|
236
|
+
*/
|
|
237
|
+
async getInitCode() {
|
|
238
|
+
await this.initialize();
|
|
239
|
+
if (await this.isDeployed()) {
|
|
240
|
+
return "0x";
|
|
241
|
+
}
|
|
242
|
+
if (this.cachedInitCode) {
|
|
243
|
+
return this.cachedInitCode;
|
|
244
|
+
}
|
|
245
|
+
const safeSetupData = await this.buildInitializer();
|
|
246
|
+
const createProxyData = (0, import_viem.encodeFunctionData)({
|
|
247
|
+
abi: PROXY_FACTORY_ABI,
|
|
248
|
+
functionName: "createProxyWithNonce",
|
|
249
|
+
args: [SAFE_4337_ADDRESSES.singleton, safeSetupData, this.saltNonce]
|
|
250
|
+
});
|
|
251
|
+
this.cachedInitCode = (0, import_viem.concat)([
|
|
252
|
+
SAFE_4337_ADDRESSES.proxyFactory,
|
|
253
|
+
createProxyData
|
|
254
|
+
]);
|
|
255
|
+
return this.cachedInitCode;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Check if the account is deployed
|
|
259
|
+
*/
|
|
260
|
+
async isDeployed() {
|
|
261
|
+
if (this.deploymentChecked) {
|
|
262
|
+
return this.isAccountDeployed;
|
|
263
|
+
}
|
|
264
|
+
await this.initialize();
|
|
265
|
+
const address = this.cachedAddress ?? await this.getAddress();
|
|
266
|
+
const code = await this.publicClient.getCode({ address });
|
|
267
|
+
this.deploymentChecked = true;
|
|
268
|
+
this.isAccountDeployed = code !== void 0 && code !== "0x";
|
|
269
|
+
return this.isAccountDeployed;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Encode a call to the account's execute function
|
|
273
|
+
*/
|
|
274
|
+
encodeExecute(target, value, data) {
|
|
275
|
+
return (0, import_viem.encodeFunctionData)({
|
|
276
|
+
abi: SAFE_4337_MODULE_ABI,
|
|
277
|
+
functionName: "executeUserOp",
|
|
278
|
+
args: [target, value, data, 0]
|
|
279
|
+
// operation: CALL
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Encode a batch call to the account's executeBatch function
|
|
284
|
+
*/
|
|
285
|
+
encodeExecuteBatch(targets, values, datas) {
|
|
286
|
+
if (targets.length !== values.length || targets.length !== datas.length) {
|
|
287
|
+
throw new Error("Array lengths must match");
|
|
288
|
+
}
|
|
289
|
+
const operations = targets.map(() => 0);
|
|
290
|
+
return (0, import_viem.encodeFunctionData)({
|
|
291
|
+
abi: SAFE_4337_MODULE_ABI,
|
|
292
|
+
functionName: "executeUserOpBatch",
|
|
293
|
+
args: [targets, values, datas, operations]
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Build the Safe setup initializer data
|
|
298
|
+
*/
|
|
299
|
+
async buildInitializer() {
|
|
300
|
+
await this.initialize();
|
|
301
|
+
const setupModulesData = (0, import_viem.encodeFunctionData)({
|
|
302
|
+
abi: ADD_MODULES_LIB_ABI,
|
|
303
|
+
functionName: "enableModules",
|
|
304
|
+
args: [[SAFE_4337_ADDRESSES.module]]
|
|
305
|
+
});
|
|
306
|
+
return (0, import_viem.encodeFunctionData)({
|
|
307
|
+
abi: SAFE_ABI,
|
|
308
|
+
functionName: "setup",
|
|
309
|
+
args: [
|
|
310
|
+
this.owners,
|
|
311
|
+
BigInt(this.threshold),
|
|
312
|
+
SAFE_4337_ADDRESSES.addModulesLib,
|
|
313
|
+
// to: AddModulesLib
|
|
314
|
+
setupModulesData,
|
|
315
|
+
// data: enableModules([module])
|
|
316
|
+
SAFE_4337_ADDRESSES.fallbackHandler,
|
|
317
|
+
"0x0000000000000000000000000000000000000000",
|
|
318
|
+
// paymentToken
|
|
319
|
+
0n,
|
|
320
|
+
// payment
|
|
321
|
+
"0x0000000000000000000000000000000000000000"
|
|
322
|
+
// paymentReceiver
|
|
323
|
+
]
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Get the Safe's owners
|
|
328
|
+
*/
|
|
329
|
+
getOwners() {
|
|
330
|
+
return [...this.owners];
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Get the Safe's threshold
|
|
334
|
+
*/
|
|
335
|
+
getThreshold() {
|
|
336
|
+
return this.threshold;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Clear cached values (useful after deployment)
|
|
340
|
+
*/
|
|
341
|
+
clearCache() {
|
|
342
|
+
this.cachedAddress = void 0;
|
|
343
|
+
this.cachedInitCode = void 0;
|
|
344
|
+
this.deploymentChecked = false;
|
|
345
|
+
this.isAccountDeployed = false;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
async function createWdkSmartAccount(config) {
|
|
349
|
+
const account = new WdkSmartAccount(config);
|
|
350
|
+
await account.initialize();
|
|
351
|
+
return account;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/constants.ts
|
|
355
|
+
var USDT0_ADDRESSES = {
|
|
356
|
+
ethereum: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
|
|
357
|
+
arbitrum: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
|
|
358
|
+
ink: "0x0200C29006150606B650577BBE7B6248F58470c1",
|
|
359
|
+
berachain: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736",
|
|
360
|
+
unichain: "0x588ce4F028D8e7B53B687865d6A67b3A54C75518",
|
|
361
|
+
base: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee",
|
|
362
|
+
optimism: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee"
|
|
363
|
+
};
|
|
364
|
+
var USDC_ADDRESSES = {
|
|
365
|
+
ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
366
|
+
arbitrum: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
367
|
+
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
368
|
+
optimism: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
|
369
|
+
polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
|
|
370
|
+
};
|
|
371
|
+
var CHAIN_IDS = {
|
|
372
|
+
ethereum: 1,
|
|
373
|
+
arbitrum: 42161,
|
|
374
|
+
base: 8453,
|
|
375
|
+
optimism: 10,
|
|
376
|
+
polygon: 137,
|
|
377
|
+
ink: 57073,
|
|
378
|
+
berachain: 80084,
|
|
379
|
+
unichain: 130
|
|
380
|
+
};
|
|
381
|
+
var DEFAULT_BUNDLER_URLS = {
|
|
382
|
+
1: "https://api.pimlico.io/v2/ethereum/rpc",
|
|
383
|
+
42161: "https://api.pimlico.io/v2/arbitrum/rpc",
|
|
384
|
+
8453: "https://api.pimlico.io/v2/base/rpc",
|
|
385
|
+
10: "https://api.pimlico.io/v2/optimism/rpc",
|
|
386
|
+
137: "https://api.pimlico.io/v2/polygon/rpc"
|
|
387
|
+
};
|
|
388
|
+
function getTokenAddress(token, chainName) {
|
|
389
|
+
if (token.startsWith("0x")) {
|
|
390
|
+
return token;
|
|
391
|
+
}
|
|
392
|
+
const addresses = token === "USDT0" ? USDT0_ADDRESSES : USDC_ADDRESSES;
|
|
393
|
+
const address = addresses[chainName.toLowerCase()];
|
|
394
|
+
if (!address) {
|
|
395
|
+
throw new Error(`Token ${token} not available on ${chainName}`);
|
|
396
|
+
}
|
|
397
|
+
return address;
|
|
398
|
+
}
|
|
399
|
+
function getChainName(chainId) {
|
|
400
|
+
const entry = Object.entries(CHAIN_IDS).find(([, id]) => id === chainId);
|
|
401
|
+
if (!entry) {
|
|
402
|
+
throw new Error(`Unsupported chain ID: ${chainId}`);
|
|
403
|
+
}
|
|
404
|
+
return entry[0];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/client.ts
|
|
408
|
+
var ERC20_TRANSFER_ABI = [
|
|
409
|
+
{
|
|
410
|
+
inputs: [
|
|
411
|
+
{ name: "to", type: "address" },
|
|
412
|
+
{ name: "amount", type: "uint256" }
|
|
413
|
+
],
|
|
414
|
+
name: "transfer",
|
|
415
|
+
outputs: [{ name: "", type: "bool" }],
|
|
416
|
+
stateMutability: "nonpayable",
|
|
417
|
+
type: "function"
|
|
418
|
+
}
|
|
419
|
+
];
|
|
420
|
+
var ERC20_BALANCE_ABI = [
|
|
421
|
+
{
|
|
422
|
+
inputs: [{ name: "account", type: "address" }],
|
|
423
|
+
name: "balanceOf",
|
|
424
|
+
outputs: [{ name: "", type: "uint256" }],
|
|
425
|
+
stateMutability: "view",
|
|
426
|
+
type: "function"
|
|
427
|
+
}
|
|
428
|
+
];
|
|
429
|
+
var WdkGaslessClient = class {
|
|
430
|
+
signer;
|
|
431
|
+
builder;
|
|
432
|
+
bundler;
|
|
433
|
+
paymaster;
|
|
434
|
+
chainId;
|
|
435
|
+
publicClient;
|
|
436
|
+
chainName;
|
|
437
|
+
constructor(config) {
|
|
438
|
+
this.signer = config.signer;
|
|
439
|
+
this.builder = new import_evm.UserOpBuilder();
|
|
440
|
+
this.bundler = new import_evm.BundlerClient(config.bundler);
|
|
441
|
+
this.paymaster = config.paymaster ? new import_evm.PaymasterClient(config.paymaster) : void 0;
|
|
442
|
+
this.chainId = config.chainId;
|
|
443
|
+
this.publicClient = config.publicClient;
|
|
444
|
+
this.chainName = getChainName(config.chainId);
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Execute a gasless payment
|
|
448
|
+
*
|
|
449
|
+
* Sends USDT0 (or other tokens) without the user paying gas fees.
|
|
450
|
+
* Gas is sponsored by a paymaster if configured.
|
|
451
|
+
*/
|
|
452
|
+
async pay(params) {
|
|
453
|
+
const token = params.token ?? "USDT0";
|
|
454
|
+
const tokenAddress = getTokenAddress(token, this.chainName);
|
|
455
|
+
const callData = (0, import_viem2.encodeFunctionData)({
|
|
456
|
+
abi: ERC20_TRANSFER_ABI,
|
|
457
|
+
functionName: "transfer",
|
|
458
|
+
args: [params.to, params.amount]
|
|
459
|
+
});
|
|
460
|
+
const intent = {
|
|
461
|
+
to: tokenAddress,
|
|
462
|
+
value: 0n,
|
|
463
|
+
data: callData
|
|
464
|
+
};
|
|
465
|
+
const gasEstimate = await this.estimateGas(intent);
|
|
466
|
+
const paymasterData = await this.getPaymasterData(gasEstimate);
|
|
467
|
+
const sponsored = paymasterData !== void 0;
|
|
468
|
+
const userOp = await this.builder.buildUserOp(
|
|
469
|
+
this.signer,
|
|
470
|
+
intent,
|
|
471
|
+
this.publicClient,
|
|
472
|
+
gasEstimate,
|
|
473
|
+
paymasterData
|
|
474
|
+
);
|
|
475
|
+
const signedUserOp = await this.builder.signUserOp(
|
|
476
|
+
userOp,
|
|
477
|
+
this.signer,
|
|
478
|
+
this.publicClient,
|
|
479
|
+
this.chainId
|
|
480
|
+
);
|
|
481
|
+
const result = await this.bundler.sendUserOperation(signedUserOp);
|
|
482
|
+
const sender = await this.signer.getAddress();
|
|
483
|
+
return {
|
|
484
|
+
userOpHash: result.userOpHash,
|
|
485
|
+
sender,
|
|
486
|
+
sponsored,
|
|
487
|
+
wait: async () => {
|
|
488
|
+
const receipt = await result.wait();
|
|
489
|
+
return {
|
|
490
|
+
userOpHash: receipt.userOpHash,
|
|
491
|
+
txHash: receipt.receipt.transactionHash,
|
|
492
|
+
blockNumber: receipt.receipt.blockNumber,
|
|
493
|
+
success: receipt.success,
|
|
494
|
+
gasUsed: receipt.actualGasUsed,
|
|
495
|
+
gasCost: receipt.actualGasCost,
|
|
496
|
+
reason: receipt.reason
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Execute multiple payments in a single transaction
|
|
503
|
+
*
|
|
504
|
+
* More gas efficient than individual payments.
|
|
505
|
+
* All payments are executed atomically.
|
|
506
|
+
*/
|
|
507
|
+
async payBatch(params) {
|
|
508
|
+
const intents = params.payments.map((payment) => {
|
|
509
|
+
const token = payment.token ?? "USDT0";
|
|
510
|
+
const tokenAddress = getTokenAddress(token, this.chainName);
|
|
511
|
+
return {
|
|
512
|
+
to: tokenAddress,
|
|
513
|
+
value: 0n,
|
|
514
|
+
data: (0, import_viem2.encodeFunctionData)({
|
|
515
|
+
abi: ERC20_TRANSFER_ABI,
|
|
516
|
+
functionName: "transfer",
|
|
517
|
+
args: [payment.to, payment.amount]
|
|
518
|
+
})
|
|
519
|
+
};
|
|
520
|
+
});
|
|
521
|
+
const gasEstimate = await this.estimateBatchGas(intents);
|
|
522
|
+
const paymasterData = await this.getPaymasterData(gasEstimate);
|
|
523
|
+
const sponsored = paymasterData !== void 0;
|
|
524
|
+
const userOp = await this.builder.buildBatchUserOp(
|
|
525
|
+
this.signer,
|
|
526
|
+
intents,
|
|
527
|
+
this.publicClient,
|
|
528
|
+
gasEstimate,
|
|
529
|
+
paymasterData
|
|
530
|
+
);
|
|
531
|
+
const signedUserOp = await this.builder.signUserOp(
|
|
532
|
+
userOp,
|
|
533
|
+
this.signer,
|
|
534
|
+
this.publicClient,
|
|
535
|
+
this.chainId
|
|
536
|
+
);
|
|
537
|
+
const result = await this.bundler.sendUserOperation(signedUserOp);
|
|
538
|
+
const sender = await this.signer.getAddress();
|
|
539
|
+
return {
|
|
540
|
+
userOpHash: result.userOpHash,
|
|
541
|
+
sender,
|
|
542
|
+
sponsored,
|
|
543
|
+
wait: async () => {
|
|
544
|
+
const receipt = await result.wait();
|
|
545
|
+
return {
|
|
546
|
+
userOpHash: receipt.userOpHash,
|
|
547
|
+
txHash: receipt.receipt.transactionHash,
|
|
548
|
+
blockNumber: receipt.receipt.blockNumber,
|
|
549
|
+
success: receipt.success,
|
|
550
|
+
gasUsed: receipt.actualGasUsed,
|
|
551
|
+
gasCost: receipt.actualGasCost,
|
|
552
|
+
reason: receipt.reason
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Check if a payment can be sponsored (free gas)
|
|
559
|
+
*/
|
|
560
|
+
async canSponsor(params) {
|
|
561
|
+
if (!this.paymaster) {
|
|
562
|
+
return {
|
|
563
|
+
canSponsor: false,
|
|
564
|
+
reason: "No paymaster configured"
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const token = params.token ?? "USDT0";
|
|
568
|
+
const tokenAddress = getTokenAddress(token, this.chainName);
|
|
569
|
+
const callData = (0, import_viem2.encodeFunctionData)({
|
|
570
|
+
abi: ERC20_TRANSFER_ABI,
|
|
571
|
+
functionName: "transfer",
|
|
572
|
+
args: [params.to, params.amount]
|
|
573
|
+
});
|
|
574
|
+
const sender = await this.signer.getAddress();
|
|
575
|
+
const encodedCallData = this.signer.encodeExecute(tokenAddress, 0n, callData);
|
|
576
|
+
try {
|
|
577
|
+
const canSponsor = await this.paymaster.willSponsor(
|
|
578
|
+
{ sender, callData: encodedCallData },
|
|
579
|
+
this.chainId,
|
|
580
|
+
import_evm.ENTRYPOINT_V07_ADDRESS
|
|
581
|
+
);
|
|
582
|
+
if (canSponsor) {
|
|
583
|
+
return { canSponsor: true };
|
|
584
|
+
} else {
|
|
585
|
+
const intent = {
|
|
586
|
+
to: tokenAddress,
|
|
587
|
+
value: 0n,
|
|
588
|
+
data: callData
|
|
589
|
+
};
|
|
590
|
+
const gasEstimate = await this.estimateGas(intent);
|
|
591
|
+
const gasPrice = await this.publicClient.getGasPrice();
|
|
592
|
+
const estimatedGasCost = (gasEstimate.verificationGasLimit + gasEstimate.callGasLimit + gasEstimate.preVerificationGas) * gasPrice;
|
|
593
|
+
return {
|
|
594
|
+
canSponsor: false,
|
|
595
|
+
reason: "Payment not eligible for sponsorship",
|
|
596
|
+
estimatedGasCost
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
} catch (error) {
|
|
600
|
+
return {
|
|
601
|
+
canSponsor: false,
|
|
602
|
+
reason: error instanceof Error ? error.message : "Unknown error"
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Get the smart account address
|
|
608
|
+
*/
|
|
609
|
+
async getAccountAddress() {
|
|
610
|
+
return this.signer.getAddress();
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Check if the smart account is deployed
|
|
614
|
+
*/
|
|
615
|
+
async isAccountDeployed() {
|
|
616
|
+
return this.signer.isDeployed();
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Get the token balance of the smart account
|
|
620
|
+
*/
|
|
621
|
+
async getBalance(token = "USDT0") {
|
|
622
|
+
const tokenAddress = getTokenAddress(token, this.chainName);
|
|
623
|
+
const accountAddress = await this.signer.getAddress();
|
|
624
|
+
const balance = await this.publicClient.readContract({
|
|
625
|
+
address: tokenAddress,
|
|
626
|
+
abi: ERC20_BALANCE_ABI,
|
|
627
|
+
functionName: "balanceOf",
|
|
628
|
+
args: [accountAddress]
|
|
629
|
+
});
|
|
630
|
+
return balance;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Get the formatted token balance
|
|
634
|
+
*/
|
|
635
|
+
async getFormattedBalance(token = "USDT0", decimals = 6) {
|
|
636
|
+
const balance = await this.getBalance(token);
|
|
637
|
+
return (0, import_viem2.formatUnits)(balance, decimals);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Estimate gas for a single transaction
|
|
641
|
+
*/
|
|
642
|
+
async estimateGas(intent) {
|
|
643
|
+
const sender = await this.signer.getAddress();
|
|
644
|
+
const callData = this.signer.encodeExecute(
|
|
645
|
+
intent.to,
|
|
646
|
+
intent.value ?? 0n,
|
|
647
|
+
intent.data ?? "0x"
|
|
648
|
+
);
|
|
649
|
+
try {
|
|
650
|
+
return await this.bundler.estimateUserOperationGas({
|
|
651
|
+
sender,
|
|
652
|
+
callData
|
|
653
|
+
});
|
|
654
|
+
} catch {
|
|
655
|
+
return {
|
|
656
|
+
verificationGasLimit: 150000n,
|
|
657
|
+
callGasLimit: 100000n,
|
|
658
|
+
preVerificationGas: 50000n
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Estimate gas for a batch transaction
|
|
664
|
+
*/
|
|
665
|
+
async estimateBatchGas(intents) {
|
|
666
|
+
const sender = await this.signer.getAddress();
|
|
667
|
+
const callData = this.signer.encodeExecuteBatch(
|
|
668
|
+
intents.map((i) => i.to),
|
|
669
|
+
intents.map((i) => i.value ?? 0n),
|
|
670
|
+
intents.map((i) => i.data ?? "0x")
|
|
671
|
+
);
|
|
672
|
+
try {
|
|
673
|
+
return await this.bundler.estimateUserOperationGas({
|
|
674
|
+
sender,
|
|
675
|
+
callData
|
|
676
|
+
});
|
|
677
|
+
} catch {
|
|
678
|
+
return {
|
|
679
|
+
verificationGasLimit: 150000n,
|
|
680
|
+
callGasLimit: 100000n * BigInt(intents.length),
|
|
681
|
+
preVerificationGas: 50000n
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Get paymaster data if configured
|
|
687
|
+
*/
|
|
688
|
+
async getPaymasterData(_gasEstimate) {
|
|
689
|
+
if (!this.paymaster) return void 0;
|
|
690
|
+
const sender = await this.signer.getAddress();
|
|
691
|
+
return this.paymaster.getPaymasterData(
|
|
692
|
+
{ sender },
|
|
693
|
+
this.chainId,
|
|
694
|
+
import_evm.ENTRYPOINT_V07_ADDRESS
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
async function createWdkGaslessClient(config) {
|
|
699
|
+
const smartAccount = await createWdkSmartAccount({
|
|
700
|
+
wdkAccount: config.wdkAccount,
|
|
701
|
+
publicClient: config.publicClient,
|
|
702
|
+
chainId: config.chainId,
|
|
703
|
+
saltNonce: config.saltNonce
|
|
704
|
+
});
|
|
705
|
+
return new WdkGaslessClient({
|
|
706
|
+
signer: smartAccount,
|
|
707
|
+
bundler: config.bundler,
|
|
708
|
+
paymaster: config.paymaster,
|
|
709
|
+
chainId: config.chainId,
|
|
710
|
+
publicClient: config.publicClient
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
714
|
+
0 && (module.exports = {
|
|
715
|
+
CHAIN_IDS,
|
|
716
|
+
DEFAULT_BUNDLER_URLS,
|
|
717
|
+
SAFE_4337_ADDRESSES,
|
|
718
|
+
USDC_ADDRESSES,
|
|
719
|
+
USDT0_ADDRESSES,
|
|
720
|
+
WdkGaslessClient,
|
|
721
|
+
WdkSmartAccount,
|
|
722
|
+
createWdkGaslessClient,
|
|
723
|
+
createWdkSmartAccount,
|
|
724
|
+
getChainName,
|
|
725
|
+
getTokenAddress
|
|
726
|
+
});
|
|
727
|
+
//# sourceMappingURL=index.js.map
|