@aastar/enduser 0.16.14 → 0.16.18
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/CommunityClient.d.ts +16 -0
- package/dist/CommunityClient.js +44 -0
- package/dist/UserClient.d.ts +4 -0
- package/dist/UserClient.js +17 -53
- package/dist/UserLifecycle.d.ts +85 -0
- package/dist/UserLifecycle.js +180 -0
- package/dist/enduser/src/CommunityClient.d.ts +93 -0
- package/dist/enduser/src/CommunityClient.js +286 -0
- package/dist/enduser/src/UserClient.d.ts +92 -0
- package/dist/enduser/src/UserClient.js +414 -0
- package/dist/enduser/src/UserLifecycle.d.ts +74 -0
- package/dist/enduser/src/UserLifecycle.js +179 -0
- package/dist/enduser/src/index.d.ts +3 -0
- package/dist/enduser/src/index.js +3 -0
- package/dist/enduser/src/testAccountManager.d.ts +149 -0
- package/dist/enduser/src/testAccountManager.js +271 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/paymaster/src/V4/BundlerCompat.d.ts +28 -0
- package/dist/paymaster/src/V4/BundlerCompat.js +37 -0
- package/dist/paymaster/src/V4/PaymasterClient.d.ts +89 -0
- package/dist/paymaster/src/V4/PaymasterClient.js +413 -0
- package/dist/paymaster/src/V4/PaymasterUtils.d.ts +66 -0
- package/dist/paymaster/src/V4/PaymasterUtils.js +141 -0
- package/package.json +4 -3
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { Address, Hash } from 'viem';
|
|
2
|
+
import { type PublicClient, type WalletClient } from '@aastar/core';
|
|
3
|
+
/**
|
|
4
|
+
* PhD Paper Experiment Test Toolkit
|
|
5
|
+
*
|
|
6
|
+
* **Purpose**: Comprehensive API suite for preparing and managing test accounts
|
|
7
|
+
* for ERC-4337 performance comparison experiments (EOA vs AA vs SuperPaymaster).
|
|
8
|
+
*
|
|
9
|
+
* **Core Features**:
|
|
10
|
+
* 1. **Account Generation**: Create random EOA keys and deploy SimpleAccounts
|
|
11
|
+
* 2. **Token Funding**: Transfer test tokens (GToken, aPNTs, bPNTs, ETH)
|
|
12
|
+
* 3. **AA Deployment**: Deploy SimpleAccount contracts using official factory
|
|
13
|
+
* 4. **UserOp Execution**: Send ERC-4337 UserOperations with various paymasters
|
|
14
|
+
* 5. **Data Collection**: Generate experiment data for PhD paper analysis
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const toolkit = new TestAccountManager(publicClient, supplierWallet);
|
|
19
|
+
*
|
|
20
|
+
* // Prepare complete test environment
|
|
21
|
+
* const env = await toolkit.prepareTestEnvironment({
|
|
22
|
+
* accountCount: 3,
|
|
23
|
+
* fundEachEOAWithETH: parseEther("0.01"),
|
|
24
|
+
* fundEachAAWithETH: parseEther("0.02"),
|
|
25
|
+
* tokens: {
|
|
26
|
+
* gToken: { address: '0x...', amount: parseEther("100") },
|
|
27
|
+
* aPNTs: { address: '0x...', amount: parseEther("50") }
|
|
28
|
+
* }
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class TestAccountManager {
|
|
33
|
+
/** @internal */
|
|
34
|
+
private publicClient;
|
|
35
|
+
/** @internal */
|
|
36
|
+
private walletClient;
|
|
37
|
+
constructor(
|
|
38
|
+
/** @internal */
|
|
39
|
+
publicClient: PublicClient,
|
|
40
|
+
/** @internal */
|
|
41
|
+
walletClient: WalletClient);
|
|
42
|
+
/**
|
|
43
|
+
* Prepare complete test environment for PhD experiments
|
|
44
|
+
*
|
|
45
|
+
* **Workflow**:
|
|
46
|
+
* 1. Generate N random EOA private keys
|
|
47
|
+
* 2. Deploy SimpleAccount for each EOA
|
|
48
|
+
* 3. Fund EOAs with ETH
|
|
49
|
+
* 4. Fund AAs with ETH
|
|
50
|
+
* 5. Transfer test tokens (GToken, aPNTs, bPNTs) to both EOAs and AAs
|
|
51
|
+
*
|
|
52
|
+
* @param config - Test environment configuration
|
|
53
|
+
* @returns Complete test environment with all accounts and tokens ready
|
|
54
|
+
*/
|
|
55
|
+
prepareTestEnvironment(config: TestEnvironmentConfig): Promise<TestEnvironment>;
|
|
56
|
+
/**
|
|
57
|
+
* Generate multiple test accounts for experiments
|
|
58
|
+
* (Simplified version without token funding)
|
|
59
|
+
*/
|
|
60
|
+
generateTestAccounts(count?: number, options?: {
|
|
61
|
+
fundEachAAWith?: bigint;
|
|
62
|
+
fundEachEOAWith?: bigint;
|
|
63
|
+
startingSalt?: number;
|
|
64
|
+
}): Promise<TestAccount[]>;
|
|
65
|
+
/**
|
|
66
|
+
* Export test accounts to .env format
|
|
67
|
+
*/
|
|
68
|
+
exportToEnv(accounts: TestAccount[]): string;
|
|
69
|
+
/**
|
|
70
|
+
* Load test accounts from environment variables
|
|
71
|
+
*/
|
|
72
|
+
static loadFromEnv(labels?: string[]): (TestAccount | null)[];
|
|
73
|
+
/**
|
|
74
|
+
* Get a single test account by label
|
|
75
|
+
*/
|
|
76
|
+
static getTestAccount(label: string): TestAccount | null;
|
|
77
|
+
private records;
|
|
78
|
+
/**
|
|
79
|
+
* Add a standardized experiment record
|
|
80
|
+
*/
|
|
81
|
+
addExperimentRecord(record: {
|
|
82
|
+
scenario: string;
|
|
83
|
+
group: 'EOA' | 'AA' | 'SuperPaymaster';
|
|
84
|
+
txHash: string;
|
|
85
|
+
gasUsed: bigint;
|
|
86
|
+
gasPrice: bigint;
|
|
87
|
+
status: string;
|
|
88
|
+
meta?: any;
|
|
89
|
+
}): {
|
|
90
|
+
id: string;
|
|
91
|
+
timestamp: number;
|
|
92
|
+
costETH: string;
|
|
93
|
+
scenario: string;
|
|
94
|
+
group: "EOA" | "AA" | "SuperPaymaster";
|
|
95
|
+
txHash: string;
|
|
96
|
+
gasUsed: bigint;
|
|
97
|
+
gasPrice: bigint;
|
|
98
|
+
status: string;
|
|
99
|
+
meta?: any;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Export collected data to CSV for PhD paper analysis
|
|
103
|
+
*/
|
|
104
|
+
exportExperimentResults(filename?: string): void;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Test environment configuration
|
|
108
|
+
*/
|
|
109
|
+
export interface TestEnvironmentConfig {
|
|
110
|
+
accountCount?: number;
|
|
111
|
+
fundEachEOAWithETH?: bigint;
|
|
112
|
+
fundEachAAWithETH?: bigint;
|
|
113
|
+
startingSalt?: number;
|
|
114
|
+
tokens?: {
|
|
115
|
+
[tokenName: string]: {
|
|
116
|
+
address: Address;
|
|
117
|
+
amount: bigint;
|
|
118
|
+
fundEOA?: boolean;
|
|
119
|
+
fundAA?: boolean;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Complete test environment
|
|
125
|
+
*/
|
|
126
|
+
export interface TestEnvironment {
|
|
127
|
+
accounts: TestAccount[];
|
|
128
|
+
tokenFunding: TokenFundingRecord[];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Token funding record
|
|
132
|
+
*/
|
|
133
|
+
export interface TokenFundingRecord {
|
|
134
|
+
account: string;
|
|
135
|
+
token: string;
|
|
136
|
+
eoaAmount: bigint;
|
|
137
|
+
aaAmount: bigint;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Test account data structure
|
|
141
|
+
*/
|
|
142
|
+
export interface TestAccount {
|
|
143
|
+
label: string;
|
|
144
|
+
ownerKey: `0x${string}`;
|
|
145
|
+
ownerAddress: Address;
|
|
146
|
+
aaAddress: Address;
|
|
147
|
+
deployTxHash: Hash;
|
|
148
|
+
salt: number;
|
|
149
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { parseEther } from 'viem';
|
|
2
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
3
|
+
import { accountFactoryActions, TEST_ACCOUNT_ADDRESSES } from '@aastar/core';
|
|
4
|
+
/**
|
|
5
|
+
* PhD Paper Experiment Test Toolkit
|
|
6
|
+
*
|
|
7
|
+
* **Purpose**: Comprehensive API suite for preparing and managing test accounts
|
|
8
|
+
* for ERC-4337 performance comparison experiments (EOA vs AA vs SuperPaymaster).
|
|
9
|
+
*
|
|
10
|
+
* **Core Features**:
|
|
11
|
+
* 1. **Account Generation**: Create random EOA keys and deploy SimpleAccounts
|
|
12
|
+
* 2. **Token Funding**: Transfer test tokens (GToken, aPNTs, bPNTs, ETH)
|
|
13
|
+
* 3. **AA Deployment**: Deploy SimpleAccount contracts using official factory
|
|
14
|
+
* 4. **UserOp Execution**: Send ERC-4337 UserOperations with various paymasters
|
|
15
|
+
* 5. **Data Collection**: Generate experiment data for PhD paper analysis
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const toolkit = new TestAccountManager(publicClient, supplierWallet);
|
|
20
|
+
*
|
|
21
|
+
* // Prepare complete test environment
|
|
22
|
+
* const env = await toolkit.prepareTestEnvironment({
|
|
23
|
+
* accountCount: 3,
|
|
24
|
+
* fundEachEOAWithETH: parseEther("0.01"),
|
|
25
|
+
* fundEachAAWithETH: parseEther("0.02"),
|
|
26
|
+
* tokens: {
|
|
27
|
+
* gToken: { address: '0x...', amount: parseEther("100") },
|
|
28
|
+
* aPNTs: { address: '0x...', amount: parseEther("50") }
|
|
29
|
+
* }
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export class TestAccountManager {
|
|
34
|
+
publicClient;
|
|
35
|
+
walletClient;
|
|
36
|
+
constructor(
|
|
37
|
+
/** @internal */
|
|
38
|
+
publicClient,
|
|
39
|
+
/** @internal */
|
|
40
|
+
walletClient) {
|
|
41
|
+
this.publicClient = publicClient;
|
|
42
|
+
this.walletClient = walletClient;
|
|
43
|
+
if (!walletClient.account) {
|
|
44
|
+
// Placeholder account if not provided to avoid strict null checks in experiments
|
|
45
|
+
// In production, the consumer must ensure the wallet is connected.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Prepare complete test environment for PhD experiments
|
|
50
|
+
*
|
|
51
|
+
* **Workflow**:
|
|
52
|
+
* 1. Generate N random EOA private keys
|
|
53
|
+
* 2. Deploy SimpleAccount for each EOA
|
|
54
|
+
* 3. Fund EOAs with ETH
|
|
55
|
+
* 4. Fund AAs with ETH
|
|
56
|
+
* 5. Transfer test tokens (GToken, aPNTs, bPNTs) to both EOAs and AAs
|
|
57
|
+
*
|
|
58
|
+
* @param config - Test environment configuration
|
|
59
|
+
* @returns Complete test environment with all accounts and tokens ready
|
|
60
|
+
*/
|
|
61
|
+
async prepareTestEnvironment(config) {
|
|
62
|
+
const { accountCount = 3, fundEachEOAWithETH = parseEther("0.01"), fundEachAAWithETH = parseEther("0.02"), tokens = {}, startingSalt = 0 } = config;
|
|
63
|
+
console.log(`🧪 Preparing PhD Experiment Test Environment (${accountCount} accounts)...\n`);
|
|
64
|
+
const accounts = [];
|
|
65
|
+
const labels = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'];
|
|
66
|
+
const factoryAddress = TEST_ACCOUNT_ADDRESSES.simpleAccountFactory;
|
|
67
|
+
const factoryRead = accountFactoryActions(factoryAddress)(this.publicClient);
|
|
68
|
+
const factoryWrite = accountFactoryActions(factoryAddress)(this.walletClient);
|
|
69
|
+
// Step 1: Generate accounts and deploy AAs
|
|
70
|
+
for (let i = 0; i < accountCount; i++) {
|
|
71
|
+
const label = labels[i] || `${i + 1}`;
|
|
72
|
+
console.log(`\n📝 [${i + 1}/${accountCount}] Setting up Account ${label}...`);
|
|
73
|
+
// Generate EOA
|
|
74
|
+
const ownerKey = generatePrivateKey();
|
|
75
|
+
const ownerAccount = privateKeyToAccount(ownerKey);
|
|
76
|
+
console.log(` 🔑 EOA: ${ownerAccount.address}`);
|
|
77
|
+
// Deploy AA
|
|
78
|
+
const salt = BigInt(startingSalt + i);
|
|
79
|
+
console.log(` 🏭 Deploying SimpleAccount (salt: ${salt})...`);
|
|
80
|
+
// Predict address
|
|
81
|
+
const accountAddress = await factoryRead.getAddress({ owner: ownerAccount.address, salt });
|
|
82
|
+
// Deploy if needed (createAccount sends tx regardless, or we can check code?)
|
|
83
|
+
// For simplicitly we just call createAccount. If already deployed it might revert?
|
|
84
|
+
// SimpleAccountFactory doesn't seem to check existence in createAccount, but CREATE2 validates.
|
|
85
|
+
// But we are using a fresh key/salt, so collision is unlikely unless we rerun.
|
|
86
|
+
// We'll try-catch or just execute.
|
|
87
|
+
let deployTxHash = '0x0';
|
|
88
|
+
try {
|
|
89
|
+
deployTxHash = await factoryWrite.createAccount({
|
|
90
|
+
owner: ownerAccount.address,
|
|
91
|
+
salt,
|
|
92
|
+
account: this.walletClient.account
|
|
93
|
+
});
|
|
94
|
+
await this.publicClient.waitForTransactionReceipt({ hash: deployTxHash });
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
console.log(` ⚠️ Deployment might have failed (or already deployed): ${e.message?.split('\n')[0]}`);
|
|
98
|
+
}
|
|
99
|
+
console.log(` ✅ AA: ${accountAddress}`);
|
|
100
|
+
// Fund AA with ETH
|
|
101
|
+
if (fundEachAAWithETH > 0n) {
|
|
102
|
+
console.log(` ⛽ Funding AA with ${fundEachAAWithETH} wei ETH...`);
|
|
103
|
+
const fundTx = await this.walletClient.sendTransaction({
|
|
104
|
+
to: accountAddress,
|
|
105
|
+
value: fundEachAAWithETH,
|
|
106
|
+
account: this.walletClient.account,
|
|
107
|
+
chain: this.walletClient.chain
|
|
108
|
+
});
|
|
109
|
+
await this.publicClient.waitForTransactionReceipt({ hash: fundTx });
|
|
110
|
+
}
|
|
111
|
+
// Fund EOA with ETH
|
|
112
|
+
if (fundEachEOAWithETH > 0n) {
|
|
113
|
+
console.log(` ⛽ Funding EOA with ${fundEachEOAWithETH} wei ETH...`);
|
|
114
|
+
const fundTx = await this.walletClient.sendTransaction({
|
|
115
|
+
to: ownerAccount.address,
|
|
116
|
+
value: fundEachEOAWithETH,
|
|
117
|
+
account: this.walletClient.account,
|
|
118
|
+
chain: this.walletClient.chain
|
|
119
|
+
});
|
|
120
|
+
await this.publicClient.waitForTransactionReceipt({ hash: fundTx });
|
|
121
|
+
}
|
|
122
|
+
accounts.push({
|
|
123
|
+
label,
|
|
124
|
+
ownerKey,
|
|
125
|
+
ownerAddress: ownerAccount.address,
|
|
126
|
+
aaAddress: accountAddress,
|
|
127
|
+
deployTxHash,
|
|
128
|
+
salt: startingSalt + i
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Step 2: Fund with test tokens
|
|
132
|
+
console.log(`\n💰 Funding accounts with test tokens...`);
|
|
133
|
+
const tokenFunding = [];
|
|
134
|
+
for (const [tokenName, tokenConfig] of Object.entries(tokens)) {
|
|
135
|
+
if (!tokenConfig)
|
|
136
|
+
continue;
|
|
137
|
+
console.log(`\n 📊 Distributing ${tokenName}...`);
|
|
138
|
+
const erc20Abi = [
|
|
139
|
+
{ name: 'transfer', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ name: '', type: 'bool' }] }
|
|
140
|
+
];
|
|
141
|
+
for (const account of accounts) {
|
|
142
|
+
// Fund EOA
|
|
143
|
+
if (tokenConfig.fundEOA !== false) {
|
|
144
|
+
const tx = await this.walletClient.writeContract({
|
|
145
|
+
address: tokenConfig.address,
|
|
146
|
+
abi: erc20Abi,
|
|
147
|
+
functionName: 'transfer',
|
|
148
|
+
args: [account.ownerAddress, tokenConfig.amount],
|
|
149
|
+
account: this.walletClient.account,
|
|
150
|
+
chain: this.walletClient.chain
|
|
151
|
+
});
|
|
152
|
+
await this.publicClient.waitForTransactionReceipt({ hash: tx });
|
|
153
|
+
console.log(` ✅ ${account.label} EOA: ${tokenConfig.amount}`);
|
|
154
|
+
}
|
|
155
|
+
// Fund AA
|
|
156
|
+
if (tokenConfig.fundAA !== false) {
|
|
157
|
+
const tx = await this.walletClient.writeContract({
|
|
158
|
+
address: tokenConfig.address,
|
|
159
|
+
abi: erc20Abi,
|
|
160
|
+
functionName: 'transfer',
|
|
161
|
+
args: [account.aaAddress, tokenConfig.amount],
|
|
162
|
+
account: this.walletClient.account,
|
|
163
|
+
chain: this.walletClient.chain
|
|
164
|
+
});
|
|
165
|
+
await this.publicClient.waitForTransactionReceipt({ hash: tx });
|
|
166
|
+
console.log(` ✅ ${account.label} AA: ${tokenConfig.amount}`);
|
|
167
|
+
}
|
|
168
|
+
tokenFunding.push({
|
|
169
|
+
account: account.label,
|
|
170
|
+
token: tokenName,
|
|
171
|
+
eoaAmount: tokenConfig.fundEOA !== false ? tokenConfig.amount : 0n,
|
|
172
|
+
aaAmount: tokenConfig.fundAA !== false ? tokenConfig.amount : 0n
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
console.log(`\n✅ Test environment ready!`);
|
|
177
|
+
return { accounts, tokenFunding };
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Generate multiple test accounts for experiments
|
|
181
|
+
* (Simplified version without token funding)
|
|
182
|
+
*/
|
|
183
|
+
async generateTestAccounts(count = 3, options = {}) {
|
|
184
|
+
const { fundEachAAWith = parseEther("0.02"), fundEachEOAWith = parseEther("0.01"), startingSalt = 0 } = options;
|
|
185
|
+
const env = await this.prepareTestEnvironment({
|
|
186
|
+
accountCount: count,
|
|
187
|
+
fundEachEOAWithETH: fundEachEOAWith,
|
|
188
|
+
fundEachAAWithETH: fundEachAAWith,
|
|
189
|
+
startingSalt
|
|
190
|
+
});
|
|
191
|
+
return env.accounts;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Export test accounts to .env format
|
|
195
|
+
*/
|
|
196
|
+
exportToEnv(accounts) {
|
|
197
|
+
const lines = [
|
|
198
|
+
'# Test Accounts for PhD Paper Experiments',
|
|
199
|
+
'# Generated by TestAccountManager API',
|
|
200
|
+
''
|
|
201
|
+
];
|
|
202
|
+
accounts.forEach(acc => lines.push(`TEST_OWNER_KEY_${acc.label}=${acc.ownerKey}`));
|
|
203
|
+
lines.push('');
|
|
204
|
+
accounts.forEach(acc => lines.push(`TEST_OWNER_EOA_${acc.label}=${acc.ownerAddress}`));
|
|
205
|
+
lines.push('');
|
|
206
|
+
accounts.forEach(acc => lines.push(`TEST_SIMPLE_ACCOUNT_${acc.label}=${acc.aaAddress}`));
|
|
207
|
+
return lines.join('\n');
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Load test accounts from environment variables
|
|
211
|
+
*/
|
|
212
|
+
static loadFromEnv(labels = ['A', 'B', 'C']) {
|
|
213
|
+
return labels.map(label => {
|
|
214
|
+
const ownerKey = process.env[`TEST_OWNER_KEY_${label}`];
|
|
215
|
+
const ownerAddress = process.env[`TEST_OWNER_EOA_${label}`];
|
|
216
|
+
const aaAddress = process.env[`TEST_SIMPLE_ACCOUNT_${label}`];
|
|
217
|
+
if (!ownerKey || !ownerAddress || !aaAddress)
|
|
218
|
+
return null;
|
|
219
|
+
return {
|
|
220
|
+
label,
|
|
221
|
+
ownerKey: ownerKey,
|
|
222
|
+
ownerAddress: ownerAddress,
|
|
223
|
+
aaAddress: aaAddress,
|
|
224
|
+
deployTxHash: '0x0',
|
|
225
|
+
salt: 0
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get a single test account by label
|
|
231
|
+
*/
|
|
232
|
+
static getTestAccount(label) {
|
|
233
|
+
const accounts = TestAccountManager.loadFromEnv([label]);
|
|
234
|
+
return accounts[0] || null;
|
|
235
|
+
}
|
|
236
|
+
// --- Data Collection Extensions ---
|
|
237
|
+
records = [];
|
|
238
|
+
/**
|
|
239
|
+
* Add a standardized experiment record
|
|
240
|
+
*/
|
|
241
|
+
addExperimentRecord(record) {
|
|
242
|
+
const fullRecord = {
|
|
243
|
+
...record,
|
|
244
|
+
id: `${Date.now()}-${Math.floor(Math.random() * 1000)}`,
|
|
245
|
+
timestamp: Date.now(),
|
|
246
|
+
costETH: formatEther(record.gasUsed * record.gasPrice)
|
|
247
|
+
};
|
|
248
|
+
this.records.push(fullRecord);
|
|
249
|
+
return fullRecord;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Export collected data to CSV for PhD paper analysis
|
|
253
|
+
*/
|
|
254
|
+
exportExperimentResults(filename = 'sdk_experiment_data.csv') {
|
|
255
|
+
const fs = require('fs');
|
|
256
|
+
const path = require('path');
|
|
257
|
+
const headers = ['ID', 'Scenario', 'Group', 'TxHash', 'GasUsed', 'GasPrice', 'CostETH', 'Status', 'Timestamp', 'Latency'];
|
|
258
|
+
const rows = this.records.map(r => [
|
|
259
|
+
r.id, r.scenario, r.group, r.txHash,
|
|
260
|
+
r.gasUsed.toString(), r.gasPrice.toString(), r.costETH,
|
|
261
|
+
r.status, new Date(r.timestamp).toISOString(),
|
|
262
|
+
r.meta?.latency || ''
|
|
263
|
+
].join(','));
|
|
264
|
+
const csv = [headers.join(','), ...rows].join('\n');
|
|
265
|
+
fs.writeFileSync(path.join(process.cwd(), filename), csv);
|
|
266
|
+
console.log(`📊 Exported ${this.records.length} records to ${filename}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function formatEther(wei) {
|
|
270
|
+
return (Number(wei) / 1e18).toString();
|
|
271
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Address } from 'viem';
|
|
2
|
+
/**
|
|
3
|
+
* Bundler types we support
|
|
4
|
+
*/
|
|
5
|
+
export declare enum BundlerType {
|
|
6
|
+
ALCHEMY = "alchemy",
|
|
7
|
+
PIMLICO = "pimlico",
|
|
8
|
+
STACKUP = "stackup",
|
|
9
|
+
CANDIDE = "candide",
|
|
10
|
+
UNKNOWN = "unknown"
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Detect bundler type from URL
|
|
14
|
+
*/
|
|
15
|
+
export declare function detectBundlerType(bundlerUrl: string): BundlerType;
|
|
16
|
+
/**
|
|
17
|
+
* Minimal interface to satisfy basic Pimlico/Bundler needs
|
|
18
|
+
* without bringing in heavy permissionless types that might conflict
|
|
19
|
+
*/
|
|
20
|
+
export interface BundlerConfig {
|
|
21
|
+
type: BundlerType;
|
|
22
|
+
url: string;
|
|
23
|
+
entryPoint: Address;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a bundler client config
|
|
27
|
+
*/
|
|
28
|
+
export declare function createBundlerClient(bundlerUrl: string, entryPoint: Address): BundlerConfig;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundler types we support
|
|
3
|
+
*/
|
|
4
|
+
export var BundlerType;
|
|
5
|
+
(function (BundlerType) {
|
|
6
|
+
BundlerType["ALCHEMY"] = "alchemy";
|
|
7
|
+
BundlerType["PIMLICO"] = "pimlico";
|
|
8
|
+
BundlerType["STACKUP"] = "stackup";
|
|
9
|
+
BundlerType["CANDIDE"] = "candide";
|
|
10
|
+
BundlerType["UNKNOWN"] = "unknown";
|
|
11
|
+
})(BundlerType || (BundlerType = {}));
|
|
12
|
+
/**
|
|
13
|
+
* Detect bundler type from URL
|
|
14
|
+
*/
|
|
15
|
+
export function detectBundlerType(bundlerUrl) {
|
|
16
|
+
const url = bundlerUrl.toLowerCase();
|
|
17
|
+
if (url.includes('alchemy.com'))
|
|
18
|
+
return BundlerType.ALCHEMY;
|
|
19
|
+
if (url.includes('pimlico.io'))
|
|
20
|
+
return BundlerType.PIMLICO;
|
|
21
|
+
if (url.includes('stackup'))
|
|
22
|
+
return BundlerType.STACKUP;
|
|
23
|
+
if (url.includes('candide.dev'))
|
|
24
|
+
return BundlerType.CANDIDE;
|
|
25
|
+
return BundlerType.UNKNOWN;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a bundler client config
|
|
29
|
+
*/
|
|
30
|
+
export function createBundlerClient(bundlerUrl, entryPoint) {
|
|
31
|
+
const bundlerType = detectBundlerType(bundlerUrl);
|
|
32
|
+
return {
|
|
33
|
+
type: bundlerType,
|
|
34
|
+
url: bundlerUrl,
|
|
35
|
+
entryPoint
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type Address, type Hex } from 'viem';
|
|
2
|
+
/**
|
|
3
|
+
* PaymasterClient
|
|
4
|
+
* Focus: Integration, Funding, Interaction.
|
|
5
|
+
*/
|
|
6
|
+
export declare class PaymasterClient {
|
|
7
|
+
/**
|
|
8
|
+
* @private
|
|
9
|
+
* Static utility class, should not be instantiated.
|
|
10
|
+
*/
|
|
11
|
+
private constructor();
|
|
12
|
+
/**
|
|
13
|
+
* Get user's deposited balance on the Paymaster.
|
|
14
|
+
*/
|
|
15
|
+
static getDepositedBalance(publicClient: any, address: Address, user: Address, token: Address): Promise<bigint>;
|
|
16
|
+
/**
|
|
17
|
+
* Deposit tokens to Paymaster for a user (enables gasless transactions).
|
|
18
|
+
*/
|
|
19
|
+
static depositFor(wallet: any, address: Address, user: Address, token: Address, amount: bigint): Promise<any>;
|
|
20
|
+
/**
|
|
21
|
+
* Approve the Paymaster (or any spender) to spend gas tokens.
|
|
22
|
+
*/
|
|
23
|
+
static approveGasToken(wallet: any, token: Address, spender: Address, amount: bigint): Promise<any>;
|
|
24
|
+
/**
|
|
25
|
+
* Estimate Gas for a UserOperation.
|
|
26
|
+
*/
|
|
27
|
+
static estimateUserOperationGas(client: any, wallet: any, aaAddress: Address, entryPoint: Address, paymasterAddress: Address, token: Address, bundlerUrl: string, callData: `0x${string}`, options?: {
|
|
28
|
+
validityWindow?: number;
|
|
29
|
+
operator?: Address;
|
|
30
|
+
factory?: Address;
|
|
31
|
+
factoryData?: Hex;
|
|
32
|
+
}): Promise<{
|
|
33
|
+
preVerificationGas: bigint;
|
|
34
|
+
verificationGasLimit: bigint;
|
|
35
|
+
callGasLimit: bigint;
|
|
36
|
+
paymasterVerificationGasLimit: bigint | undefined;
|
|
37
|
+
paymasterPostOpGasLimit: bigint;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* High-level API to submit a gasless UserOperation.
|
|
41
|
+
* Automatically handles nonce fetching, gas estimation (if not provided), signing, and submission.
|
|
42
|
+
*/
|
|
43
|
+
static submitGaslessUserOperation(client: any, wallet: any, aaAddress: Address, entryPoint: Address, paymasterAddress: Address, token: Address, bundlerUrl: string, callData: `0x${string}`, options?: {
|
|
44
|
+
validityWindow?: number;
|
|
45
|
+
verificationGasLimit?: bigint;
|
|
46
|
+
callGasLimit?: bigint;
|
|
47
|
+
preVerificationGas?: bigint;
|
|
48
|
+
maxFeePerGas?: bigint;
|
|
49
|
+
maxPriorityFeePerGas?: bigint;
|
|
50
|
+
autoEstimate?: boolean;
|
|
51
|
+
operator?: Address;
|
|
52
|
+
paymasterVerificationGasLimit?: bigint;
|
|
53
|
+
paymasterPostOpGasLimit?: bigint;
|
|
54
|
+
factory?: Address;
|
|
55
|
+
factoryData?: Hex;
|
|
56
|
+
}): Promise<`0x${string}`>;
|
|
57
|
+
/**
|
|
58
|
+
* Helper to extract the actual Gas Token fee from a UserOperation receipt.
|
|
59
|
+
* Looks for the 'PostOpProcessed' event emitted by the Paymaster.
|
|
60
|
+
*/
|
|
61
|
+
static getFeeFromReceipt(receipt: any, paymasterAddress: Address): {
|
|
62
|
+
tokenCost: bigint;
|
|
63
|
+
actualGasCostWei: bigint;
|
|
64
|
+
} | null;
|
|
65
|
+
/**
|
|
66
|
+
* Get the fee for a specific transaction hash.
|
|
67
|
+
* Fetches the receipt (no scanning required) and decodes the log.
|
|
68
|
+
*/
|
|
69
|
+
static getTransactionFee(publicClient: any, txHash: `0x${string}`, paymasterAddress: Address): Promise<{
|
|
70
|
+
tokenCost: bigint;
|
|
71
|
+
actualGasCostWei: bigint;
|
|
72
|
+
} | null>;
|
|
73
|
+
/**
|
|
74
|
+
* Helper: Encode a standardized ERC-20 Transfer.
|
|
75
|
+
* Returns the raw function data: `transfer(to, amount)`
|
|
76
|
+
*/
|
|
77
|
+
static encodeTokenTransfer(recipient: Address, amount: bigint): `0x${string}`;
|
|
78
|
+
/**
|
|
79
|
+
* Helper: Encode a SimpleAccount execution.
|
|
80
|
+
* Wraps the inner call into: `execute(target, value, data)`
|
|
81
|
+
* This is the payload signed by the user.
|
|
82
|
+
*/
|
|
83
|
+
static encodeExecution(target: Address, value: bigint, data: `0x${string}`): `0x${string}`;
|
|
84
|
+
/**
|
|
85
|
+
* More robust version of waitForUserOperationReceipt.
|
|
86
|
+
* Catches timeouts and returns a cleaner result.
|
|
87
|
+
*/
|
|
88
|
+
static waitForUserOperation(bundlerClient: any, hash: `0x${string}`, timeout?: number): Promise<any>;
|
|
89
|
+
}
|