@pioneer-platform/pioneer-cache 1.0.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/.turbo/turbo-build.log +2 -0
- package/README.md +451 -0
- package/dist/core/base-cache.d.ts +75 -0
- package/dist/core/base-cache.js +493 -0
- package/dist/core/cache-manager.d.ts +62 -0
- package/dist/core/cache-manager.js +238 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +25 -0
- package/dist/stores/balance-cache.d.ts +47 -0
- package/dist/stores/balance-cache.js +158 -0
- package/dist/stores/price-cache.d.ts +39 -0
- package/dist/stores/price-cache.js +179 -0
- package/dist/stores/transaction-cache.d.ts +42 -0
- package/dist/stores/transaction-cache.js +148 -0
- package/dist/types/index.d.ts +98 -0
- package/dist/types/index.js +5 -0
- package/dist/workers/refresh-worker.d.ts +57 -0
- package/dist/workers/refresh-worker.js +212 -0
- package/package.json +31 -0
- package/src/core/base-cache.ts +595 -0
- package/src/core/cache-manager.ts +293 -0
- package/src/index.ts +36 -0
- package/src/stores/balance-cache.ts +196 -0
- package/src/stores/price-cache.ts +215 -0
- package/src/stores/transaction-cache.ts +172 -0
- package/src/types/index.ts +121 -0
- package/src/workers/refresh-worker.ts +267 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/*
|
|
2
|
+
CacheManager - Orchestrates all cache instances
|
|
3
|
+
|
|
4
|
+
Central coordinator for all cache types (balance, price, transaction).
|
|
5
|
+
Manages cache lifecycle, health monitoring, and worker coordination.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BalanceCache } from '../stores/balance-cache';
|
|
9
|
+
import { PriceCache } from '../stores/price-cache';
|
|
10
|
+
import { TransactionCache } from '../stores/transaction-cache';
|
|
11
|
+
import { RefreshWorker, startUnifiedWorker } from '../workers/refresh-worker';
|
|
12
|
+
import type { BaseCache } from './base-cache';
|
|
13
|
+
import type { HealthCheckResult } from '../types';
|
|
14
|
+
|
|
15
|
+
const log = require('@pioneer-platform/loggerdog')();
|
|
16
|
+
const TAG = ' | CacheManager | ';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Configuration for CacheManager
|
|
20
|
+
*/
|
|
21
|
+
export interface CacheManagerConfig {
|
|
22
|
+
redis: any;
|
|
23
|
+
balanceModule?: any; // Optional: if not provided, balance cache won't be initialized
|
|
24
|
+
markets?: any; // Optional: if not provided, price cache won't be initialized
|
|
25
|
+
enableBalanceCache?: boolean;
|
|
26
|
+
enablePriceCache?: boolean;
|
|
27
|
+
enableTransactionCache?: boolean;
|
|
28
|
+
startWorkers?: boolean; // Auto-start workers on initialization
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* CacheManager - Central coordinator for all caches
|
|
33
|
+
*/
|
|
34
|
+
export class CacheManager {
|
|
35
|
+
private redis: any;
|
|
36
|
+
private balanceCache?: BalanceCache;
|
|
37
|
+
private priceCache?: PriceCache;
|
|
38
|
+
private transactionCache?: TransactionCache;
|
|
39
|
+
private workers: RefreshWorker[] = [];
|
|
40
|
+
|
|
41
|
+
constructor(config: CacheManagerConfig) {
|
|
42
|
+
this.redis = config.redis;
|
|
43
|
+
|
|
44
|
+
// Initialize Balance Cache
|
|
45
|
+
if (config.enableBalanceCache !== false && config.balanceModule) {
|
|
46
|
+
this.balanceCache = new BalanceCache(this.redis, config.balanceModule);
|
|
47
|
+
log.info(TAG, '✅ Balance cache initialized');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Initialize Price Cache
|
|
51
|
+
if (config.enablePriceCache !== false && config.markets) {
|
|
52
|
+
this.priceCache = new PriceCache(this.redis, config.markets);
|
|
53
|
+
log.info(TAG, '✅ Price cache initialized');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Initialize Transaction Cache
|
|
57
|
+
if (config.enableTransactionCache !== false) {
|
|
58
|
+
this.transactionCache = new TransactionCache(this.redis);
|
|
59
|
+
log.info(TAG, '✅ Transaction cache initialized');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Auto-start workers if requested
|
|
63
|
+
if (config.startWorkers) {
|
|
64
|
+
setImmediate(() => {
|
|
65
|
+
this.startWorkers();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
log.info(TAG, '✅ CacheManager initialized');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Start background refresh workers for all caches
|
|
74
|
+
*/
|
|
75
|
+
async startWorkers(): Promise<void> {
|
|
76
|
+
const tag = TAG + 'startWorkers | ';
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
log.info(tag, 'Starting refresh workers...');
|
|
80
|
+
|
|
81
|
+
// Create cache registry for workers
|
|
82
|
+
const cacheRegistry = new Map<string, BaseCache<any>>();
|
|
83
|
+
|
|
84
|
+
if (this.balanceCache) {
|
|
85
|
+
cacheRegistry.set('balance', this.balanceCache);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this.priceCache) {
|
|
89
|
+
cacheRegistry.set('price', this.priceCache);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Start unified worker if we have any caches with queues
|
|
93
|
+
if (cacheRegistry.size > 0) {
|
|
94
|
+
const worker = await startUnifiedWorker(
|
|
95
|
+
this.redis,
|
|
96
|
+
cacheRegistry,
|
|
97
|
+
'cache-refresh', // Unified queue name
|
|
98
|
+
{
|
|
99
|
+
maxRetries: 3,
|
|
100
|
+
retryDelay: 5000,
|
|
101
|
+
pollInterval: 100
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
this.workers.push(worker);
|
|
106
|
+
log.info(tag, '✅ Refresh worker started for all caches');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
} catch (error) {
|
|
110
|
+
log.error(tag, '❌ Failed to start workers:', error);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Stop all workers gracefully
|
|
117
|
+
*/
|
|
118
|
+
async stopWorkers(): Promise<void> {
|
|
119
|
+
const tag = TAG + 'stopWorkers | ';
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
log.info(tag, 'Stopping all workers...');
|
|
123
|
+
|
|
124
|
+
for (const worker of this.workers) {
|
|
125
|
+
await worker.stop();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.workers = [];
|
|
129
|
+
log.info(tag, '✅ All workers stopped');
|
|
130
|
+
|
|
131
|
+
} catch (error) {
|
|
132
|
+
log.error(tag, 'Error stopping workers:', error);
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get aggregate health status for all caches
|
|
139
|
+
* @param forceRefresh - Force refresh stats (bypasses 30s cache)
|
|
140
|
+
*/
|
|
141
|
+
async getHealth(forceRefresh: boolean = false): Promise<HealthCheckResult & { checks?: any }> {
|
|
142
|
+
const tag = TAG + 'getHealth | ';
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const checks: any = {};
|
|
146
|
+
const issues: string[] = [];
|
|
147
|
+
const warnings: string[] = [];
|
|
148
|
+
|
|
149
|
+
// Check balance cache
|
|
150
|
+
if (this.balanceCache) {
|
|
151
|
+
const balanceHealth = await this.balanceCache.getHealth(forceRefresh);
|
|
152
|
+
checks.balance = balanceHealth;
|
|
153
|
+
|
|
154
|
+
if (balanceHealth.status === 'unhealthy') {
|
|
155
|
+
issues.push(...balanceHealth.issues.map(i => `Balance: ${i}`));
|
|
156
|
+
} else if (balanceHealth.status === 'degraded') {
|
|
157
|
+
warnings.push(...balanceHealth.warnings.map(w => `Balance: ${w}`));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check price cache
|
|
162
|
+
if (this.priceCache) {
|
|
163
|
+
const priceHealth = await this.priceCache.getHealth(forceRefresh);
|
|
164
|
+
checks.price = priceHealth;
|
|
165
|
+
|
|
166
|
+
if (priceHealth.status === 'unhealthy') {
|
|
167
|
+
issues.push(...priceHealth.issues.map(i => `Price: ${i}`));
|
|
168
|
+
} else if (priceHealth.status === 'degraded') {
|
|
169
|
+
warnings.push(...priceHealth.warnings.map(w => `Price: ${w}`));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check transaction cache (simple stats check)
|
|
174
|
+
if (this.transactionCache) {
|
|
175
|
+
const txStats = await this.transactionCache.getStats();
|
|
176
|
+
checks.transaction = {
|
|
177
|
+
status: 'healthy',
|
|
178
|
+
stats: txStats
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Check workers
|
|
183
|
+
for (const worker of this.workers) {
|
|
184
|
+
const workerStats = await worker.getStats();
|
|
185
|
+
checks.worker = workerStats;
|
|
186
|
+
|
|
187
|
+
if (!workerStats.isRunning) {
|
|
188
|
+
issues.push('Refresh worker not running');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Determine overall status
|
|
193
|
+
let status: 'healthy' | 'degraded' | 'unhealthy' = 'healthy';
|
|
194
|
+
if (issues.length > 0) {
|
|
195
|
+
status = 'unhealthy';
|
|
196
|
+
} else if (warnings.length > 0) {
|
|
197
|
+
status = 'degraded';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
status,
|
|
202
|
+
queueInitialized: this.workers.length > 0,
|
|
203
|
+
redisConnected: true,
|
|
204
|
+
stats: {
|
|
205
|
+
totalEntries: 0,
|
|
206
|
+
staleEntries: 0,
|
|
207
|
+
freshEntries: 0,
|
|
208
|
+
stalenessRate: '0%'
|
|
209
|
+
},
|
|
210
|
+
issues,
|
|
211
|
+
warnings,
|
|
212
|
+
timestamp: Date.now(),
|
|
213
|
+
timestampISO: new Date().toISOString(),
|
|
214
|
+
checks
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
} catch (error) {
|
|
218
|
+
log.error(tag, 'Health check failed:', error);
|
|
219
|
+
return {
|
|
220
|
+
status: 'unhealthy',
|
|
221
|
+
queueInitialized: false,
|
|
222
|
+
redisConnected: false,
|
|
223
|
+
stats: {
|
|
224
|
+
totalEntries: 0,
|
|
225
|
+
staleEntries: 0,
|
|
226
|
+
freshEntries: 0,
|
|
227
|
+
stalenessRate: '0%'
|
|
228
|
+
},
|
|
229
|
+
issues: [`Health check error: ${error instanceof Error ? error.message : String(error)}`],
|
|
230
|
+
warnings: [],
|
|
231
|
+
timestamp: Date.now(),
|
|
232
|
+
timestampISO: new Date().toISOString()
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get all cache instances
|
|
239
|
+
*/
|
|
240
|
+
getCaches() {
|
|
241
|
+
return {
|
|
242
|
+
balance: this.balanceCache,
|
|
243
|
+
price: this.priceCache,
|
|
244
|
+
transaction: this.transactionCache
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get specific cache by name
|
|
250
|
+
*/
|
|
251
|
+
getCache(name: 'balance' | 'price' | 'transaction') {
|
|
252
|
+
switch (name) {
|
|
253
|
+
case 'balance':
|
|
254
|
+
return this.balanceCache;
|
|
255
|
+
case 'price':
|
|
256
|
+
return this.priceCache;
|
|
257
|
+
case 'transaction':
|
|
258
|
+
return this.transactionCache;
|
|
259
|
+
default:
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Clear all caches (use with caution!)
|
|
266
|
+
*/
|
|
267
|
+
async clearAll(): Promise<{ balance?: number; price?: number; transaction?: number }> {
|
|
268
|
+
const tag = TAG + 'clearAll | ';
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const result: any = {};
|
|
272
|
+
|
|
273
|
+
if (this.balanceCache) {
|
|
274
|
+
result.balance = await this.balanceCache.clearAll();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (this.priceCache) {
|
|
278
|
+
result.price = await this.priceCache.clearAll();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (this.transactionCache) {
|
|
282
|
+
result.transaction = await this.transactionCache.clearAll();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
log.info(tag, 'Cleared all caches:', result);
|
|
286
|
+
return result;
|
|
287
|
+
|
|
288
|
+
} catch (error) {
|
|
289
|
+
log.error(tag, 'Error clearing caches:', error);
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
@pioneer-platform/pioneer-cache
|
|
3
|
+
|
|
4
|
+
Unified caching system for Pioneer Platform.
|
|
5
|
+
Provides stale-while-revalidate caching with TTL, background refresh, and health monitoring.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Core exports
|
|
9
|
+
export { BaseCache } from './core/base-cache';
|
|
10
|
+
export { CacheManager } from './core/cache-manager';
|
|
11
|
+
|
|
12
|
+
// Cache implementations
|
|
13
|
+
export { BalanceCache } from './stores/balance-cache';
|
|
14
|
+
export { PriceCache } from './stores/price-cache';
|
|
15
|
+
export { TransactionCache } from './stores/transaction-cache';
|
|
16
|
+
|
|
17
|
+
// Worker exports
|
|
18
|
+
export { RefreshWorker, startUnifiedWorker } from './workers/refresh-worker';
|
|
19
|
+
|
|
20
|
+
// Type exports
|
|
21
|
+
export type {
|
|
22
|
+
CacheConfig,
|
|
23
|
+
CachedValue,
|
|
24
|
+
CacheResult,
|
|
25
|
+
RefreshJob,
|
|
26
|
+
HealthCheckResult,
|
|
27
|
+
CacheStats
|
|
28
|
+
} from './types';
|
|
29
|
+
|
|
30
|
+
// Data type exports
|
|
31
|
+
export type { BalanceData } from './stores/balance-cache';
|
|
32
|
+
export type { PriceData } from './stores/price-cache';
|
|
33
|
+
|
|
34
|
+
// Config type export
|
|
35
|
+
export type { CacheManagerConfig } from './core/cache-manager';
|
|
36
|
+
export type { WorkerConfig } from './workers/refresh-worker';
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/*
|
|
2
|
+
BalanceCache - Balance-specific cache implementation
|
|
3
|
+
|
|
4
|
+
Extends BaseCache with balance-specific logic.
|
|
5
|
+
All common logic is inherited from BaseCache.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BaseCache } from '../core/base-cache';
|
|
9
|
+
import type { CacheConfig } from '../types';
|
|
10
|
+
|
|
11
|
+
const log = require('@pioneer-platform/loggerdog')();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Balance data structure
|
|
15
|
+
*/
|
|
16
|
+
export interface BalanceData {
|
|
17
|
+
caip: string;
|
|
18
|
+
pubkey: string;
|
|
19
|
+
balance: string;
|
|
20
|
+
priceUsd?: string;
|
|
21
|
+
valueUsd?: string;
|
|
22
|
+
symbol?: string;
|
|
23
|
+
name?: string;
|
|
24
|
+
networkId?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* BalanceCache - Caches blockchain balance data
|
|
29
|
+
*/
|
|
30
|
+
export class BalanceCache extends BaseCache<BalanceData> {
|
|
31
|
+
private balanceModule: any;
|
|
32
|
+
|
|
33
|
+
constructor(redis: any, balanceModule: any, config?: Partial<CacheConfig>) {
|
|
34
|
+
const defaultConfig: CacheConfig = {
|
|
35
|
+
name: 'balance',
|
|
36
|
+
keyPrefix: 'balance_v2:',
|
|
37
|
+
ttl: 5 * 60 * 1000, // 5 minutes
|
|
38
|
+
staleThreshold: 2 * 60 * 1000, // 2 minutes
|
|
39
|
+
enableTTL: true,
|
|
40
|
+
queueName: 'balance-refresh-v2',
|
|
41
|
+
enableQueue: true,
|
|
42
|
+
maxRetries: 3,
|
|
43
|
+
retryDelay: 10000,
|
|
44
|
+
blockOnMiss: true, // Wait for fresh data on first request
|
|
45
|
+
enableLegacyFallback: true,
|
|
46
|
+
defaultValue: {
|
|
47
|
+
caip: '',
|
|
48
|
+
pubkey: '',
|
|
49
|
+
balance: '0',
|
|
50
|
+
},
|
|
51
|
+
maxConcurrentJobs: 10,
|
|
52
|
+
apiTimeout: 15000,
|
|
53
|
+
logCacheHits: false,
|
|
54
|
+
logCacheMisses: true,
|
|
55
|
+
logRefreshJobs: true
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
super(redis, { ...defaultConfig, ...config });
|
|
59
|
+
this.balanceModule = balanceModule;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build Redis key for balance data
|
|
64
|
+
* Format: balance_v2:caip:pubkey
|
|
65
|
+
*/
|
|
66
|
+
protected buildKey(params: Record<string, any>): string {
|
|
67
|
+
const { caip, pubkey } = params;
|
|
68
|
+
if (!caip || !pubkey) {
|
|
69
|
+
throw new Error('BalanceCache.buildKey: caip and pubkey required');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const normalizedCaip = caip.toLowerCase();
|
|
73
|
+
// Don't lowercase pubkeys - xpub/ypub/zpub are case-sensitive base58
|
|
74
|
+
const normalizedPubkey = pubkey;
|
|
75
|
+
|
|
76
|
+
return `${this.config.keyPrefix}${normalizedCaip}:${normalizedPubkey}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Fetch balance from blockchain via balance module
|
|
81
|
+
*/
|
|
82
|
+
protected async fetchFromSource(params: Record<string, any>): Promise<BalanceData> {
|
|
83
|
+
const tag = this.TAG + 'fetchFromSource | ';
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const { caip, pubkey } = params;
|
|
87
|
+
|
|
88
|
+
// Fetch balance using balance module
|
|
89
|
+
const asset = { caip };
|
|
90
|
+
const owner = { pubkey };
|
|
91
|
+
const balanceInfo = await this.balanceModule.getBalance(asset, owner);
|
|
92
|
+
|
|
93
|
+
if (!balanceInfo || !balanceInfo.balance) {
|
|
94
|
+
log.warn(tag, `No balance returned for ${caip}/${pubkey.substring(0, 10)}...`);
|
|
95
|
+
return {
|
|
96
|
+
caip,
|
|
97
|
+
pubkey,
|
|
98
|
+
balance: '0'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Get asset metadata
|
|
103
|
+
const assetData = require('@pioneer-platform/pioneer-discovery').assetData;
|
|
104
|
+
const { caipToNetworkId } = require('@pioneer-platform/pioneer-caip');
|
|
105
|
+
|
|
106
|
+
const assetInfo = assetData[caip.toUpperCase()] || assetData[caip.toLowerCase()] || {};
|
|
107
|
+
const networkId = caipToNetworkId(caip);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
caip,
|
|
111
|
+
pubkey,
|
|
112
|
+
balance: balanceInfo.balance,
|
|
113
|
+
symbol: assetInfo.symbol,
|
|
114
|
+
name: assetInfo.name,
|
|
115
|
+
networkId
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
} catch (error) {
|
|
119
|
+
log.error(tag, 'Error fetching balance:', error);
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Try to get balance from legacy cache format
|
|
126
|
+
*/
|
|
127
|
+
protected async getLegacyCached(params: Record<string, any>): Promise<BalanceData | null> {
|
|
128
|
+
const tag = this.TAG + 'getLegacyCached | ';
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const { caip, pubkey } = params;
|
|
132
|
+
const { caipToNetworkId } = require('@pioneer-platform/pioneer-caip');
|
|
133
|
+
const networkId = caipToNetworkId(caip);
|
|
134
|
+
|
|
135
|
+
// Try legacy format: cache:balance:pubkey:asset
|
|
136
|
+
const legacyKey = `cache:balance:${pubkey}:${networkId}`;
|
|
137
|
+
const legacyData = await this.redis.get(legacyKey);
|
|
138
|
+
|
|
139
|
+
if (legacyData) {
|
|
140
|
+
const parsed = JSON.parse(legacyData);
|
|
141
|
+
if (parsed.balance !== undefined) {
|
|
142
|
+
return {
|
|
143
|
+
caip,
|
|
144
|
+
pubkey,
|
|
145
|
+
balance: typeof parsed.balance === 'string' ? parsed.balance : String(parsed.balance)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return null;
|
|
151
|
+
|
|
152
|
+
} catch (error) {
|
|
153
|
+
log.error(tag, 'Error getting legacy cached balance:', error);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get balance for a specific asset and pubkey
|
|
160
|
+
* Convenience method that wraps base get()
|
|
161
|
+
*/
|
|
162
|
+
async getBalance(caip: string, pubkey: string, waitForFresh?: boolean): Promise<BalanceData> {
|
|
163
|
+
const result = await this.get({ caip, pubkey }, waitForFresh);
|
|
164
|
+
return result.value || this.config.defaultValue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get balances for multiple assets (batch operation)
|
|
169
|
+
*/
|
|
170
|
+
async getBatchBalances(items: Array<{ caip: string; pubkey: string }>, waitForFresh?: boolean): Promise<BalanceData[]> {
|
|
171
|
+
const tag = this.TAG + 'getBatchBalances | ';
|
|
172
|
+
const startTime = Date.now();
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
log.info(tag, `Batch request for ${items.length} balances`);
|
|
176
|
+
|
|
177
|
+
// Get all balances in parallel
|
|
178
|
+
const promises = items.map(item => this.getBalance(item.caip, item.pubkey, waitForFresh));
|
|
179
|
+
const results = await Promise.all(promises);
|
|
180
|
+
|
|
181
|
+
const responseTime = Date.now() - startTime;
|
|
182
|
+
log.info(tag, `Batch completed: ${results.length} balances in ${responseTime}ms (${(responseTime / results.length).toFixed(1)}ms avg)`);
|
|
183
|
+
|
|
184
|
+
return results;
|
|
185
|
+
|
|
186
|
+
} catch (error) {
|
|
187
|
+
log.error(tag, 'Error in batch balance request:', error);
|
|
188
|
+
// Return defaults for all items
|
|
189
|
+
return items.map(item => ({
|
|
190
|
+
caip: item.caip,
|
|
191
|
+
pubkey: item.pubkey,
|
|
192
|
+
balance: '0'
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|