@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.
@@ -0,0 +1,610 @@
1
+ import { ValidationError } from '@aboutcircles/sdk-utils';
2
+ import { SdkError } from '../errors';
3
+ import { BaseGroupContract } from '@aboutcircles/sdk-core';
4
+ import { encodeAbiParameters, parseAbiParameters } from 'viem';
5
+ import { CommonAvatar } from './CommonAvatar';
6
+ /**
7
+ * HumanAvatar class implementation
8
+ * Provides a simplified, user-friendly wrapper around Circles protocol for human avatars
9
+ *
10
+ * This class represents a human avatar in the Circles ecosystem and provides
11
+ * methods for managing trust relationships, personal token minting, transfers, and more.
12
+ */
13
+ export class HumanAvatar extends CommonAvatar {
14
+ constructor(address, core, contractRunner, avatarInfo) {
15
+ super(address, core, contractRunner, avatarInfo);
16
+ }
17
+ // ============================================================================
18
+ // Override Balance Methods with Human-Specific Features
19
+ // ============================================================================
20
+ balances = {
21
+ getTotal: async () => {
22
+ return await this.rpc.balance.getTotalBalance(this.address);
23
+ },
24
+ getTokenBalances: async () => {
25
+ return await this.rpc.balance.getTokenBalances(this.address);
26
+ },
27
+ getTotalSupply: async () => {
28
+ throw SdkError.unsupportedOperation('getTotalSupply', 'This method is not yet implemented');
29
+ },
30
+ /**
31
+ * Get the maximum amount that can be replenished (converted to unwrapped personal CRC)
32
+ * This calculates how much of your wrapped tokens and other tokens can be converted
33
+ * into your own unwrapped ERC1155 personal CRC tokens using pathfinding
34
+ *
35
+ * @param options Optional pathfinding options
36
+ * @returns Maximum replenishable amount in atto-circles
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const maxReplenishable = await avatar.balances.getMaxReplenishable();
41
+ * console.log('Can replenish:', CirclesConverter.attoCirclesToCircles(maxReplenishable), 'CRC');
42
+ * ```
43
+ */
44
+ //@todo improve calculation
45
+ getMaxReplenishable: async (options) => {
46
+ const addr = this.address.toLowerCase();
47
+ // Find maximum flow from avatar to itself, targeting personal tokens as destination
48
+ // This effectively asks: "How much can I convert to my own personal tokens?"
49
+ // @todo add sim trust
50
+ const maxFlow = await this.rpc.pathfinder.findMaxFlow({
51
+ from: addr,
52
+ to: addr,
53
+ toTokens: [addr], // Destination token is sender's own personal CRC
54
+ useWrappedBalances: true, // Include wrapped tokens
55
+ ...options,
56
+ });
57
+ return maxFlow;
58
+ },
59
+ /**
60
+ * Replenish unwrapped personal CRC tokens by converting wrapped/other tokens
61
+ *
62
+ * This method uses pathfinding to find the best way to convert your available tokens
63
+ * (including wrapped tokens) into unwrapped ERC1155 personal CRC tokens.
64
+ *
65
+ * Useful when you have wrapped tokens or other people's tokens and want to
66
+ * convert them to your own personal unwrapped tokens for transfers.
67
+ *
68
+ * @param options Optional pathfinding options
69
+ * @returns Transaction receipt
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * // Convert all available wrapped/other tokens to unwrapped personal CRC
74
+ * const receipt = await avatar.balances.replenish();
75
+ * console.log('Replenished personal tokens, tx hash:', receipt.hash);
76
+ * ```
77
+ */
78
+ //@todo add amount to replenish
79
+ replenish: async (options) => {
80
+ // Construct replenish transactions using TransferBuilder
81
+ const transactions = await this.transferBuilder.constructReplenish(this.address, options);
82
+ // If no transactions needed, return early
83
+ if (transactions.length === 0) {
84
+ throw SdkError.configError('No tokens available to replenish', { message: 'You may not have any wrapped tokens or convertible tokens' });
85
+ }
86
+ // Execute the constructed transactions
87
+ return await this.runner.sendTransaction(transactions);
88
+ },
89
+ };
90
+ // ============================================================================
91
+ // Transfer methods are inherited from CommonAvatar
92
+ // ============================================================================
93
+ // ============================================================================
94
+ // Trust methods are inherited from CommonAvatar
95
+ // ============================================================================
96
+ // Invitation methods
97
+ invite = {
98
+ /**
99
+ * Invite someone to Circles by escrowing 100 CRC tokens
100
+ *
101
+ * This batches two transactions atomically:
102
+ * 1. Establishes trust with the invitee (with indefinite expiry)
103
+ * 2. Transfers 100 of your personal CRC tokens to the InvitationEscrow contract
104
+ *
105
+ * The tokens are held in escrow until the invitee redeems the invitation by registering.
106
+ *
107
+ * Requirements:
108
+ * - You must have at least 100 CRC available
109
+ * - Invitee must not be already registered in Circles
110
+ * - You can only have one active invitation per invitee
111
+ *
112
+ * @param invitee The address to invite
113
+ * @returns Transaction response
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * // Invite someone with 100 CRC (automatically establishes trust)
118
+ * await avatar.invite.send('0x123...');
119
+ * ```
120
+ */
121
+ send: async (invitee) => {
122
+ //@todo add replenish/unwrap logic
123
+ // Create trust transaction (indefinite trust)
124
+ const trustTx = this.core.hubV2.trust(invitee, BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFF'));
125
+ // Get the token ID for this avatar's personal token
126
+ const tokenId = await this.core.hubV2.toTokenId(this.address);
127
+ // ABI-encode the invitee address as 32 bytes
128
+ const encodedInvitee = encodeAbiParameters(parseAbiParameters('address'), [invitee]);
129
+ // Create the safeTransferFrom transaction to the InvitationEscrow contract
130
+ const transferTx = this.core.hubV2.safeTransferFrom(this.address, this.core.config.invitationEscrowAddress, tokenId, BigInt(100e18), encodedInvitee);
131
+ // Batch both transactions: trust + invitation transfer
132
+ return await this.runner.sendTransaction([trustTx, transferTx]);
133
+ },
134
+ /**
135
+ * Revoke a previously sent invitation
136
+ *
137
+ * This returns the escrowed tokens (with demurrage applied) back to you
138
+ * as wrapped ERC20 tokens.
139
+ *
140
+ * @param invitee The address whose invitation to revoke
141
+ * @returns Transaction response
142
+ *
143
+ * @example
144
+ * ```typescript
145
+ * await avatar.invite.revoke('0x123...');
146
+ * ```
147
+ */
148
+ revoke: async (invitee) => {
149
+ const revokeTx = this.core.invitationEscrow.revokeInvitation(invitee);
150
+ return await this.runner.sendTransaction([revokeTx]);
151
+ },
152
+ /**
153
+ * Revoke all active invitations at once
154
+ *
155
+ * This returns all escrowed tokens (with demurrage applied) back to you
156
+ * as wrapped ERC20 tokens in a single transaction.
157
+ *
158
+ * @returns Transaction response
159
+ *
160
+ * @example
161
+ * ```typescript
162
+ * await avatar.invite.revokeAll();
163
+ * ```
164
+ */
165
+ revokeAll: async () => {
166
+ const revokeAllTx = this.core.invitationEscrow.revokeAllInvitations();
167
+ return await this.runner.sendTransaction([revokeAllTx]);
168
+ },
169
+ /**
170
+ * Redeem an invitation received from an inviter
171
+ *
172
+ * This claims the escrowed tokens from a specific inviter and refunds
173
+ * all other inviters' escrows back to them.
174
+ *
175
+ * @param inviter The address of the inviter whose invitation to redeem
176
+ * @returns Transaction response
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * // Get all inviters first
181
+ * const inviters = await avatar.invite.getInviters();
182
+ *
183
+ * // Redeem invitation from the first inviter
184
+ * await avatar.invite.redeem(inviters[0]);
185
+ * ```
186
+ */
187
+ // @todo check if it functionable
188
+ redeem: async (inviter) => {
189
+ const redeemTx = this.core.invitationEscrow.redeemInvitation(inviter);
190
+ return await this.runner.sendTransaction([redeemTx]);
191
+ },
192
+ /**
193
+ * Get all addresses that have sent invitations to you
194
+ *
195
+ * @returns Array of inviter addresses
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const inviters = await avatar.invite.getInviters();
200
+ * console.log(`You have ${inviters.length} pending invitations`);
201
+ * ```
202
+ */
203
+ getInviters: async () => {
204
+ return await this.core.invitationEscrow.getInviters(this.address);
205
+ },
206
+ /**
207
+ * Get all addresses you have invited
208
+ *
209
+ * @returns Array of invitee addresses
210
+ *
211
+ * @example
212
+ * ```typescript
213
+ * const invitees = await avatar.invite.getInvitees();
214
+ * console.log(`You have invited ${invitees.length} people`);
215
+ * ```
216
+ */
217
+ getInvitees: async () => {
218
+ return await this.core.invitationEscrow.getInvitees(this.address);
219
+ },
220
+ /**
221
+ * Get the escrowed amount and days since escrow for a specific invitation
222
+ *
223
+ * The amount returned has demurrage applied, so it decreases over time.
224
+ *
225
+ * @param inviter The inviter address (when checking invitations you received)
226
+ * @param invitee The invitee address (when checking invitations you sent)
227
+ * @returns Object with escrowedAmount (in atto-circles) and days since escrow
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * // Check an invitation you sent
232
+ * const { escrowedAmount, days_ } = await avatar.invite.getEscrowedAmount(
233
+ * avatar.address,
234
+ * '0xinvitee...'
235
+ * );
236
+ * console.log(`Escrowed: ${CirclesConverter.attoCirclesToCircles(escrowedAmount)} CRC`);
237
+ * console.log(`Days since escrow: ${days_}`);
238
+ *
239
+ * // Check an invitation you received
240
+ * const { escrowedAmount, days_ } = await avatar.invite.getEscrowedAmount(
241
+ * '0xinviter...',
242
+ * avatar.address
243
+ * );
244
+ * ```
245
+ */
246
+ getEscrowedAmount: async (inviter, invitee) => {
247
+ return await this.core.invitationEscrow.getEscrowedAmountAndDays(inviter, invitee);
248
+ },
249
+ };
250
+ // Personal token / Minting methods
251
+ personalToken = {
252
+ /**
253
+ * Get the available amount of personal tokens that can be minted
254
+ *
255
+ * This method calls the HubV2 contract's calculateIssuance function which returns:
256
+ * - Total issuance amount: The total amount of tokens that can be minted
257
+ * - Start period: The period when minting started
258
+ * - End period: The current period
259
+ *
260
+ * @returns Object containing issuance amount (in atto-circles), start period, and end period
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * const { amount, startPeriod, endPeriod } = await avatar.personalToken.getMintableAmount();
265
+ * console.log('Mintable amount:', CirclesConverter.attoCirclesToCircles(amount), 'CRC');
266
+ * console.log('Start period:', startPeriod.toString());
267
+ * console.log('End period:', endPeriod.toString());
268
+ * ```
269
+ */
270
+ getMintableAmount: async () => {
271
+ const [amount, startPeriod, endPeriod] = await this.core.hubV2.calculateIssuance(this.address);
272
+ return {
273
+ amount,
274
+ startPeriod,
275
+ endPeriod,
276
+ };
277
+ },
278
+ /**
279
+ * Mint personal Circles tokens
280
+ * This claims all available personal tokens that have accrued since last mint
281
+ *
282
+ * @returns Transaction response
283
+ *
284
+ * @example
285
+ * ```typescript
286
+ * const receipt = await avatar.personalToken.mint();
287
+ * console.log('Minted tokens, tx hash:', receipt.hash);
288
+ * ```
289
+ */
290
+ mint: async () => {
291
+ const mintTx = this.core.hubV2.personalMint();
292
+ return await this.runner.sendTransaction([mintTx]);
293
+ },
294
+ /**
295
+ * Stop personal token minting
296
+ * This permanently stops the ability to mint new personal tokens
297
+ *
298
+ * WARNING: This action is irreversible!
299
+ *
300
+ * @returns Transaction response
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * const receipt = await avatar.personalToken.stop();
305
+ * console.log('Stopped minting, tx hash:', receipt.hash);
306
+ * ```
307
+ */
308
+ stop: async () => {
309
+ // Use the stop method from core
310
+ const stopTx = this.core.hubV2.stop();
311
+ return await this.runner.sendTransaction([stopTx]);
312
+ },
313
+ };
314
+ // ============================================================================
315
+ // Profile methods are inherited from CommonAvatar
316
+ // ============================================================================
317
+ // ============================================================================
318
+ // History methods are inherited from CommonAvatar
319
+ // ============================================================================
320
+ // Group token methods
321
+ groupToken = {
322
+ /**
323
+ * Mint group tokens by transferring collateral to the group's mint handler
324
+ * Uses pathfinding to transfer tokens along the trust network, including wrapped tokens
325
+ *
326
+ * @param group The group address to mint tokens from
327
+ * @param amount Amount of tokens to transfer to the mint handler (in atto-circles)
328
+ * @returns Transaction receipt
329
+ *
330
+ * @example
331
+ * ```typescript
332
+ * // Mint group tokens by sending 100 CRC to the group's mint handler
333
+ * const receipt = await avatar.groupToken.mint('0xGroupAddress...', BigInt(100e18));
334
+ * ```
335
+ */
336
+ mint: async (group, amount) => {
337
+ // Create BaseGroupContract instance to get mint handler
338
+ const groupContract = new BaseGroupContract({
339
+ address: group,
340
+ rpcUrl: this.core.rpcUrl,
341
+ });
342
+ const mintHandler = await groupContract.BASE_MINT_HANDLER();
343
+ // Use transferBuilder to construct transfer to mint handler with pathfinding
344
+ // Include wrapped tokens in pathfinding
345
+ const transactions = await this.transferBuilder.constructAdvancedTransfer(this.address, mintHandler, amount, { useWrappedBalances: true });
346
+ return await this.runner.sendTransaction(transactions);
347
+ },
348
+ /**
349
+ * Get the maximum amount that can be minted for a group
350
+ * This calculates the maximum transferable amount to the group's mint handler
351
+ * including wrapped token balances
352
+ *
353
+ * @param group The group address
354
+ * @returns Maximum mintable amount in atto-circles
355
+ *
356
+ * @example
357
+ * ```typescript
358
+ * const maxMintable = await avatar.groupToken.getMaxMintableAmount('0xGroupAddress...');
359
+ * console.log('Can mint up to:', maxMintable.toString(), 'atto-circles');
360
+ * ```
361
+ */
362
+ getMaxMintableAmount: async (group) => {
363
+ // Create BaseGroupContract instance to get mint handler
364
+ const groupContract = new BaseGroupContract({
365
+ address: group,
366
+ rpcUrl: this.core.rpcUrl,
367
+ });
368
+ const mintHandler = await groupContract.BASE_MINT_HANDLER();
369
+ // Find max flow to mint handler including wrapped tokens
370
+ return await this.rpc.pathfinder.findMaxFlow({
371
+ from: this.address.toLowerCase(),
372
+ to: mintHandler.toLowerCase(),
373
+ useWrappedBalances: true,
374
+ });
375
+ },
376
+ /**
377
+ * Automatically redeem collateral tokens from a Base Group's treasury
378
+ *
379
+ * Performs automatic redemption by determining trusted collaterals and using pathfinder for optimal flow.
380
+ * Only supports Base Group types. The function uses pathfinder to determine the optimal redemption path
381
+ * and validates that sufficient liquidity exists before attempting redemption.
382
+ *
383
+ * @param group The address of the Base Group from which to redeem collateral tokens
384
+ * @param amount The amount of group tokens to redeem for collateral (must be > 0 and <= max redeemable)
385
+ * @returns A Promise resolving to the transaction receipt upon successful automatic redemption
386
+ *
387
+ * @example
388
+ * ```typescript
389
+ * // Redeem 100 group tokens for collateral
390
+ * const receipt = await avatar.groupToken.redeem('0xGroupAddress...', BigInt(100e18));
391
+ * ```
392
+ */
393
+ //@todo check
394
+ redeem: async (group, amount) => {
395
+ group = group.toLowerCase();
396
+ // Get group information to validate it's a Base Group
397
+ // @todo replace with the request to the group contract
398
+ const groupInfo = await this.rpc.token.getTokenInfo(group);
399
+ if (!groupInfo) {
400
+ throw SdkError.configError(`Group not found: ${group}`, { group });
401
+ }
402
+ // Validate it's a Base Group (supports CrcV2_RegisterGroup event type)
403
+ // In V2, base groups are registered with CrcV2_RegisterGroup
404
+ if (groupInfo.tokenType !== 'CrcV2_RegisterGroup') {
405
+ throw SdkError.unsupportedOperation('redeem', 'Only Base Groups support this method');
406
+ }
407
+ // Address of the redeemer
408
+ const currentAvatar = this.address.toLowerCase();
409
+ // Create BaseGroupContract instance to get treasury
410
+ const groupContract = new BaseGroupContract({
411
+ address: group,
412
+ rpcUrl: this.core.rpcUrl,
413
+ });
414
+ const treasuryAddress = (await groupContract.BASE_TREASURY()).toLowerCase();
415
+ // Get list of all tokens in the treasury
416
+ const treasuryBalances = await this.rpc.balance.getTokenBalances(treasuryAddress);
417
+ const treasuryTokens = treasuryBalances
418
+ .filter((balance) => balance.isErc1155)
419
+ .map((balance) => balance.tokenAddress.toLowerCase());
420
+ // Get trust relationships to determine expected collateral tokens
421
+ const trustRelationships = await this.rpc.trust.getAggregatedTrustRelations(currentAvatar);
422
+ // Filter for tokens that:
423
+ // 1. Are mutually trusted or trusted by current avatar
424
+ // 2. Exist in the treasury
425
+ const expectedToTokens = trustRelationships
426
+ .filter((trustObject) => {
427
+ if ((trustObject.relation === 'mutuallyTrusts' || trustObject.relation === 'trusts') &&
428
+ treasuryTokens.includes(trustObject.objectAvatar.toLowerCase())) {
429
+ return true;
430
+ }
431
+ return false;
432
+ })
433
+ .map((trustObject) => trustObject.objectAvatar.toLowerCase());
434
+ // Check if enough tokens as amount - validate max redeemable
435
+ const maxRedeemableAmount = await this.rpc.pathfinder.findMaxFlow({
436
+ from: currentAvatar,
437
+ to: currentAvatar,
438
+ useWrappedBalances: false,
439
+ fromTokens: [group],
440
+ toTokens: expectedToTokens,
441
+ });
442
+ if (BigInt(maxRedeemableAmount) < amount) {
443
+ throw ValidationError.invalidAmount(amount, `Specified amount ${amount} exceeds max tokens flow ${maxRedeemableAmount}`);
444
+ }
445
+ // Construct the redemption transfer using TransferBuilder
446
+ const transactions = await this.transferBuilder.constructAdvancedTransfer(currentAvatar, currentAvatar, // Redeem to self
447
+ amount, {
448
+ useWrappedBalances: false,
449
+ fromTokens: [group], // Redeem from group tokens
450
+ toTokens: expectedToTokens, // Receive trusted collateral tokens
451
+ });
452
+ if (!transactions || transactions.length === 0) {
453
+ throw SdkError.operationFailed('groupToken.redeem', 'No transactions generated');
454
+ }
455
+ return await this.runner.sendTransaction(transactions);
456
+ },
457
+ properties: {
458
+ /**
459
+ * Get the owner of a specific group
460
+ * @param group The group address
461
+ * @returns The owner address of the group
462
+ */
463
+ owner: async (group) => {
464
+ const groupContract = new BaseGroupContract({
465
+ address: group,
466
+ rpcUrl: this.core.rpcUrl,
467
+ });
468
+ return await groupContract.owner();
469
+ },
470
+ /**
471
+ * Get the mint handler address of a specific group
472
+ * @param group The group address
473
+ * @returns The mint handler address
474
+ */
475
+ mintHandler: async (group) => {
476
+ const groupContract = new BaseGroupContract({
477
+ address: group,
478
+ rpcUrl: this.core.rpcUrl,
479
+ });
480
+ return await groupContract.BASE_MINT_HANDLER();
481
+ },
482
+ /**
483
+ * Get the treasury (redemption handler) address of a specific group
484
+ * @param group The group address
485
+ * @returns The treasury address where redemptions are handled
486
+ */
487
+ treasury: async (group) => {
488
+ const groupContract = new BaseGroupContract({
489
+ address: group,
490
+ rpcUrl: this.core.rpcUrl,
491
+ });
492
+ return await groupContract.BASE_TREASURY();
493
+ },
494
+ /**
495
+ * Get the service address of a specific group
496
+ * @param group The group address
497
+ * @returns The service address
498
+ */
499
+ service: async (group) => {
500
+ const groupContract = new BaseGroupContract({
501
+ address: group,
502
+ rpcUrl: this.core.rpcUrl,
503
+ });
504
+ return await groupContract.service();
505
+ },
506
+ /**
507
+ * Get the fee collection address of a specific group
508
+ * @param group The group address
509
+ * @returns The fee collection address
510
+ */
511
+ feeCollection: async (group) => {
512
+ const groupContract = new BaseGroupContract({
513
+ address: group,
514
+ rpcUrl: this.core.rpcUrl,
515
+ });
516
+ return await groupContract.feeCollection();
517
+ },
518
+ /**
519
+ * Get all membership conditions for a specific group
520
+ * @param group The group address
521
+ * @returns Array of membership condition addresses
522
+ */
523
+ getMembershipConditions: async (group) => {
524
+ const groupContract = new BaseGroupContract({
525
+ address: group,
526
+ rpcUrl: this.core.rpcUrl,
527
+ });
528
+ const conditions = await groupContract.getMembershipConditions();
529
+ return Array.from(conditions);
530
+ },
531
+ },
532
+ };
533
+ // Group methods (alias for groupToken)
534
+ group = {
535
+ properties: this.groupToken.properties,
536
+ /**
537
+ * Get group memberships for this avatar using cursor-based pagination
538
+ *
539
+ * Returns a PagedQuery instance for iterating through all groups that this avatar is a member of,
540
+ * including membership details such as expiry time and when the membership was created.
541
+ *
542
+ * @param limit Number of memberships per page (default: 50)
543
+ * @param sortOrder Sort order for results (default: 'DESC')
544
+ * @returns PagedQuery instance for iterating through memberships
545
+ *
546
+ * @example
547
+ * ```typescript
548
+ * const query = avatar.group.getGroupMemberships();
549
+ *
550
+ * // Get first page
551
+ * await query.queryNextPage();
552
+ * console.log(`Member of ${query.currentPage.size} groups (page 1)`);
553
+ *
554
+ * // Iterate through all memberships
555
+ * while (await query.queryNextPage()) {
556
+ * query.currentPage.results.forEach(membership => {
557
+ * console.log(`Group: ${membership.group}`);
558
+ * console.log(`Expiry: ${new Date(membership.expiryTime * 1000).toLocaleDateString()}`);
559
+ * });
560
+ * }
561
+ * ```
562
+ */
563
+ getGroupMemberships: (limit = 50, sortOrder = 'DESC') => {
564
+ return this.rpc.group.getGroupMemberships(this.address, limit, sortOrder);
565
+ },
566
+ /**
567
+ * Get detailed information about all groups this avatar is a member of
568
+ *
569
+ * This method fetches group memberships and enriches them with full group details including
570
+ * name, symbol, owner, treasury, mint handler, member count, and other properties.
571
+ *
572
+ * @param limit Maximum number of memberships to return (default: 50)
573
+ * @returns Array of group detail rows
574
+ *
575
+ * @example
576
+ * ```typescript
577
+ * // Get detailed information about all group memberships
578
+ * const groups = await avatar.group.getGroupMembershipsWithDetails();
579
+ *
580
+ * groups.forEach(group => {
581
+ * console.log(`Group: ${group.name} (${group.symbol})`);
582
+ * console.log(`Owner: ${group.owner}`);
583
+ * console.log(`Member count: ${group.memberCount}`);
584
+ * console.log(`Treasury: ${group.treasury}`);
585
+ * });
586
+ * ```
587
+ */
588
+ getGroupMembershipsWithDetails: async (limit = 50) => {
589
+ // Get memberships for this avatar using pagination
590
+ const query = this.rpc.group.getGroupMemberships(this.address, limit);
591
+ const memberships = [];
592
+ // Fetch all memberships
593
+ while (await query.queryNextPage()) {
594
+ memberships.push(...query.currentPage.results);
595
+ if (!query.currentPage.hasMore)
596
+ break;
597
+ }
598
+ if (memberships.length === 0) {
599
+ return [];
600
+ }
601
+ // Extract group addresses
602
+ const groupAddresses = memberships.map((m) => m.group);
603
+ // Fetch group details using groupAddressIn filter
604
+ const groups = await this.rpc.group.findGroups(groupAddresses.length, {
605
+ groupAddressIn: groupAddresses,
606
+ });
607
+ return groups;
608
+ },
609
+ };
610
+ }