gokite-aa-sdk 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/README.md +141 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +12 -0
- package/dist/example.d.ts +47 -0
- package/dist/example.js +397 -0
- package/dist/gokite-aa-sdk.d.ts +70 -0
- package/dist/gokite-aa-sdk.js +428 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +23 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.js +13 -0
- package/dist/utils.d.ts +49 -0
- package/dist/utils.js +156 -0
- package/package.json +39 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { UserOperation, UserOperationRequest, BatchUserOperationRequest, UserOperationGasEstimate, UserOperationStatus, SignFunction, PollingOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Main Gokite AA SDK class
|
|
4
|
+
*/
|
|
5
|
+
export declare class GokiteAASDK {
|
|
6
|
+
private config;
|
|
7
|
+
private provider;
|
|
8
|
+
private ethersProvider;
|
|
9
|
+
constructor(network: string, rpcUrl: string, bundlerUrl?: string);
|
|
10
|
+
/**
|
|
11
|
+
* Calculate account address for a given owner and salt
|
|
12
|
+
*/
|
|
13
|
+
getAccountAddress(owner: string, salt?: bigint): string;
|
|
14
|
+
/**
|
|
15
|
+
* Get account nonce
|
|
16
|
+
*/
|
|
17
|
+
getAccountNonce(accountAddress: string): Promise<bigint>;
|
|
18
|
+
/**
|
|
19
|
+
* Get user operation hash from EntryPoint contract
|
|
20
|
+
*/
|
|
21
|
+
getUserOpHash(userOp: any): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Check if account is deployed
|
|
24
|
+
*/
|
|
25
|
+
isAccountDeloyed(accountAddress: string): Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Build init code for account creation
|
|
28
|
+
*/
|
|
29
|
+
buildInitCode(owner: string, salt: bigint): string;
|
|
30
|
+
/**
|
|
31
|
+
* Build call data for single transaction
|
|
32
|
+
*/
|
|
33
|
+
buildCallData(request: UserOperationRequest): string;
|
|
34
|
+
/**
|
|
35
|
+
* Build call data for batch transactions
|
|
36
|
+
*/
|
|
37
|
+
buildBatchCallData(request: BatchUserOperationRequest): string;
|
|
38
|
+
/**
|
|
39
|
+
* Create user operation
|
|
40
|
+
*/
|
|
41
|
+
createUserOperation(owner: string, request: UserOperationRequest | BatchUserOperationRequest, salt?: bigint, paymasterAddress?: string): Promise<Partial<UserOperation>>;
|
|
42
|
+
/**
|
|
43
|
+
* Estimate gas for user operation
|
|
44
|
+
*/
|
|
45
|
+
estimateGas(userOp: Partial<UserOperation>): Promise<UserOperationGasEstimate>;
|
|
46
|
+
/**
|
|
47
|
+
* Sign and send user operation
|
|
48
|
+
*/
|
|
49
|
+
sendUserOperation(owner: string, request: UserOperationRequest | BatchUserOperationRequest, signFn: SignFunction, salt?: bigint, paymasterAddress?: string): Promise<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Send user operation directly to EntryPoint contract (bypassing bundler)
|
|
52
|
+
* Note: This requires a signer to pay for gas. For now, we'll try with the read-only provider.
|
|
53
|
+
*/
|
|
54
|
+
sendUserOperationDirectly(owner: string, userOp: UserOperation): Promise<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Get user operation status
|
|
57
|
+
*/
|
|
58
|
+
getUserOperationStatus(userOpHash: string): Promise<UserOperationStatus>;
|
|
59
|
+
/**
|
|
60
|
+
* Enhanced polling for user operation status with detailed result parsing
|
|
61
|
+
*/
|
|
62
|
+
pollUserOperationStatus(userOpHash: string, options?: PollingOptions): Promise<UserOperationStatus>;
|
|
63
|
+
/**
|
|
64
|
+
* Send user operation and wait for completion with detailed status
|
|
65
|
+
*/
|
|
66
|
+
sendUserOperationAndWait(owner: string, request: UserOperationRequest | BatchUserOperationRequest, signFn: SignFunction, salt?: bigint, paymasterAddress?: string, pollingOptions?: PollingOptions): Promise<{
|
|
67
|
+
userOpHash: string;
|
|
68
|
+
status: UserOperationStatus;
|
|
69
|
+
}>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.GokiteAASDK = void 0;
|
|
7
|
+
const ethers_1 = require("ethers");
|
|
8
|
+
const types_1 = require("./types");
|
|
9
|
+
const config_1 = require("./config");
|
|
10
|
+
const utils_1 = require("./utils");
|
|
11
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
12
|
+
dotenv_1.default.config();
|
|
13
|
+
// Error classification helper
|
|
14
|
+
function classifyError(error, context) {
|
|
15
|
+
// Network/fetch errors
|
|
16
|
+
if (error.name === 'TypeError' || error.message?.includes('fetch')) {
|
|
17
|
+
return {
|
|
18
|
+
type: 'NETWORK_ERROR',
|
|
19
|
+
message: `Network error during ${context}: ${error.message}`,
|
|
20
|
+
details: error
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Bundler RPC errors
|
|
24
|
+
if (error.code) {
|
|
25
|
+
const errorType = context.includes('estimate') ? 'ESTIMATE_GAS_FAILED' : 'SEND_USEROP_FAILED';
|
|
26
|
+
let specificType = errorType;
|
|
27
|
+
// Common error codes mapping
|
|
28
|
+
if (error.code === -32602 || error.message?.includes('insufficient funds')) {
|
|
29
|
+
specificType = 'INSUFFICIENT_FUNDS';
|
|
30
|
+
}
|
|
31
|
+
else if (error.code === -32602 || error.message?.includes('signature')) {
|
|
32
|
+
specificType = 'INVALID_SIGNATURE';
|
|
33
|
+
}
|
|
34
|
+
else if (error.code < 0) {
|
|
35
|
+
specificType = 'BUNDLER_ERROR';
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
type: specificType,
|
|
39
|
+
message: error.message || `${context} failed`,
|
|
40
|
+
code: error.code,
|
|
41
|
+
details: error
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// Generic error
|
|
45
|
+
return {
|
|
46
|
+
type: 'UNKNOWN_ERROR',
|
|
47
|
+
message: error.message || `Unknown error during ${context}`,
|
|
48
|
+
details: error
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
class BundlerProvider {
|
|
52
|
+
constructor(bundlerUrl) {
|
|
53
|
+
this.bundlerUrl = bundlerUrl;
|
|
54
|
+
}
|
|
55
|
+
async estimateUserOperationGas(userOp, entryPoint) {
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(this.bundlerUrl, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
jsonrpc: '2.0',
|
|
62
|
+
id: Date.now(),
|
|
63
|
+
method: 'eth_estimateUserOperationGas',
|
|
64
|
+
params: [(0, utils_1.serializeUserOperation)(userOp), entryPoint]
|
|
65
|
+
})
|
|
66
|
+
});
|
|
67
|
+
const result = await response.json();
|
|
68
|
+
if (result.error) {
|
|
69
|
+
throw new types_1.AASDKError(classifyError(result.error, 'estimate gas'));
|
|
70
|
+
}
|
|
71
|
+
if (!result.result) {
|
|
72
|
+
throw new types_1.AASDKError({
|
|
73
|
+
type: 'ESTIMATE_GAS_FAILED',
|
|
74
|
+
message: 'No gas estimate result returned from bundler'
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
callGasLimit: BigInt(result.result.callGasLimit),
|
|
79
|
+
verificationGasLimit: BigInt(result.result.verificationGasLimit),
|
|
80
|
+
preVerificationGas: BigInt(result.result.preVerificationGas),
|
|
81
|
+
maxFeePerGas: BigInt(result.result.maxFeePerGas || 1000000000n),
|
|
82
|
+
maxPriorityFeePerGas: BigInt(result.result.maxPriorityFeePerGas || 1000000000n), // 1 gwei
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error instanceof types_1.AASDKError)
|
|
87
|
+
throw error;
|
|
88
|
+
throw new types_1.AASDKError(classifyError(error, 'estimate gas'));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async sendUserOperation(userOp, entryPoint) {
|
|
92
|
+
try {
|
|
93
|
+
const serializedUserOp = (0, utils_1.serializeUserOperation)(userOp);
|
|
94
|
+
console.log('Sending UserOp to bundler:', JSON.stringify(serializedUserOp, null, 2));
|
|
95
|
+
const response = await fetch(this.bundlerUrl, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: { 'Content-Type': 'application/json' },
|
|
98
|
+
body: JSON.stringify({
|
|
99
|
+
jsonrpc: '2.0',
|
|
100
|
+
id: Date.now(),
|
|
101
|
+
method: 'eth_sendUserOperation',
|
|
102
|
+
params: [serializedUserOp, entryPoint]
|
|
103
|
+
})
|
|
104
|
+
});
|
|
105
|
+
const result = await response.json();
|
|
106
|
+
if (result.error) {
|
|
107
|
+
throw new types_1.AASDKError(classifyError(result.error, 'send user operation'));
|
|
108
|
+
}
|
|
109
|
+
if (!result.result) {
|
|
110
|
+
throw new types_1.AASDKError({
|
|
111
|
+
type: 'SEND_USEROP_FAILED',
|
|
112
|
+
message: 'No user operation hash returned from bundler'
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return result.result;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (error instanceof types_1.AASDKError)
|
|
119
|
+
throw error;
|
|
120
|
+
throw new types_1.AASDKError(classifyError(error, 'send user operation'));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async getUserOperationStatus(userOpHash) {
|
|
124
|
+
const response = await fetch(this.bundlerUrl, {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: { 'Content-Type': 'application/json' },
|
|
127
|
+
body: JSON.stringify({
|
|
128
|
+
jsonrpc: '2.0',
|
|
129
|
+
id: Date.now(),
|
|
130
|
+
method: 'eth_getUserOperationReceipt',
|
|
131
|
+
params: [userOpHash]
|
|
132
|
+
})
|
|
133
|
+
});
|
|
134
|
+
const result = await response.json();
|
|
135
|
+
// If error or no result, operation is still pending
|
|
136
|
+
if (result.error || !result.result) {
|
|
137
|
+
return { userOpHash, status: 'pending' };
|
|
138
|
+
}
|
|
139
|
+
const receipt = result.result;
|
|
140
|
+
// Determine status based on success field
|
|
141
|
+
let status;
|
|
142
|
+
if (receipt.success) {
|
|
143
|
+
status = 'success';
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
status = 'reverted';
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
userOpHash,
|
|
150
|
+
status,
|
|
151
|
+
transactionHash: receipt.receipt.transactionHash,
|
|
152
|
+
blockNumber: parseInt(receipt.receipt.blockNumber, 16),
|
|
153
|
+
gasUsed: receipt.receipt.gasUsed,
|
|
154
|
+
actualGasCost: receipt.actualGasCost,
|
|
155
|
+
reason: receipt.reason,
|
|
156
|
+
receipt
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async getUserOperationByHash(userOpHash) {
|
|
160
|
+
const response = await fetch(this.bundlerUrl, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: { 'Content-Type': 'application/json' },
|
|
163
|
+
body: JSON.stringify({
|
|
164
|
+
jsonrpc: '2.0',
|
|
165
|
+
id: Date.now(),
|
|
166
|
+
method: 'eth_getUserOperationByHash',
|
|
167
|
+
params: [userOpHash]
|
|
168
|
+
})
|
|
169
|
+
});
|
|
170
|
+
const result = await response.json();
|
|
171
|
+
return result.result || null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Main Gokite AA SDK class
|
|
176
|
+
*/
|
|
177
|
+
class GokiteAASDK {
|
|
178
|
+
constructor(network, rpcUrl, bundlerUrl) {
|
|
179
|
+
this.config = config_1.NETWORKS[network];
|
|
180
|
+
if (!this.config) {
|
|
181
|
+
throw new Error(`Unsupported network: ${network}`);
|
|
182
|
+
}
|
|
183
|
+
this.ethersProvider = new ethers_1.ethers.JsonRpcProvider(rpcUrl);
|
|
184
|
+
if (bundlerUrl) {
|
|
185
|
+
this.provider = new BundlerProvider(bundlerUrl);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
throw new Error('Bundler URL is required');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Calculate account address for a given owner and salt
|
|
193
|
+
*/
|
|
194
|
+
getAccountAddress(owner, salt) {
|
|
195
|
+
const actualSalt = salt || (0, utils_1.generateSalt)();
|
|
196
|
+
return (0, utils_1.getAccountAddress)(this.config.accountFactory, this.config.accountImplementation, owner, actualSalt);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get account nonce
|
|
200
|
+
*/
|
|
201
|
+
async getAccountNonce(accountAddress) {
|
|
202
|
+
const entryPoint = new ethers_1.ethers.Contract(this.config.entryPoint, ['function getNonce(address,uint192) view returns (uint256)'], this.ethersProvider);
|
|
203
|
+
return await entryPoint.getNonce(accountAddress, 0);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get user operation hash from EntryPoint contract
|
|
207
|
+
*/
|
|
208
|
+
async getUserOpHash(userOp) {
|
|
209
|
+
const entryPoint = new ethers_1.ethers.Contract(this.config.entryPoint, ['function getUserOpHash((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes)) view returns (bytes32)'], this.ethersProvider);
|
|
210
|
+
// Convert userOp to the format expected by the contract - ensure proper types
|
|
211
|
+
const packedUserOp = [
|
|
212
|
+
userOp.sender,
|
|
213
|
+
userOp.nonce,
|
|
214
|
+
userOp.initCode,
|
|
215
|
+
userOp.callData,
|
|
216
|
+
userOp.accountGasLimits,
|
|
217
|
+
userOp.preVerificationGas,
|
|
218
|
+
userOp.gasFees,
|
|
219
|
+
userOp.paymasterAndData,
|
|
220
|
+
'0x' // signature (empty for hash calculation)
|
|
221
|
+
];
|
|
222
|
+
console.log('getUserOpHash packedUserOp:', packedUserOp);
|
|
223
|
+
const hash = await entryPoint.getUserOpHash(packedUserOp);
|
|
224
|
+
console.log('Raw hash from contract:', hash);
|
|
225
|
+
return hash;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Check if account is deployed
|
|
229
|
+
*/
|
|
230
|
+
async isAccountDeloyed(accountAddress) {
|
|
231
|
+
const code = await this.ethersProvider.getCode(accountAddress);
|
|
232
|
+
return code !== '0x';
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Build init code for account creation
|
|
236
|
+
*/
|
|
237
|
+
buildInitCode(owner, salt) {
|
|
238
|
+
const initCallData = (0, utils_1.encodeFunctionCall)(['function createAccount(address,uint256) returns (address)'], 'createAccount', [owner, salt]);
|
|
239
|
+
return this.config.accountFactory + initCallData.slice(2);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Build call data for single transaction
|
|
243
|
+
*/
|
|
244
|
+
buildCallData(request) {
|
|
245
|
+
return (0, utils_1.encodeFunctionCall)(['function execute(address,uint256,bytes)'], 'execute', [request.target, request.value || 0n, request.callData]);
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Build call data for batch transactions
|
|
249
|
+
*/
|
|
250
|
+
buildBatchCallData(request) {
|
|
251
|
+
const values = request.values || new Array(request.targets.length).fill(0n);
|
|
252
|
+
return (0, utils_1.encodeFunctionCall)(['function executeBatch(address[],uint256[],bytes[])'], 'executeBatch', [request.targets, values, request.callDatas]);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Create user operation
|
|
256
|
+
*/
|
|
257
|
+
async createUserOperation(owner, request, salt, paymasterAddress) {
|
|
258
|
+
const actualSalt = salt || (0, utils_1.generateSalt)();
|
|
259
|
+
const accountAddress = this.getAccountAddress(owner, actualSalt);
|
|
260
|
+
const isDeployed = await this.isAccountDeloyed(accountAddress);
|
|
261
|
+
const callData = 'targets' in request
|
|
262
|
+
? this.buildBatchCallData(request)
|
|
263
|
+
: this.buildCallData(request);
|
|
264
|
+
// Gas settings (matching Go implementation)
|
|
265
|
+
const callGasLimit = BigInt(300000);
|
|
266
|
+
const verificationGasLimit = BigInt(300000);
|
|
267
|
+
const preVerificationGas = BigInt(1000000);
|
|
268
|
+
const maxFeePerGas = BigInt(10000000); // 0.001 gwei
|
|
269
|
+
const maxPriorityFeePerGas = BigInt(1); // 1 wei
|
|
270
|
+
// Pack gas limits and fees (note: verificationGasLimit first, callGasLimit second)
|
|
271
|
+
const accountGasLimits = (0, utils_1.packAccountGasLimits)(verificationGasLimit, callGasLimit);
|
|
272
|
+
const gasFees = (0, utils_1.packAccountGasLimits)(maxPriorityFeePerGas, maxFeePerGas);
|
|
273
|
+
if (!paymasterAddress) {
|
|
274
|
+
paymasterAddress = this.config.paymaster;
|
|
275
|
+
}
|
|
276
|
+
// Pack paymaster data (paymasterAddress, paymasterVerificationGasLimit, postOpGasLimit, paymasterData)
|
|
277
|
+
const paymasterAndData = paymasterAddress
|
|
278
|
+
? (0, utils_1.packPaymasterAndData)(paymasterAddress, BigInt(100000), BigInt(100000), '0x')
|
|
279
|
+
: '0x';
|
|
280
|
+
return {
|
|
281
|
+
sender: accountAddress,
|
|
282
|
+
nonce: await this.getAccountNonce(accountAddress),
|
|
283
|
+
initCode: isDeployed ? '0x' : this.buildInitCode(owner, actualSalt),
|
|
284
|
+
callData,
|
|
285
|
+
accountGasLimits,
|
|
286
|
+
preVerificationGas,
|
|
287
|
+
gasFees,
|
|
288
|
+
paymasterAndData,
|
|
289
|
+
signature: '0x', // Will be set later
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Estimate gas for user operation
|
|
294
|
+
*/
|
|
295
|
+
async estimateGas(userOp) {
|
|
296
|
+
// Create a version with dummy signature for estimation
|
|
297
|
+
const userOpForEstimation = (0, utils_1.createUserOpForEstimation)(userOp);
|
|
298
|
+
return await this.provider.estimateUserOperationGas(userOpForEstimation, this.config.entryPoint);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Sign and send user operation
|
|
302
|
+
*/
|
|
303
|
+
async sendUserOperation(owner, request, signFn, salt, paymasterAddress) {
|
|
304
|
+
// Create user operation
|
|
305
|
+
const userOp = await this.createUserOperation(owner, request, salt, paymasterAddress);
|
|
306
|
+
// Add dummy signature for gas estimation
|
|
307
|
+
const userOpWithDummy = (0, utils_1.createUserOpForEstimation)(userOp);
|
|
308
|
+
// Estimate gas using bundler
|
|
309
|
+
const gasEstimate = await this.provider.estimateUserOperationGas(userOpWithDummy, this.config.entryPoint);
|
|
310
|
+
console.log('gasEstimate:', gasEstimate);
|
|
311
|
+
// Update gas fields in packed format (verificationGasLimit first, callGasLimit second)
|
|
312
|
+
userOp.accountGasLimits = (0, utils_1.packAccountGasLimits)(gasEstimate.verificationGasLimit, gasEstimate.callGasLimit);
|
|
313
|
+
userOp.preVerificationGas = gasEstimate.preVerificationGas;
|
|
314
|
+
const userOpHash = await this.getUserOpHash(userOp);
|
|
315
|
+
// Sign user operation
|
|
316
|
+
const signature = await signFn(userOpHash);
|
|
317
|
+
userOp.signature = signature;
|
|
318
|
+
// // Send user operation directly to EntryPoint contract (bypassing bundler)
|
|
319
|
+
// return await this.sendUserOperationDirectly(owner, userOp as UserOperation);
|
|
320
|
+
return await this.provider.sendUserOperation(userOp, this.config.entryPoint);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Send user operation directly to EntryPoint contract (bypassing bundler)
|
|
324
|
+
* Note: This requires a signer to pay for gas. For now, we'll try with the read-only provider.
|
|
325
|
+
*/
|
|
326
|
+
async sendUserOperationDirectly(owner, userOp) {
|
|
327
|
+
// TODO: We need a signer here to pay for gas
|
|
328
|
+
// For now, let's see what error we get with the read-only provider
|
|
329
|
+
const entryPoint = new ethers_1.ethers.Contract(this.config.entryPoint, [
|
|
330
|
+
'function handleOps((address,uint256,bytes,bytes,bytes32,uint256,bytes32,bytes,bytes)[] calldata ops, address payable beneficiary) external'
|
|
331
|
+
], this.ethersProvider);
|
|
332
|
+
// Convert userOp to the format expected by the contract
|
|
333
|
+
const packedUserOp = [
|
|
334
|
+
userOp.sender,
|
|
335
|
+
userOp.nonce,
|
|
336
|
+
userOp.initCode,
|
|
337
|
+
userOp.callData,
|
|
338
|
+
userOp.accountGasLimits,
|
|
339
|
+
userOp.preVerificationGas,
|
|
340
|
+
userOp.gasFees,
|
|
341
|
+
userOp.paymasterAndData,
|
|
342
|
+
userOp.signature
|
|
343
|
+
];
|
|
344
|
+
// Create operations array (only one operation)
|
|
345
|
+
const ops = [packedUserOp];
|
|
346
|
+
// Set beneficiary (use owner address)
|
|
347
|
+
const beneficiary = owner;
|
|
348
|
+
console.log('Calling EntryPoint.handleOps directly...');
|
|
349
|
+
console.log('ops:', ops);
|
|
350
|
+
console.log('beneficiary:', beneficiary);
|
|
351
|
+
const signer_pk = process.env.PRIVATE_KEY;
|
|
352
|
+
const signer = new ethers_1.ethers.Wallet(signer_pk, this.ethersProvider);
|
|
353
|
+
console.log('signer:', signer.address);
|
|
354
|
+
try {
|
|
355
|
+
// Call handleOps on EntryPoint contract
|
|
356
|
+
const tx = await entryPoint.connect(signer).handleOps(ops, beneficiary, {
|
|
357
|
+
gasLimit: 1000000,
|
|
358
|
+
});
|
|
359
|
+
console.log('Transaction sent:', tx.hash);
|
|
360
|
+
// Wait for transaction confirmation
|
|
361
|
+
const receipt = await tx.wait();
|
|
362
|
+
console.log('Transaction confirmed:', receipt.hash);
|
|
363
|
+
return tx.hash;
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
console.error('EntryPoint.handleOps failed:', error);
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Get user operation status
|
|
372
|
+
*/
|
|
373
|
+
async getUserOperationStatus(userOpHash) {
|
|
374
|
+
return await this.provider.getUserOperationStatus(userOpHash);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Enhanced polling for user operation status with detailed result parsing
|
|
378
|
+
*/
|
|
379
|
+
async pollUserOperationStatus(userOpHash, options = {}) {
|
|
380
|
+
const { interval = 2000, timeout = 60000, maxRetries = 30 } = options;
|
|
381
|
+
const startTime = Date.now();
|
|
382
|
+
let retryCount = 0;
|
|
383
|
+
while (Date.now() - startTime < timeout && retryCount < maxRetries) {
|
|
384
|
+
try {
|
|
385
|
+
const status = await this.getUserOperationStatus(userOpHash);
|
|
386
|
+
// Check if operation is completed (either success or failed)
|
|
387
|
+
if (status.status === 'success' || status.status === 'reverted' || status.status === 'failed') {
|
|
388
|
+
console.log(`UserOp completed:`, {
|
|
389
|
+
status: status.status,
|
|
390
|
+
transactionHash: status.transactionHash,
|
|
391
|
+
gasUsed: status.gasUsed,
|
|
392
|
+
actualGasCost: status.actualGasCost,
|
|
393
|
+
reason: status.reason
|
|
394
|
+
});
|
|
395
|
+
return status;
|
|
396
|
+
}
|
|
397
|
+
retryCount++;
|
|
398
|
+
if (retryCount < maxRetries) {
|
|
399
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
console.error(`polling error (attempt ${retryCount + 1}):`, error);
|
|
404
|
+
retryCount++;
|
|
405
|
+
if (retryCount < maxRetries) {
|
|
406
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
throw new Error(`UserOp polling timeout: ${userOpHash} (attempt ${retryCount})`);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Send user operation and wait for completion with detailed status
|
|
414
|
+
*/
|
|
415
|
+
async sendUserOperationAndWait(owner, request, signFn, salt, paymasterAddress, pollingOptions) {
|
|
416
|
+
console.log('Sending UserOp and waiting for completion...');
|
|
417
|
+
// Step 1: Send user operation
|
|
418
|
+
const userOpHash = await this.sendUserOperation(owner, request, signFn, salt, paymasterAddress);
|
|
419
|
+
console.log(`UserOp sent: ${userOpHash}`);
|
|
420
|
+
// Step 2: Poll for status until completion
|
|
421
|
+
const status = await this.pollUserOperationStatus(userOpHash, pollingOptions);
|
|
422
|
+
return {
|
|
423
|
+
userOpHash,
|
|
424
|
+
status
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
exports.GokiteAASDK = GokiteAASDK;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.GokiteAASDK = void 0;
|
|
18
|
+
// Gokite AA SDK - Entry point
|
|
19
|
+
var gokite_aa_sdk_1 = require("./gokite-aa-sdk");
|
|
20
|
+
Object.defineProperty(exports, "GokiteAASDK", { enumerable: true, get: function () { return gokite_aa_sdk_1.GokiteAASDK; } });
|
|
21
|
+
__exportStar(require("./types"), exports);
|
|
22
|
+
__exportStar(require("./config"), exports);
|
|
23
|
+
__exportStar(require("./utils"), exports);
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export interface UserOperation {
|
|
2
|
+
sender: string;
|
|
3
|
+
nonce: bigint;
|
|
4
|
+
initCode: string;
|
|
5
|
+
callData: string;
|
|
6
|
+
accountGasLimits: string;
|
|
7
|
+
preVerificationGas: bigint;
|
|
8
|
+
gasFees: string;
|
|
9
|
+
paymasterAndData: string;
|
|
10
|
+
signature: string;
|
|
11
|
+
}
|
|
12
|
+
export interface UserOperationGasEstimate {
|
|
13
|
+
callGasLimit: bigint;
|
|
14
|
+
verificationGasLimit: bigint;
|
|
15
|
+
preVerificationGas: bigint;
|
|
16
|
+
maxFeePerGas: bigint;
|
|
17
|
+
maxPriorityFeePerGas: bigint;
|
|
18
|
+
}
|
|
19
|
+
export interface UserOperationRequest {
|
|
20
|
+
target: string;
|
|
21
|
+
value?: bigint;
|
|
22
|
+
callData: string;
|
|
23
|
+
}
|
|
24
|
+
export interface BatchUserOperationRequest {
|
|
25
|
+
targets: string[];
|
|
26
|
+
values?: bigint[];
|
|
27
|
+
callDatas: string[];
|
|
28
|
+
}
|
|
29
|
+
export interface UserOperationStatus {
|
|
30
|
+
userOpHash: string;
|
|
31
|
+
status: 'pending' | 'included' | 'failed' | 'success' | 'reverted';
|
|
32
|
+
transactionHash?: string;
|
|
33
|
+
blockNumber?: number;
|
|
34
|
+
gasUsed?: string;
|
|
35
|
+
actualGasCost?: string;
|
|
36
|
+
reason?: string;
|
|
37
|
+
receipt?: UserOperationReceipt;
|
|
38
|
+
}
|
|
39
|
+
export interface PollingOptions {
|
|
40
|
+
interval?: number;
|
|
41
|
+
timeout?: number;
|
|
42
|
+
maxRetries?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface AAError {
|
|
45
|
+
type: 'ESTIMATE_GAS_FAILED' | 'SEND_USEROP_FAILED' | 'INSUFFICIENT_FUNDS' | 'INVALID_SIGNATURE' | 'BUNDLER_ERROR' | 'NETWORK_ERROR' | 'UNKNOWN_ERROR';
|
|
46
|
+
message: string;
|
|
47
|
+
code?: number;
|
|
48
|
+
details?: any;
|
|
49
|
+
}
|
|
50
|
+
export declare class AASDKError extends Error {
|
|
51
|
+
readonly type: AAError['type'];
|
|
52
|
+
readonly code?: number;
|
|
53
|
+
readonly details?: any;
|
|
54
|
+
constructor(error: AAError);
|
|
55
|
+
}
|
|
56
|
+
export interface SignFunction {
|
|
57
|
+
(userOpHash: string): Promise<string>;
|
|
58
|
+
}
|
|
59
|
+
export interface AAProvider {
|
|
60
|
+
estimateUserOperationGas(userOp: Partial<UserOperation>, entryPoint: string): Promise<UserOperationGasEstimate>;
|
|
61
|
+
sendUserOperation(userOp: UserOperation, entryPoint: string): Promise<string>;
|
|
62
|
+
getUserOperationStatus(userOpHash: string): Promise<UserOperationStatus>;
|
|
63
|
+
getUserOperationByHash(userOpHash: string): Promise<UserOperation | null>;
|
|
64
|
+
}
|
|
65
|
+
export interface BundlerResponse<T = any> {
|
|
66
|
+
jsonrpc: string;
|
|
67
|
+
id: number;
|
|
68
|
+
result?: T;
|
|
69
|
+
error?: {
|
|
70
|
+
code: number;
|
|
71
|
+
message: string;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export interface GasEstimateResult {
|
|
75
|
+
callGasLimit: string;
|
|
76
|
+
verificationGasLimit: string;
|
|
77
|
+
preVerificationGas: string;
|
|
78
|
+
maxFeePerGas?: string;
|
|
79
|
+
maxPriorityFeePerGas?: string;
|
|
80
|
+
}
|
|
81
|
+
export interface UserOperationReceipt {
|
|
82
|
+
userOpHash: string;
|
|
83
|
+
sender: string;
|
|
84
|
+
nonce: string;
|
|
85
|
+
actualGasCost: string;
|
|
86
|
+
actualGasUsed: string;
|
|
87
|
+
success: boolean;
|
|
88
|
+
receipt: {
|
|
89
|
+
transactionHash: string;
|
|
90
|
+
blockNumber: string;
|
|
91
|
+
gasUsed: string;
|
|
92
|
+
logs: any[];
|
|
93
|
+
};
|
|
94
|
+
reason?: string;
|
|
95
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AASDKError = void 0;
|
|
4
|
+
class AASDKError extends Error {
|
|
5
|
+
constructor(error) {
|
|
6
|
+
super(error.message);
|
|
7
|
+
this.name = 'AASDKError';
|
|
8
|
+
this.type = error.type;
|
|
9
|
+
this.code = error.code;
|
|
10
|
+
this.details = error.details;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.AASDKError = AASDKError;
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate the counterfactual address of a GokiteAccount
|
|
3
|
+
* This matches the logic in GokiteAccountFactory.getAddress()
|
|
4
|
+
*/
|
|
5
|
+
export declare function getAccountAddress(factoryAddress: string, implementationAddress: string, owner: string, salt: bigint): string;
|
|
6
|
+
/**
|
|
7
|
+
* Encode function call data
|
|
8
|
+
*/
|
|
9
|
+
export declare function encodeFunctionCall(abi: string[], functionName: string, params: any[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* Pack user operation for hashing
|
|
12
|
+
*/
|
|
13
|
+
export declare function packUserOperation(userOp: any): string;
|
|
14
|
+
/**
|
|
15
|
+
* Pack account gas limits into 32 bytes
|
|
16
|
+
* @param verificationGasLimit Verification gas limit (16 bytes, first half)
|
|
17
|
+
* @param callGasLimit Call gas limit (16 bytes, second half)
|
|
18
|
+
* @returns 32 bytes packed data as hex string
|
|
19
|
+
*/
|
|
20
|
+
export declare function packAccountGasLimits(verificationGasLimit: bigint, callGasLimit: bigint): string;
|
|
21
|
+
/**
|
|
22
|
+
* Pack paymaster and data
|
|
23
|
+
* @param paymasterAddress Paymaster contract address (20 bytes)
|
|
24
|
+
* @param paymasterVerificationGasLimit Paymaster verification gas limit (16 bytes)
|
|
25
|
+
* @param postOpGasLimit Post operation gas limit (16 bytes)
|
|
26
|
+
* @param paymasterData Additional paymaster data (variable length)
|
|
27
|
+
* @returns Packed paymaster and data as hex string
|
|
28
|
+
*/
|
|
29
|
+
export declare function packPaymasterAndData(paymasterAddress: string, paymasterVerificationGasLimit: bigint, postOpGasLimit: bigint, paymasterData?: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Get user operation hash
|
|
32
|
+
*/
|
|
33
|
+
export declare function getUserOperationHash(userOp: any, entryPointAddress: string, chainId: number): string;
|
|
34
|
+
/**
|
|
35
|
+
* Convert bigint values to hex strings for JSON serialization
|
|
36
|
+
* Also unpacks PackedUserOperation format to legacy format for bundler compatibility
|
|
37
|
+
*/
|
|
38
|
+
export declare function serializeUserOperation(userOp: any): any;
|
|
39
|
+
export declare function generateSalt(): bigint;
|
|
40
|
+
/**
|
|
41
|
+
* Generate a dummy signature for gas estimation
|
|
42
|
+
* The signature needs to be the correct length but doesn't need to be valid
|
|
43
|
+
* Using a format that won't cause ECDSA.recover to revert
|
|
44
|
+
*/
|
|
45
|
+
export declare function generateDummySignature(): string;
|
|
46
|
+
/**
|
|
47
|
+
* Create a user operation with dummy signature for gas estimation
|
|
48
|
+
*/
|
|
49
|
+
export declare function createUserOpForEstimation(userOp: any): any;
|