@aastar/operator 0.16.11
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/LICENSE +21 -0
- package/__tests__/PaymasterOperatorClient.test.ts +258 -0
- package/__tests__/ProtocolClient.test.ts +135 -0
- package/__tests__/index.test.ts +16 -0
- package/__tests__/mocks/client.ts +22 -0
- package/dist/PaymasterOperatorClient.d.ts +82 -0
- package/dist/PaymasterOperatorClient.js +375 -0
- package/dist/ProtocolClient.d.ts +38 -0
- package/dist/ProtocolClient.js +137 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +26 -0
- package/src/PaymasterOperatorClient.ts +454 -0
- package/src/ProtocolClient.ts +154 -0
- package/src/index.ts +2 -0
- package/tsconfig.json +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 AAStar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { PaymasterOperatorClient } from '../src/PaymasterOperatorClient';
|
|
3
|
+
import { createMockPublicClient, createMockWalletClient, resetMocks } from './mocks/client';
|
|
4
|
+
import { parseEther } from 'viem';
|
|
5
|
+
|
|
6
|
+
// Define mocks
|
|
7
|
+
const mocks = vi.hoisted(() => {
|
|
8
|
+
const mockRegistry = { ROLE_COMMUNITY: vi.fn(), ROLE_PAYMASTER_SUPER: vi.fn(), ROLE_PAYMASTER_AOA: vi.fn(), hasRole: vi.fn(), registerRoleSelf: vi.fn() };
|
|
9
|
+
const mockToken = { allowance: vi.fn(), approve: vi.fn() };
|
|
10
|
+
const mockSuperPaymaster = { APNTS_TOKEN: vi.fn(), deposit: vi.fn(), configureOperator: vi.fn(), operators: vi.fn() };
|
|
11
|
+
const mockPaymasterFactory = { deployPaymaster: vi.fn(), getPaymaster: vi.fn() };
|
|
12
|
+
const mockPaymaster = { setTokenPrice: vi.fn(), tokenPrices: vi.fn(), depositFor: vi.fn() };
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
mockRegistry,
|
|
16
|
+
mockToken,
|
|
17
|
+
mockSuperPaymaster,
|
|
18
|
+
mockPaymasterFactory,
|
|
19
|
+
mockPaymaster,
|
|
20
|
+
// Factory Functions
|
|
21
|
+
mockRegistryActions: vi.fn(() => () => mockRegistry),
|
|
22
|
+
mockTokenActions: vi.fn(() => () => mockToken),
|
|
23
|
+
mockSuperPaymasterActions: vi.fn(() => () => mockSuperPaymaster),
|
|
24
|
+
mockPaymasterFactoryActions: vi.fn(() => () => mockPaymasterFactory),
|
|
25
|
+
mockPaymasterActions: vi.fn(() => () => mockPaymaster),
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
vi.mock('@aastar/core', async () => {
|
|
30
|
+
const actual = await vi.importActual('@aastar/core');
|
|
31
|
+
return {
|
|
32
|
+
...actual,
|
|
33
|
+
registryActions: mocks.mockRegistryActions,
|
|
34
|
+
tokenActions: mocks.mockTokenActions,
|
|
35
|
+
superPaymasterActions: mocks.mockSuperPaymasterActions,
|
|
36
|
+
paymasterFactoryActions: mocks.mockPaymasterFactoryActions,
|
|
37
|
+
paymasterActions: mocks.mockPaymasterActions,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
vi.mock('viem', async () => {
|
|
42
|
+
const actual = await vi.importActual('viem');
|
|
43
|
+
return {
|
|
44
|
+
...actual,
|
|
45
|
+
encodeFunctionData: vi.fn().mockReturnValue('0xInitData'),
|
|
46
|
+
// encodeAbiParameters: vi.fn().mockReturnValue('0xAbiData') // Use real implementation if possible, or mock carefully
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('PaymasterOperatorClient', () => {
|
|
51
|
+
let client: PaymasterOperatorClient;
|
|
52
|
+
const mockPublicClient = createMockPublicClient();
|
|
53
|
+
const mockWalletClient = createMockWalletClient();
|
|
54
|
+
|
|
55
|
+
const config = {
|
|
56
|
+
params: { chainId: 11155111, rpcUrl: 'https://rpc.sepolia.org' },
|
|
57
|
+
services: {},
|
|
58
|
+
client: mockWalletClient as any,
|
|
59
|
+
superPaymasterAddress: '0x1111111111111111111111111111111111111111' as `0x${string}`,
|
|
60
|
+
tokenAddress: '0x2222222222222222222222222222222222222222' as `0x${string}`,
|
|
61
|
+
registryAddress: '0x3333333333333333333333333333333333333333' as `0x${string}`,
|
|
62
|
+
gTokenAddress: '0x4444444444444444444444444444444444444444' as `0x${string}`,
|
|
63
|
+
gTokenStakingAddress: '0x5555555555555555555555555555555555555555' as `0x${string}`,
|
|
64
|
+
paymasterFactoryAddress: '0x6666666666666666666666666666666666666666' as `0x${string}`,
|
|
65
|
+
entryPointAddress: '0x0000000071727De22E5E9d8BAf0edAc6f37da032' as `0x${string}`,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
resetMocks();
|
|
70
|
+
vi.clearAllMocks();
|
|
71
|
+
|
|
72
|
+
// Default Mocks
|
|
73
|
+
mocks.mockRegistry.ROLE_COMMUNITY.mockResolvedValue('0x1111111111111111111111111111111111111111111111111111111111111111');
|
|
74
|
+
mocks.mockRegistry.ROLE_PAYMASTER_SUPER.mockResolvedValue('0x2222222222222222222222222222222222222222222222222222222222222222');
|
|
75
|
+
mocks.mockRegistry.ROLE_PAYMASTER_AOA.mockResolvedValue('0x3333333333333333333333333333333333333333333333333333333333333333');
|
|
76
|
+
mocks.mockRegistry.hasRole.mockResolvedValue(true); // Default has prerequisite
|
|
77
|
+
mocks.mockRegistry.registerRoleSelf.mockResolvedValue('0xTxHash');
|
|
78
|
+
mocks.mockToken.allowance.mockResolvedValue(parseEther('1000'));
|
|
79
|
+
mocks.mockToken.approve.mockResolvedValue('0xTxHash');
|
|
80
|
+
mocks.mockSuperPaymaster.APNTS_TOKEN.mockResolvedValue('0xAPNTS');
|
|
81
|
+
mocks.mockSuperPaymaster.deposit.mockResolvedValue('0xTxHash');
|
|
82
|
+
mocks.mockSuperPaymaster.configureOperator.mockResolvedValue('0xTxHash');
|
|
83
|
+
mocks.mockSuperPaymaster.operators.mockResolvedValue({
|
|
84
|
+
aPNTsBalance: 0n,
|
|
85
|
+
xPNTsToken: '0x2222222222222222222222222222222222222222',
|
|
86
|
+
treasury: '0x3333333333333333333333333333333333333333',
|
|
87
|
+
exchangeRate: 150n,
|
|
88
|
+
isConfigured: true,
|
|
89
|
+
isPaused: false,
|
|
90
|
+
reputation: 100,
|
|
91
|
+
minTxInterval: 60,
|
|
92
|
+
totalSpent: 0n,
|
|
93
|
+
totalTxSponsored: 0n
|
|
94
|
+
});
|
|
95
|
+
mocks.mockPaymasterFactory.deployPaymaster.mockResolvedValue('0xTxHash');
|
|
96
|
+
mocks.mockPaymasterFactory.getPaymaster.mockResolvedValue('0x7777777777777777777777777777777777777777');
|
|
97
|
+
mocks.mockPaymaster.setTokenPrice.mockResolvedValue('0xTxHash');
|
|
98
|
+
mocks.mockPaymaster.tokenPrices.mockResolvedValue(100n);
|
|
99
|
+
mocks.mockPaymaster.depositFor.mockResolvedValue('0xTxHash');
|
|
100
|
+
|
|
101
|
+
client = new PaymasterOperatorClient(config);
|
|
102
|
+
(client as any).client = mockWalletClient;
|
|
103
|
+
(client as any).publicClient = mockPublicClient;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('Registration', () => {
|
|
107
|
+
it('registerAsSuperPaymasterOperator should verify roles and register', async () => {
|
|
108
|
+
mocks.mockRegistry.hasRole
|
|
109
|
+
.mockResolvedValueOnce(true) // Has Community
|
|
110
|
+
.mockResolvedValueOnce(false); // Does not have SuperRole yet
|
|
111
|
+
|
|
112
|
+
await client.registerAsSuperPaymasterOperator({ stakeAmount: 100n });
|
|
113
|
+
|
|
114
|
+
expect(mocks.mockRegistry.registerRoleSelf).toHaveBeenCalledWith(expect.objectContaining({
|
|
115
|
+
roleId: '0x2222222222222222222222222222222222222222222222222222222222222222'
|
|
116
|
+
}));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('registerAsSuperPaymasterOperator should throw if no community role', async () => {
|
|
120
|
+
mocks.mockRegistry.hasRole.mockResolvedValue(false);
|
|
121
|
+
await expect(client.registerAsSuperPaymasterOperator()).rejects.toThrow('Must have ROLE_COMMUNITY');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('registerAsSuperPaymasterOperator should approve if needed', async () => {
|
|
125
|
+
mocks.mockRegistry.hasRole
|
|
126
|
+
.mockResolvedValueOnce(true)
|
|
127
|
+
.mockResolvedValueOnce(false);
|
|
128
|
+
mocks.mockToken.allowance.mockResolvedValue(0n);
|
|
129
|
+
|
|
130
|
+
await client.registerAsSuperPaymasterOperator({ stakeAmount: 100n });
|
|
131
|
+
|
|
132
|
+
expect(mocks.mockToken.approve).toHaveBeenCalled();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('registerAsSuperPaymasterOperator should handle optional deposit', async () => {
|
|
136
|
+
mocks.mockRegistry.hasRole
|
|
137
|
+
.mockResolvedValueOnce(true)
|
|
138
|
+
.mockResolvedValueOnce(false);
|
|
139
|
+
|
|
140
|
+
await client.registerAsSuperPaymasterOperator({ depositAmount: 50n });
|
|
141
|
+
|
|
142
|
+
expect(mocks.mockSuperPaymaster.deposit).toHaveBeenCalled();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('Deployment', () => {
|
|
147
|
+
it('deployAndRegisterPaymasterV4 should deploy and register', async () => {
|
|
148
|
+
// Mock role checks: Community exists, AOA doesn't
|
|
149
|
+
mocks.mockRegistry.hasRole.mockImplementation(async ({ roleId }: any) => {
|
|
150
|
+
if (roleId === '0x1111111111111111111111111111111111111111111111111111111111111111') return true;
|
|
151
|
+
if (roleId === '0x3333333333333333333333333333333333333333333333333333333333333333') return false;
|
|
152
|
+
return false;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
mocks.mockPaymasterFactory.getPaymaster.mockResolvedValueOnce('0x0000000000000000000000000000000000000000'); // Clean deploy
|
|
156
|
+
mocks.mockPaymasterFactory.getPaymaster.mockResolvedValueOnce('0x7777777777777777777777777777777777777777'); // After deploy
|
|
157
|
+
|
|
158
|
+
const result = await client.deployAndRegisterPaymasterV4();
|
|
159
|
+
|
|
160
|
+
expect(mocks.mockPaymasterFactory.deployPaymaster).toHaveBeenCalled();
|
|
161
|
+
expect(mocks.mockPaymasterFactory.getPaymaster).toHaveBeenCalled();
|
|
162
|
+
expect(mocks.mockRegistry.registerRoleSelf).toHaveBeenCalledWith(expect.objectContaining({
|
|
163
|
+
roleId: '0x3333333333333333333333333333333333333333333333333333333333333333'
|
|
164
|
+
}));
|
|
165
|
+
expect(result.paymasterAddress).toBe('0x7777777777777777777777777777777777777777');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('deployAndRegisterPaymasterV4 should skip register if already has role', async () => {
|
|
169
|
+
// Mock getPaymaster to return existing address to exercise idempotency
|
|
170
|
+
mocks.mockPaymasterFactory.getPaymaster.mockResolvedValue('0x7777777777777777777777777777777777777777');
|
|
171
|
+
|
|
172
|
+
mocks.mockRegistry.hasRole.mockImplementation(async ({ roleId }: any) => {
|
|
173
|
+
if (roleId === '0x1111111111111111111111111111111111111111111111111111111111111111') return true;
|
|
174
|
+
if (roleId === '0x3333333333333333333333333333333333333333333333333333333333333333') return true; // Already registered
|
|
175
|
+
return false;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const result = await client.deployAndRegisterPaymasterV4();
|
|
179
|
+
|
|
180
|
+
expect(mocks.mockPaymasterFactory.deployPaymaster).not.toHaveBeenCalled();
|
|
181
|
+
expect(mocks.mockRegistry.registerRoleSelf).not.toHaveBeenCalled();
|
|
182
|
+
expect(result.registerHash).toBe('0x0000000000000000000000000000000000000000000000000000000000000000');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('Management', () => {
|
|
187
|
+
it('depositCollateral should approve and deposit', async () => {
|
|
188
|
+
mocks.mockToken.allowance.mockResolvedValue(0n);
|
|
189
|
+
await client.depositCollateral(100n);
|
|
190
|
+
|
|
191
|
+
expect(mocks.mockSuperPaymaster.APNTS_TOKEN).toHaveBeenCalled();
|
|
192
|
+
expect(mocks.mockToken.approve).toHaveBeenCalled();
|
|
193
|
+
expect(mocks.mockSuperPaymaster.deposit).toHaveBeenCalledWith(expect.objectContaining({
|
|
194
|
+
amount: 100n
|
|
195
|
+
}));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('updateExchangeRate should fetch config and call configureOperator', async () => {
|
|
199
|
+
// Mock operators return [balance, token, treasury, rate]
|
|
200
|
+
mocks.mockSuperPaymaster.operators.mockResolvedValue({
|
|
201
|
+
aPNTsBalance: 1000n,
|
|
202
|
+
xPNTsToken: '0x2222222222222222222222222222222222222222',
|
|
203
|
+
treasury: '0x3333333333333333333333333333333333333333',
|
|
204
|
+
exchangeRate: 150n,
|
|
205
|
+
isConfigured: true,
|
|
206
|
+
isPaused: false,
|
|
207
|
+
reputation: 100,
|
|
208
|
+
minTxInterval: 60,
|
|
209
|
+
totalSpent: 0n,
|
|
210
|
+
totalTxSponsored: 0n
|
|
211
|
+
});
|
|
212
|
+
mocks.mockSuperPaymaster.configureOperator.mockResolvedValue('0xTxHash');
|
|
213
|
+
|
|
214
|
+
await client.updateExchangeRate(200n);
|
|
215
|
+
|
|
216
|
+
expect(mocks.mockSuperPaymaster.operators).toHaveBeenCalledWith(expect.objectContaining({
|
|
217
|
+
operator: '0x1234567890123456789012345678901234567890' // Default mock address from client.ts
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
expect(mocks.mockSuperPaymaster.configureOperator).toHaveBeenCalledWith(expect.objectContaining({
|
|
221
|
+
xPNTsToken: '0x2222222222222222222222222222222222222222',
|
|
222
|
+
opTreasury: '0x3333333333333333333333333333333333333333',
|
|
223
|
+
exchangeRate: 200n
|
|
224
|
+
}));
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
describe('Token Config', () => {
|
|
229
|
+
it('addGasToken should set token price', async () => {
|
|
230
|
+
await client.addGasToken('0x4444444444444444444444444444444444444444', 100n);
|
|
231
|
+
expect(mocks.mockPaymasterActions).toHaveBeenCalled(); // Should be called with SP address? No, superPaymasterAddress IS the paymaster for addGasToken (based on code reading)
|
|
232
|
+
// Wait, addGasToken uses paymasterActions(this.superPaymasterAddress)
|
|
233
|
+
expect(mocks.mockPaymasterActions).toHaveBeenCalledWith(config.superPaymasterAddress);
|
|
234
|
+
expect(mocks.mockPaymaster.setTokenPrice).toHaveBeenCalled();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('getTokenPrice should read price', async () => {
|
|
238
|
+
const price = await client.getTokenPrice('0x4444444444444444444444444444444444444444');
|
|
239
|
+
expect(price).toBe(100n);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('Deposit', () => {
|
|
244
|
+
it('setupPaymasterDeposit should deposit for user', async () => {
|
|
245
|
+
await client.setupPaymasterDeposit({
|
|
246
|
+
paymaster: '0x5555555555555555555555555555555555555555',
|
|
247
|
+
user: '0x6666666666666666666666666666666666666666',
|
|
248
|
+
token: '0x4444444444444444444444444444444444444444',
|
|
249
|
+
amount: 100n
|
|
250
|
+
});
|
|
251
|
+
expect(mocks.mockPaymasterActions).toHaveBeenCalledWith('0x5555555555555555555555555555555555555555');
|
|
252
|
+
expect(mocks.mockPaymaster.depositFor).toHaveBeenCalledWith(expect.objectContaining({
|
|
253
|
+
user: '0x6666666666666666666666666666666666666666',
|
|
254
|
+
amount: 100n
|
|
255
|
+
}));
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { ProtocolClient } from '../src/ProtocolClient';
|
|
3
|
+
import { createMockPublicClient, createMockWalletClient, resetMocks } from './mocks/client';
|
|
4
|
+
|
|
5
|
+
// Define mocks using vi.hoisted
|
|
6
|
+
const mocks = vi.hoisted(() => {
|
|
7
|
+
const mockDVT = { createSlashProposal: vi.fn(), signSlashProposal: vi.fn(), executeSlashWithProof: vi.fn() };
|
|
8
|
+
const mockAggregator = { registerBLSPublicKey: vi.fn() };
|
|
9
|
+
const mockSuperPaymaster = { setProtocolFee: vi.fn(), setTreasury: vi.fn() };
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
mockDVT,
|
|
13
|
+
mockAggregator,
|
|
14
|
+
mockSuperPaymaster,
|
|
15
|
+
// Factory functions
|
|
16
|
+
mockDVTActions: vi.fn(() => () => mockDVT),
|
|
17
|
+
mockAggregatorActions: vi.fn(() => () => mockAggregator),
|
|
18
|
+
mockSuperPaymasterActions: vi.fn(() => () => mockSuperPaymaster),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
vi.mock('@aastar/core', async () => {
|
|
23
|
+
const actual = await vi.importActual('@aastar/core');
|
|
24
|
+
return {
|
|
25
|
+
...actual,
|
|
26
|
+
dvtActions: mocks.mockDVTActions,
|
|
27
|
+
aggregatorActions: mocks.mockAggregatorActions,
|
|
28
|
+
superPaymasterActions: mocks.mockSuperPaymasterActions,
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('ProtocolClient', () => {
|
|
33
|
+
let client: ProtocolClient;
|
|
34
|
+
const mockPublicClient = createMockPublicClient();
|
|
35
|
+
const mockWalletClient = createMockWalletClient();
|
|
36
|
+
|
|
37
|
+
const config = {
|
|
38
|
+
params: {
|
|
39
|
+
chainId: 11155111,
|
|
40
|
+
rpcUrl: 'https://rpc.sepolia.org',
|
|
41
|
+
},
|
|
42
|
+
services: {
|
|
43
|
+
paymasterUrl: 'https://paymaster.example.com',
|
|
44
|
+
},
|
|
45
|
+
client: mockWalletClient as any,
|
|
46
|
+
dvtValidatorAddress: '0x1111111111111111111111111111111111111111' as `0x${string}`,
|
|
47
|
+
blsAggregatorAddress: '0x2222222222222222222222222222222222222222' as `0x${string}`,
|
|
48
|
+
superPaymasterAddress: '0x3333333333333333333333333333333333333333' as `0x${string}`
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
resetMocks();
|
|
53
|
+
vi.clearAllMocks();
|
|
54
|
+
|
|
55
|
+
// Default Resolves
|
|
56
|
+
mocks.mockDVT.createSlashProposal.mockResolvedValue('0xTxHash');
|
|
57
|
+
mocks.mockDVT.signSlashProposal.mockResolvedValue('0xTxHash');
|
|
58
|
+
mocks.mockDVT.executeSlashWithProof.mockResolvedValue('0xTxHash');
|
|
59
|
+
mocks.mockAggregator.registerBLSPublicKey.mockResolvedValue('0xTxHash');
|
|
60
|
+
mocks.mockSuperPaymaster.setProtocolFee.mockResolvedValue('0xTxHash');
|
|
61
|
+
mocks.mockSuperPaymaster.setTreasury.mockResolvedValue('0xTxHash');
|
|
62
|
+
|
|
63
|
+
client = new ProtocolClient(config);
|
|
64
|
+
(client as any).client = mockWalletClient;
|
|
65
|
+
(client as any).publicClient = mockPublicClient;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('DVT Proposals', () => {
|
|
69
|
+
it('createProposal should call createSlashProposal', async () => {
|
|
70
|
+
const result = await client.createProposal('0x4444444444444444444444444444444444444444', '0x1234', 'Description');
|
|
71
|
+
|
|
72
|
+
expect(mocks.mockDVTActions).toHaveBeenCalledWith(config.dvtValidatorAddress);
|
|
73
|
+
expect(mocks.mockDVT.createSlashProposal).toHaveBeenCalledWith(expect.objectContaining({
|
|
74
|
+
operator: '0x4444444444444444444444444444444444444444',
|
|
75
|
+
level: 1,
|
|
76
|
+
reason: 'Description'
|
|
77
|
+
}));
|
|
78
|
+
expect(result).toBe('0xTxHash');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('signProposal should call signSlashProposal', async () => {
|
|
82
|
+
await client.signProposal(1n, '0x5555');
|
|
83
|
+
expect(mocks.mockDVT.signSlashProposal).toHaveBeenCalledWith(expect.objectContaining({
|
|
84
|
+
proposalId: 1n,
|
|
85
|
+
signature: '0x5555'
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('executeWithProof should call executeSlashWithProof', async () => {
|
|
90
|
+
await client.executeWithProof(1n, ['0x5555']);
|
|
91
|
+
expect(mocks.mockDVT.executeSlashWithProof).toHaveBeenCalledWith(expect.objectContaining({
|
|
92
|
+
proposalId: 1n,
|
|
93
|
+
// proof is mocked inside implementation
|
|
94
|
+
}));
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('BLS Management', () => {
|
|
99
|
+
it('registerBLSKey should call registerBLSPublicKey', async () => {
|
|
100
|
+
await client.registerBLSKey('0x123456');
|
|
101
|
+
expect(mocks.mockAggregatorActions).toHaveBeenCalledWith(config.blsAggregatorAddress);
|
|
102
|
+
expect(mocks.mockAggregator.registerBLSPublicKey).toHaveBeenCalledWith(expect.objectContaining({
|
|
103
|
+
publicKey: '0x123456'
|
|
104
|
+
}));
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should throw if blsAggregatorAddress missing', async () => {
|
|
108
|
+
client.blsAggregatorAddress = undefined;
|
|
109
|
+
await expect(client.registerBLSKey('0x12')).rejects.toThrow('BLS Aggregator address required');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('Global Params', () => {
|
|
114
|
+
it('setProtocolFee should call superPaymaster', async () => {
|
|
115
|
+
await client.setProtocolFee(100n);
|
|
116
|
+
expect(mocks.mockSuperPaymasterActions).toHaveBeenCalledWith(config.superPaymasterAddress);
|
|
117
|
+
expect(mocks.mockSuperPaymaster.setProtocolFee).toHaveBeenCalledWith(expect.objectContaining({
|
|
118
|
+
newFeeBPS: 100n
|
|
119
|
+
}));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('setTreasury should call superPaymaster', async () => {
|
|
123
|
+
await client.setTreasury('0x7777777777777777777777777777777777777777');
|
|
124
|
+
expect(mocks.mockSuperPaymaster.setTreasury).toHaveBeenCalledWith(expect.objectContaining({
|
|
125
|
+
treasury: '0x7777777777777777777777777777777777777777'
|
|
126
|
+
}));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should throw if superPaymasterAddress missing', async () => {
|
|
130
|
+
client.superPaymasterAddress = undefined;
|
|
131
|
+
await expect(client.setTreasury('0x1111111111111111111111111111111111111111')).rejects.toThrow('SuperPaymaster address required');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
import { PaymasterOperatorClient } from '../src/PaymasterOperatorClient';
|
|
3
|
+
import { ProtocolClient } from '../src/ProtocolClient';
|
|
4
|
+
import * as Exports from '../src/index';
|
|
5
|
+
|
|
6
|
+
describe('Operator Package Exports', () => {
|
|
7
|
+
it('should export PaymasterOperatorClient', () => {
|
|
8
|
+
expect(Exports.PaymasterOperatorClient).toBeDefined();
|
|
9
|
+
expect(Exports.PaymasterOperatorClient).toBe(PaymasterOperatorClient);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should export ProtocolClient', () => {
|
|
13
|
+
expect(Exports.ProtocolClient).toBeDefined();
|
|
14
|
+
expect(Exports.ProtocolClient).toBe(ProtocolClient);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import type { PublicClient, WalletClient } from 'viem';
|
|
3
|
+
|
|
4
|
+
export const createMockPublicClient = (): PublicClient => {
|
|
5
|
+
return {
|
|
6
|
+
readContract: vi.fn(),
|
|
7
|
+
chain: { id: 11155111 },
|
|
8
|
+
waitForTransactionReceipt: vi.fn().mockResolvedValue({ status: 'success' }),
|
|
9
|
+
} as any;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const createMockWalletClient = (): WalletClient => {
|
|
13
|
+
return {
|
|
14
|
+
writeContract: vi.fn(),
|
|
15
|
+
account: { address: '0x1234567890123456789012345678901234567890' },
|
|
16
|
+
chain: { id: 11155111 },
|
|
17
|
+
} as any;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const resetMocks = () => {
|
|
21
|
+
vi.clearAllMocks();
|
|
22
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type Address, type Hash } from 'viem';
|
|
2
|
+
import { BaseClient, type ClientConfig, type TransactionOptions } from '@aastar/core';
|
|
3
|
+
export interface OperatorClientConfig extends ClientConfig {
|
|
4
|
+
superPaymasterAddress: Address;
|
|
5
|
+
tokenAddress?: Address;
|
|
6
|
+
}
|
|
7
|
+
export interface SponsorshipPolicy {
|
|
8
|
+
globalLimit: bigint;
|
|
9
|
+
userLimit: bigint;
|
|
10
|
+
itemPrice: bigint;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Client for Paymaster Operators (ROLE_PAYMASTER_SUPER)
|
|
14
|
+
*/
|
|
15
|
+
export declare class PaymasterOperatorClient extends BaseClient {
|
|
16
|
+
superPaymasterAddress: Address;
|
|
17
|
+
tokenAddress?: Address;
|
|
18
|
+
ethUsdPriceFeed: Address;
|
|
19
|
+
xpntsFactory: Address;
|
|
20
|
+
constructor(config: OperatorClientConfig);
|
|
21
|
+
/**
|
|
22
|
+
* Register as SuperPaymaster Operator (one-stop API).
|
|
23
|
+
* This method handles all necessary steps:
|
|
24
|
+
* 1. Checks prerequisites (must have ROLE_COMMUNITY)
|
|
25
|
+
* 2. Checks and approves GToken to GTokenStaking
|
|
26
|
+
* 3. Registers ROLE_PAYMASTER_SUPER
|
|
27
|
+
* 4. Optionally deposits collateral to SuperPaymaster
|
|
28
|
+
*
|
|
29
|
+
* @param params Registration parameters
|
|
30
|
+
* @param options Transaction options
|
|
31
|
+
* @returns Transaction hash of role registration
|
|
32
|
+
*/
|
|
33
|
+
registerAsSuperPaymasterOperator(params?: {
|
|
34
|
+
stakeAmount?: bigint;
|
|
35
|
+
depositAmount?: bigint;
|
|
36
|
+
}, options?: TransactionOptions): Promise<Hash>;
|
|
37
|
+
/**
|
|
38
|
+
* Deploy a new Paymaster V4 and Register as AOA Operator (one-stop API).
|
|
39
|
+
* This method handles:
|
|
40
|
+
* 1. Checks prerequisites (ROLE_COMMUNITY)
|
|
41
|
+
* 2. Predicts new Paymaster address
|
|
42
|
+
* 3. Deploys Paymaster V4 via Factory
|
|
43
|
+
* 4. Registers ROLE_PAYMASTER_AOA with staking
|
|
44
|
+
*
|
|
45
|
+
* @param params Deployment parameters
|
|
46
|
+
* @param options Transaction options
|
|
47
|
+
* @returns Object containing new paymaster address and transaction hashes
|
|
48
|
+
*/
|
|
49
|
+
deployAndRegisterPaymasterV4(params?: {
|
|
50
|
+
stakeAmount?: bigint;
|
|
51
|
+
version?: string;
|
|
52
|
+
salt?: bigint;
|
|
53
|
+
}, options?: TransactionOptions): Promise<{
|
|
54
|
+
paymasterAddress: Address;
|
|
55
|
+
deployHash: Hash;
|
|
56
|
+
registerHash: Hash;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Deposit collateral (aPNTs/GToken) to SuperPaymaster.
|
|
60
|
+
* This is a helper method used by registerAsSuperPaymasterOperator.
|
|
61
|
+
*/
|
|
62
|
+
depositCollateral(amount: bigint, options?: TransactionOptions): Promise<Hash>;
|
|
63
|
+
updateExchangeRate(exchangeRate: bigint, options?: TransactionOptions): Promise<Hash>;
|
|
64
|
+
/**
|
|
65
|
+
* Configure operator parameters (Token, Treasury, Exchange Rate).
|
|
66
|
+
* If parameters are undefined, existing values are preserved.
|
|
67
|
+
*/
|
|
68
|
+
configureOperator(xPNTsToken?: Address, treasury?: Address, exchangeRate?: bigint, options?: TransactionOptions): Promise<Hash>;
|
|
69
|
+
withdrawCollateral(to: Address, amount: bigint, options?: TransactionOptions): Promise<Hash>;
|
|
70
|
+
isOperator(operator: Address): Promise<boolean>;
|
|
71
|
+
getOperatorDetails(operator?: Address): Promise<any>;
|
|
72
|
+
initiateExit(options?: TransactionOptions): Promise<Hash>;
|
|
73
|
+
withdrawStake(to: Address, options?: TransactionOptions): Promise<Hash>;
|
|
74
|
+
addGasToken(token: Address, price: bigint, options?: TransactionOptions): Promise<Hash>;
|
|
75
|
+
getTokenPrice(token: Address): Promise<bigint>;
|
|
76
|
+
setupPaymasterDeposit(params: {
|
|
77
|
+
paymaster: Address;
|
|
78
|
+
user: Address;
|
|
79
|
+
token: Address;
|
|
80
|
+
amount: bigint;
|
|
81
|
+
}, options?: TransactionOptions): Promise<Hash>;
|
|
82
|
+
}
|