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