@pioneer-platform/pioneer-discovery 0.8.4 → 4.21.15

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.
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Update Icon URLs to use api.keepkey.info
4
+ * Converts all assets.coingecko.com URLs to base64-encoded CAIP format
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ const DATA_FILE = path.join(__dirname, '..', 'src', 'generatedAssetData.json');
11
+
12
+ /**
13
+ * Convert assetId to base64-encoded format for icon URL
14
+ * @param {string} assetId - CAIP format assetId (e.g., "eip155:8453/erc20:0x833...")
15
+ * @returns {string} - Base64 encoded assetId
16
+ */
17
+ function encodeAssetId(assetId) {
18
+ return Buffer.from(assetId).toString('base64');
19
+ }
20
+
21
+ /**
22
+ * Generate icon URL from assetId
23
+ * @param {string} assetId - CAIP format assetId
24
+ * @returns {string} - Full icon URL
25
+ */
26
+ function generateIconUrl(assetId) {
27
+ const encoded = encodeAssetId(assetId);
28
+ return `https://api.keepkey.info/coins/${encoded}.png`;
29
+ }
30
+
31
+ async function updateIconUrls() {
32
+ console.log('Reading generatedAssetData.json...');
33
+
34
+ const data = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
35
+
36
+ let updatedCount = 0;
37
+ let alreadyCorrect = 0;
38
+ let skippedCount = 0;
39
+
40
+ for (const [assetId, assetData] of Object.entries(data)) {
41
+ if (!assetData.icon) {
42
+ console.log(`⚠️ Missing icon for ${assetId}`);
43
+ skippedCount++;
44
+ continue;
45
+ }
46
+
47
+ // Check if already using api.keepkey.info
48
+ if (assetData.icon.includes('api.keepkey.info')) {
49
+ alreadyCorrect++;
50
+ continue;
51
+ }
52
+
53
+ // Update to use api.keepkey.info with base64-encoded CAIP
54
+ const newIconUrl = generateIconUrl(assetId);
55
+ const oldIconUrl = assetData.icon;
56
+
57
+ assetData.icon = newIconUrl;
58
+ updatedCount++;
59
+
60
+ if (updatedCount <= 10) {
61
+ console.log(`\n✅ Updated ${assetId}:`);
62
+ console.log(` Old: ${oldIconUrl}`);
63
+ console.log(` New: ${newIconUrl}`);
64
+ }
65
+ }
66
+
67
+ console.log('\n📊 Summary:');
68
+ console.log(` Total entries: ${Object.keys(data).length}`);
69
+ console.log(` Already correct: ${alreadyCorrect}`);
70
+ console.log(` Updated: ${updatedCount}`);
71
+ console.log(` Skipped (no icon): ${skippedCount}`);
72
+
73
+ if (updatedCount > 0) {
74
+ console.log('\n💾 Writing updated data...');
75
+ fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2), 'utf8');
76
+ console.log('✅ Successfully updated generatedAssetData.json');
77
+ } else {
78
+ console.log('\n✨ No updates needed - all icons already correct!');
79
+ }
80
+ }
81
+
82
+ // Run the update
83
+ updateIconUrls().catch(err => {
84
+ console.error('❌ Error:', err);
85
+ process.exit(1);
86
+ });
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Fast Icon Validation and Download
4
+ *
5
+ * Optimizations:
6
+ * - Faster timeouts (2s instead of 5s)
7
+ * - Progress saving (can resume)
8
+ * - Better concurrency handling
9
+ * - Skip already processed assets
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const https = require('https');
15
+ const http = require('http');
16
+
17
+ const DATA_FILE = path.join(__dirname, '..', 'src', 'generatedAssetData.json');
18
+ const LOCAL_COINS_DIR = path.join(__dirname, '..', '..', '..', '..', '..', 'services', 'pioneer-server', 'public', 'coins');
19
+ const MISSING_ASSETS_FILE = path.join(__dirname, 'missing-assets.json');
20
+ const PROGRESS_FILE = path.join(__dirname, '.download-progress.json');
21
+
22
+ // Ensure local coins directory exists
23
+ if (!fs.existsSync(LOCAL_COINS_DIR)) {
24
+ fs.mkdirSync(LOCAL_COINS_DIR, { recursive: true });
25
+ }
26
+
27
+ /**
28
+ * Convert assetId to base64-encoded format
29
+ */
30
+ function encodeAssetId(assetId) {
31
+ return Buffer.from(assetId).toString('base64');
32
+ }
33
+
34
+ /**
35
+ * Check if URL exists (HEAD request) - Fast version
36
+ */
37
+ async function urlExists(url, timeout = 2000) {
38
+ return new Promise((resolve) => {
39
+ try {
40
+ const urlObj = new URL(url);
41
+ const client = urlObj.protocol === 'https:' ? https : http;
42
+
43
+ const req = client.request(url, { method: 'HEAD', timeout }, (res) => {
44
+ resolve(res.statusCode >= 200 && res.statusCode < 400);
45
+ });
46
+
47
+ req.on('error', () => resolve(false));
48
+ req.on('timeout', () => {
49
+ req.destroy();
50
+ resolve(false);
51
+ });
52
+
53
+ req.end();
54
+ } catch (e) {
55
+ resolve(false);
56
+ }
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Download file from URL - Fast version
62
+ */
63
+ async function downloadFile(url, outputPath, timeout = 5000) {
64
+ return new Promise((resolve) => {
65
+ try {
66
+ const urlObj = new URL(url);
67
+ const client = urlObj.protocol === 'https:' ? https : http;
68
+
69
+ const req = client.get(url, { timeout }, (res) => {
70
+ if (res.statusCode >= 200 && res.statusCode < 300) {
71
+ const fileStream = fs.createWriteStream(outputPath);
72
+ res.pipe(fileStream);
73
+ fileStream.on('finish', () => {
74
+ fileStream.close();
75
+ resolve(true);
76
+ });
77
+ fileStream.on('error', () => resolve(false));
78
+ } else if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
79
+ // Follow redirect
80
+ downloadFile(res.headers.location, outputPath, timeout).then(resolve);
81
+ } else {
82
+ resolve(false);
83
+ }
84
+ });
85
+
86
+ req.on('error', () => resolve(false));
87
+ req.on('timeout', () => {
88
+ req.destroy();
89
+ resolve(false);
90
+ });
91
+ } catch (e) {
92
+ resolve(false);
93
+ }
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Get CoinGecko fallbacks
99
+ */
100
+ function getCoinGeckoFallbacks(assetData) {
101
+ const symbol = assetData.symbol?.toLowerCase() || '';
102
+
103
+ const knownIds = {
104
+ 'btc': '1', 'eth': '279', 'usdc': '6319', 'usdt': '325',
105
+ 'dai': '9956', 'wbtc': '7598', 'link': '877', 'uni': '12504',
106
+ 'atom': '1481', 'osmo': '16724', 'rune': '6595', 'cacao': '27929',
107
+ 'bnb': '825', 'bch': '780', 'ltc': '2', 'doge': '5', 'ada': '2010',
108
+ 'dot': '6636', 'matic': '4713', 'avax': '12559', 'sol': '4128',
109
+ };
110
+
111
+ const fallbacks = [];
112
+ if (knownIds[symbol]) {
113
+ fallbacks.push(`https://assets.coingecko.com/coins/images/${knownIds[symbol]}/large/${symbol}.png`);
114
+ fallbacks.push(`https://assets.coingecko.com/coins/images/${knownIds[symbol]}/small/${symbol}.png`);
115
+ }
116
+
117
+ return fallbacks;
118
+ }
119
+
120
+ /**
121
+ * Get TrustWallet fallbacks
122
+ */
123
+ function getTrustWalletFallbacks(assetData) {
124
+ const fallbacks = [];
125
+
126
+ if (assetData.assetId?.includes('erc20:')) {
127
+ const match = assetData.assetId.match(/erc20:(0x[a-fA-F0-9]+)/);
128
+ if (match) {
129
+ const contract = match[1];
130
+ // Checksum the address properly
131
+ const checksummed = contract.substring(0, 2) + contract.substring(2).split('')
132
+ .map((char, i) => i % 2 === 0 && parseInt(char, 16) >= 8 ? char.toUpperCase() : char)
133
+ .join('');
134
+
135
+ fallbacks.push(`https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${checksummed}/logo.png`);
136
+ }
137
+ }
138
+
139
+ return fallbacks;
140
+ }
141
+
142
+ /**
143
+ * Process a single asset - Fast version
144
+ */
145
+ async function processAsset(assetId, assetData, stats, progress) {
146
+ const encoded = encodeAssetId(assetId);
147
+ const localPath = path.join(LOCAL_COINS_DIR, `${encoded}.png`);
148
+
149
+ stats.total++;
150
+
151
+ // Skip if already processed
152
+ if (progress.processed[assetId]) {
153
+ stats.skipped++;
154
+ return true;
155
+ }
156
+
157
+ // Check local file first (fastest)
158
+ if (fs.existsSync(localPath)) {
159
+ const fileSize = fs.statSync(localPath).size;
160
+ if (fileSize > 0) {
161
+ stats.alreadyLocal++;
162
+ progress.processed[assetId] = 'local';
163
+ assetData.icon = `https://api.keepkey.com/coins/${encoded}.png`;
164
+ return true;
165
+ }
166
+ }
167
+
168
+ console.log(`[${stats.total}/${stats.totalAssets}] ${assetData.symbol} - ${assetId.substring(0, 50)}...`);
169
+
170
+ // Try old URL if it exists
171
+ if (assetData.icon && !assetData.icon.includes('api.keepkey.com') && !assetData.icon.includes('api.keepkey.info')) {
172
+ const oldUrl = assetData.icon;
173
+ const downloaded = await downloadFile(oldUrl, localPath);
174
+ if (downloaded) {
175
+ console.log(` ✅ Downloaded from old URL`);
176
+ stats.downloaded++;
177
+ progress.processed[assetId] = 'old_url';
178
+ assetData.icon = `https://api.keepkey.com/coins/${encoded}.png`;
179
+ return true;
180
+ }
181
+ }
182
+
183
+ // Try fallbacks
184
+ const fallbacks = [
185
+ ...getCoinGeckoFallbacks(assetData),
186
+ ...getTrustWalletFallbacks(assetData),
187
+ ];
188
+
189
+ for (const url of fallbacks) {
190
+ const downloaded = await downloadFile(url, localPath);
191
+ if (downloaded) {
192
+ console.log(` ✅ Downloaded from fallback: ${url.substring(0, 60)}...`);
193
+ stats.downloadedFallback++;
194
+ progress.processed[assetId] = 'fallback';
195
+ assetData.icon = `https://api.keepkey.com/coins/${encoded}.png`;
196
+ return true;
197
+ }
198
+ }
199
+
200
+ // Mark as missing
201
+ console.log(` ❌ No icon found`);
202
+ stats.missing.push({
203
+ assetId,
204
+ symbol: assetData.symbol,
205
+ name: assetData.name,
206
+ chainId: assetData.chainId,
207
+ oldIcon: assetData.icon || null,
208
+ });
209
+ stats.missingCount++;
210
+ progress.processed[assetId] = 'missing';
211
+
212
+ // Still set the target URL
213
+ assetData.icon = `https://api.keepkey.com/coins/${encoded}.png`;
214
+ return false;
215
+ }
216
+
217
+ /**
218
+ * Save progress
219
+ */
220
+ function saveProgress(progress, data, stats) {
221
+ fs.writeFileSync(PROGRESS_FILE, JSON.stringify(progress, null, 2));
222
+ fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
223
+ if (stats.missing.length > 0) {
224
+ fs.writeFileSync(MISSING_ASSETS_FILE, JSON.stringify(stats.missing, null, 2));
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Main execution
230
+ */
231
+ async function main() {
232
+ console.log('🚀 Fast icon validation and download\n');
233
+
234
+ const data = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
235
+ const entries = Object.entries(data);
236
+
237
+ // Load progress if exists
238
+ let progress = { processed: {}, timestamp: Date.now() };
239
+ if (fs.existsSync(PROGRESS_FILE)) {
240
+ progress = JSON.parse(fs.readFileSync(PROGRESS_FILE, 'utf8'));
241
+ console.log(`📂 Resuming from previous run (${Object.keys(progress.processed).length} already processed)\n`);
242
+ }
243
+
244
+ const stats = {
245
+ total: 0,
246
+ totalAssets: entries.length,
247
+ skipped: 0,
248
+ alreadyLocal: 0,
249
+ downloaded: 0,
250
+ downloadedFallback: 0,
251
+ missingCount: 0,
252
+ missing: [],
253
+ };
254
+
255
+ // Process in batches with concurrency
256
+ const batchSize = 10;
257
+ const saveInterval = 50; // Save progress every 50 assets
258
+
259
+ for (let i = 0; i < entries.length; i += batchSize) {
260
+ const batch = entries.slice(i, i + batchSize);
261
+ await Promise.all(
262
+ batch.map(([assetId, assetData]) => processAsset(assetId, assetData, stats, progress))
263
+ );
264
+
265
+ // Save progress periodically
266
+ if (i % saveInterval === 0) {
267
+ saveProgress(progress, data, stats);
268
+ }
269
+ }
270
+
271
+ // Final save
272
+ saveProgress(progress, data, stats);
273
+
274
+ // Print summary
275
+ console.log('\n' + '='.repeat(80));
276
+ console.log('📊 SUMMARY');
277
+ console.log('='.repeat(80));
278
+ console.log(`Total assets: ${stats.totalAssets}`);
279
+ console.log(`Skipped (already processed): ${stats.skipped}`);
280
+ console.log(`Already in local storage: ${stats.alreadyLocal}`);
281
+ console.log(`Downloaded from old URL: ${stats.downloaded}`);
282
+ console.log(`Downloaded from fallback: ${stats.downloadedFallback}`);
283
+ console.log(`Missing (needs manual work): ${stats.missingCount}`);
284
+ console.log('='.repeat(80));
285
+
286
+ const localFiles = fs.readdirSync(LOCAL_COINS_DIR).filter(f => f.endsWith('.png')).length;
287
+ console.log(`\n📁 Local coins directory: ${localFiles} PNG files\n`);
288
+
289
+ if (stats.missing.length > 0) {
290
+ console.log(`⚠️ ${stats.missing.length} assets missing icons - see ${MISSING_ASSETS_FILE}\n`);
291
+ }
292
+
293
+ // Clean up progress file on completion
294
+ if (fs.existsSync(PROGRESS_FILE)) {
295
+ fs.unlinkSync(PROGRESS_FILE);
296
+ }
297
+
298
+ console.log('✅ Complete!\n');
299
+ }
300
+
301
+ main().catch(err => {
302
+ console.error('❌ Error:', err);
303
+ process.exit(1);
304
+ });