@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 ADDED
@@ -0,0 +1,249 @@
1
+ # @aboutcircles/sdk
2
+
3
+ Simplified Circles SDK for non-crypto users with a low entrance barrier. This package provides user-friendly abstractions over the Circles protocol, making it easier for developers to build applications without deep blockchain knowledge.
4
+
5
+ ## Overview
6
+
7
+ The SDK package provides two main models:
8
+
9
+ 1. **Sdk** - Main entry point for interacting with Circles
10
+ 2. **Avatar** - Represents a Circles identity (human, organization, or group)
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @aboutcircles/sdk
16
+ # or
17
+ bun add @aboutcircles/sdk
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ### Basic Setup
23
+
24
+ ```typescript
25
+ import { Sdk } from '@aboutcircles/sdk';
26
+
27
+ // Create SDK instance with default configuration (Gnosis Chain)
28
+ const sdk = new Sdk();
29
+
30
+ // Or with custom configuration
31
+ import { circlesConfig } from '@aboutcircles/sdk-core';
32
+ const sdk = new Sdk(circlesConfig[100], 'https://custom-rpc.com');
33
+ ```
34
+
35
+ ### Working with Avatars
36
+
37
+ ```typescript
38
+ // Get an avatar
39
+ const avatar = await sdk.getAvatar('0xAvatarAddress');
40
+
41
+ // Access avatar properties
42
+ console.log(avatar.address);
43
+ console.log(avatar.avatarInfo);
44
+ ```
45
+
46
+ ### Registration (Not Yet Implemented)
47
+
48
+ ```typescript
49
+ // Register as a human
50
+ const avatar = await sdk.register.asHuman('0xInviterAddress', {
51
+ name: 'Alice',
52
+ description: 'Developer'
53
+ });
54
+
55
+ // Register as an organization
56
+ const avatar = await sdk.register.asOrganization({
57
+ name: 'My Organization',
58
+ description: 'A great organization'
59
+ });
60
+
61
+ // Register as a group
62
+ const avatar = await sdk.register.asGroup('0xMintPolicyAddress', {
63
+ name: 'My Group',
64
+ symbol: 'MGP',
65
+ description: 'A collaborative group'
66
+ });
67
+ ```
68
+
69
+ ### Avatar Methods
70
+
71
+ The Avatar class provides methods for common Circles operations:
72
+
73
+ #### Balance Methods
74
+
75
+ ```typescript
76
+ // Get total balance
77
+ const total = await avatar.balances.getTotal();
78
+
79
+ // Get detailed token balances
80
+ const balances = await avatar.balances.getDetailed();
81
+
82
+ // Get gas token balance
83
+ const gasBalance = await avatar.balances.getGasToken();
84
+ ```
85
+
86
+ #### Transfer Methods
87
+
88
+ ```typescript
89
+ // Simple direct transfer
90
+ await avatar.transfer.direct('0xRecipient', 100);
91
+
92
+ // Advanced transfer with options
93
+ await avatar.transfer.advanced('0xRecipient', 100, {
94
+ token: '0xTokenAddress',
95
+ useWrappedBalances: true
96
+ });
97
+
98
+ // Get maximum transferable amount
99
+ const maxAmount = await avatar.transfer.getMaxAmount('0xRecipient');
100
+ ```
101
+
102
+ #### Trust Methods
103
+
104
+ ```typescript
105
+ // Add trust
106
+ await avatar.trust.add('0xTrustee');
107
+
108
+ // Remove trust
109
+ await avatar.trust.remove('0xTrustee');
110
+
111
+ // Check trust status
112
+ const isTrusting = await avatar.trust.isTrusting('0xOtherAvatar');
113
+ const isTrustedBy = await avatar.trust.isTrustedBy('0xOtherAvatar');
114
+
115
+ // Get all trust relations
116
+ const trustRelations = await avatar.trust.getAll();
117
+ ```
118
+
119
+ #### Personal Token Methods
120
+
121
+ ```typescript
122
+ // Get available minting amount
123
+ const available = await avatar.personalToken.getAvailableAmount();
124
+
125
+ // Mint personal tokens
126
+ await avatar.personalToken.mint();
127
+
128
+ // Stop minting (irreversible)
129
+ await avatar.personalToken.stop();
130
+ ```
131
+
132
+ #### Profile Methods
133
+
134
+ ```typescript
135
+ // Get profile
136
+ const profile = await avatar.profile.get();
137
+
138
+ // Update profile
139
+ const cid = await avatar.profile.update({
140
+ name: 'Alice',
141
+ description: 'Updated description'
142
+ });
143
+
144
+ // Update metadata (CID only)
145
+ await avatar.profile.updateMetadata('QmNewCID');
146
+
147
+ // Register short name
148
+ await avatar.profile.registerShortName(123);
149
+ ```
150
+
151
+ #### Group Methods
152
+
153
+ ```typescript
154
+ // Mint group tokens
155
+ await avatar.groupToken.mint(
156
+ '0xGroupAddress',
157
+ ['0xCollateral1', '0xCollateral2'],
158
+ [BigInt(100), BigInt(200)],
159
+ new Uint8Array()
160
+ );
161
+
162
+ // Redeem collateral
163
+ await avatar.groupToken.redeem(
164
+ '0xGroupAddress',
165
+ ['0xCollateral1'],
166
+ [BigInt(100)]
167
+ );
168
+
169
+ // Get group properties
170
+ const owner = await avatar.group.properties.owner();
171
+ const mintHandler = await avatar.group.properties.mintHandler();
172
+
173
+ // Set group properties
174
+ await avatar.group.setProperties.owner('0xNewOwner');
175
+ ```
176
+
177
+ #### Wrapping Methods
178
+
179
+ ```typescript
180
+ // Wrap as demurraged ERC20
181
+ const tokenAddress = await avatar.wrap.asDemurraged('0xAvatarAddress', BigInt(100));
182
+
183
+ // Wrap as inflationary ERC20
184
+ const tokenAddress = await avatar.wrap.asInflationary('0xAvatarAddress', BigInt(100));
185
+
186
+ // Unwrap tokens
187
+ await avatar.wrap.unwrapDemurraged('0xTokenAddress', BigInt(100));
188
+ await avatar.wrap.unwrapInflationary('0xAvatarAddress', BigInt(100));
189
+ ```
190
+
191
+ ### Profile Management
192
+
193
+ ```typescript
194
+ // Get profile by CID
195
+ const profile = await sdk.profiles.get('QmProfileCID');
196
+
197
+ // Create or update profile
198
+ await sdk.profiles.createOrUpdate({
199
+ name: 'Alice',
200
+ description: 'Developer'
201
+ });
202
+ ```
203
+
204
+ ## Implementation Status
205
+
206
+ ⚠️ **Note**: This package is currently a skeleton implementation. Most methods throw "Not yet implemented" errors and are marked with TODO comments. The purpose is to establish the API surface for future implementation.
207
+
208
+ ### Implemented Features
209
+
210
+ - ✅ SDK initialization with configuration
211
+ - ✅ Avatar retrieval (getAvatar)
212
+ - ✅ Profile retrieval by CID (via profiles package)
213
+
214
+ ### Not Yet Implemented
215
+
216
+ Most features are marked with TODO and will throw errors:
217
+
218
+ - Registration methods (asHuman, asOrganization, asGroup)
219
+ - Balance operations
220
+ - Transfer operations
221
+ - Trust operations
222
+ - Personal token minting
223
+ - Profile updates
224
+ - Group operations
225
+ - Token wrapping
226
+ - Event streaming
227
+
228
+ ## Architecture
229
+
230
+ The SDK package wraps and simplifies the following packages:
231
+
232
+ - `@aboutcircles/sdk-core` - Core contract interactions
233
+ - `@aboutcircles/sdk-rpc` - RPC client for Circles-specific methods
234
+ - `@aboutcircles/sdk-profiles` - Profile management
235
+ - `@aboutcircles/sdk-types` - TypeScript type definitions
236
+ - `@aboutcircles/sdk-utils` - Utility functions
237
+
238
+ ## Contributing
239
+
240
+ To implement a method:
241
+
242
+ 1. Remove the "TODO" comment
243
+ 2. Replace the `throw new Error('not yet implemented')` with actual implementation
244
+ 3. Use the underlying packages (core, rpc, profiles, etc.) to implement functionality
245
+ 4. Add tests and update documentation
246
+
247
+ ## License
248
+
249
+ MIT
package/dist/Sdk.d.ts ADDED
@@ -0,0 +1,282 @@
1
+ import type { Address, CirclesConfig, ContractRunner, Profile, TokenBalance, SortOrder } from '@aboutcircles/sdk-types';
2
+ import type { GroupTokenHolderRow } from '@aboutcircles/sdk-rpc';
3
+ import { Core } from '@aboutcircles/sdk-core';
4
+ import { CirclesRpc, PagedQuery } from '@aboutcircles/sdk-rpc';
5
+ import { HumanAvatar, OrganisationAvatar, BaseGroupAvatar } from './avatars';
6
+ import type { GroupType } from '@aboutcircles/sdk-types';
7
+ import type { CirclesData } from './types';
8
+ /**
9
+ * Simplified Circles SDK
10
+ * Provides a user-friendly API for non-crypto users with low entrance barrier
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const sdk = new Sdk();
15
+ *
16
+ * // Register as a human
17
+ * const avatar = await sdk.register.asHuman('0xInviterAddress', {
18
+ * name: 'Alice',
19
+ * description: 'Developer'
20
+ * });
21
+ *
22
+ * // Get an avatar
23
+ * const avatar = await sdk.getAvatar('0xAvatarAddress');
24
+ *
25
+ * // Transfer tokens
26
+ * await avatar.transfer.direct('0xRecipient', 100);
27
+ *
28
+ * // Mint personal tokens
29
+ * await avatar.personalToken.mint();
30
+ * ```
31
+ */
32
+ export declare class Sdk {
33
+ readonly circlesConfig: CirclesConfig;
34
+ readonly contractRunner?: ContractRunner;
35
+ readonly senderAddress?: Address;
36
+ readonly core: Core;
37
+ readonly rpc: CirclesRpc;
38
+ private readonly profilesClient;
39
+ readonly data: CirclesData;
40
+ /**
41
+ * Create a new Sdk instance
42
+ *
43
+ * @param config Circles configuration (defaults to Gnosis Chain mainnet)
44
+ * @param contractRunner Optional contract runner for executing transactions
45
+ * @throws Error if contractRunner is provided but doesn't support sendTransaction or has no address
46
+ */
47
+ constructor(config?: CirclesConfig, contractRunner?: ContractRunner);
48
+ /**
49
+ * Get an avatar by address
50
+ * Automatically detects the avatar type and returns the appropriate avatar instance
51
+ * @returns HumanAvatar, OrganisationAvatar, or BaseGroupAvatar depending on type
52
+ */
53
+ getAvatar(avatarAddress: Address): Promise<HumanAvatar | OrganisationAvatar | BaseGroupAvatar>;
54
+ /**
55
+ * Registration methods for creating new Circles identities
56
+ */
57
+ readonly register: {
58
+ /**
59
+ * Register as a human in the Circles ecosystem
60
+ *
61
+ * This function:
62
+ * 1. Checks for pending invitations from inviters in the InvitationEscrow
63
+ * 2. If invitations exist, redeems one to claim escrowed tokens
64
+ * 3. Otherwise, checks if the specified inviter has enough unwrapped CRC
65
+ * 4. Creates and uploads profile data to IPFS
66
+ * 5. Registers the human with the profile CID
67
+ * 6. Returns a HumanAvatar instance for the registered account
68
+ *
69
+ * Requirements:
70
+ * - Contract runner must be configured to execute transactions
71
+ * - Either: pending invitations from inviters, OR inviter has 96+ CRC unwrapped
72
+ *
73
+ * @param inviter Address of the inviting avatar (fallback if no invitations found)
74
+ * @param profile Profile data with name, description, etc.
75
+ * @returns HumanAvatar instance for the newly registered human
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * const avatar = await sdk.register.asHuman('0xInviter', {
80
+ * name: 'Alice',
81
+ * description: 'Developer'
82
+ * });
83
+ * ```
84
+ */
85
+ asHuman: (inviter: Address, profile: Profile | string) => Promise<HumanAvatar>;
86
+ /**
87
+ * Register as an organization
88
+ * Organizations can participate in Circles without minting personal tokens
89
+ * and do not require invitations to register
90
+ *
91
+ * @param profile Profile data for the organization or CID string
92
+ * @returns OrganisationAvatar instance for the newly registered organization
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * const orgAvatar = await sdk.register.asOrganization({
97
+ * name: 'My Organization',
98
+ * description: 'A Circles organization'
99
+ * });
100
+ * ```
101
+ */
102
+ asOrganization: (profile: Profile | string) => Promise<OrganisationAvatar>;
103
+ /**
104
+ * Register a base group
105
+ * Creates a new base group using the BaseGroupFactory and registers it in the Circles ecosystem
106
+ *
107
+ * @param owner The address that will own the newly created BaseGroup
108
+ * @param service The address of the service for the new BaseGroup
109
+ * @param feeCollection The address of the fee collection for the new BaseGroup
110
+ * @param initialConditions An array of initial condition addresses
111
+ * @param name The group name (must be 19 characters or fewer)
112
+ * @param symbol The group symbol (e.g., 'MYG')
113
+ * @param profile Profile data (name, description, images, etc.) or CID string
114
+ * @returns BaseGroupAvatar instance for the newly registered group
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * const groupAvatar = await sdk.register.asGroup(
119
+ * '0xOwnerAddress',
120
+ * '0xServiceAddress',
121
+ * '0xFeeCollectionAddress',
122
+ * [], // initial conditions
123
+ * 'My Group', // name
124
+ * 'MYG', // symbol
125
+ * {
126
+ * name: 'My Group',
127
+ * description: 'A Circles base group'
128
+ * }
129
+ * );
130
+ * ```
131
+ */
132
+ asGroup: (owner: Address, service: Address, feeCollection: Address, initialConditions: Address[], name: string, symbol: string, profile: Profile | string) => Promise<BaseGroupAvatar>;
133
+ };
134
+ /**
135
+ * Profile management methods
136
+ */
137
+ readonly profiles: {
138
+ /**
139
+ * Create and pin a profile to IPFS
140
+ * @param profile Profile data or CID string
141
+ * @returns CID of the pinned profile
142
+ */
143
+ create: (profile: Profile) => Promise<string>;
144
+ /**
145
+ * Get a profile by CID
146
+ * @param cid Content identifier
147
+ * @returns Profile data or undefined if not found
148
+ */
149
+ get: (cid: string) => Promise<Profile | undefined>;
150
+ };
151
+ /**
152
+ * Token utilities
153
+ */
154
+ readonly tokens: {
155
+ /**
156
+ * Get an inflationary wrapper for a token
157
+ * @param address Avatar address
158
+ * @returns The ERC20 inflationary wrapper address, or zero address if not deployed
159
+ */
160
+ getInflationaryWrapper: (address: Address) => Promise<Address>;
161
+ /**
162
+ * Get a demurraged wrapper for a token
163
+ * @param address Avatar address
164
+ * @returns The ERC20 demurraged wrapper address, or zero address if not deployed
165
+ */
166
+ getDemurragedWrapper: (address: Address) => Promise<Address>;
167
+ /**
168
+ * Get token holders for a specific token address with pagination
169
+ * @param tokenAddress The token address to query holders for
170
+ * @param limit Maximum number of results per page (default: 100)
171
+ * @param sortOrder Sort order for results (default: 'DESC' - highest balance first)
172
+ * @returns PagedQuery instance for token holders
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * const holdersQuery = sdk.tokens.getHolders('0x42cedde51198d1773590311e2a340dc06b24cb37', 10);
177
+ *
178
+ * while (await holdersQuery.queryNextPage()) {
179
+ * const page = holdersQuery.currentPage!;
180
+ * console.log(`Found ${page.size} holders`);
181
+ * page.results.forEach(holder => {
182
+ * console.log(`${holder.account}: ${holder.demurragedTotalBalance}`);
183
+ * });
184
+ * }
185
+ * ```
186
+ */
187
+ getHolders: (tokenAddress: Address, limit?: number, sortOrder?: SortOrder) => PagedQuery<import("@aboutcircles/sdk-types").TokenHolder>;
188
+ };
189
+ /**
190
+ * Group utilities
191
+ */
192
+ readonly groups: {
193
+ /**
194
+ * Get the type of a group
195
+ * @param avatar Group avatar address
196
+ * @returns Group type or undefined if not a group
197
+ */
198
+ getType: (avatar: Address) => Promise<GroupType | undefined>;
199
+ /**
200
+ * Get all members of a specific group using cursor-based pagination
201
+ *
202
+ * Returns a PagedQuery instance for iterating through members of the specified group,
203
+ * including membership details such as expiry time and when the membership was created.
204
+ *
205
+ * @param groupAddress The address of the group to query members for
206
+ * @param limit Number of members per page (default: 100)
207
+ * @param sortOrder Sort order for results (default: 'DESC')
208
+ * @returns PagedQuery instance for iterating through group members
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * const query = sdk.groups.getMembers('0xGroupAddress...');
213
+ *
214
+ * // Get first page
215
+ * await query.queryNextPage();
216
+ * console.log(`${query.currentPage.size} members in the group`);
217
+ *
218
+ * // Iterate through all members
219
+ * while (await query.queryNextPage()) {
220
+ * query.currentPage.results.forEach(membership => {
221
+ * console.log(`Member: ${membership.member}`);
222
+ * console.log(`Expiry: ${new Date(membership.expiryTime * 1000).toLocaleDateString()}`);
223
+ * });
224
+ * }
225
+ * ```
226
+ */
227
+ getMembers: (groupAddress: Address, limit?: number, sortOrder?: "ASC" | "DESC") => PagedQuery<import("@aboutcircles/sdk-types").GroupMembershipRow>;
228
+ /**
229
+ * Get collateral tokens in a group's treasury
230
+ *
231
+ * This convenience method fetches the treasury address of a group and returns
232
+ * all token balances held in the treasury.
233
+ *
234
+ * @param groupAddress The address of the group
235
+ * @returns Array of token balances in the treasury
236
+ *
237
+ * @example
238
+ * ```typescript
239
+ * // Get collateral tokens in a group treasury
240
+ * const collateral = await sdk.groups.getCollateral('0xGroupAddress...');
241
+ *
242
+ * collateral.forEach(balance => {
243
+ * console.log(`Token: ${balance.tokenAddress}`);
244
+ * console.log(`Balance: ${balance.circles} CRC`);
245
+ * });
246
+ * ```
247
+ */
248
+ getCollateral: (groupAddress: Address) => Promise<TokenBalance[]>;
249
+ /**
250
+ * Get all holders of a group token using cursor-based pagination
251
+ *
252
+ * Returns all avatars that hold the specified group token, ordered by balance (highest first),
253
+ * including balance amounts and ownership fractions.
254
+ *
255
+ * @param groupAddress The address of the group token
256
+ * @param limit Maximum number of holders to return (default: 100)
257
+ * @returns PagedQuery instance for iterating through group token holders
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * // Get all holders of a group token
262
+ * const query = sdk.groups.getHolders('0xGroupAddress...');
263
+ *
264
+ * // Get first page (ordered by balance DESC)
265
+ * await query.queryNextPage();
266
+ * console.log(`${query.currentPage.size} holders of this group token`);
267
+ *
268
+ * // Iterate through all holders
269
+ * while (await query.queryNextPage()) {
270
+ * query.currentPage.results.forEach(holder => {
271
+ * const balanceInCrc = Number(holder.totalBalance) / 1e18;
272
+ * console.log(`Holder: ${holder.holder}`);
273
+ * console.log(`Balance: ${balanceInCrc.toFixed(2)} CRC`);
274
+ * console.log(`Ownership: ${(holder.fractionOwnership * 100).toFixed(2)}%`);
275
+ * });
276
+ * }
277
+ * ```
278
+ */
279
+ getHolders: (groupAddress: Address, limit?: number) => PagedQuery<GroupTokenHolderRow>;
280
+ };
281
+ }
282
+ //# sourceMappingURL=Sdk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sdk.d.ts","sourceRoot":"","sources":["../src/Sdk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EACb,cAAc,EACd,OAAO,EAEP,YAAY,EACZ,SAAS,EAEV,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAiB,IAAI,EAAkC,MAAM,wBAAwB,CAAC;AAE7F,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAE/D,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAI7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG3C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,qBAAa,GAAG;IACd,SAAgB,aAAa,EAAE,aAAa,CAAC;IAC7C,SAAgB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChD,SAAgB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxC,SAAgB,IAAI,EAAE,IAAI,CAAC;IAC3B,SAAgB,GAAG,EAAE,UAAU,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAW;IAE1C,SAAgB,IAAI,EAAE,WAAW,CAU/B;IAEF;;;;;;OAMG;gBACS,MAAM,GAAE,aAAkC,EAAE,cAAc,CAAC,EAAE,cAAc;IAsBvF;;;;OAIG;IACG,SAAS,CAAC,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,GAAG,kBAAkB,GAAG,eAAe,CAAC;IAsBpG;;OAEG;IACH,SAAgB,QAAQ;QACtB;;;;;;;;;;;;;;;;;;;;;;;;;;WA0BG;2BAEQ,OAAO,WACP,OAAO,GAAG,MAAM,KACxB,OAAO,CAAC,WAAW,CAAC;QAqEvB;;;;;;;;;;;;;;;WAeG;kCAC6B,OAAO,GAAG,MAAM,KAAG,OAAO,CAAC,kBAAkB,CAAC;QAqD9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA4BG;yBAEM,OAAO,WACL,OAAO,iBACD,OAAO,qBACH,OAAO,EAAE,QACtB,MAAM,UACJ,MAAM,WACL,OAAO,GAAG,MAAM,KACxB,OAAO,CAAC,eAAe,CAAC;MAsF3B;IAEF;;OAEG;IACH,SAAgB,QAAQ;QACtB;;;;WAIG;0BACqB,OAAO,KAAG,OAAO,CAAC,MAAM,CAAC;QAIjD;;;;WAIG;mBACc,MAAM,KAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;MAGtD;IAEF;;OAEG;IACH,SAAgB,MAAM;QACpB;;;;WAIG;0CACqC,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;QAIlE;;;;WAIG;wCACmC,OAAO,KAAG,OAAO,CAAC,OAAO,CAAC;QAIhE;;;;;;;;;;;;;;;;;;;WAmBG;mCAEa,OAAO,UACd,MAAM,cACF,SAAS;MAItB;IAEF;;OAEG;IACH,SAAgB,MAAM;QACpB;;;;WAIG;0BACqB,OAAO,KAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;QAIhE;;;;;;;;;;;;;;;;;;;;;;;;;;;WA2BG;mCAEa,OAAO,UACd,MAAM,cACF,KAAK,GAAG,MAAM;QAK3B;;;;;;;;;;;;;;;;;;;WAmBG;sCACiC,OAAO,KAAG,OAAO,CAAC,YAAY,EAAE,CAAC;QAcrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA6BG;mCACwB,OAAO,UAAS,MAAM,KAAS,UAAU,CAAC,mBAAmB,CAAC;MAGzF;CACH"}