@aboutcircles/sdk 0.1.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 +249 -0
- package/dist/Sdk.d.ts +282 -0
- package/dist/Sdk.d.ts.map +1 -0
- package/dist/Sdk.js +501 -0
- package/dist/avatars/BaseGroupAvatar.d.ts +97 -0
- package/dist/avatars/BaseGroupAvatar.d.ts.map +1 -0
- package/dist/avatars/BaseGroupAvatar.js +221 -0
- package/dist/avatars/CommonAvatar.d.ts +402 -0
- package/dist/avatars/CommonAvatar.d.ts.map +1 -0
- package/dist/avatars/CommonAvatar.js +644 -0
- package/dist/avatars/HumanAvatar.d.ts +412 -0
- package/dist/avatars/HumanAvatar.d.ts.map +1 -0
- package/dist/avatars/HumanAvatar.js +610 -0
- package/dist/avatars/OrganisationAvatar.d.ts +187 -0
- package/dist/avatars/OrganisationAvatar.d.ts.map +1 -0
- package/dist/avatars/OrganisationAvatar.js +310 -0
- package/dist/avatars/index.d.ts +9 -0
- package/dist/avatars/index.d.ts.map +1 -0
- package/dist/avatars/index.js +7 -0
- package/dist/ccip-dw9vbgp0.js +4533 -0
- package/dist/errors.d.ts +64 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +131 -0
- package/dist/index-3hqyeswk.js +25 -0
- package/dist/index-pps0tkk3.js +356 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15308 -0
- package/dist/native-45v6vh69.js +20 -0
- package/dist/secp256k1-mjj44cj6.js +2115 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +44 -0
package/dist/Sdk.js
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import { circlesConfig, Core, CirclesType, BaseGroupContract } from '@aboutcircles/sdk-core';
|
|
2
|
+
import { Profiles } from '@aboutcircles/sdk-profiles';
|
|
3
|
+
import { CirclesRpc, PagedQuery } from '@aboutcircles/sdk-rpc';
|
|
4
|
+
import { cidV0ToHex } from '@aboutcircles/sdk-utils';
|
|
5
|
+
import { HumanAvatar, OrganisationAvatar, BaseGroupAvatar } from './avatars';
|
|
6
|
+
import { SdkError } from './errors';
|
|
7
|
+
import { decodeEventLog } from 'viem';
|
|
8
|
+
import { baseGroupFactoryAbi } from '@aboutcircles/sdk-abis';
|
|
9
|
+
/**
|
|
10
|
+
* Simplified Circles SDK
|
|
11
|
+
* Provides a user-friendly API for non-crypto users with low entrance barrier
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const sdk = new Sdk();
|
|
16
|
+
*
|
|
17
|
+
* // Register as a human
|
|
18
|
+
* const avatar = await sdk.register.asHuman('0xInviterAddress', {
|
|
19
|
+
* name: 'Alice',
|
|
20
|
+
* description: 'Developer'
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // Get an avatar
|
|
24
|
+
* const avatar = await sdk.getAvatar('0xAvatarAddress');
|
|
25
|
+
*
|
|
26
|
+
* // Transfer tokens
|
|
27
|
+
* await avatar.transfer.direct('0xRecipient', 100);
|
|
28
|
+
*
|
|
29
|
+
* // Mint personal tokens
|
|
30
|
+
* await avatar.personalToken.mint();
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
// @todo build common avatar inst
|
|
34
|
+
// @todo implement methods to get the safe of some owner
|
|
35
|
+
export class Sdk {
|
|
36
|
+
circlesConfig;
|
|
37
|
+
contractRunner;
|
|
38
|
+
senderAddress;
|
|
39
|
+
core;
|
|
40
|
+
rpc;
|
|
41
|
+
profilesClient;
|
|
42
|
+
data = {
|
|
43
|
+
getAvatar: async (address) => {
|
|
44
|
+
return await this.rpc.avatar.getAvatarInfo(address);
|
|
45
|
+
},
|
|
46
|
+
getTrustRelations: async (address) => {
|
|
47
|
+
return await this.rpc.trust.getAggregatedTrustRelations(address);
|
|
48
|
+
},
|
|
49
|
+
getBalances: async (address) => {
|
|
50
|
+
return await this.rpc.balance.getTokenBalances(address);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Create a new Sdk instance
|
|
55
|
+
*
|
|
56
|
+
* @param config Circles configuration (defaults to Gnosis Chain mainnet)
|
|
57
|
+
* @param contractRunner Optional contract runner for executing transactions
|
|
58
|
+
* @throws Error if contractRunner is provided but doesn't support sendTransaction or has no address
|
|
59
|
+
*/
|
|
60
|
+
constructor(config = circlesConfig[100], contractRunner) {
|
|
61
|
+
this.circlesConfig = config;
|
|
62
|
+
this.contractRunner = contractRunner;
|
|
63
|
+
this.core = new Core(config);
|
|
64
|
+
this.rpc = new CirclesRpc(config.circlesRpcUrl);
|
|
65
|
+
this.profilesClient = new Profiles(config.profileServiceUrl);
|
|
66
|
+
// Validate and extract sender address from contract runner
|
|
67
|
+
if (contractRunner) {
|
|
68
|
+
if (!contractRunner.sendTransaction) {
|
|
69
|
+
throw SdkError.configError('Contract runner must support sendTransaction method');
|
|
70
|
+
}
|
|
71
|
+
const address = contractRunner.address;
|
|
72
|
+
if (!address) {
|
|
73
|
+
throw SdkError.configError('Cannot determine sender address from contract runner');
|
|
74
|
+
}
|
|
75
|
+
this.senderAddress = address;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get an avatar by address
|
|
80
|
+
* Automatically detects the avatar type and returns the appropriate avatar instance
|
|
81
|
+
* @returns HumanAvatar, OrganisationAvatar, or BaseGroupAvatar depending on type
|
|
82
|
+
*/
|
|
83
|
+
async getAvatar(avatarAddress) {
|
|
84
|
+
try {
|
|
85
|
+
const avatarInfo = await this.rpc.avatar.getAvatarInfo(avatarAddress);
|
|
86
|
+
// Detect avatar type and return appropriate avatar class
|
|
87
|
+
const avatarType = avatarInfo?.type;
|
|
88
|
+
if (avatarType === 'CrcV2_RegisterGroup') {
|
|
89
|
+
return new BaseGroupAvatar(avatarAddress, this.core, this.contractRunner, avatarInfo);
|
|
90
|
+
}
|
|
91
|
+
if (avatarType === 'CrcV2_RegisterOrganization') {
|
|
92
|
+
return new OrganisationAvatar(avatarAddress, this.core, this.contractRunner, avatarInfo);
|
|
93
|
+
}
|
|
94
|
+
// Default to HumanAvatar for human type
|
|
95
|
+
return new HumanAvatar(avatarAddress, this.core, this.contractRunner, avatarInfo);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
throw SdkError.avatarNotFound(avatarAddress);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Registration methods for creating new Circles identities
|
|
103
|
+
*/
|
|
104
|
+
register = {
|
|
105
|
+
/**
|
|
106
|
+
* Register as a human in the Circles ecosystem
|
|
107
|
+
*
|
|
108
|
+
* This function:
|
|
109
|
+
* 1. Checks for pending invitations from inviters in the InvitationEscrow
|
|
110
|
+
* 2. If invitations exist, redeems one to claim escrowed tokens
|
|
111
|
+
* 3. Otherwise, checks if the specified inviter has enough unwrapped CRC
|
|
112
|
+
* 4. Creates and uploads profile data to IPFS
|
|
113
|
+
* 5. Registers the human with the profile CID
|
|
114
|
+
* 6. Returns a HumanAvatar instance for the registered account
|
|
115
|
+
*
|
|
116
|
+
* Requirements:
|
|
117
|
+
* - Contract runner must be configured to execute transactions
|
|
118
|
+
* - Either: pending invitations from inviters, OR inviter has 96+ CRC unwrapped
|
|
119
|
+
*
|
|
120
|
+
* @param inviter Address of the inviting avatar (fallback if no invitations found)
|
|
121
|
+
* @param profile Profile data with name, description, etc.
|
|
122
|
+
* @returns HumanAvatar instance for the newly registered human
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```typescript
|
|
126
|
+
* const avatar = await sdk.register.asHuman('0xInviter', {
|
|
127
|
+
* name: 'Alice',
|
|
128
|
+
* description: 'Developer'
|
|
129
|
+
* });
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
asHuman: async (inviter, profile) => {
|
|
133
|
+
if (!this.contractRunner || !this.senderAddress) {
|
|
134
|
+
throw SdkError.missingContractRunner('Human registration');
|
|
135
|
+
}
|
|
136
|
+
const contractRunner = this.contractRunner;
|
|
137
|
+
const senderAddress = this.senderAddress;
|
|
138
|
+
// List of transactions to execute
|
|
139
|
+
const transactions = [];
|
|
140
|
+
// Step 1: Check for pending invitations in the InvitationEscrow
|
|
141
|
+
const inviters = await this.core.invitationEscrow.getInviters(senderAddress);
|
|
142
|
+
if (inviters.length > 0) {
|
|
143
|
+
// Redeem the invitation from the first available inviter
|
|
144
|
+
const redeemTx = this.core.invitationEscrow.redeemInvitation(inviters[0]);
|
|
145
|
+
transactions.push(redeemTx);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// No invitations found, check if inviter has enough unwrapped CRC
|
|
149
|
+
// Minimum required: 96 CRC (after demurrage it becomes valid amount)
|
|
150
|
+
const minRequiredCRC = BigInt(96e18); // 96 CRC in atto-circles
|
|
151
|
+
// Get the token ID for the inviter's personal token
|
|
152
|
+
const tokenId = await this.core.hubV2.toTokenId(inviter);
|
|
153
|
+
// Check balance at the Inviter address
|
|
154
|
+
const balance = await this.core.hubV2.balanceOf(inviter, tokenId);
|
|
155
|
+
if (balance < minRequiredCRC) {
|
|
156
|
+
throw SdkError.insufficientBalance('96 CRC', `${Number(balance) / 1e18} CRC`, 'unwrapped CRC');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Step 2: Create and upload profile to IPFS
|
|
160
|
+
let profileCid;
|
|
161
|
+
if (typeof profile === 'string') {
|
|
162
|
+
// Profile is already a CID
|
|
163
|
+
profileCid = profile;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// Create profile and upload to IPFS
|
|
167
|
+
profileCid = await this.profilesClient.create(profile);
|
|
168
|
+
}
|
|
169
|
+
// Convert CID to metadata digest hex (format expected by registerHuman)
|
|
170
|
+
const metadataDigest = cidV0ToHex(profileCid);
|
|
171
|
+
// Step 3: Call registerHuman with profile data
|
|
172
|
+
const registerTx = this.core.hubV2.registerHuman(inviter, metadataDigest);
|
|
173
|
+
transactions.push(registerTx);
|
|
174
|
+
// Step 4: Execute all transactions
|
|
175
|
+
await contractRunner.sendTransaction(transactions);
|
|
176
|
+
// Step 5: Return a HumanAvatar instance for the newly registered account
|
|
177
|
+
const avatarInfo = await this.rpc.avatar.getAvatarInfo(senderAddress);
|
|
178
|
+
return new HumanAvatar(senderAddress, this.core, contractRunner, avatarInfo // @todo remove any
|
|
179
|
+
);
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* Register as an organization
|
|
183
|
+
* Organizations can participate in Circles without minting personal tokens
|
|
184
|
+
* and do not require invitations to register
|
|
185
|
+
*
|
|
186
|
+
* @param profile Profile data for the organization or CID string
|
|
187
|
+
* @returns OrganisationAvatar instance for the newly registered organization
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* const orgAvatar = await sdk.register.asOrganization({
|
|
192
|
+
* name: 'My Organization',
|
|
193
|
+
* description: 'A Circles organization'
|
|
194
|
+
* });
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
asOrganization: async (profile) => {
|
|
198
|
+
if (!this.contractRunner || !this.senderAddress) {
|
|
199
|
+
throw SdkError.missingContractRunner('Organization registration');
|
|
200
|
+
}
|
|
201
|
+
const contractRunner = this.contractRunner;
|
|
202
|
+
const senderAddress = this.senderAddress;
|
|
203
|
+
// Step 1: Create and upload profile to IPFS, and extract name
|
|
204
|
+
let profileCid;
|
|
205
|
+
let profileData;
|
|
206
|
+
if (typeof profile === 'string') {
|
|
207
|
+
// Profile is already a CID - fetch it to get the name
|
|
208
|
+
profileCid = profile;
|
|
209
|
+
const fetchedProfile = await this.profilesClient.get(profileCid);
|
|
210
|
+
if (!fetchedProfile) {
|
|
211
|
+
throw SdkError.profileOperationFailed('fetch', `Profile not found with CID: ${profileCid}`);
|
|
212
|
+
}
|
|
213
|
+
profileData = fetchedProfile;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// Create profile and upload to IPFS
|
|
217
|
+
profileData = profile;
|
|
218
|
+
profileCid = await this.profilesClient.create(profile);
|
|
219
|
+
}
|
|
220
|
+
// Validate that profile has a name
|
|
221
|
+
if (!profileData.name) {
|
|
222
|
+
throw SdkError.invalidProfile('Profile must have a name field for organization registration');
|
|
223
|
+
}
|
|
224
|
+
// Convert CID to metadata digest hex (format expected by registerOrganization)
|
|
225
|
+
const metadataDigest = cidV0ToHex(profileCid);
|
|
226
|
+
// Step 2: Call registerOrganization with name and profile data
|
|
227
|
+
const registerTx = this.core.hubV2.registerOrganization(profileData.name, metadataDigest);
|
|
228
|
+
// Step 3: Execute the transaction
|
|
229
|
+
await contractRunner.sendTransaction([registerTx]);
|
|
230
|
+
// Step 4: Return an OrganisationAvatar instance for the newly registered account
|
|
231
|
+
const avatarInfo = await this.rpc.avatar.getAvatarInfo(senderAddress);
|
|
232
|
+
return new OrganisationAvatar(senderAddress, this.core, contractRunner, avatarInfo // @todo remove any
|
|
233
|
+
);
|
|
234
|
+
},
|
|
235
|
+
/**
|
|
236
|
+
* Register a base group
|
|
237
|
+
* Creates a new base group using the BaseGroupFactory and registers it in the Circles ecosystem
|
|
238
|
+
*
|
|
239
|
+
* @param owner The address that will own the newly created BaseGroup
|
|
240
|
+
* @param service The address of the service for the new BaseGroup
|
|
241
|
+
* @param feeCollection The address of the fee collection for the new BaseGroup
|
|
242
|
+
* @param initialConditions An array of initial condition addresses
|
|
243
|
+
* @param name The group name (must be 19 characters or fewer)
|
|
244
|
+
* @param symbol The group symbol (e.g., 'MYG')
|
|
245
|
+
* @param profile Profile data (name, description, images, etc.) or CID string
|
|
246
|
+
* @returns BaseGroupAvatar instance for the newly registered group
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```typescript
|
|
250
|
+
* const groupAvatar = await sdk.register.asGroup(
|
|
251
|
+
* '0xOwnerAddress',
|
|
252
|
+
* '0xServiceAddress',
|
|
253
|
+
* '0xFeeCollectionAddress',
|
|
254
|
+
* [], // initial conditions
|
|
255
|
+
* 'My Group', // name
|
|
256
|
+
* 'MYG', // symbol
|
|
257
|
+
* {
|
|
258
|
+
* name: 'My Group',
|
|
259
|
+
* description: 'A Circles base group'
|
|
260
|
+
* }
|
|
261
|
+
* );
|
|
262
|
+
* ```
|
|
263
|
+
*/
|
|
264
|
+
asGroup: async (owner, service, feeCollection, initialConditions, name, symbol, profile) => {
|
|
265
|
+
if (!this.contractRunner || !this.senderAddress) {
|
|
266
|
+
throw SdkError.missingContractRunner('Group registration');
|
|
267
|
+
}
|
|
268
|
+
const contractRunner = this.contractRunner;
|
|
269
|
+
const senderAddress = this.senderAddress;
|
|
270
|
+
// Validate name and symbol
|
|
271
|
+
if (!name) {
|
|
272
|
+
throw SdkError.invalidProfile('Name is required for group registration');
|
|
273
|
+
}
|
|
274
|
+
if (!symbol) {
|
|
275
|
+
throw SdkError.invalidProfile('Symbol is required for group registration');
|
|
276
|
+
}
|
|
277
|
+
// Validate name length (must be 19 characters or fewer)
|
|
278
|
+
if (name.length > 19) {
|
|
279
|
+
throw SdkError.invalidProfile('Group name must be 19 characters or fewer', {
|
|
280
|
+
name,
|
|
281
|
+
length: name.length
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
// Step 1: Create and upload profile to IPFS
|
|
285
|
+
let profileCid;
|
|
286
|
+
if (typeof profile === 'string') {
|
|
287
|
+
// Profile is already a CID
|
|
288
|
+
profileCid = profile;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
// Create profile and upload to IPFS
|
|
292
|
+
profileCid = await this.profilesClient.create(profile);
|
|
293
|
+
}
|
|
294
|
+
// Convert CID to metadata digest hex (format expected by createBaseGroup)
|
|
295
|
+
const metadataDigest = cidV0ToHex(profileCid);
|
|
296
|
+
// Step 2: Create the base group using the factory
|
|
297
|
+
const createGroupTx = this.core.baseGroupFactory.createBaseGroup(owner, service, feeCollection, initialConditions, name, symbol, metadataDigest);
|
|
298
|
+
// Step 3: Execute the transaction
|
|
299
|
+
const receipt = await contractRunner.sendTransaction([createGroupTx]);
|
|
300
|
+
// Step 4: Decode the BaseGroupCreated event to extract the group address
|
|
301
|
+
let groupAddress;
|
|
302
|
+
for (const log of receipt.logs) {
|
|
303
|
+
try {
|
|
304
|
+
const decoded = decodeEventLog({
|
|
305
|
+
abi: baseGroupFactoryAbi,
|
|
306
|
+
data: log.data,
|
|
307
|
+
topics: log.topics,
|
|
308
|
+
});
|
|
309
|
+
if (decoded.eventName === 'BaseGroupCreated') {
|
|
310
|
+
groupAddress = decoded.args.group;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
// Not the event we're looking for, continue
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (!groupAddress) {
|
|
320
|
+
throw SdkError.transactionDataExtractionFailed('group address', 'BaseGroupCreated event not found in transaction receipt');
|
|
321
|
+
}
|
|
322
|
+
// Step 5: Return a BaseGroupAvatar instance for the newly created group
|
|
323
|
+
const avatarInfo = await this.rpc.avatar.getAvatarInfo(groupAddress);
|
|
324
|
+
return new BaseGroupAvatar(groupAddress, this.core, contractRunner, avatarInfo // @todo remove any
|
|
325
|
+
);
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
/**
|
|
329
|
+
* Profile management methods
|
|
330
|
+
*/
|
|
331
|
+
profiles = {
|
|
332
|
+
/**
|
|
333
|
+
* Create and pin a profile to IPFS
|
|
334
|
+
* @param profile Profile data or CID string
|
|
335
|
+
* @returns CID of the pinned profile
|
|
336
|
+
*/
|
|
337
|
+
create: async (profile) => {
|
|
338
|
+
return await this.profilesClient.create(profile);
|
|
339
|
+
},
|
|
340
|
+
/**
|
|
341
|
+
* Get a profile by CID
|
|
342
|
+
* @param cid Content identifier
|
|
343
|
+
* @returns Profile data or undefined if not found
|
|
344
|
+
*/
|
|
345
|
+
get: async (cid) => {
|
|
346
|
+
return await this.profilesClient.get(cid);
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
/**
|
|
350
|
+
* Token utilities
|
|
351
|
+
*/
|
|
352
|
+
tokens = {
|
|
353
|
+
/**
|
|
354
|
+
* Get an inflationary wrapper for a token
|
|
355
|
+
* @param address Avatar address
|
|
356
|
+
* @returns The ERC20 inflationary wrapper address, or zero address if not deployed
|
|
357
|
+
*/
|
|
358
|
+
getInflationaryWrapper: async (address) => {
|
|
359
|
+
return await this.core.liftERC20.erc20Circles(CirclesType.Inflation, address);
|
|
360
|
+
},
|
|
361
|
+
/**
|
|
362
|
+
* Get a demurraged wrapper for a token
|
|
363
|
+
* @param address Avatar address
|
|
364
|
+
* @returns The ERC20 demurraged wrapper address, or zero address if not deployed
|
|
365
|
+
*/
|
|
366
|
+
getDemurragedWrapper: async (address) => {
|
|
367
|
+
return await this.core.liftERC20.erc20Circles(CirclesType.Demurrage, address);
|
|
368
|
+
},
|
|
369
|
+
/**
|
|
370
|
+
* Get token holders for a specific token address with pagination
|
|
371
|
+
* @param tokenAddress The token address to query holders for
|
|
372
|
+
* @param limit Maximum number of results per page (default: 100)
|
|
373
|
+
* @param sortOrder Sort order for results (default: 'DESC' - highest balance first)
|
|
374
|
+
* @returns PagedQuery instance for token holders
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* ```typescript
|
|
378
|
+
* const holdersQuery = sdk.tokens.getHolders('0x42cedde51198d1773590311e2a340dc06b24cb37', 10);
|
|
379
|
+
*
|
|
380
|
+
* while (await holdersQuery.queryNextPage()) {
|
|
381
|
+
* const page = holdersQuery.currentPage!;
|
|
382
|
+
* console.log(`Found ${page.size} holders`);
|
|
383
|
+
* page.results.forEach(holder => {
|
|
384
|
+
* console.log(`${holder.account}: ${holder.demurragedTotalBalance}`);
|
|
385
|
+
* });
|
|
386
|
+
* }
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
389
|
+
getHolders: (tokenAddress, limit = 100, sortOrder = 'DESC') => {
|
|
390
|
+
return this.rpc.token.getTokenHolders(tokenAddress, limit, sortOrder);
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* Group utilities
|
|
395
|
+
*/
|
|
396
|
+
groups = {
|
|
397
|
+
/**
|
|
398
|
+
* Get the type of a group
|
|
399
|
+
* @param avatar Group avatar address
|
|
400
|
+
* @returns Group type or undefined if not a group
|
|
401
|
+
*/
|
|
402
|
+
getType: async (avatar) => {
|
|
403
|
+
throw SdkError.unsupportedOperation('groups.getType', 'not yet implemented');
|
|
404
|
+
},
|
|
405
|
+
/**
|
|
406
|
+
* Get all members of a specific group using cursor-based pagination
|
|
407
|
+
*
|
|
408
|
+
* Returns a PagedQuery instance for iterating through members of the specified group,
|
|
409
|
+
* including membership details such as expiry time and when the membership was created.
|
|
410
|
+
*
|
|
411
|
+
* @param groupAddress The address of the group to query members for
|
|
412
|
+
* @param limit Number of members per page (default: 100)
|
|
413
|
+
* @param sortOrder Sort order for results (default: 'DESC')
|
|
414
|
+
* @returns PagedQuery instance for iterating through group members
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```typescript
|
|
418
|
+
* const query = sdk.groups.getMembers('0xGroupAddress...');
|
|
419
|
+
*
|
|
420
|
+
* // Get first page
|
|
421
|
+
* await query.queryNextPage();
|
|
422
|
+
* console.log(`${query.currentPage.size} members in the group`);
|
|
423
|
+
*
|
|
424
|
+
* // Iterate through all members
|
|
425
|
+
* while (await query.queryNextPage()) {
|
|
426
|
+
* query.currentPage.results.forEach(membership => {
|
|
427
|
+
* console.log(`Member: ${membership.member}`);
|
|
428
|
+
* console.log(`Expiry: ${new Date(membership.expiryTime * 1000).toLocaleDateString()}`);
|
|
429
|
+
* });
|
|
430
|
+
* }
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
getMembers: (groupAddress, limit = 100, sortOrder = 'DESC') => {
|
|
434
|
+
return this.rpc.group.getGroupMembers(groupAddress, limit, sortOrder);
|
|
435
|
+
},
|
|
436
|
+
/**
|
|
437
|
+
* Get collateral tokens in a group's treasury
|
|
438
|
+
*
|
|
439
|
+
* This convenience method fetches the treasury address of a group and returns
|
|
440
|
+
* all token balances held in the treasury.
|
|
441
|
+
*
|
|
442
|
+
* @param groupAddress The address of the group
|
|
443
|
+
* @returns Array of token balances in the treasury
|
|
444
|
+
*
|
|
445
|
+
* @example
|
|
446
|
+
* ```typescript
|
|
447
|
+
* // Get collateral tokens in a group treasury
|
|
448
|
+
* const collateral = await sdk.groups.getCollateral('0xGroupAddress...');
|
|
449
|
+
*
|
|
450
|
+
* collateral.forEach(balance => {
|
|
451
|
+
* console.log(`Token: ${balance.tokenAddress}`);
|
|
452
|
+
* console.log(`Balance: ${balance.circles} CRC`);
|
|
453
|
+
* });
|
|
454
|
+
* ```
|
|
455
|
+
*/
|
|
456
|
+
getCollateral: async (groupAddress) => {
|
|
457
|
+
// Get the treasury address for this group
|
|
458
|
+
const groupContract = new BaseGroupContract({
|
|
459
|
+
address: groupAddress,
|
|
460
|
+
rpcUrl: this.core.rpcUrl,
|
|
461
|
+
});
|
|
462
|
+
const treasuryAddress = await groupContract.BASE_TREASURY();
|
|
463
|
+
// Get all token balances in the treasury
|
|
464
|
+
// @todo filter out the erc20 tokens
|
|
465
|
+
return await this.rpc.balance.getTokenBalances(treasuryAddress);
|
|
466
|
+
},
|
|
467
|
+
/**
|
|
468
|
+
* Get all holders of a group token using cursor-based pagination
|
|
469
|
+
*
|
|
470
|
+
* Returns all avatars that hold the specified group token, ordered by balance (highest first),
|
|
471
|
+
* including balance amounts and ownership fractions.
|
|
472
|
+
*
|
|
473
|
+
* @param groupAddress The address of the group token
|
|
474
|
+
* @param limit Maximum number of holders to return (default: 100)
|
|
475
|
+
* @returns PagedQuery instance for iterating through group token holders
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* ```typescript
|
|
479
|
+
* // Get all holders of a group token
|
|
480
|
+
* const query = sdk.groups.getHolders('0xGroupAddress...');
|
|
481
|
+
*
|
|
482
|
+
* // Get first page (ordered by balance DESC)
|
|
483
|
+
* await query.queryNextPage();
|
|
484
|
+
* console.log(`${query.currentPage.size} holders of this group token`);
|
|
485
|
+
*
|
|
486
|
+
* // Iterate through all holders
|
|
487
|
+
* while (await query.queryNextPage()) {
|
|
488
|
+
* query.currentPage.results.forEach(holder => {
|
|
489
|
+
* const balanceInCrc = Number(holder.totalBalance) / 1e18;
|
|
490
|
+
* console.log(`Holder: ${holder.holder}`);
|
|
491
|
+
* console.log(`Balance: ${balanceInCrc.toFixed(2)} CRC`);
|
|
492
|
+
* console.log(`Ownership: ${(holder.fractionOwnership * 100).toFixed(2)}%`);
|
|
493
|
+
* });
|
|
494
|
+
* }
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
getHolders: (groupAddress, limit = 100) => {
|
|
498
|
+
return this.rpc.group.getGroupHolders(groupAddress, limit);
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { Address, ContractRunner, Profile, AvatarRow, TokenBalanceRow, AggregatedTrustRelation } from '@aboutcircles/sdk-types';
|
|
2
|
+
import type { TransactionReceipt } from 'viem';
|
|
3
|
+
import type { Core } from '@aboutcircles/sdk-core';
|
|
4
|
+
import { CommonAvatar } from './CommonAvatar';
|
|
5
|
+
/**
|
|
6
|
+
* BaseGroupAvatar class implementation
|
|
7
|
+
* Provides a simplified wrapper around Circles protocol for base group avatars
|
|
8
|
+
*
|
|
9
|
+
* This class represents a base group avatar in the Circles ecosystem and provides
|
|
10
|
+
* methods for managing trust relationships, group properties, and metadata.
|
|
11
|
+
*/
|
|
12
|
+
export declare class BaseGroupAvatar extends CommonAvatar {
|
|
13
|
+
private readonly baseGroup;
|
|
14
|
+
constructor(address: Address, core: Core, contractRunner?: ContractRunner, avatarInfo?: AvatarRow);
|
|
15
|
+
readonly balances: {
|
|
16
|
+
getTotal: () => Promise<bigint>;
|
|
17
|
+
getTokenBalances: () => Promise<TokenBalanceRow[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Get total supply of this group's token
|
|
20
|
+
*/
|
|
21
|
+
getTotalSupply: () => Promise<bigint>;
|
|
22
|
+
};
|
|
23
|
+
readonly trust: {
|
|
24
|
+
add: (avatar: Address | Address[], expiry?: bigint) => Promise<TransactionReceipt>;
|
|
25
|
+
remove: (avatar: Address | Address[]) => Promise<TransactionReceipt>;
|
|
26
|
+
isTrusting: (otherAvatar: Address) => Promise<boolean>;
|
|
27
|
+
isTrustedBy: (otherAvatar: Address) => Promise<boolean>;
|
|
28
|
+
getAll: () => Promise<AggregatedTrustRelation[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Trust a batch of members with membership condition checks
|
|
31
|
+
*
|
|
32
|
+
* This is a group-specific method that validates members against membership conditions
|
|
33
|
+
* before establishing trust.
|
|
34
|
+
*
|
|
35
|
+
* @param members Array of member addresses
|
|
36
|
+
* @param expiry Trust expiry timestamp. Defaults to 0
|
|
37
|
+
* @returns Transaction response
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // Trust multiple members with condition checks
|
|
42
|
+
* await groupAvatar.trust.addBatchWithConditions(
|
|
43
|
+
* ['0x123...', '0x456...', '0x789...'],
|
|
44
|
+
* BigInt(Date.now() / 1000 + 31536000) // 1 year expiry
|
|
45
|
+
* );
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
addBatchWithConditions: (members: Address[], expiry?: bigint) => Promise<TransactionReceipt>;
|
|
49
|
+
};
|
|
50
|
+
readonly profile: {
|
|
51
|
+
get: () => Promise<Profile | undefined>;
|
|
52
|
+
update: (profile: Profile) => Promise<string>;
|
|
53
|
+
updateMetadata: (cid: string) => Promise<TransactionReceipt>;
|
|
54
|
+
registerShortName: (nonce: number) => Promise<TransactionReceipt>;
|
|
55
|
+
};
|
|
56
|
+
readonly properties: {
|
|
57
|
+
/**
|
|
58
|
+
* Get the owner of this group
|
|
59
|
+
*/
|
|
60
|
+
owner: () => Promise<Address>;
|
|
61
|
+
/**
|
|
62
|
+
* Get the mint handler address
|
|
63
|
+
*/
|
|
64
|
+
mintHandler: () => Promise<Address>;
|
|
65
|
+
/**
|
|
66
|
+
* Get the service address
|
|
67
|
+
*/
|
|
68
|
+
service: () => Promise<Address>;
|
|
69
|
+
/**
|
|
70
|
+
* Get the fee collection address
|
|
71
|
+
*/
|
|
72
|
+
feeCollection: () => Promise<Address>;
|
|
73
|
+
/**
|
|
74
|
+
* Get all membership conditions
|
|
75
|
+
*/
|
|
76
|
+
getMembershipConditions: () => Promise<Address[]>;
|
|
77
|
+
};
|
|
78
|
+
readonly setProperties: {
|
|
79
|
+
/**
|
|
80
|
+
* Set a new owner for this group
|
|
81
|
+
*/
|
|
82
|
+
owner: (newOwner: Address) => Promise<TransactionReceipt>;
|
|
83
|
+
/**
|
|
84
|
+
* Set a new service address
|
|
85
|
+
*/
|
|
86
|
+
service: (newService: Address) => Promise<TransactionReceipt>;
|
|
87
|
+
/**
|
|
88
|
+
* Set a new fee collection address
|
|
89
|
+
*/
|
|
90
|
+
feeCollection: (newFeeCollection: Address) => Promise<TransactionReceipt>;
|
|
91
|
+
/**
|
|
92
|
+
* Enable or disable a membership condition
|
|
93
|
+
*/
|
|
94
|
+
membershipCondition: (condition: Address, enabled: boolean) => Promise<TransactionReceipt>;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=BaseGroupAvatar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BaseGroupAvatar.d.ts","sourceRoot":"","sources":["../../src/avatars/BaseGroupAvatar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,cAAc,EACd,OAAO,EACP,SAAS,EACT,eAAe,EACf,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,MAAM,CAAC;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAInD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C;;;;;;GAMG;AACH,qBAAa,eAAgB,SAAQ,YAAY;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;gBAG5C,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,IAAI,EACV,cAAc,CAAC,EAAE,cAAc,EAC/B,UAAU,CAAC,EAAE,SAAS;IAexB,SAAgB,QAAQ;wBACF,OAAO,CAAC,MAAM,CAAC;gCAIP,OAAO,CAAC,eAAe,EAAE,CAAC;QAItD;;WAEG;8BACuB,OAAO,CAAC,MAAM,CAAC;MAIzC;IAQF,SAAyB,KAAK;sBAElB,OAAO,GAAG,OAAO,EAAE,WAClB,MAAM,KACd,OAAO,CAAC,kBAAkB,CAAC;yBAeP,OAAO,GAAG,OAAO,EAAE,KAAG,OAAO,CAAC,kBAAkB,CAAC;kCAiBxC,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;mCAIzB,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;sBAIzC,OAAO,CAAC,uBAAuB,EAAE,CAAC;QAIpD;;;;;;;;;;;;;;;;;;WAkBG;0CAEQ,OAAO,EAAE,WACT,MAAM,KACd,OAAO,CAAC,kBAAkB,CAAC;MAK9B;IAQF,SAAyB,OAAO;mBACf,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;0BAyBnB,OAAO,KAAG,OAAO,CAAC,MAAM,CAAC;8BAqBrB,MAAM,KAAG,OAAO,CAAC,kBAAkB,CAAC;mCAM/B,MAAM,KAAG,OAAO,CAAC,kBAAkB,CAAC;MAIrE;IAMF,SAAgB,UAAU;QACxB;;WAEG;qBACc,OAAO,CAAC,OAAO,CAAC;QAIjC;;WAEG;2BACoB,OAAO,CAAC,OAAO,CAAC;QAIvC;;WAEG;uBACgB,OAAO,CAAC,OAAO,CAAC;QAInC;;WAEG;6BACsB,OAAO,CAAC,OAAO,CAAC;QAIzC;;WAEG;uCACgC,OAAO,CAAC,OAAO,EAAE,CAAC;MAIrD;IAMF,SAAgB,aAAa;QAC3B;;WAEG;0BACqB,OAAO,KAAG,OAAO,CAAC,kBAAkB,CAAC;QAK7D;;WAEG;8BACyB,OAAO,KAAG,OAAO,CAAC,kBAAkB,CAAC;QAKjE;;WAEG;0CACqC,OAAO,KAAG,OAAO,CAAC,kBAAkB,CAAC;QAK7E;;WAEG;yCAEU,OAAO,WACT,OAAO,KACf,OAAO,CAAC,kBAAkB,CAAC;MAI9B;CACH"}
|