@aastar/community 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__/index.test.ts +9 -0
- package/package.json +27 -0
- package/src/index.ts +326 -0
- package/tsconfig.json +8 -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.
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aastar/community",
|
|
3
|
+
"version": "0.16.11",
|
|
4
|
+
"description": "Community management client for AAstar SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"aastar",
|
|
9
|
+
"community",
|
|
10
|
+
"dao",
|
|
11
|
+
"web3"
|
|
12
|
+
],
|
|
13
|
+
"author": "AAstar Team",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"viem": "2.43.3",
|
|
17
|
+
"@aastar/core": "0.16.11",
|
|
18
|
+
"@aastar/tokens": "0.16.11"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"typescript": "5.7.2"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"test": "vitest run"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { Address, Hash, PublicClient, WalletClient, parseEther, parseAbi } from 'viem';
|
|
2
|
+
import { ROLE_COMMUNITY, RequirementChecker, type RoleRequirement } from '@aastar/core';
|
|
3
|
+
|
|
4
|
+
// Import contract addresses dynamically to avoid circular dependency
|
|
5
|
+
let CONTRACTS: any;
|
|
6
|
+
import('@aastar/core').then(m => { CONTRACTS = m.CONTRACTS; });
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Community configuration for launch
|
|
10
|
+
*/
|
|
11
|
+
export interface CommunityLaunchConfig {
|
|
12
|
+
name: string;
|
|
13
|
+
ensName?: string;
|
|
14
|
+
website?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
logoURI?: string;
|
|
17
|
+
stakeAmount: bigint;
|
|
18
|
+
entryBurn?: bigint;
|
|
19
|
+
sbtRules?: SBTRuleConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* SBT minting rules configuration
|
|
24
|
+
*/
|
|
25
|
+
export interface SBTRuleConfig {
|
|
26
|
+
minStake: bigint;
|
|
27
|
+
maxSupply: bigint;
|
|
28
|
+
mintPrice: bigint;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* xPNTs issuance parameters
|
|
33
|
+
*/
|
|
34
|
+
export interface XPNTsIssuanceParams {
|
|
35
|
+
symbol: string;
|
|
36
|
+
initialSupply: bigint;
|
|
37
|
+
exchangeRate: bigint;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Community statistics
|
|
42
|
+
*/
|
|
43
|
+
export interface CommunityStats {
|
|
44
|
+
totalMembers: number;
|
|
45
|
+
totalStaked: bigint;
|
|
46
|
+
xpntsSupply: bigint;
|
|
47
|
+
reputationAvg: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Community management client
|
|
52
|
+
*
|
|
53
|
+
* @roleRequired ROLE_COMMUNITY (for most operations after launch)
|
|
54
|
+
* @description Provides high-level APIs for community lifecycle operations
|
|
55
|
+
*
|
|
56
|
+
* ## Permission Requirements:
|
|
57
|
+
* - **Launch Community**: Requires GToken balance >= stakeAmount + entryBurn
|
|
58
|
+
* - **Issue xPNTs**: Requires COMMUNITY role
|
|
59
|
+
* - **Configure SBT**: Requires COMMUNITY role + community ownership
|
|
60
|
+
*
|
|
61
|
+
* ## Typical Users:
|
|
62
|
+
* - Community Administrators
|
|
63
|
+
* - DAO Operators
|
|
64
|
+
* - Protocol Partners
|
|
65
|
+
*/
|
|
66
|
+
export class CommunityClient {
|
|
67
|
+
private publicClient: PublicClient;
|
|
68
|
+
private walletClient: WalletClient;
|
|
69
|
+
private requirementChecker: RequirementChecker;
|
|
70
|
+
private registryAddress?: Address;
|
|
71
|
+
private gtokenAddress?: Address;
|
|
72
|
+
private stakingAddress?: Address;
|
|
73
|
+
|
|
74
|
+
constructor(
|
|
75
|
+
publicClient: PublicClient,
|
|
76
|
+
walletClient: WalletClient,
|
|
77
|
+
addresses?: {
|
|
78
|
+
registry?: Address;
|
|
79
|
+
gtoken?: Address;
|
|
80
|
+
staking?: Address;
|
|
81
|
+
}
|
|
82
|
+
) {
|
|
83
|
+
this.publicClient = publicClient;
|
|
84
|
+
this.walletClient = walletClient;
|
|
85
|
+
this.requirementChecker = new RequirementChecker(publicClient as any, addresses);
|
|
86
|
+
|
|
87
|
+
this.registryAddress = addresses?.registry;
|
|
88
|
+
this.gtokenAddress = addresses?.gtoken;
|
|
89
|
+
this.stakingAddress = addresses?.staking;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Check if user meets requirements to launch a community
|
|
94
|
+
*
|
|
95
|
+
* @roleRequired None (pre-check before registration)
|
|
96
|
+
* @param address User address to check (optional, defaults to wallet account)
|
|
97
|
+
* @param requiredAmount Total GToken required (stake + burn)
|
|
98
|
+
* @returns Requirement check result
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* const check = await communityClient.checkLaunchRequirements(
|
|
103
|
+
* myAddress,
|
|
104
|
+
* parseEther("33") // 30 stake + 3 burn
|
|
105
|
+
* );
|
|
106
|
+
* if (!check.hasEnoughGToken) {
|
|
107
|
+
* console.error(`❌ ${check.missingRequirements.join('\n')}`);
|
|
108
|
+
* return;
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
async checkLaunchRequirements(
|
|
113
|
+
address?: Address,
|
|
114
|
+
requiredAmount?: bigint
|
|
115
|
+
): Promise<RoleRequirement> {
|
|
116
|
+
const userAddress = address || this.walletClient.account?.address;
|
|
117
|
+
if (!userAddress) throw new Error('No wallet account found');
|
|
118
|
+
|
|
119
|
+
const amount = requiredAmount || parseEther("33"); // Default: 30+3
|
|
120
|
+
|
|
121
|
+
return await this.requirementChecker.checkRequirements({
|
|
122
|
+
address: userAddress,
|
|
123
|
+
requiredGToken: amount,
|
|
124
|
+
requireSBT: false
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Launch a community with one-click operation
|
|
130
|
+
*
|
|
131
|
+
* @roleRequired None (will register ROLE_COMMUNITY)
|
|
132
|
+
* @permission Requires GToken balance >= stakeAmount + entryBurn
|
|
133
|
+
*
|
|
134
|
+
* @description Combines: approve → stake → register → configure
|
|
135
|
+
* - Auto-approves GToken for staking contract
|
|
136
|
+
* - Registers caller as COMMUNITY role
|
|
137
|
+
* - Stakes required amount
|
|
138
|
+
* - **Pre-checks requirements before execution**
|
|
139
|
+
*
|
|
140
|
+
* @param config Community configuration
|
|
141
|
+
* @returns Community ID and transaction hash
|
|
142
|
+
*
|
|
143
|
+
* @throws Error if requirements not met
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* const communityClient = new CommunityClient(publicClient, walletClient);
|
|
148
|
+
*
|
|
149
|
+
* try {
|
|
150
|
+
* const { communityId, txHash } = await communityClient.launchCommunity({
|
|
151
|
+
* name: "MyDAO",
|
|
152
|
+
* stakeAmount: parseEther("30"),
|
|
153
|
+
* entryBurn: parseEther("3"),
|
|
154
|
+
* logoURI: "ipfs://..."
|
|
155
|
+
* });
|
|
156
|
+
* console.log(`✅ Community launched: ${communityId}`);
|
|
157
|
+
* } catch (error) {
|
|
158
|
+
* console.error(`❌ Failed: ${error.message}`);
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
async launchCommunity(config: CommunityLaunchConfig): Promise<{
|
|
163
|
+
communityId: Address;
|
|
164
|
+
txHash: Hash;
|
|
165
|
+
}> {
|
|
166
|
+
const account = this.walletClient.account;
|
|
167
|
+
if (!account) throw new Error('Wallet account not found');
|
|
168
|
+
|
|
169
|
+
// PRE-CHECK: Verify requirements
|
|
170
|
+
const totalRequired = config.stakeAmount + (config.entryBurn || 0n);
|
|
171
|
+
const check = await this.checkLaunchRequirements(account.address, totalRequired);
|
|
172
|
+
|
|
173
|
+
if (!check.hasEnoughGToken) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Insufficient funds to launch community:\n` +
|
|
176
|
+
check.missingRequirements.join('\n')
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Load contract addresses
|
|
181
|
+
const { CONTRACTS } = await import('@aastar/core');
|
|
182
|
+
const registryAddress = this.registryAddress || CONTRACTS.sepolia.core.registry;
|
|
183
|
+
const gtokenAddress = this.gtokenAddress || CONTRACTS.sepolia.core.gToken;
|
|
184
|
+
const stakingAddress = this.stakingAddress || CONTRACTS.sepolia.core.gTokenStaking;
|
|
185
|
+
|
|
186
|
+
// Step 1: Approve GToken
|
|
187
|
+
const approveTx = await this.walletClient.writeContract({
|
|
188
|
+
address: gtokenAddress,
|
|
189
|
+
abi: [{
|
|
190
|
+
name: 'approve',
|
|
191
|
+
type: 'function',
|
|
192
|
+
stateMutability: 'nonpayable',
|
|
193
|
+
inputs: [
|
|
194
|
+
{ name: 'spender', type: 'address' },
|
|
195
|
+
{ name: 'amount', type: 'uint256' }
|
|
196
|
+
],
|
|
197
|
+
outputs: [{ type: 'bool' }]
|
|
198
|
+
}],
|
|
199
|
+
functionName: 'approve',
|
|
200
|
+
args: [stakingAddress, totalRequired],
|
|
201
|
+
chain: this.walletClient.chain
|
|
202
|
+
} as any);
|
|
203
|
+
|
|
204
|
+
await this.publicClient.waitForTransactionReceipt({ hash: approveTx });
|
|
205
|
+
|
|
206
|
+
// Step 2: Register role
|
|
207
|
+
const roleData = '0x'; // Simplified - needs proper encoding
|
|
208
|
+
const registerTx = await this.walletClient.writeContract({
|
|
209
|
+
address: registryAddress,
|
|
210
|
+
abi: [{
|
|
211
|
+
name: 'registerRole',
|
|
212
|
+
type: 'function',
|
|
213
|
+
stateMutability: 'nonpayable',
|
|
214
|
+
inputs: [
|
|
215
|
+
{ name: 'roleId', type: 'bytes32' },
|
|
216
|
+
{ name: 'user', type: 'address' },
|
|
217
|
+
{ name: 'roleData', type: 'bytes' }
|
|
218
|
+
],
|
|
219
|
+
outputs: []
|
|
220
|
+
}],
|
|
221
|
+
functionName: 'registerRole',
|
|
222
|
+
args: [ROLE_COMMUNITY, account.address, roleData],
|
|
223
|
+
chain: this.walletClient.chain
|
|
224
|
+
} as any);
|
|
225
|
+
|
|
226
|
+
await this.publicClient.waitForTransactionReceipt({ hash: registerTx });
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
communityId: account.address,
|
|
230
|
+
txHash: registerTx
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Issue community-specific xPNTs token
|
|
236
|
+
*
|
|
237
|
+
* @roleRequired ROLE_COMMUNITY
|
|
238
|
+
* @permission Must be registered community admin
|
|
239
|
+
*
|
|
240
|
+
* @param params xPNTs issuance parameters
|
|
241
|
+
* @returns xPNTs contract address and transaction hash
|
|
242
|
+
*/
|
|
243
|
+
async issueXPNTs(params: XPNTsIssuanceParams): Promise<{
|
|
244
|
+
xpntsAddress: Address;
|
|
245
|
+
txHash: Hash;
|
|
246
|
+
}> {
|
|
247
|
+
const account = this.walletClient.account;
|
|
248
|
+
if (!account) throw new Error('Wallet account not found');
|
|
249
|
+
|
|
250
|
+
// PRE-CHECK: Verify COMMUNITY role
|
|
251
|
+
const hasRole = await this.requirementChecker.checkHasRole(
|
|
252
|
+
ROLE_COMMUNITY,
|
|
253
|
+
account.address
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
if (!hasRole) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`Missing ROLE_COMMUNITY. Please register as a community first.`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Load contract addresses
|
|
263
|
+
const { CORE_ADDRESSES } = await import('@aastar/core');
|
|
264
|
+
const factoryAddress = CORE_ADDRESSES.xPNTsFactory;
|
|
265
|
+
|
|
266
|
+
if (!factoryAddress) throw new Error('xPNTsFactory address not found');
|
|
267
|
+
|
|
268
|
+
// Deploy xPNTs via Factory
|
|
269
|
+
// Assuming ABI: createXPNTs(string symbol, uint256 supply, uint256 rate)
|
|
270
|
+
const deployTx = await this.walletClient.writeContract({
|
|
271
|
+
address: factoryAddress,
|
|
272
|
+
abi: parseAbi(['function createXPNTs(string,uint256,uint256) returns (address)']),
|
|
273
|
+
functionName: 'createXPNTs',
|
|
274
|
+
args: [params.symbol, params.initialSupply, params.exchangeRate],
|
|
275
|
+
chain: this.walletClient.chain,
|
|
276
|
+
account
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// We can't easily get the address without parsing logs, so we return zero address for now or simulate
|
|
280
|
+
return {
|
|
281
|
+
xpntsAddress: '0x0000000000000000000000000000000000000000',
|
|
282
|
+
txHash: deployTx
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Configure SBT minting rules for the community
|
|
288
|
+
*
|
|
289
|
+
* @roleRequired ROLE_COMMUNITY
|
|
290
|
+
* @permission Must be registered community admin + community ownership
|
|
291
|
+
*
|
|
292
|
+
* @param rules SBT rule configuration
|
|
293
|
+
* @returns Transaction hash
|
|
294
|
+
*/
|
|
295
|
+
async configureSBTRules(rules: SBTRuleConfig): Promise<Hash> {
|
|
296
|
+
const account = this.walletClient.account;
|
|
297
|
+
if (!account) throw new Error('Wallet account not found');
|
|
298
|
+
|
|
299
|
+
// PRE-CHECK: Verify COMMUNITY role
|
|
300
|
+
const hasRole = await this.requirementChecker.checkHasRole(
|
|
301
|
+
ROLE_COMMUNITY,
|
|
302
|
+
account.address
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
if (!hasRole) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`Missing ROLE_COMMUNITY. Please register as a community first.`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// TODO: Implement MySBT configuration
|
|
312
|
+
throw new Error('Not implemented yet - requires MySBT rule configuration');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get community statistics
|
|
317
|
+
*
|
|
318
|
+
* @roleRequired None (public query)
|
|
319
|
+
* @param communityId Community address
|
|
320
|
+
* @returns Community statistics
|
|
321
|
+
*/
|
|
322
|
+
async getCommunityStats(communityId: Address): Promise<CommunityStats> {
|
|
323
|
+
// TODO: Implement by querying Registry, Staking, and Reputation contracts
|
|
324
|
+
throw new Error('Not implemented yet - requires multi-contract aggregation');
|
|
325
|
+
}
|
|
326
|
+
}
|