@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,644 @@
|
|
|
1
|
+
import { Observable as ObservableClass, CirclesRpc } from '@aboutcircles/sdk-rpc';
|
|
2
|
+
import { cidV0ToHex, bytesToHex, ValidationError } from '@aboutcircles/sdk-utils';
|
|
3
|
+
import { Profiles } from '@aboutcircles/sdk-profiles';
|
|
4
|
+
import { SdkError } from '../errors';
|
|
5
|
+
import { CirclesType, DemurrageCirclesContract, InflationaryCirclesContract } from '@aboutcircles/sdk-core';
|
|
6
|
+
import { encodeFunctionData } from 'viem';
|
|
7
|
+
import { TransferBuilder } from '@aboutcircles/sdk-transfers';
|
|
8
|
+
/**
|
|
9
|
+
* CommonAvatar abstract class
|
|
10
|
+
* Provides common functionality shared across all avatar types (Human, Organisation, Group)
|
|
11
|
+
*
|
|
12
|
+
* This class extracts common logic for:
|
|
13
|
+
* - Profile management (get, update, metadata)
|
|
14
|
+
* - Balance queries
|
|
15
|
+
* - Trust relationships
|
|
16
|
+
* - Transaction history
|
|
17
|
+
* - Event subscriptions
|
|
18
|
+
*/
|
|
19
|
+
export class CommonAvatar {
|
|
20
|
+
address;
|
|
21
|
+
avatarInfo;
|
|
22
|
+
core;
|
|
23
|
+
contractRunner;
|
|
24
|
+
events;
|
|
25
|
+
runner;
|
|
26
|
+
profiles;
|
|
27
|
+
rpc;
|
|
28
|
+
transferBuilder;
|
|
29
|
+
_cachedProfile;
|
|
30
|
+
_cachedProfileCid;
|
|
31
|
+
_eventSubscription;
|
|
32
|
+
constructor(address, core, contractRunner, avatarInfo) {
|
|
33
|
+
this.address = address;
|
|
34
|
+
this.core = core;
|
|
35
|
+
this.contractRunner = contractRunner;
|
|
36
|
+
this.avatarInfo = avatarInfo;
|
|
37
|
+
// Validate contract runner is available
|
|
38
|
+
if (!contractRunner) {
|
|
39
|
+
throw SdkError.notInitialized('ContractRunner');
|
|
40
|
+
}
|
|
41
|
+
if (!contractRunner.sendTransaction) {
|
|
42
|
+
throw SdkError.unsupportedOperation('sendTransaction', 'Contract runner does not support transaction sending');
|
|
43
|
+
}
|
|
44
|
+
this.runner = contractRunner;
|
|
45
|
+
// Initialize profiles client with the profile service URL from config
|
|
46
|
+
this.profiles = new Profiles(core.config.profileServiceUrl);
|
|
47
|
+
// Initialize RPC client
|
|
48
|
+
this.rpc = new CirclesRpc(core.config.circlesRpcUrl);
|
|
49
|
+
// Initialize transfer builder
|
|
50
|
+
this.transferBuilder = new TransferBuilder(core);
|
|
51
|
+
// Event subscription is optional - initialize with stub observable
|
|
52
|
+
const stub = ObservableClass.create();
|
|
53
|
+
this.events = stub.property;
|
|
54
|
+
}
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Common Balance Methods
|
|
57
|
+
// ============================================================================
|
|
58
|
+
balances = {
|
|
59
|
+
/**
|
|
60
|
+
* Get total balance across all tokens
|
|
61
|
+
*/
|
|
62
|
+
getTotal: async () => {
|
|
63
|
+
return await this.rpc.balance.getTotalBalance(this.address);
|
|
64
|
+
},
|
|
65
|
+
/**
|
|
66
|
+
* Get detailed token balances
|
|
67
|
+
*/
|
|
68
|
+
getTokenBalances: async () => {
|
|
69
|
+
return await this.rpc.balance.getTokenBalances(this.address);
|
|
70
|
+
},
|
|
71
|
+
/**
|
|
72
|
+
* Get total supply of this avatar's token
|
|
73
|
+
* Override this in subclasses if needed
|
|
74
|
+
*/
|
|
75
|
+
getTotalSupply: async () => {
|
|
76
|
+
throw SdkError.unsupportedOperation('getTotalSupply', 'This method is not yet implemented');
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Common Trust Methods
|
|
81
|
+
// ============================================================================
|
|
82
|
+
trust = {
|
|
83
|
+
/**
|
|
84
|
+
* Trust another avatar or multiple avatars
|
|
85
|
+
*
|
|
86
|
+
* When using Safe runner, all trust operations are executed atomically in a single transaction.
|
|
87
|
+
* When using EOA runner, only single avatars are supported (pass array length 1).
|
|
88
|
+
*
|
|
89
|
+
* @param avatar Single avatar address or array of avatar addresses
|
|
90
|
+
* @param expiry Trust expiry timestamp (in seconds since epoch). Defaults to max uint96 for indefinite trust
|
|
91
|
+
* @returns Transaction response
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* // Trust single avatar indefinitely
|
|
96
|
+
* await avatar.trust.add('0x123...');
|
|
97
|
+
*
|
|
98
|
+
* // Trust with custom expiry
|
|
99
|
+
* const oneYear = BigInt(Date.now() / 1000 + 31536000);
|
|
100
|
+
* await avatar.trust.add('0x123...', oneYear);
|
|
101
|
+
*
|
|
102
|
+
* // Trust multiple avatars (Safe only - throws error with EOA)
|
|
103
|
+
* await avatar.trust.add(['0x123...', '0x456...', '0x789...']);
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
add: async (avatar, expiry) => {
|
|
107
|
+
// Default to max uint96 for indefinite trust
|
|
108
|
+
const trustExpiry = expiry ?? BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFF');
|
|
109
|
+
// Prepare transaction(s)
|
|
110
|
+
const avatars = Array.isArray(avatar) ? avatar : [avatar];
|
|
111
|
+
if (avatars.length === 0) {
|
|
112
|
+
throw ValidationError.missingParameter('avatar');
|
|
113
|
+
}
|
|
114
|
+
// Create trust transactions for all avatars
|
|
115
|
+
const transactions = avatars.map((trustee) => this.core.hubV2.trust(trustee, trustExpiry));
|
|
116
|
+
// Send transactions to runner
|
|
117
|
+
return await this.runner.sendTransaction(transactions);
|
|
118
|
+
},
|
|
119
|
+
/**
|
|
120
|
+
* Remove trust from another avatar or multiple avatars
|
|
121
|
+
* This is done by setting the trust expiry to 0
|
|
122
|
+
*
|
|
123
|
+
* When using Safe runner, all operations are batched atomically.
|
|
124
|
+
* When using EOA runner, only single avatars are supported (pass array length 1).
|
|
125
|
+
*
|
|
126
|
+
* @param avatar Single avatar address or array of avatar addresses
|
|
127
|
+
* @returns Transaction response
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* // Remove trust from single avatar
|
|
132
|
+
* await avatar.trust.remove('0x123...');
|
|
133
|
+
*
|
|
134
|
+
* // Remove trust from multiple avatars (Safe only)
|
|
135
|
+
* await avatar.trust.remove(['0x123...', '0x456...', '0x789...']);
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
remove: async (avatar) => {
|
|
139
|
+
// Prepare transaction(s)
|
|
140
|
+
const avatars = Array.isArray(avatar) ? avatar : [avatar];
|
|
141
|
+
if (avatars.length === 0) {
|
|
142
|
+
throw ValidationError.missingParameter('avatar');
|
|
143
|
+
}
|
|
144
|
+
// Validate addresses
|
|
145
|
+
for (const addr of avatars) {
|
|
146
|
+
if (!addr || addr.length !== 42 || !addr.startsWith('0x')) {
|
|
147
|
+
throw ValidationError.invalidAddress(addr);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Untrust by setting expiry to 0
|
|
151
|
+
const untrustExpiry = BigInt(0);
|
|
152
|
+
// Create untrust transactions for all avatars
|
|
153
|
+
const transactions = avatars.map((trustee) => this.core.hubV2.trust(trustee, untrustExpiry));
|
|
154
|
+
// Send transactions to runner
|
|
155
|
+
return await this.runner.sendTransaction(transactions);
|
|
156
|
+
},
|
|
157
|
+
/**
|
|
158
|
+
* Check if this avatar trusts another avatar
|
|
159
|
+
* @param otherAvatar The avatar address to check
|
|
160
|
+
* @returns True if this avatar trusts the other avatar
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```typescript
|
|
164
|
+
* const trusting = await avatar.trust.isTrusting('0x123...');
|
|
165
|
+
* if (trusting) {
|
|
166
|
+
* console.log('You trust this avatar');
|
|
167
|
+
* }
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
isTrusting: async (otherAvatar) => {
|
|
171
|
+
return await this.core.hubV2.isTrusted(this.address, otherAvatar);
|
|
172
|
+
},
|
|
173
|
+
/**
|
|
174
|
+
* Check if another avatar trusts this avatar
|
|
175
|
+
* @param otherAvatar The avatar address to check
|
|
176
|
+
* @returns True if the other avatar trusts this avatar
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```typescript
|
|
180
|
+
* const trustedBy = await avatar.trust.isTrustedBy('0x123...');
|
|
181
|
+
* if (trustedBy) {
|
|
182
|
+
* console.log('This avatar trusts you');
|
|
183
|
+
* }
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
isTrustedBy: async (otherAvatar) => {
|
|
187
|
+
return await this.core.hubV2.isTrusted(otherAvatar, this.address);
|
|
188
|
+
},
|
|
189
|
+
/**
|
|
190
|
+
* Get all trust relations for this avatar
|
|
191
|
+
*/
|
|
192
|
+
getAll: async () => {
|
|
193
|
+
return await this.rpc.trust.getAggregatedTrustRelations(this.address);
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
// ============================================================================
|
|
197
|
+
// Common Profile Methods
|
|
198
|
+
// ============================================================================
|
|
199
|
+
profile = {
|
|
200
|
+
/**
|
|
201
|
+
* Get the profile for this avatar from IPFS
|
|
202
|
+
* Uses caching to avoid redundant fetches for the same CID
|
|
203
|
+
*
|
|
204
|
+
* @returns The profile data, or undefined if no profile is set or fetch fails
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```typescript
|
|
208
|
+
* const profile = await avatar.profile.get();
|
|
209
|
+
* if (profile) {
|
|
210
|
+
* console.log('Name:', profile.name);
|
|
211
|
+
* console.log('Description:', profile.description);
|
|
212
|
+
* }
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
get: async () => {
|
|
216
|
+
const profileCid = this.avatarInfo?.cidV0;
|
|
217
|
+
// Return cached profile if CID hasn't changed
|
|
218
|
+
if (this._cachedProfile && this._cachedProfileCid === profileCid) {
|
|
219
|
+
return this._cachedProfile;
|
|
220
|
+
}
|
|
221
|
+
if (!profileCid) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const profileData = await this.profiles.get(profileCid);
|
|
226
|
+
if (profileData) {
|
|
227
|
+
this._cachedProfile = profileData;
|
|
228
|
+
this._cachedProfileCid = profileCid;
|
|
229
|
+
return this._cachedProfile;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
console.warn(`Couldn't load profile for CID ${profileCid}`, e);
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
},
|
|
237
|
+
/**
|
|
238
|
+
* Update the profile for this avatar
|
|
239
|
+
* This will:
|
|
240
|
+
* 1. Pin the new profile data to IPFS via the profile service
|
|
241
|
+
* 2. Update the metadata digest in the name registry contract
|
|
242
|
+
*
|
|
243
|
+
* @param profile The profile data to update
|
|
244
|
+
* @returns The CID of the newly pinned profile
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```typescript
|
|
248
|
+
* const profile = {
|
|
249
|
+
* name: 'Alice',
|
|
250
|
+
* description: 'Hello, Circles!',
|
|
251
|
+
* avatarUrl: 'https://example.com/avatar.png'
|
|
252
|
+
* };
|
|
253
|
+
*
|
|
254
|
+
* const cid = await avatar.profile.update(profile);
|
|
255
|
+
* console.log('Profile updated with CID:', cid);
|
|
256
|
+
* ```
|
|
257
|
+
*/
|
|
258
|
+
update: async (profile) => {
|
|
259
|
+
// Step 1: Pin the profile to IPFS and get CID
|
|
260
|
+
const cid = await this.profiles.create(profile);
|
|
261
|
+
if (!cid) {
|
|
262
|
+
throw SdkError.configError('Profile service did not return a CID', { profile });
|
|
263
|
+
}
|
|
264
|
+
// Step 2: Update the metadata digest in the name registry
|
|
265
|
+
const updateReceipt = await this.profile.updateMetadata(cid);
|
|
266
|
+
if (!updateReceipt) {
|
|
267
|
+
throw SdkError.configError('Failed to update metadata digest in name registry', { cid });
|
|
268
|
+
}
|
|
269
|
+
// Update local avatar info if available
|
|
270
|
+
if (this.avatarInfo) {
|
|
271
|
+
this.avatarInfo.cidV0 = cid;
|
|
272
|
+
}
|
|
273
|
+
// Clear cache to force re-fetch
|
|
274
|
+
this._cachedProfile = undefined;
|
|
275
|
+
this._cachedProfileCid = undefined;
|
|
276
|
+
return cid;
|
|
277
|
+
},
|
|
278
|
+
/**
|
|
279
|
+
* Update the metadata digest (CID) in the name registry
|
|
280
|
+
* This updates the on-chain pointer to the profile data stored on IPFS
|
|
281
|
+
*
|
|
282
|
+
* @param cid The IPFS CIDv0 to set as the metadata digest (e.g., "QmXxxx...")
|
|
283
|
+
* @returns Transaction receipt
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```typescript
|
|
287
|
+
* const receipt = await avatar.profile.updateMetadata('QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG');
|
|
288
|
+
* console.log('Metadata updated, tx hash:', receipt.hash);
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
updateMetadata: async (cid) => {
|
|
292
|
+
// Convert CIDv0 (base58-encoded multihash) to bytes32 hex format
|
|
293
|
+
// This extracts the 32-byte SHA-256 digest from the CID
|
|
294
|
+
const cidHex = cidV0ToHex(cid);
|
|
295
|
+
const updateTx = this.core.nameRegistry.updateMetadataDigest(cidHex);
|
|
296
|
+
return await this.runner.sendTransaction([updateTx]);
|
|
297
|
+
},
|
|
298
|
+
/**
|
|
299
|
+
* Register a short name for this avatar using a specific nonce
|
|
300
|
+
* Short names are numeric identifiers that can be used instead of addresses
|
|
301
|
+
*
|
|
302
|
+
* @param nonce The nonce to use for generating the short name
|
|
303
|
+
* @returns Transaction receipt
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```typescript
|
|
307
|
+
* // Find available nonce first
|
|
308
|
+
* const [shortName, nonce] = await core.nameRegistry.searchShortName(avatar.address);
|
|
309
|
+
* console.log('Available short name:', shortName.toString());
|
|
310
|
+
*
|
|
311
|
+
* // Register it
|
|
312
|
+
* const receipt = await avatar.profile.registerShortName(Number(nonce));
|
|
313
|
+
* console.log('Short name registered, tx hash:', receipt.hash);
|
|
314
|
+
* ```
|
|
315
|
+
*/
|
|
316
|
+
registerShortName: async (nonce) => {
|
|
317
|
+
const registerTx = this.core.nameRegistry.registerShortNameWithNonce(BigInt(nonce));
|
|
318
|
+
return await this.runner.sendTransaction([registerTx]);
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
// ============================================================================
|
|
322
|
+
// Common History Methods
|
|
323
|
+
// ============================================================================
|
|
324
|
+
history = {
|
|
325
|
+
/**
|
|
326
|
+
* Get transaction history for this avatar using cursor-based pagination
|
|
327
|
+
* Returns incoming/outgoing transactions and minting events
|
|
328
|
+
*
|
|
329
|
+
* @param limit Number of transactions per page (default: 50)
|
|
330
|
+
* @param sortOrder Sort order for results (default: 'DESC')
|
|
331
|
+
* @returns PagedQuery instance for iterating through transactions
|
|
332
|
+
*
|
|
333
|
+
* @example
|
|
334
|
+
* ```typescript
|
|
335
|
+
* const query = avatar.history.getTransactions(20);
|
|
336
|
+
*
|
|
337
|
+
* // Get first page
|
|
338
|
+
* await query.queryNextPage();
|
|
339
|
+
* query.currentPage.results.forEach(tx => {
|
|
340
|
+
* console.log(`${tx.from} -> ${tx.to}: ${tx.circles} CRC`);
|
|
341
|
+
* });
|
|
342
|
+
*
|
|
343
|
+
* // Get next page if available
|
|
344
|
+
* if (query.currentPage.hasMore) {
|
|
345
|
+
* await query.queryNextPage();
|
|
346
|
+
* }
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
getTransactions: (limit = 50, sortOrder = 'DESC') => {
|
|
350
|
+
return this.rpc.transaction.getTransactionHistory(this.address, limit, sortOrder);
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
// ============================================================================
|
|
354
|
+
// Common Transfer Methods
|
|
355
|
+
// ============================================================================
|
|
356
|
+
transfer = {
|
|
357
|
+
/**
|
|
358
|
+
* Send Circles tokens directly to another address
|
|
359
|
+
* This is a simple direct transfer without pathfinding
|
|
360
|
+
*
|
|
361
|
+
* Supports both ERC1155 (personal/group tokens) and ERC20 (wrapped tokens) transfers.
|
|
362
|
+
* The token type is automatically detected and the appropriate transfer method is used.
|
|
363
|
+
*
|
|
364
|
+
* For transfers using pathfinding (which can use trust network and multiple token types),
|
|
365
|
+
* use transfer.advanced() instead.
|
|
366
|
+
*
|
|
367
|
+
* @param to Recipient address
|
|
368
|
+
* @param amount Amount to transfer (in atto-circles)
|
|
369
|
+
* @param tokenAddress Token address to transfer (defaults to sender's personal token)
|
|
370
|
+
* @param txData Optional transaction data (only used for ERC1155 transfers)
|
|
371
|
+
* @returns Transaction receipt
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* ```typescript
|
|
375
|
+
* // Send 100 of your personal CRC directly
|
|
376
|
+
* const receipt = await avatar.transfer.direct('0x123...', BigInt(100e18));
|
|
377
|
+
*
|
|
378
|
+
* // Send wrapped tokens
|
|
379
|
+
* const receipt = await avatar.transfer.direct('0x123...', BigInt(100e18), '0xWrappedTokenAddress...');
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
direct: async (to, amount, tokenAddress, txData) => {
|
|
383
|
+
// Validate inputs
|
|
384
|
+
if (!to || to.length !== 42 || !to.startsWith('0x')) {
|
|
385
|
+
throw ValidationError.invalidAddress(to);
|
|
386
|
+
}
|
|
387
|
+
if (amount <= 0n) {
|
|
388
|
+
throw ValidationError.invalidAmount(amount, 'Amount must be positive');
|
|
389
|
+
}
|
|
390
|
+
const token = tokenAddress || this.address;
|
|
391
|
+
// Validate token address if provided
|
|
392
|
+
if (tokenAddress && (!tokenAddress || tokenAddress.length !== 42 || !tokenAddress.startsWith('0x'))) {
|
|
393
|
+
throw ValidationError.invalidAddress(tokenAddress);
|
|
394
|
+
}
|
|
395
|
+
// Get token info to determine transfer type
|
|
396
|
+
const tokenInfo = await this.rpc.token.getTokenInfo(token);
|
|
397
|
+
if (!tokenInfo) {
|
|
398
|
+
throw SdkError.configError(`Token not found: ${token}`, { token });
|
|
399
|
+
}
|
|
400
|
+
// Define token type sets
|
|
401
|
+
const erc1155Types = new Set(['CrcV2_RegisterHuman', 'CrcV2_RegisterGroup']);
|
|
402
|
+
const erc20Types = new Set([
|
|
403
|
+
'CrcV2_ERC20WrapperDeployed_Demurraged',
|
|
404
|
+
'CrcV2_ERC20WrapperDeployed_Inflationary'
|
|
405
|
+
]);
|
|
406
|
+
// Route to appropriate transfer method based on token type
|
|
407
|
+
if (erc1155Types.has(tokenInfo.tokenType)) {
|
|
408
|
+
return await this._transferErc1155(token, to, amount, txData);
|
|
409
|
+
}
|
|
410
|
+
else if (erc20Types.has(tokenInfo.tokenType)) {
|
|
411
|
+
return await this._transferErc20(to, amount, token);
|
|
412
|
+
}
|
|
413
|
+
throw SdkError.unsupportedOperation('direct transfer', `Token type ${tokenInfo.tokenType} is not supported for direct transfers`);
|
|
414
|
+
},
|
|
415
|
+
/**
|
|
416
|
+
* Send tokens using pathfinding through the trust network
|
|
417
|
+
* This enables transfers even when you don't have the recipient's token
|
|
418
|
+
*
|
|
419
|
+
* @param to Recipient address
|
|
420
|
+
* @param amount Amount to transfer (in atto-circles or CRC)
|
|
421
|
+
* @param options Advanced transfer options (pathfinding parameters)
|
|
422
|
+
* @returns Transaction receipt
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* ```typescript
|
|
426
|
+
* // Send 100 CRC using pathfinding
|
|
427
|
+
* await avatar.transfer.advanced('0x123...', BigInt(100e18));
|
|
428
|
+
*
|
|
429
|
+
* // With custom options
|
|
430
|
+
* await avatar.transfer.advanced('0x123...', 100, {
|
|
431
|
+
* maxTransfers: 5,
|
|
432
|
+
* maxDistance: 3
|
|
433
|
+
* });
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
436
|
+
advanced: async (to, amount, options) => {
|
|
437
|
+
// Construct transfer using TransferBuilder
|
|
438
|
+
const transactions = await this.transferBuilder.constructAdvancedTransfer(this.address, to, amount, options);
|
|
439
|
+
// Execute the constructed transactions
|
|
440
|
+
return await this.runner.sendTransaction(transactions);
|
|
441
|
+
},
|
|
442
|
+
/**
|
|
443
|
+
* Get the maximum amount that can be transferred to an address using pathfinding
|
|
444
|
+
*
|
|
445
|
+
* @param to Recipient address
|
|
446
|
+
* @returns Maximum transferable amount (in atto-circles)
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```typescript
|
|
450
|
+
* const maxAmount = await avatar.transfer.getMaxAmount('0x123...');
|
|
451
|
+
* console.log(`Can transfer up to: ${maxAmount}`);
|
|
452
|
+
* ```
|
|
453
|
+
*/
|
|
454
|
+
getMaxAmount: async (to) => {
|
|
455
|
+
return await this.rpc.pathfinder.findMaxFlow({
|
|
456
|
+
from: this.address.toLowerCase(),
|
|
457
|
+
to: to.toLowerCase()
|
|
458
|
+
});
|
|
459
|
+
},
|
|
460
|
+
/**
|
|
461
|
+
* Get the maximum amount that can be transferred with custom pathfinding options
|
|
462
|
+
*
|
|
463
|
+
* @param to Recipient address
|
|
464
|
+
* @param options Pathfinding options (maxTransfers, maxDistance, etc.)
|
|
465
|
+
* @returns Maximum transferable amount (in atto-circles)
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```typescript
|
|
469
|
+
* const maxAmount = await avatar.transfer.getMaxAmountAdvanced('0x123...', {
|
|
470
|
+
* maxTransfers: 3,
|
|
471
|
+
* maxDistance: 2
|
|
472
|
+
* });
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
getMaxAmountAdvanced: async (to, options) => {
|
|
476
|
+
return await this.rpc.pathfinder.findMaxFlow({
|
|
477
|
+
from: this.address.toLowerCase(),
|
|
478
|
+
to: to.toLowerCase(),
|
|
479
|
+
...options
|
|
480
|
+
});
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
// ============================================================================
|
|
484
|
+
// Common Wrap Methods
|
|
485
|
+
// ============================================================================
|
|
486
|
+
wrap = {
|
|
487
|
+
/**
|
|
488
|
+
* Wrap personal CRC tokens as demurraged ERC20 tokens
|
|
489
|
+
*
|
|
490
|
+
* @param avatarAddress The avatar whose tokens to wrap
|
|
491
|
+
* @param amount Amount to wrap (in atto-circles)
|
|
492
|
+
* @returns Transaction receipt
|
|
493
|
+
*
|
|
494
|
+
* @example
|
|
495
|
+
* ```typescript
|
|
496
|
+
* // Wrap 100 CRC as demurraged ERC20
|
|
497
|
+
* const receipt = await avatar.wrap.asDemurraged(avatar.address, BigInt(100e18));
|
|
498
|
+
* ```
|
|
499
|
+
*/
|
|
500
|
+
asDemurraged: async (avatarAddress, amount) => {
|
|
501
|
+
const wrapTx = this.core.hubV2.wrap(avatarAddress, amount, CirclesType.Demurrage);
|
|
502
|
+
return await this.runner.sendTransaction([wrapTx]);
|
|
503
|
+
},
|
|
504
|
+
/**
|
|
505
|
+
* Wrap personal CRC tokens as inflationary ERC20 tokens
|
|
506
|
+
*
|
|
507
|
+
* @param avatarAddress The avatar whose tokens to wrap
|
|
508
|
+
* @param amount Amount to wrap (in atto-circles)
|
|
509
|
+
* @returns Transaction receipt
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* ```typescript
|
|
513
|
+
* // Wrap 100 CRC as inflationary ERC20
|
|
514
|
+
* const receipt = await avatar.wrap.asInflationary(avatar.address, BigInt(100e18));
|
|
515
|
+
* ```
|
|
516
|
+
*/
|
|
517
|
+
asInflationary: async (avatarAddress, amount) => {
|
|
518
|
+
const wrapTx = this.core.hubV2.wrap(avatarAddress, amount, CirclesType.Inflation);
|
|
519
|
+
return await this.runner.sendTransaction([wrapTx]);
|
|
520
|
+
},
|
|
521
|
+
/**
|
|
522
|
+
* Unwrap demurraged ERC20 tokens back to personal CRC
|
|
523
|
+
*
|
|
524
|
+
* @param tokenAddress The demurraged token address to unwrap
|
|
525
|
+
* @param amount Amount to unwrap (in atto-circles)
|
|
526
|
+
* @returns Transaction receipt
|
|
527
|
+
*
|
|
528
|
+
* @example
|
|
529
|
+
* ```typescript
|
|
530
|
+
* const receipt = await avatar.wrap.unwrapDemurraged('0xTokenAddress...', BigInt(100e18));
|
|
531
|
+
* ```
|
|
532
|
+
*/
|
|
533
|
+
unwrapDemurraged: async (tokenAddress, amount) => {
|
|
534
|
+
const demurrageContract = new DemurrageCirclesContract({
|
|
535
|
+
address: tokenAddress,
|
|
536
|
+
rpcUrl: this.core.rpcUrl
|
|
537
|
+
});
|
|
538
|
+
const unwrapTx = demurrageContract.unwrap(amount);
|
|
539
|
+
return await this.runner.sendTransaction([unwrapTx]);
|
|
540
|
+
},
|
|
541
|
+
/**
|
|
542
|
+
* Unwrap inflationary ERC20 tokens back to personal CRC
|
|
543
|
+
*
|
|
544
|
+
* @param tokenAddress The inflationary token address to unwrap
|
|
545
|
+
* @param amount Amount to unwrap (in atto-circles)
|
|
546
|
+
* @returns Transaction receipt
|
|
547
|
+
*
|
|
548
|
+
* @example
|
|
549
|
+
* ```typescript
|
|
550
|
+
* const receipt = await avatar.wrap.unwrapInflationary('0xTokenAddress...', BigInt(100e18));
|
|
551
|
+
* ```
|
|
552
|
+
*/
|
|
553
|
+
unwrapInflationary: async (tokenAddress, amount) => {
|
|
554
|
+
const inflationaryContract = new InflationaryCirclesContract({
|
|
555
|
+
address: tokenAddress,
|
|
556
|
+
rpcUrl: this.core.rpcUrl
|
|
557
|
+
});
|
|
558
|
+
const unwrapTx = inflationaryContract.unwrap(amount);
|
|
559
|
+
return await this.runner.sendTransaction([unwrapTx]);
|
|
560
|
+
},
|
|
561
|
+
};
|
|
562
|
+
// ============================================================================
|
|
563
|
+
// Common Event Subscription Methods
|
|
564
|
+
// ============================================================================
|
|
565
|
+
/**
|
|
566
|
+
* Subscribe to Circles events for this avatar
|
|
567
|
+
* Events are filtered to only include events related to this avatar's address
|
|
568
|
+
*
|
|
569
|
+
* @returns Promise that resolves when subscription is established
|
|
570
|
+
*
|
|
571
|
+
* @example
|
|
572
|
+
* ```typescript
|
|
573
|
+
* await avatar.subscribeToEvents();
|
|
574
|
+
*
|
|
575
|
+
* // Listen for events
|
|
576
|
+
* avatar.events.subscribe((event) => {
|
|
577
|
+
* console.log('Event received:', event.$event, event);
|
|
578
|
+
*
|
|
579
|
+
* if (event.$event === 'CrcV2_PersonalMint') {
|
|
580
|
+
* console.log('Minted:', event.amount);
|
|
581
|
+
* }
|
|
582
|
+
* });
|
|
583
|
+
* ```
|
|
584
|
+
*/
|
|
585
|
+
async subscribeToEvents() {
|
|
586
|
+
// Subscribe to events via RPC WebSocket
|
|
587
|
+
const observable = await this.rpc.client.subscribe(this.address);
|
|
588
|
+
this.events = observable;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Unsubscribe from events
|
|
592
|
+
* Cleans up the WebSocket connection and event listeners
|
|
593
|
+
*/
|
|
594
|
+
unsubscribeFromEvents() {
|
|
595
|
+
if (this._eventSubscription) {
|
|
596
|
+
this._eventSubscription();
|
|
597
|
+
this._eventSubscription = undefined;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
// ============================================================================
|
|
601
|
+
// Protected Helper Methods
|
|
602
|
+
// ============================================================================
|
|
603
|
+
/**
|
|
604
|
+
* Transfer ERC1155 tokens using safeTransferFrom
|
|
605
|
+
* @protected
|
|
606
|
+
*/
|
|
607
|
+
async _transferErc1155(tokenAddress, to, amount, txData) {
|
|
608
|
+
// Get the token ID for the token address
|
|
609
|
+
const tokenId = await this.core.hubV2.toTokenId(tokenAddress);
|
|
610
|
+
// Convert txData to hex string if provided, otherwise use empty hex
|
|
611
|
+
const data = txData ? bytesToHex(txData) : '0x';
|
|
612
|
+
// Create the safeTransferFrom transaction
|
|
613
|
+
const transferTx = this.core.hubV2.safeTransferFrom(this.address, to, tokenId, amount, data);
|
|
614
|
+
// Execute the transaction
|
|
615
|
+
return await this.runner.sendTransaction([transferTx]);
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Transfer ERC20 tokens using the standard transfer function
|
|
619
|
+
* @protected
|
|
620
|
+
*/
|
|
621
|
+
async _transferErc20(to, amount, tokenAddress) {
|
|
622
|
+
// Encode the ERC20 transfer function call
|
|
623
|
+
const data = encodeFunctionData({
|
|
624
|
+
abi: [{
|
|
625
|
+
type: 'function',
|
|
626
|
+
name: 'transfer',
|
|
627
|
+
inputs: [
|
|
628
|
+
{ name: 'to', type: 'address' },
|
|
629
|
+
{ name: 'value', type: 'uint256' }
|
|
630
|
+
],
|
|
631
|
+
outputs: [{ name: '', type: 'bool' }],
|
|
632
|
+
stateMutability: 'nonpayable',
|
|
633
|
+
}],
|
|
634
|
+
functionName: 'transfer',
|
|
635
|
+
args: [to, amount],
|
|
636
|
+
});
|
|
637
|
+
// Create and send the transaction
|
|
638
|
+
return await this.runner.sendTransaction([{
|
|
639
|
+
to: tokenAddress,
|
|
640
|
+
data,
|
|
641
|
+
value: 0n,
|
|
642
|
+
}]);
|
|
643
|
+
}
|
|
644
|
+
}
|