@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
|
@@ -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
|
+
}
|