@aastar/enduser 0.16.12 → 0.16.16

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.
@@ -49,6 +49,22 @@ export declare class CommunityClient extends BaseClient {
49
49
  logoURI?: string;
50
50
  stakeAmount?: bigint;
51
51
  }, options?: TransactionOptions): Promise<Hash>;
52
+ /**
53
+ * One-click Setup: Register Community + Deploy Token
54
+ * Orchestrates the complete community initialization flow.
55
+ */
56
+ setupCommunity(params: {
57
+ name: string;
58
+ tokenName: string;
59
+ tokenSymbol: string;
60
+ description?: string;
61
+ logoURI?: string;
62
+ website?: string;
63
+ stakeAmount?: bigint;
64
+ }, options?: TransactionOptions): Promise<{
65
+ tokenAddress: Address;
66
+ hashes: Hash[];
67
+ }>;
52
68
  /**
53
69
  * Airdrop SBTs to users to make them members
54
70
  */
@@ -103,6 +103,50 @@ export class CommunityClient extends BaseClient {
103
103
  throw error;
104
104
  }
105
105
  }
106
+ /**
107
+ * One-click Setup: Register Community + Deploy Token
108
+ * Orchestrates the complete community initialization flow.
109
+ */
110
+ async setupCommunity(params, options) {
111
+ const hashes = [];
112
+ let tokenAddress = '0x0000000000000000000000000000000000000000';
113
+ // 1. Register as Community (Idempotent check handled inside or by registry)
114
+ // We should check hasRole first to avoid errors if already registered
115
+ const registry = registryActions(this.requireRegistry())(this.getStartPublicClient());
116
+ const ROLE_COMMUNITY = await registry.ROLE_COMMUNITY();
117
+ const hasRole = await registry.hasRole({ roleId: ROLE_COMMUNITY, user: this.getAddress() });
118
+ if (!hasRole) {
119
+ const hReg = await this.registerAsCommunity({
120
+ name: params.name,
121
+ ensName: params.website, // Mapping website to ENS param for now as per legacy behavior
122
+ website: params.website,
123
+ description: params.description,
124
+ logoURI: params.logoURI,
125
+ stakeAmount: params.stakeAmount
126
+ }, options);
127
+ hashes.push(hReg);
128
+ }
129
+ // 2. Deploy Token (Idempotent check via Factory)
130
+ if (this.factoryAddress) {
131
+ const factoryReader = xPNTsFactoryActions(this.factoryAddress)(this.getStartPublicClient());
132
+ const existingToken = await factoryReader.getTokenAddress({ community: this.getAddress() });
133
+ if (existingToken && existingToken !== '0x0000000000000000000000000000000000000000') {
134
+ tokenAddress = existingToken;
135
+ }
136
+ else {
137
+ const hToken = await this.createCommunityToken({
138
+ name: params.tokenName,
139
+ tokenSymbol: params.tokenSymbol,
140
+ description: params.description
141
+ }, options);
142
+ hashes.push(hToken);
143
+ // Note: We can't get the address synchronously without waiting.
144
+ // In L3 pattern, we usually return hashes.
145
+ // The user can fetch token address later using factory.getTokenAddress(community)
146
+ }
147
+ }
148
+ return { tokenAddress, hashes };
149
+ }
106
150
  // ========================================
107
151
  // 2. 成员管理
108
152
  // ========================================
@@ -8,6 +8,7 @@ export interface UserClientConfig extends ClientConfig {
8
8
  gTokenStakingAddress?: Address;
9
9
  registryAddress?: Address;
10
10
  gTokenAddress?: Address;
11
+ bundlerClient?: any;
11
12
  }
