@explorins/pers-sdk 2.1.37 → 2.1.40
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/dist/business/api/business-api.d.ts +4 -0
- package/dist/business/api/business-api.d.ts.map +1 -1
- package/dist/business/index.d.ts +1 -1
- package/dist/business/index.d.ts.map +1 -1
- package/dist/business.cjs +1 -1
- package/dist/business.js +1 -1
- package/dist/chunks/{base-token-service-BGuuZX4b.js → base-token-service-N1gwRD8N.js} +2 -2
- package/dist/chunks/{base-token-service-BGuuZX4b.js.map → base-token-service-N1gwRD8N.js.map} +1 -1
- package/dist/chunks/{base-token-service-DSye0WD2.cjs → base-token-service-W7TU23qU.cjs} +2 -2
- package/dist/chunks/{base-token-service-DSye0WD2.cjs.map → base-token-service-W7TU23qU.cjs.map} +1 -1
- package/dist/chunks/{business-membership-service-B9ItWZ2_.cjs → business-membership-service-Dkb780NX.cjs} +21 -1
- package/dist/chunks/business-membership-service-Dkb780NX.cjs.map +1 -0
- package/dist/chunks/{business-membership-service-CPcE-AW0.js → business-membership-service-NLoqoFpG.js} +21 -1
- package/dist/chunks/business-membership-service-NLoqoFpG.js.map +1 -0
- package/dist/chunks/{pers-sdk-DemghJ3a.cjs → pers-sdk-CBvzmlL_.cjs} +158 -77
- package/dist/chunks/pers-sdk-CBvzmlL_.cjs.map +1 -0
- package/dist/chunks/{pers-sdk-Dds2lB27.js → pers-sdk-DuDWwRWC.js} +158 -77
- package/dist/chunks/pers-sdk-DuDWwRWC.js.map +1 -0
- package/dist/chunks/{redemption-service-Dc_0Kzd0.cjs → redemption-service-KamEndzB.cjs} +7 -1
- package/dist/chunks/{redemption-service-Dc_0Kzd0.cjs.map → redemption-service-KamEndzB.cjs.map} +1 -1
- package/dist/chunks/{redemption-service-DWhZgrZT.js → redemption-service-w0GMdF0m.js} +7 -1
- package/dist/chunks/{redemption-service-DWhZgrZT.js.map → redemption-service-w0GMdF0m.js.map} +1 -1
- package/dist/chunks/{token-service-qRSYG9uT.js → token-service-BcuDj292.js} +79 -14
- package/dist/chunks/token-service-BcuDj292.js.map +1 -0
- package/dist/chunks/{token-service-BJqu5Xap.cjs → token-service-CnnrCOsg.cjs} +79 -14
- package/dist/chunks/token-service-CnnrCOsg.cjs.map +1 -0
- package/dist/chunks/{web3-manager-Dvcq4xmn.js → web3-manager-B-IsluxI.js} +770 -34
- package/dist/chunks/web3-manager-B-IsluxI.js.map +1 -0
- package/dist/chunks/{web3-manager-C-JflQ86.cjs → web3-manager-NJaeBrci.cjs} +770 -32
- package/dist/chunks/web3-manager-NJaeBrci.cjs.map +1 -0
- package/dist/core/auth/refresh-manager.d.ts +12 -2
- package/dist/core/auth/refresh-manager.d.ts.map +1 -1
- package/dist/core/auth/services/auth-service.d.ts +4 -1
- package/dist/core/auth/services/auth-service.d.ts.map +1 -1
- package/dist/core/pers-api-client.d.ts.map +1 -1
- package/dist/core.cjs +4 -4
- package/dist/core.js +4 -4
- package/dist/index.cjs +5 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/managers/redemption-manager.d.ts +13 -0
- package/dist/managers/redemption-manager.d.ts.map +1 -1
- package/dist/managers/token-manager.d.ts +66 -40
- package/dist/managers/token-manager.d.ts.map +1 -1
- package/dist/managers/web3-manager.d.ts +69 -1
- package/dist/managers/web3-manager.d.ts.map +1 -1
- package/dist/node.cjs +4 -4
- package/dist/node.js +4 -4
- package/dist/package.json +3 -3
- package/dist/redemption/services/redemption-service.d.ts +4 -0
- package/dist/redemption/services/redemption-service.d.ts.map +1 -1
- package/dist/redemption.cjs +1 -1
- package/dist/redemption.js +1 -1
- package/dist/token/api/token-api.d.ts +50 -12
- package/dist/token/api/token-api.d.ts.map +1 -1
- package/dist/token/index.d.ts +1 -1
- package/dist/token/index.d.ts.map +1 -1
- package/dist/token/services/token-service.d.ts +40 -9
- package/dist/token/services/token-service.d.ts.map +1 -1
- package/dist/token.cjs +2 -2
- package/dist/token.js +2 -2
- package/dist/trigger-source/api/trigger-source-api.d.ts +9 -10
- package/dist/trigger-source/api/trigger-source-api.d.ts.map +1 -1
- package/dist/trigger-source/index.d.ts +1 -0
- package/dist/trigger-source/index.d.ts.map +1 -1
- package/dist/trigger-source/models/index.d.ts +5 -13
- package/dist/trigger-source/models/index.d.ts.map +1 -1
- package/dist/trigger-source/services/trigger-source-service.d.ts +3 -11
- package/dist/trigger-source/services/trigger-source-service.d.ts.map +1 -1
- package/dist/trigger-source.cjs +20 -1
- package/dist/trigger-source.cjs.map +1 -1
- package/dist/trigger-source.js +20 -1
- package/dist/trigger-source.js.map +1 -1
- package/dist/web3/domain/services/balance-manager.d.ts +160 -0
- package/dist/web3/domain/services/balance-manager.d.ts.map +1 -0
- package/dist/web3/domain/services/index.d.ts +2 -0
- package/dist/web3/domain/services/index.d.ts.map +1 -1
- package/dist/web3/domain/services/token-collection-manager.d.ts +155 -0
- package/dist/web3/domain/services/token-collection-manager.d.ts.map +1 -0
- package/dist/web3/domain/services/token-domain.service.d.ts.map +1 -1
- package/dist/web3/index.d.ts +1 -0
- package/dist/web3/index.d.ts.map +1 -1
- package/dist/web3/infrastructure/api/web3-api.d.ts +16 -0
- package/dist/web3/infrastructure/api/web3-api.d.ts.map +1 -1
- package/dist/web3-manager.cjs +1 -1
- package/dist/web3-manager.js +1 -1
- package/dist/web3.cjs +4 -2
- package/dist/web3.cjs.map +1 -1
- package/dist/web3.js +2 -2
- package/package.json +3 -3
- package/dist/chunks/business-membership-service-B9ItWZ2_.cjs.map +0 -1
- package/dist/chunks/business-membership-service-CPcE-AW0.js.map +0 -1
- package/dist/chunks/pers-sdk-Dds2lB27.js.map +0 -1
- package/dist/chunks/pers-sdk-DemghJ3a.cjs.map +0 -1
- package/dist/chunks/token-service-BJqu5Xap.cjs.map +0 -1
- package/dist/chunks/token-service-qRSYG9uT.js.map +0 -1
- package/dist/chunks/web3-manager-C-JflQ86.cjs.map +0 -1
- package/dist/chunks/web3-manager-Dvcq4xmn.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { convertAbiToInterface, createSafeContract, getAccountTokenBalance, getTokenUri, getTokenOfOwnerByIndex } from '@explorins/web3-ts/ethers';
|
|
1
|
+
import { convertAbiToInterface, createSafeContract, getAccountTokenBalance, getBatchBalancesWithFallback, getTokenUri, getTokenOfOwnerByIndex } from '@explorins/web3-ts/ethers';
|
|
2
2
|
import { g as globalCacheService, C as CacheTTL } from './index--OssIds0.js';
|
|
3
3
|
import { a as Web3ChainService, W as Web3ChainApi } from './web3-chain-service-BWRmwmmJ.js';
|
|
4
4
|
import '@explorins/web3-types';
|
|
@@ -74,7 +74,10 @@ class TokenDomainService {
|
|
|
74
74
|
try {
|
|
75
75
|
const abi = convertAbiToInterface(params.abi);
|
|
76
76
|
const analysis = this.contractService.analyzeContract(abi);
|
|
77
|
-
|
|
77
|
+
// ERC-1155 balanceOfBatch can handle large batches efficiently (1 RPC call)
|
|
78
|
+
// Default to 100 for ERC-1155, 20 for ERC-721 (which has no batch function)
|
|
79
|
+
const defaultBatchSize = analysis.isERC1155 ? 100 : 20;
|
|
80
|
+
const batchSize = Math.min(params.batchSize || defaultBatchSize, analysis.isERC1155 ? 200 : 50);
|
|
78
81
|
// ERC-1155: Requires specific token IDs
|
|
79
82
|
if (analysis.isERC1155) {
|
|
80
83
|
if (!params.tokenIds?.length) {
|
|
@@ -128,31 +131,44 @@ class TokenDomainService {
|
|
|
128
131
|
async processTokenBatch(params, tokenIds, batchSize, tokenStandard) {
|
|
129
132
|
const tokens = [];
|
|
130
133
|
const errors = [];
|
|
134
|
+
const abi = convertAbiToInterface(params.abi);
|
|
131
135
|
// Process in batches
|
|
132
136
|
for (let i = 0; i < tokenIds.length; i += batchSize) {
|
|
133
137
|
const batch = tokenIds.slice(i, i + batchSize);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
138
|
+
let validTokens;
|
|
139
|
+
if (tokenStandard === 'ERC-721') {
|
|
140
|
+
// For ERC-721: Skip balance check, we know ownership from enumeration or explicit tokenIds
|
|
141
|
+
validTokens = batch.map(tokenId => ({
|
|
142
|
+
tokenId,
|
|
143
|
+
balance: 1,
|
|
144
|
+
hasBalance: true,
|
|
145
|
+
metadata: null
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// For ERC-1155: Use native balanceOfBatch (1 RPC call for entire batch)
|
|
150
|
+
const batchResults = await this.web3Api.getBatchTokenBalances({
|
|
151
|
+
accountAddress: params.accountAddress,
|
|
152
|
+
contractAddress: params.contractAddress,
|
|
153
|
+
abi,
|
|
154
|
+
tokenIds: batch,
|
|
155
|
+
chainId: params.chainId,
|
|
156
|
+
isERC1155: true
|
|
157
|
+
});
|
|
158
|
+
// Convert batch results to TokenBalance format
|
|
159
|
+
validTokens = batchResults
|
|
160
|
+
.filter(r => r.success && r.balance > 0n)
|
|
161
|
+
.map(r => ({
|
|
162
|
+
tokenId: r.tokenId,
|
|
163
|
+
balance: Number(r.balance),
|
|
164
|
+
hasBalance: true,
|
|
165
|
+
metadata: null
|
|
166
|
+
}));
|
|
167
|
+
// Track errors
|
|
168
|
+
batchResults.filter(r => !r.success).forEach(r => {
|
|
169
|
+
errors.push(`${r.tokenId}: ${r.error}`);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
156
172
|
// Step 2: Get metadata for tokens that have balance
|
|
157
173
|
if (validTokens.length > 0) {
|
|
158
174
|
const metadataPromises = validTokens.map(token => this.getTokenMetadata({
|
|
@@ -276,6 +292,627 @@ class ContractDomainService {
|
|
|
276
292
|
}
|
|
277
293
|
}
|
|
278
294
|
|
|
295
|
+
/**
|
|
296
|
+
* TokenCollectionManager - Auto-refresh token collections on wallet events
|
|
297
|
+
*
|
|
298
|
+
* This manager provides:
|
|
299
|
+
* 1. Cached token collection storage with automatic refresh
|
|
300
|
+
* 2. Auto-subscription to wallet events (transfers, mints, burns)
|
|
301
|
+
* 3. Targeted invalidation based on contract address
|
|
302
|
+
* 4. Subscriber pattern for reactive updates
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```typescript
|
|
306
|
+
* // Get from SDK
|
|
307
|
+
* const manager = sdk.tokens.collectionManager;
|
|
308
|
+
*
|
|
309
|
+
* // Subscribe to collection updates
|
|
310
|
+
* const unsubscribe = manager.subscribe((collection, contractAddress) => {
|
|
311
|
+
* console.log(`Collection updated for ${contractAddress}`);
|
|
312
|
+
* updateUI(collection);
|
|
313
|
+
* });
|
|
314
|
+
*
|
|
315
|
+
* // Fetch collection (auto-cached, auto-refreshed on wallet events)
|
|
316
|
+
* const collection = await manager.getCollection({
|
|
317
|
+
* accountAddress: '0x...',
|
|
318
|
+
* contractAddress: '0x...',
|
|
319
|
+
* abi: contractAbi,
|
|
320
|
+
* chainId: 39123,
|
|
321
|
+
* tokenIds: ['1', '2', '3']
|
|
322
|
+
* });
|
|
323
|
+
*
|
|
324
|
+
* // Cleanup
|
|
325
|
+
* unsubscribe();
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
328
|
+
/**
|
|
329
|
+
* Manages token collections with automatic refresh on wallet events
|
|
330
|
+
*/
|
|
331
|
+
class TokenCollectionManager {
|
|
332
|
+
constructor(tokenService, eventEmitter, config) {
|
|
333
|
+
this.tokenService = tokenService;
|
|
334
|
+
this.eventEmitter = eventEmitter;
|
|
335
|
+
this.collections = new Map();
|
|
336
|
+
this.handlers = new Set();
|
|
337
|
+
this.pendingRefresh = new Map();
|
|
338
|
+
this.config = {
|
|
339
|
+
autoRefreshOnWalletEvents: config?.autoRefreshOnWalletEvents ?? true,
|
|
340
|
+
eventDebounceMs: config?.eventDebounceMs ?? 500,
|
|
341
|
+
debug: config?.debug ?? false
|
|
342
|
+
};
|
|
343
|
+
if (this.config.autoRefreshOnWalletEvents && eventEmitter) {
|
|
344
|
+
this.setupEventSubscription();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Subscribe to wallet events for auto-refresh
|
|
349
|
+
*/
|
|
350
|
+
setupEventSubscription() {
|
|
351
|
+
if (!this.eventEmitter) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
this.eventUnsubscribe = this.eventEmitter.subscribe((event) => {
|
|
355
|
+
const contractAddress = event.details?.contractAddress;
|
|
356
|
+
this.handleWalletEvent(contractAddress, event.type);
|
|
357
|
+
}, { domains: ['wallet'] });
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Get a token collection (cached, auto-refreshed on wallet events)
|
|
361
|
+
*
|
|
362
|
+
* @param params - Collection parameters
|
|
363
|
+
* @returns Promise resolving to the token collection
|
|
364
|
+
*/
|
|
365
|
+
async getCollection(params) {
|
|
366
|
+
const key = this.getCacheKey(params);
|
|
367
|
+
const cached = this.collections.get(key);
|
|
368
|
+
// Return cached if available and not stale
|
|
369
|
+
if (cached?.lastCollection) {
|
|
370
|
+
this.log(`Cache HIT for ${key}`);
|
|
371
|
+
return cached.lastCollection;
|
|
372
|
+
}
|
|
373
|
+
this.log(`Cache MISS for ${key}, fetching...`);
|
|
374
|
+
return this.fetchAndCache(params);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Force refresh a specific collection
|
|
378
|
+
*
|
|
379
|
+
* @param contractAddress - Contract address to refresh
|
|
380
|
+
* @param accountAddress - Account address to refresh
|
|
381
|
+
* @returns Promise that resolves when refresh is complete
|
|
382
|
+
*/
|
|
383
|
+
async refreshCollection(contractAddress, accountAddress) {
|
|
384
|
+
const toRefresh = [];
|
|
385
|
+
for (const [key, entry] of this.collections) {
|
|
386
|
+
if (entry.params.contractAddress === contractAddress) {
|
|
387
|
+
if (!accountAddress || entry.params.accountAddress === accountAddress) {
|
|
388
|
+
toRefresh.push(key);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
await Promise.all(toRefresh.map(key => {
|
|
393
|
+
const entry = this.collections.get(key);
|
|
394
|
+
if (entry) {
|
|
395
|
+
return this.refreshEntry(key, entry, 'refresh');
|
|
396
|
+
}
|
|
397
|
+
}));
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Subscribe to collection change events
|
|
401
|
+
*
|
|
402
|
+
* @param handler - Callback when any tracked collection changes
|
|
403
|
+
* @returns Unsubscribe function
|
|
404
|
+
*/
|
|
405
|
+
subscribe(handler) {
|
|
406
|
+
this.handlers.add(handler);
|
|
407
|
+
return () => this.handlers.delete(handler);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Get all currently tracked collections
|
|
411
|
+
*/
|
|
412
|
+
getTrackedCollections() {
|
|
413
|
+
return Array.from(this.collections.entries()).map(([_, entry]) => ({
|
|
414
|
+
contractAddress: entry.params.contractAddress,
|
|
415
|
+
accountAddress: entry.params.accountAddress,
|
|
416
|
+
collection: entry.lastCollection
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Check if a collection is currently being tracked
|
|
421
|
+
*/
|
|
422
|
+
isTracking(contractAddress, accountAddress) {
|
|
423
|
+
const key = `${contractAddress}:${accountAddress}`;
|
|
424
|
+
return this.collections.has(key);
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Clear tracking for a specific collection
|
|
428
|
+
*/
|
|
429
|
+
untrack(contractAddress, accountAddress) {
|
|
430
|
+
if (accountAddress) {
|
|
431
|
+
const key = `${contractAddress}:${accountAddress}`;
|
|
432
|
+
this.collections.delete(key);
|
|
433
|
+
this.log(`Untracked ${key}`);
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
// Untrack all for this contract
|
|
437
|
+
for (const key of this.collections.keys()) {
|
|
438
|
+
if (key.startsWith(`${contractAddress}:`)) {
|
|
439
|
+
this.collections.delete(key);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
this.log(`Untracked all collections for ${contractAddress}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Clear all tracked collections
|
|
447
|
+
*/
|
|
448
|
+
clearAll() {
|
|
449
|
+
this.collections.clear();
|
|
450
|
+
this.log('Cleared all tracked collections');
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Handle wallet events (with debouncing)
|
|
454
|
+
*/
|
|
455
|
+
handleWalletEvent(contractAddress, eventType) {
|
|
456
|
+
// Determine which collections to refresh
|
|
457
|
+
const keysToRefresh = [];
|
|
458
|
+
for (const [key, entry] of this.collections) {
|
|
459
|
+
// If no specific contract, refresh all; otherwise match contract
|
|
460
|
+
if (!contractAddress || entry.params.contractAddress.toLowerCase() === contractAddress.toLowerCase()) {
|
|
461
|
+
keysToRefresh.push(key);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (keysToRefresh.length === 0) {
|
|
465
|
+
this.log(`No tracked collections affected by event for ${contractAddress || 'all contracts'}`);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
// Debounce refresh per key
|
|
469
|
+
for (const key of keysToRefresh) {
|
|
470
|
+
const existing = this.pendingRefresh.get(key);
|
|
471
|
+
if (existing) {
|
|
472
|
+
clearTimeout(existing);
|
|
473
|
+
}
|
|
474
|
+
const timeout = setTimeout(async () => {
|
|
475
|
+
this.pendingRefresh.delete(key);
|
|
476
|
+
const entry = this.collections.get(key);
|
|
477
|
+
if (entry) {
|
|
478
|
+
this.log(`Refreshing ${key} due to wallet event: ${eventType}`);
|
|
479
|
+
await this.refreshEntry(key, entry, eventType);
|
|
480
|
+
}
|
|
481
|
+
}, this.config.eventDebounceMs);
|
|
482
|
+
this.pendingRefresh.set(key, timeout);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Fetch and cache a collection
|
|
487
|
+
*/
|
|
488
|
+
async fetchAndCache(params) {
|
|
489
|
+
const key = this.getCacheKey(params);
|
|
490
|
+
// Mark as refreshing to prevent duplicate fetches
|
|
491
|
+
const existing = this.collections.get(key);
|
|
492
|
+
if (existing?.refreshing) {
|
|
493
|
+
// Wait for existing refresh to complete
|
|
494
|
+
return existing.lastCollection || this.emptyCollection(params);
|
|
495
|
+
}
|
|
496
|
+
this.collections.set(key, {
|
|
497
|
+
params,
|
|
498
|
+
lastCollection: null,
|
|
499
|
+
timestamp: Date.now(),
|
|
500
|
+
refreshing: true
|
|
501
|
+
});
|
|
502
|
+
try {
|
|
503
|
+
const collection = await this.tokenService.getTokenCollection(params);
|
|
504
|
+
this.collections.set(key, {
|
|
505
|
+
params,
|
|
506
|
+
lastCollection: collection,
|
|
507
|
+
timestamp: Date.now(),
|
|
508
|
+
refreshing: false
|
|
509
|
+
});
|
|
510
|
+
this.log(`Cached collection for ${key}: ${collection.tokensRetrieved} tokens`);
|
|
511
|
+
return collection;
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
// Remove failed entry
|
|
515
|
+
this.collections.delete(key);
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Refresh a cached entry and notify handlers
|
|
521
|
+
*/
|
|
522
|
+
async refreshEntry(key, entry, source) {
|
|
523
|
+
if (entry.refreshing) {
|
|
524
|
+
this.log(`Skipping refresh for ${key} - already refreshing`);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
entry.refreshing = true;
|
|
528
|
+
try {
|
|
529
|
+
const newCollection = await this.tokenService.getTokenCollection(entry.params);
|
|
530
|
+
entry.lastCollection = newCollection;
|
|
531
|
+
entry.timestamp = Date.now();
|
|
532
|
+
entry.refreshing = false;
|
|
533
|
+
this.log(`Refreshed ${key}: ${newCollection.tokensRetrieved} tokens`);
|
|
534
|
+
// Notify subscribers
|
|
535
|
+
for (const handler of this.handlers) {
|
|
536
|
+
try {
|
|
537
|
+
handler(newCollection, entry.params.contractAddress, {
|
|
538
|
+
type: source === 'refresh' ? 'refresh' : 'wallet_event',
|
|
539
|
+
source
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
catch (handlerError) {
|
|
543
|
+
console.error('[TokenCollectionManager] Handler error:', handlerError);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
entry.refreshing = false;
|
|
549
|
+
console.error(`[TokenCollectionManager] Refresh failed for ${key}:`, error);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Generate cache key from params
|
|
554
|
+
*/
|
|
555
|
+
getCacheKey(params) {
|
|
556
|
+
return `${params.contractAddress}:${params.accountAddress}`;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Create empty collection placeholder
|
|
560
|
+
*/
|
|
561
|
+
emptyCollection(params) {
|
|
562
|
+
return {
|
|
563
|
+
accountAddress: params.accountAddress,
|
|
564
|
+
contractAddress: params.contractAddress,
|
|
565
|
+
totalBalance: 0,
|
|
566
|
+
tokensRetrieved: 0,
|
|
567
|
+
tokens: [],
|
|
568
|
+
note: 'Loading...'
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Debug logging
|
|
573
|
+
*/
|
|
574
|
+
log(message, data) {
|
|
575
|
+
if (this.config.debug) {
|
|
576
|
+
console.log(`[TokenCollectionManager] ${message}`, data || '');
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Cleanup resources
|
|
581
|
+
*/
|
|
582
|
+
destroy() {
|
|
583
|
+
// Clear pending refreshes
|
|
584
|
+
for (const timeout of this.pendingRefresh.values()) {
|
|
585
|
+
clearTimeout(timeout);
|
|
586
|
+
}
|
|
587
|
+
this.pendingRefresh.clear();
|
|
588
|
+
// Unsubscribe from events
|
|
589
|
+
this.eventUnsubscribe?.();
|
|
590
|
+
// Clear handlers and collections
|
|
591
|
+
this.handlers.clear();
|
|
592
|
+
this.collections.clear();
|
|
593
|
+
this.log('Destroyed');
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* BalanceManager - Auto-refresh ERC-20 token balances on wallet events
|
|
599
|
+
*
|
|
600
|
+
* This manager provides:
|
|
601
|
+
* 1. Cached balance storage with automatic refresh
|
|
602
|
+
* 2. Auto-subscription to wallet events (transfers)
|
|
603
|
+
* 3. Targeted invalidation based on contract address
|
|
604
|
+
* 4. Subscriber pattern for reactive updates
|
|
605
|
+
*
|
|
606
|
+
* @example
|
|
607
|
+
* ```typescript
|
|
608
|
+
* // Get from SDK
|
|
609
|
+
* const manager = sdk.tokens.balanceManager;
|
|
610
|
+
*
|
|
611
|
+
* // Subscribe to balance updates
|
|
612
|
+
* const unsubscribe = manager.subscribe((balance, contractAddress) => {
|
|
613
|
+
* console.log(`Balance updated for ${contractAddress}: ${balance}`);
|
|
614
|
+
* updateUI(balance);
|
|
615
|
+
* });
|
|
616
|
+
*
|
|
617
|
+
* // Fetch balance (auto-cached, auto-refreshed on wallet events)
|
|
618
|
+
* const balance = await manager.getBalance({
|
|
619
|
+
* accountAddress: '0x...',
|
|
620
|
+
* contractAddress: '0x...',
|
|
621
|
+
* abi: contractAbi,
|
|
622
|
+
* chainId: 39123
|
|
623
|
+
* });
|
|
624
|
+
*
|
|
625
|
+
* // Manual refresh
|
|
626
|
+
* await manager.refreshBalance(contractAddress);
|
|
627
|
+
*
|
|
628
|
+
* // Cleanup
|
|
629
|
+
* unsubscribe();
|
|
630
|
+
* ```
|
|
631
|
+
*/
|
|
632
|
+
/**
|
|
633
|
+
* Manages ERC-20 token balances with automatic refresh on wallet events
|
|
634
|
+
*/
|
|
635
|
+
class BalanceManager {
|
|
636
|
+
constructor(tokenService, eventEmitter, config) {
|
|
637
|
+
this.tokenService = tokenService;
|
|
638
|
+
this.eventEmitter = eventEmitter;
|
|
639
|
+
this.balances = new Map();
|
|
640
|
+
this.handlers = new Set();
|
|
641
|
+
this.pendingRefresh = new Map();
|
|
642
|
+
this.config = {
|
|
643
|
+
autoRefreshOnWalletEvents: config?.autoRefreshOnWalletEvents ?? true,
|
|
644
|
+
eventDebounceMs: config?.eventDebounceMs ?? 500,
|
|
645
|
+
debug: config?.debug ?? false
|
|
646
|
+
};
|
|
647
|
+
if (this.config.autoRefreshOnWalletEvents && eventEmitter) {
|
|
648
|
+
this.setupEventSubscription();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Subscribe to wallet events for auto-refresh
|
|
653
|
+
*/
|
|
654
|
+
setupEventSubscription() {
|
|
655
|
+
if (!this.eventEmitter) {
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
this.eventUnsubscribe = this.eventEmitter.subscribe((event) => {
|
|
659
|
+
const contractAddress = event.details?.contractAddress;
|
|
660
|
+
this.handleWalletEvent(contractAddress, event.type);
|
|
661
|
+
}, { domains: ['wallet'] });
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Get a token balance (cached, auto-refreshed on wallet events)
|
|
665
|
+
*
|
|
666
|
+
* @param params - Balance parameters
|
|
667
|
+
* @returns Promise resolving to the token balance
|
|
668
|
+
*/
|
|
669
|
+
async getBalance(params) {
|
|
670
|
+
const key = this.getCacheKey(params);
|
|
671
|
+
const cached = this.balances.get(key);
|
|
672
|
+
// Return cached if available and not stale
|
|
673
|
+
if (cached?.lastBalance) {
|
|
674
|
+
this.log(`Cache HIT for ${key}`);
|
|
675
|
+
return cached.lastBalance;
|
|
676
|
+
}
|
|
677
|
+
this.log(`Cache MISS for ${key}, fetching...`);
|
|
678
|
+
return this.fetchAndCache(params);
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Force refresh a specific balance
|
|
682
|
+
*
|
|
683
|
+
* @param contractAddress - Contract address to refresh
|
|
684
|
+
* @param accountAddress - Account address to refresh (optional, refreshes all if not provided)
|
|
685
|
+
* @returns Promise that resolves when refresh is complete
|
|
686
|
+
*/
|
|
687
|
+
async refreshBalance(contractAddress, accountAddress) {
|
|
688
|
+
const toRefresh = [];
|
|
689
|
+
for (const [key, entry] of this.balances) {
|
|
690
|
+
if (entry.params.contractAddress.toLowerCase() === contractAddress.toLowerCase()) {
|
|
691
|
+
if (!accountAddress || entry.params.accountAddress.toLowerCase() === accountAddress.toLowerCase()) {
|
|
692
|
+
toRefresh.push(key);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
await Promise.all(toRefresh.map(key => {
|
|
697
|
+
const entry = this.balances.get(key);
|
|
698
|
+
if (entry) {
|
|
699
|
+
return this.refreshEntry(key, entry, 'refresh');
|
|
700
|
+
}
|
|
701
|
+
}));
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Force refresh all tracked balances
|
|
705
|
+
*
|
|
706
|
+
* @returns Promise that resolves when all refreshes are complete
|
|
707
|
+
*/
|
|
708
|
+
async refreshAll() {
|
|
709
|
+
const refreshPromises = Array.from(this.balances.entries()).map(([key, entry]) => this.refreshEntry(key, entry, 'refresh'));
|
|
710
|
+
await Promise.all(refreshPromises);
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Subscribe to balance change events
|
|
714
|
+
*
|
|
715
|
+
* @param handler - Callback when any tracked balance changes
|
|
716
|
+
* @returns Unsubscribe function
|
|
717
|
+
*/
|
|
718
|
+
subscribe(handler) {
|
|
719
|
+
this.handlers.add(handler);
|
|
720
|
+
return () => this.handlers.delete(handler);
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Get all currently tracked balances
|
|
724
|
+
*/
|
|
725
|
+
getTrackedBalances() {
|
|
726
|
+
return Array.from(this.balances.entries()).map(([_, entry]) => ({
|
|
727
|
+
contractAddress: entry.params.contractAddress,
|
|
728
|
+
accountAddress: entry.params.accountAddress,
|
|
729
|
+
balance: entry.lastBalance
|
|
730
|
+
}));
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Check if a balance is currently being tracked
|
|
734
|
+
*/
|
|
735
|
+
isTracking(contractAddress, accountAddress) {
|
|
736
|
+
const key = `${contractAddress.toLowerCase()}:${accountAddress.toLowerCase()}`;
|
|
737
|
+
return this.balances.has(key);
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Clear tracking for a specific balance
|
|
741
|
+
*/
|
|
742
|
+
untrack(contractAddress, accountAddress) {
|
|
743
|
+
if (accountAddress) {
|
|
744
|
+
const key = `${contractAddress.toLowerCase()}:${accountAddress.toLowerCase()}`;
|
|
745
|
+
this.balances.delete(key);
|
|
746
|
+
this.log(`Untracked ${key}`);
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
// Untrack all for this contract
|
|
750
|
+
const toDelete = [];
|
|
751
|
+
for (const key of this.balances.keys()) {
|
|
752
|
+
if (key.startsWith(`${contractAddress.toLowerCase()}:`)) {
|
|
753
|
+
toDelete.push(key);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
toDelete.forEach(key => this.balances.delete(key));
|
|
757
|
+
this.log(`Untracked all balances for ${contractAddress}`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Clear all tracked balances
|
|
762
|
+
*/
|
|
763
|
+
clearAll() {
|
|
764
|
+
this.balances.clear();
|
|
765
|
+
this.log('Cleared all tracked balances');
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Handle wallet events (with debouncing)
|
|
769
|
+
*/
|
|
770
|
+
handleWalletEvent(contractAddress, eventType) {
|
|
771
|
+
// Determine which balances to refresh
|
|
772
|
+
const keysToRefresh = [];
|
|
773
|
+
for (const [key, entry] of this.balances) {
|
|
774
|
+
// If no specific contract, refresh all; otherwise match contract
|
|
775
|
+
if (!contractAddress || entry.params.contractAddress.toLowerCase() === contractAddress.toLowerCase()) {
|
|
776
|
+
keysToRefresh.push(key);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (keysToRefresh.length === 0) {
|
|
780
|
+
this.log(`No tracked balances affected by event for ${contractAddress || 'all contracts'}`);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
// Debounce refresh per key
|
|
784
|
+
for (const key of keysToRefresh) {
|
|
785
|
+
const existing = this.pendingRefresh.get(key);
|
|
786
|
+
if (existing) {
|
|
787
|
+
clearTimeout(existing);
|
|
788
|
+
}
|
|
789
|
+
const timeout = setTimeout(async () => {
|
|
790
|
+
this.pendingRefresh.delete(key);
|
|
791
|
+
const entry = this.balances.get(key);
|
|
792
|
+
if (entry) {
|
|
793
|
+
this.log(`Refreshing ${key} due to wallet event: ${eventType}`);
|
|
794
|
+
await this.refreshEntry(key, entry, eventType);
|
|
795
|
+
}
|
|
796
|
+
}, this.config.eventDebounceMs);
|
|
797
|
+
this.pendingRefresh.set(key, timeout);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Fetch and cache a balance
|
|
802
|
+
*/
|
|
803
|
+
async fetchAndCache(params) {
|
|
804
|
+
const key = this.getCacheKey(params);
|
|
805
|
+
// Mark as refreshing to prevent duplicate fetches
|
|
806
|
+
const existing = this.balances.get(key);
|
|
807
|
+
if (existing?.refreshing) {
|
|
808
|
+
// Wait for existing refresh to complete
|
|
809
|
+
return existing.lastBalance || this.emptyBalance();
|
|
810
|
+
}
|
|
811
|
+
this.balances.set(key, {
|
|
812
|
+
params,
|
|
813
|
+
lastBalance: null,
|
|
814
|
+
timestamp: Date.now(),
|
|
815
|
+
refreshing: true
|
|
816
|
+
});
|
|
817
|
+
try {
|
|
818
|
+
const balance = await this.tokenService.getTokenBalance({
|
|
819
|
+
...params,
|
|
820
|
+
tokenId: null // ERC-20 has no tokenId
|
|
821
|
+
});
|
|
822
|
+
this.balances.set(key, {
|
|
823
|
+
params,
|
|
824
|
+
lastBalance: balance,
|
|
825
|
+
timestamp: Date.now(),
|
|
826
|
+
refreshing: false
|
|
827
|
+
});
|
|
828
|
+
this.log(`Cached balance for ${key}: ${balance.balance}`);
|
|
829
|
+
return balance;
|
|
830
|
+
}
|
|
831
|
+
catch (error) {
|
|
832
|
+
// Remove failed entry
|
|
833
|
+
this.balances.delete(key);
|
|
834
|
+
throw error;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Refresh a cached entry and notify handlers
|
|
839
|
+
*/
|
|
840
|
+
async refreshEntry(key, entry, source) {
|
|
841
|
+
if (entry.refreshing) {
|
|
842
|
+
this.log(`Skipping refresh for ${key} - already refreshing`);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
entry.refreshing = true;
|
|
846
|
+
try {
|
|
847
|
+
const newBalance = await this.tokenService.getTokenBalance({
|
|
848
|
+
...entry.params,
|
|
849
|
+
tokenId: null // ERC-20 has no tokenId
|
|
850
|
+
});
|
|
851
|
+
entry.lastBalance = newBalance;
|
|
852
|
+
entry.timestamp = Date.now();
|
|
853
|
+
entry.refreshing = false;
|
|
854
|
+
this.log(`Refreshed ${key}: ${newBalance.balance}`);
|
|
855
|
+
// Notify subscribers
|
|
856
|
+
for (const handler of this.handlers) {
|
|
857
|
+
try {
|
|
858
|
+
handler(newBalance, entry.params.contractAddress, {
|
|
859
|
+
type: source === 'refresh' ? 'refresh' : 'wallet_event',
|
|
860
|
+
source
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
catch (handlerError) {
|
|
864
|
+
console.error('[BalanceManager] Handler error:', handlerError);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
catch (error) {
|
|
869
|
+
entry.refreshing = false;
|
|
870
|
+
console.error(`[BalanceManager] Refresh failed for ${key}:`, error);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Generate cache key from params
|
|
875
|
+
*/
|
|
876
|
+
getCacheKey(params) {
|
|
877
|
+
return `${params.contractAddress.toLowerCase()}:${params.accountAddress.toLowerCase()}`;
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Create empty balance placeholder
|
|
881
|
+
*/
|
|
882
|
+
emptyBalance() {
|
|
883
|
+
return {
|
|
884
|
+
tokenId: null,
|
|
885
|
+
balance: 0,
|
|
886
|
+
hasBalance: false,
|
|
887
|
+
metadata: null
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Debug logging
|
|
892
|
+
*/
|
|
893
|
+
log(message, data) {
|
|
894
|
+
if (this.config.debug) {
|
|
895
|
+
console.log(`[BalanceManager] ${message}`, data || '');
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Cleanup resources
|
|
900
|
+
*/
|
|
901
|
+
destroy() {
|
|
902
|
+
// Clear pending refreshes
|
|
903
|
+
for (const timeout of this.pendingRefresh.values()) {
|
|
904
|
+
clearTimeout(timeout);
|
|
905
|
+
}
|
|
906
|
+
this.pendingRefresh.clear();
|
|
907
|
+
// Unsubscribe from events
|
|
908
|
+
this.eventUnsubscribe?.();
|
|
909
|
+
// Clear handlers and balances
|
|
910
|
+
this.handlers.clear();
|
|
911
|
+
this.balances.clear();
|
|
912
|
+
this.log('Destroyed');
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
279
916
|
/**
|
|
280
917
|
* Web3ApplicationService - Application layer entrance point
|
|
281
918
|
* Orchestrates domain services and provides clean public interface
|
|
@@ -384,6 +1021,30 @@ class Web3InfrastructureApi {
|
|
|
384
1021
|
return 0;
|
|
385
1022
|
}
|
|
386
1023
|
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Get multiple ERC-1155 token balances in a single RPC call using native balanceOfBatch.
|
|
1026
|
+
* Falls back to individual calls if balanceOfBatch fails.
|
|
1027
|
+
*
|
|
1028
|
+
* @param request - Batch balance request
|
|
1029
|
+
* @returns Array of balance results per tokenId
|
|
1030
|
+
*/
|
|
1031
|
+
async getBatchTokenBalances(request) {
|
|
1032
|
+
try {
|
|
1033
|
+
const ethersProvider = await this.web3ChainService.getEthersProviderByChainId(request.chainId);
|
|
1034
|
+
const results = await getBatchBalancesWithFallback(ethersProvider, request.contractAddress, request.abi, request.accountAddress, request.tokenIds, request.isERC1155 ?? true);
|
|
1035
|
+
return results;
|
|
1036
|
+
}
|
|
1037
|
+
catch (error) {
|
|
1038
|
+
console.error(`Failed to get batch token balances:`, error);
|
|
1039
|
+
// Return empty results on failure
|
|
1040
|
+
return request.tokenIds.map(tokenId => ({
|
|
1041
|
+
tokenId,
|
|
1042
|
+
balance: 0n,
|
|
1043
|
+
success: false,
|
|
1044
|
+
error: `${error}`
|
|
1045
|
+
}));
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
387
1048
|
async getTokenUri(request) {
|
|
388
1049
|
try {
|
|
389
1050
|
const ethersProvider = await this.web3ChainService.getEthersProviderByChainId(request.chainId);
|
|
@@ -621,17 +1282,11 @@ async function getExplorerUrlByChainId(getChainData, chainId, address, type) {
|
|
|
621
1282
|
* ```
|
|
622
1283
|
*/
|
|
623
1284
|
class Web3Manager {
|
|
624
|
-
|
|
625
|
-
// When ready, add:
|
|
626
|
-
// 1. constructor param: private events?: PersEventEmitter
|
|
627
|
-
// 2. Subscribe to contract events (Transfer, Approval, etc.)
|
|
628
|
-
// 3. Emit via: this.events?.emitSuccess({ domain: 'web3', type: 'NFT_TRANSFERRED', ... })
|
|
629
|
-
// 4. Filter to only user's address (not all transfers)
|
|
630
|
-
// 5. Add cleanup for event listeners on SDK destroy
|
|
631
|
-
constructor(apiClient, tenantManager) {
|
|
1285
|
+
constructor(apiClient, tenantManager, eventEmitter) {
|
|
632
1286
|
this.apiClient = apiClient;
|
|
633
1287
|
// Use provided TenantManager or create one
|
|
634
1288
|
this.tenantManager = tenantManager || new TenantManager(apiClient);
|
|
1289
|
+
this.eventEmitter = eventEmitter;
|
|
635
1290
|
// Initialize Web3 Chain service
|
|
636
1291
|
const web3ChainApi = new Web3ChainApi(apiClient);
|
|
637
1292
|
this.web3ChainService = new Web3ChainService(web3ChainApi);
|
|
@@ -639,6 +1294,75 @@ class Web3Manager {
|
|
|
639
1294
|
const web3InfrastructureApi = new Web3InfrastructureApi(this.web3ChainService);
|
|
640
1295
|
const ipfsInfrastructureApi = new IPFSInfrastructureApi(this.tenantManager);
|
|
641
1296
|
this.web3ApplicationService = new Web3ApplicationService(web3InfrastructureApi, ipfsInfrastructureApi);
|
|
1297
|
+
// Initialize domain services for managers
|
|
1298
|
+
const contractDomainService = new ContractDomainService();
|
|
1299
|
+
const metadataDomainService = new MetadataDomainService(ipfsInfrastructureApi);
|
|
1300
|
+
this.tokenDomainService = new TokenDomainService(web3InfrastructureApi, metadataDomainService, contractDomainService);
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Get the TokenCollectionManager for NFT collection operations.
|
|
1304
|
+
*
|
|
1305
|
+
* Features:
|
|
1306
|
+
* - Auto-refresh on wallet events (transfers, mints, burns)
|
|
1307
|
+
* - Cached collections
|
|
1308
|
+
* - Subscriber pattern for reactive updates
|
|
1309
|
+
*
|
|
1310
|
+
* @example
|
|
1311
|
+
* ```typescript
|
|
1312
|
+
* const manager = sdk.web3.collectionManager;
|
|
1313
|
+
*
|
|
1314
|
+
* // Subscribe to updates
|
|
1315
|
+
* const unsubscribe = manager.subscribe((collection, contractAddress) => {
|
|
1316
|
+
* console.log(`Collection updated for ${contractAddress}`);
|
|
1317
|
+
* });
|
|
1318
|
+
*
|
|
1319
|
+
* // Fetch collection (auto-cached, auto-refreshed)
|
|
1320
|
+
* const collection = await manager.getCollection({...});
|
|
1321
|
+
*
|
|
1322
|
+
* // Manual refresh
|
|
1323
|
+
* await manager.refreshCollection(contractAddress);
|
|
1324
|
+
* ```
|
|
1325
|
+
*/
|
|
1326
|
+
get collectionManager() {
|
|
1327
|
+
if (!this._collectionManager) {
|
|
1328
|
+
this._collectionManager = new TokenCollectionManager(this.tokenDomainService, this.eventEmitter);
|
|
1329
|
+
}
|
|
1330
|
+
return this._collectionManager;
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Get the BalanceManager for ERC-20 (credit token) balance operations.
|
|
1334
|
+
*
|
|
1335
|
+
* Features:
|
|
1336
|
+
* - Auto-refresh on wallet events (transfers)
|
|
1337
|
+
* - Cached balances
|
|
1338
|
+
* - Subscriber pattern for reactive updates
|
|
1339
|
+
*
|
|
1340
|
+
* @example
|
|
1341
|
+
* ```typescript
|
|
1342
|
+
* const manager = sdk.web3.balanceManager;
|
|
1343
|
+
*
|
|
1344
|
+
* // Subscribe to balance updates
|
|
1345
|
+
* const unsubscribe = manager.subscribe((balance, contractAddress) => {
|
|
1346
|
+
* console.log(`Balance updated: ${balance.balance}`);
|
|
1347
|
+
* });
|
|
1348
|
+
*
|
|
1349
|
+
* // Fetch balance (auto-cached, auto-refreshed)
|
|
1350
|
+
* const balance = await manager.getBalance({
|
|
1351
|
+
* accountAddress: '0x...',
|
|
1352
|
+
* contractAddress: '0x...',
|
|
1353
|
+
* abi: creditTokenAbi,
|
|
1354
|
+
* chainId: 39123
|
|
1355
|
+
* });
|
|
1356
|
+
*
|
|
1357
|
+
* // Manual refresh
|
|
1358
|
+
* await manager.refreshBalance(contractAddress);
|
|
1359
|
+
* ```
|
|
1360
|
+
*/
|
|
1361
|
+
get balanceManager() {
|
|
1362
|
+
if (!this._balanceManager) {
|
|
1363
|
+
this._balanceManager = new BalanceManager(this.tokenDomainService, this.eventEmitter);
|
|
1364
|
+
}
|
|
1365
|
+
return this._balanceManager;
|
|
642
1366
|
}
|
|
643
1367
|
/**
|
|
644
1368
|
* Get token balance for a specific token
|
|
@@ -837,7 +1561,19 @@ class Web3Manager {
|
|
|
837
1561
|
maxTokens
|
|
838
1562
|
};
|
|
839
1563
|
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Cleanup resources.
|
|
1566
|
+
*
|
|
1567
|
+
* Call this when the SDK is being destroyed to clean up
|
|
1568
|
+
* event subscriptions and cached data.
|
|
1569
|
+
*/
|
|
1570
|
+
destroy() {
|
|
1571
|
+
this._collectionManager?.destroy();
|
|
1572
|
+
this._balanceManager?.destroy();
|
|
1573
|
+
this._collectionManager = undefined;
|
|
1574
|
+
this._balanceManager = undefined;
|
|
1575
|
+
}
|
|
840
1576
|
}
|
|
841
1577
|
|
|
842
|
-
export { IPFSInfrastructureApi as I, Web3Manager as W, Web3ApplicationService as a, Web3InfrastructureApi as b, getExplorerUrlByChainId as c, getExplorerUrl as g };
|
|
843
|
-
//# sourceMappingURL=web3-manager-
|
|
1578
|
+
export { BalanceManager as B, IPFSInfrastructureApi as I, TokenCollectionManager as T, Web3Manager as W, Web3ApplicationService as a, Web3InfrastructureApi as b, getExplorerUrlByChainId as c, getExplorerUrl as g };
|
|
1579
|
+
//# sourceMappingURL=web3-manager-B-IsluxI.js.map
|