12
13
  export declare class UserClient extends BaseClient {
13
14
  accountAddress: Address;
@@ -16,6 +17,7 @@ export declare class UserClient extends BaseClient {
16
17
  gTokenStakingAddress?: Address;
17
18
  registryAddress?: Address;
18
19
  gTokenAddress?: Address;
20
+ bundlerClient?: any;
19
21
  constructor(config: UserClientConfig);
20
22
  /**
21
23
  * Get the nonce of the account from EntryPoint (more reliable for 4337)
@@ -83,5 +85,7 @@ export declare class UserClient extends BaseClient {
83
85
  data: Hex;
84
86
  paymaster: Address;
85
87
  paymasterType: 'V4' | 'Super';
88
+ operator?: Address;
89
+ maxRate?: bigint;
86
90
  }, options?: TransactionOptions): Promise<Hash>;
87
91
  }
@@ -1,7 +1,7 @@
1
+ import { encodeFunctionData } from 'viem';
1
2
  import { BaseClient } from '@aastar/core';
2
3
  import { accountActions, sbtActions, tokenActions, entryPointActions, stakingActions, registryActions } from '@aastar/core';
3
- import { bundlerActions, getUserOperationHash } from 'viem/account-abstraction';
4
- import { encodeFunctionData } from 'viem';
4
+ import { bundlerActions } from 'viem/account-abstraction';
5
5
  export class UserClient extends BaseClient {
6
6
  accountAddress;
7
7
  sbtAddress;
@@ -9,8 +9,10 @@ export class UserClient extends BaseClient {
9
9
  gTokenStakingAddress;
10
10
  registryAddress;
11
11
  gTokenAddress;
12
+ bundlerClient;
12
13
  constructor(config) {
13
14
  super(config);
15
+ this.bundlerClient = config.bundlerClient;
14
16
  this.accountAddress = config.accountAddress;
15
17
  this.sbtAddress = config.sbtAddress;
16
18
  this.entryPointAddress = config.entryPointAddress;
@@ -328,67 +330,29 @@ export class UserClient extends BaseClient {
328
330
  */
329
331
  async executeGasless(params, options) {
330
332
  try {
331
- const client = this.client.extend(bundlerActions);
333
+ const client = this.bundlerClient ? this.bundlerClient.extend(bundlerActions) : this.client.extend(bundlerActions);
332
334
  const ep = this.requireEntryPoint();
333
- const publicClient = this.getStartPublicClient();
334
335
  // 1. Prepare Call Data
335
336
  const callData = encodeFunctionData({
336
337
  abi: [{ name: 'execute', type: 'function', inputs: [{ name: 'dest', type: 'address' }, { name: 'value', type: 'uint256' }, { name: 'func', type: 'bytes' }], outputs: [] }],
337
338
  functionName: 'execute',
338
339
  args: [params.target, params.value, params.data]
339
340
  });
340
- // 2. Prepare Paymaster Data
341
- let paymasterAndData = params.paymaster;
342
- // Note: In real scenarios, PM V4 or Super might need additional encoded data.
343
- // For now, we use the address as the base.
344
- // 3. Estimate Gas
345
- const sender = this.accountAddress;
346
- const nonce = await this.getNonce();
347
- const userOpPartial = {
348
- sender,
349
- nonce,
350
- initCode: '0x',
351
- callData,
352
- paymasterAndData,
353
- signature: '0x'
354
- };
355
- const gasEstimate = await client.estimateUserOperationGas({
356
- userOperation: userOpPartial,
357
- entryPoint: ep
358
- });
359
- // 4. Construct Final UserOp
360
- const fees = await publicClient.estimateFeesPerGas();
361
- const userOp = {
362
- ...userOpPartial,
363
- callGasLimit: gasEstimate.callGasLimit,
364
- verificationGasLimit: gasEstimate.verificationGasLimit + 50000n,
365
- preVerificationGas: gasEstimate.preVerificationGas,
366
- maxFeePerGas: fees.maxFeePerGas || fees.gasPrice || 1000000000n,
367
- maxPriorityFeePerGas: fees.maxPriorityFeePerGas || 1000000000n
368
- };
369
- // 5. Sign
370
- const chainId = this.client.chain?.id || 31337;
371
- const hash = getUserOperationHash({
372
- userOperation: userOp,
373
- entryPointAddress: ep,
374
- entryPointVersion: '0.7',
375
- chainId
376
- });
377
- const signature = await this.client.signMessage({
378
- message: { raw: hash },
379
- account: this.client.account
380
- });
381
- const signedUserOp = {
382
- ...userOp,
383
- signature
384
- };
385
- // 6. Send
386
- return await client.sendUserOperation({
387
- userOperation: signedUserOp,
388
- entryPoint: ep
341
+ // 3. Delegate to PaymasterClient for v0.7 Gasless Submission
342
+ // This ensures we follow the exact same logic as successful demo scripts
343
+ // We dynamic import to avoid circular dependencies if any
344
+ const { PaymasterClient: SDKPaymasterClient } = await import('../../paymaster/src/V4/PaymasterClient.js');
345
+ const txHash = await SDKPaymasterClient.submitGaslessUserOperation(this.client, this.client, // WalletClient acts as signer
346
+ this.accountAddress, ep, params.paymaster, params.target, // placeholder for token if V4
347
+ this.bundlerClient?.transport?.url || this.client.transport.url || '', callData, {
348
+ operator: params.operator,
349
+ autoEstimate: true, // Let SDK handle estimation & tuning
350
+ paymasterPostOpGasLimit: 100000n // conservative default
389
351
  });
352
+ return txHash;
390
353
  }
391
354
  catch (error) {
355
+ console.error(" ❌ executeGasless Error:", error.message);
392
356
  throw error;
393
357
  }
394
358
  }
@@ -0,0 +1,85 @@
1
+ import { type Address, type Hash, type Hex } from 'viem';
2
+ import { BaseClient, type ClientConfig } from '@aastar/core';
3
+ export interface GaslessConfig {
4
+ paymasterUrl: string;
5
+ policy?: 'CREDIT' | 'TOKEN' | 'SPONSORED';
6
+ }
7
+ export interface UserLifecycleConfig extends ClientConfig {
8
+ accountAddress: Address;
9
+ registryAddress: Address;
10
+ sbtAddress: Address;
11
+ gTokenAddress: Address;
12
+ gTokenStakingAddress: Address;
13
+ entryPointAddress: Address;
14
+ gasless?: GaslessConfig;
15
+ }
16
+ export interface OnboardResult {
17
+ success: boolean;
18
+ sbtId?: bigint;
19
+ txHash?: Hash;
20
+ }
21
+ export interface ReputationData {
22
+ score: bigint;
23
+ level: bigint;
24
+ creditLimit: bigint;
25
+ }
26
+ /**
27
+ * UserLifecycle - L3 Pattern
28
+ *
29
+ * Responsibilities:
30
+ * 1. Managing the complete lifecycle of an End User (Onboard -> Operate -> Exit)
31
+ * 2. Providing a unified interface for Gasless operations
32
+ * 3. Abstracting underlying contract interactions via L2 Actions
33
+ */
34
+ export declare class UserLifecycle extends BaseClient {
35
+ accountAddress: Address;
36
+ registryAddress: Address;
37
+ sbtAddress: Address;
38
+ gTokenAddress: Address;
39
+ gTokenStakingAddress: Address;
40
+ entryPointAddress: Address;
41
+ gaslessConfig?: GaslessConfig;
42
+ constructor(config: UserLifecycleConfig);
43
+ /**
44
+ * Check if user is eligible to join a community
45
+ * @param community Address of the community
46
+ */
47
+ checkEligibility(community: Address): Promise<boolean>;
48
+ /**
49
+ * One-click Onboarding: Approve -> Stake -> Register -> Mint SBT
50
+ * @param community Address of the community to join
51
+ * @param stakeAmount Amount of GToken to stake (default 0.4 GT)
52
+ */
53
+ onboard(community: Address, stakeAmount?: bigint): Promise<OnboardResult>;
54
+ /**
55
+ * Enable or update Gasless configuration
56
+ */
57
+ enableGasless(config: GaslessConfig): Promise<void>;
58
+ /**
59
+ * Execute a transaction effectively using Gasless configuration if available
60
+ */
61
+ executeGaslessTx(params: {
62
+ target: Address;
63
+ value: bigint;
64
+ data: Hex;
65
+ operator?: Address;
66
+ }): Promise<Hash>;
67
+ /**
68
+ * Claim a specific SBT Role
69
+ */
70
+ claimSBT(roleId: Hex): Promise<Hash>;
71
+ getMyReputation(): Promise<ReputationData>;
72
+ getCreditLimit(): Promise<bigint>;
73
+ /**
74
+ * Leave community: Unbind assets, Burn SBT, Remove Role
75
+ */
76
+ leaveCommunity(community: Address): Promise<Hash>;
77
+ /**
78
+ * Exit a specific role in Registry
79
+ */
80
+ exitRole(roleId: Hex): Promise<Hash>;
81
+ /**
82
+ * Unstake all GTokens from Staking contract
83
+ */
84
+ unstakeAll(roleId: Hex): Promise<Hash>;
85
+ }
@@ -0,0 +1,180 @@
1
+ import { parseEther } from 'viem';
2
+ import { BaseClient } from '@aastar/core';
3
+ import { registryActions, sbtActions, stakingActions } from '@aastar/core'; // L2/L1 Actions
4
+ /**
5
+ * UserLifecycle - L3 Pattern
6
+ *
7
+ * Responsibilities:
8
+ * 1. Managing the complete lifecycle of an End User (Onboard -> Operate -> Exit)
9
+ * 2. Providing a unified interface for Gasless operations
10
+ * 3. Abstracting underlying contract interactions via L2 Actions
11
+ */
12
+ export class UserLifecycle extends BaseClient {
13
+ accountAddress;
14
+ registryAddress;
15
+ sbtAddress;
16
+ gTokenAddress;
17
+ gTokenStakingAddress;
18
+ entryPointAddress;
19
+ gaslessConfig;
20
+ constructor(config) {
21
+ super(config);
22
+ this.accountAddress = config.accountAddress;
23
+ this.registryAddress = config.registryAddress;
24
+ this.sbtAddress = config.sbtAddress;
25
+ this.gTokenAddress = config.gTokenAddress;
26
+ this.gTokenStakingAddress = config.gTokenStakingAddress;
27
+ this.entryPointAddress = config.entryPointAddress;
28
+ this.gaslessConfig = config.gasless;
29
+ }
30
+ // ===========================================
31
+ // 1. Onboarding Phase (Registration)
32
+ // ===========================================
33
+ /**
34
+ * Check if user is eligible to join a community
35
+ * @param community Address of the community
36
+ */
37
+ async checkEligibility(community) {
38
+ // Validation logic (e.g., check blacklist or whitelist via Registry)
39
+ const registry = registryActions(this.registryAddress)(this.client);
40
+ // Placeholder: simplistic check, real logic might involve community specific rules
41
+ return true;
42
+ }
43
+ /**
44
+ * One-click Onboarding: Approve -> Stake -> Register -> Mint SBT
45
+ * @param community Address of the community to join
46
+ * @param stakeAmount Amount of GToken to stake (default 0.4 GT)
47
+ */
48
+ async onboard(community, stakeAmount = parseEther('0.4')) {
49
+ try {
50
+ const registry = registryActions(this.registryAddress)(this.client);
51
+ const userClient = await import('./UserClient.js').then(m => new m.UserClient({
52
+ ...this.config,
53
+ accountAddress: this.accountAddress,
54
+ registryAddress: this.registryAddress,
55
+ gTokenStakingAddress: this.gTokenStakingAddress,
56
+ gTokenAddress: this.gTokenAddress,
57
+ sbtAddress: this.sbtAddress
58
+ }));
59
+ // Use UserClient's batch execution capability for atomic onboarding
60
+ const txHash = await userClient.registerAsEndUser(community, stakeAmount);
61
+ // Post-check: Verify role
62
+ const hasRole = await registry.hasRole({
63
+ roleId: await registry.ROLE_ENDUSER(),
64
+ user: this.accountAddress
65
+ });
66
+ return {
67
+ success: hasRole,
68
+ txHash
69
+ };
70
+ }
71
+ catch (error) {
72
+ console.error("Onboarding failed:", error);
73
+ return { success: false };
74
+ }
75
+ }
76
+ /**
77
+ * Enable or update Gasless configuration
78
+ */
79
+ async enableGasless(config) {
80
+ this.gaslessConfig = config;
81
+ // In future: verify paymaster connection here
82
+ }
83
+ // ===========================================
84
+ // 2. Operational Phase (Execute & Interact)
85
+ // ===========================================
86
+ /**
87
+ * Execute a transaction effectively using Gasless configuration if available
88
+ */
89
+ async executeGaslessTx(params) {
90
+ if (!this.gaslessConfig) {
91
+ throw new Error("Gasless configuration not enabled. Call enableGasless() first.");
92
+ }
93
+ const userClient = await import('./UserClient.js').then(m => new m.UserClient({
94
+ ...this.config,
95
+ accountAddress: this.accountAddress,
96
+ // Pass minimal config needed for execution
97
+ entryPointAddress: this.entryPointAddress,
98
+ bundlerClient: this.config.bundlerClient
99
+ }));
100
+ // Determine Paymaster Type based on policy
101
+ const paymasterType = this.gaslessConfig.policy === 'CREDIT' ? 'Super' : 'V4';
102
+ // Note: Real implementation needs to resolve actual Paymaster Address from Registry/Config
103
+ // This is a placeholder address resolution
104
+ const registry = registryActions(this.registryAddress)(this.client);
105
+ const paymasterAddress = await registry.SUPER_PAYMASTER();
106
+ return await userClient.executeGasless({
107
+ target: params.target,
108
+ value: params.value,
109
+ data: params.data,
110
+ paymaster: paymasterAddress,
111
+ paymasterType,
112
+ operator: params.operator
113
+ });
114
+ }
115
+ /**
116
+ * Claim a specific SBT Role
117
+ */
118
+ async claimSBT(roleId) {
119
+ const userClient = await import('./UserClient.js').then(m => new m.UserClient({
120
+ ...this.config,
121
+ accountAddress: this.accountAddress,
122
+ sbtAddress: this.sbtAddress
123
+ }));
124
+ return await userClient.mintSBT(roleId);
125
+ }
126
+ // ===========================================
127
+ // 3. Query Phase (Info & Stats)
128
+ // ===========================================
129
+ async getMyReputation() {
130
+ const registry = registryActions(this.registryAddress)(this.client);
131
+ const [score, creditLimit] = await Promise.all([
132
+ registry.globalReputation({ user: this.accountAddress }),
133
+ registry.getCreditLimit({ user: this.accountAddress })
134
+ ]);
135
+ return {
136
+ score,
137
+ level: 0n, // TODO: Add level calculation logic
138
+ creditLimit
139
+ };
140
+ }
141
+ async getCreditLimit() {
142
+ const registry = registryActions(this.registryAddress)(this.client);
143
+ return await registry.getCreditLimit({ user: this.accountAddress });
144
+ }
145
+ // ===========================================
146
+ // 4. Exit Phase (Cleanup)
147
+ // ===========================================
148
+ /**
149
+ * Leave community: Unbind assets, Burn SBT, Remove Role
150
+ */
151
+ async leaveCommunity(community) {
152
+ const sbt = sbtActions(this.sbtAddress)(this.client);
153
+ // 1. Leave Logic (contracts usually handle burn)
154
+ return await sbt.leaveCommunity({
155
+ community,
156
+ account: this.accountAddress // Self-exit
157
+ });
158
+ }
159
+ /**
160
+ * Exit a specific role in Registry
161
+ */
162
+ async exitRole(roleId) {
163
+ const registry = registryActions(this.registryAddress)(this.client);
164
+ return await registry.exitRole({
165
+ roleId,
166
+ account: this.accountAddress
167
+ });
168
+ }
169
+ /**
170
+ * Unstake all GTokens from Staking contract
171
+ */
172
+ async unstakeAll(roleId) {
173
+ const staking = stakingActions(this.gTokenStakingAddress)(this.client);
174
+ return await staking.unlockAndTransfer({
175
+ user: this.accountAddress,
176
+ roleId,
177
+ account: this.accountAddress
178
+ });
179
+ }
180
+ }
@@ -0,0 +1,81 @@
1
+ import { type Address, type Hash } from 'viem';
2
+ import { BaseClient, type ClientConfig, type TransactionOptions } from '@aastar/core';
3
+ export interface CommunityClientConfig extends ClientConfig {
4
+ sbtAddress?: Address;
5
+ factoryAddress?: Address;
6
+ reputationAddress?: Address;
7
+ }
8
+ export interface CreateCommunityParams {
9
+ name: string;
10
+ tokenSymbol: string;
11
+ ensName?: string;
12
+ description?: string;
13
+ }
14
+ export interface CommunityInfo {
15
+ address: Address;
16
+ }
17
+ /**
18
+ * Client for Community Managers (`ROLE_COMMUNITY`)
19
+ */
20
+ export declare class CommunityClient extends BaseClient {
21
+ sbtAddress?: Address;
22
+ factoryAddress?: Address;
23
+ reputationAddress?: Address;
24
+ constructor(config: CommunityClientConfig);
25
+ /**
26
+ * Create a new Community Token (xPNTs) and register it.
27
+ * Note: In the current architecture, creating a community often involves:
28
+ * 1. Registering the ROLE_COMMUNITY on Registry (if not exists) -> usually manual or self-register
29
+ * 2. Deploying a Token (xPNTs) via Factory
30
+ * 3. Linking the Token to the Community in Registry
31
+ */
32
+ createCommunityToken(params: CreateCommunityParams, options?: TransactionOptions): Promise<Hash>;
33
+ /**
34
+ * Register self as a Community Manager.
35
+ * This method handles all necessary steps:
36
+ * 1. Checks and approves GToken to GTokenStaking
37
+ * 2. Encodes CommunityRoleData with provided parameters
38
+ * 3. Calls registerRoleSelf on Registry
39
+ *
40
+ * @param params Community registration parameters
41
+ * @param options Transaction options
42
+ * @returns Transaction hash
43
+ */
44
+ registerAsCommunity(params: {
45
+ name: string;
46
+ ensName?: string;
47
+ website?: string;
48
+ description?: string;
49
+ logoURI?: string;
50
+ stakeAmount?: bigint;
51
+ }, options?: TransactionOptions): Promise<Hash>;
52
+ /**
53
+ * One-click Setup: Register Community + Deploy Token
54
+ * Orchestrates the complete community initialization flow.
55
+ */
56
+ setupCommunity(params: {
57
+ name: string;
58
+ tokenName: string;
59
+ tokenSymbol: string;
60
+ description?: string;
61
+ logoURI?: string;
62
+ website?: string;
63
+ stakeAmount?: bigint;
64
+ }, options?: TransactionOptions): Promise<{
65
+ tokenAddress: Address;
66
+ hashes: Hash[];
67
+ }>;
68
+ /**
69
+ * Airdrop SBTs to users to make them members
70
+ */
71
+ airdropSBT(users: Address[], roleId: bigint, options?: TransactionOptions): Promise<Hash>;
72
+ setReputationRule(ruleId: bigint, ruleConfig: any, options?: TransactionOptions): Promise<Hash>;
73
+ /**
74
+ * Revoke membership (Burn SBT)
75
+ */
76
+ revokeMembership(userAddr: Address, options?: TransactionOptions): Promise<Hash>;
77
+ /**
78
+ * Transfer ownership of the Community Token
79
+ */
80
+ transferCommunityTokenOwnership(tokenAddress: Address, newOwner: Address, options?: TransactionOptions): Promise<Hash>;
81
+ }