@pioneer-platform/pioneer-discovery 0.8.3 → 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.
- package/.claude/settings.local.json +12 -0
- package/.turbo/turbo-build.log +2 -1
- package/CHANGELOG.md +12 -0
- package/lib/generatedAssetData.json +14221 -14217
- package/package.json +1 -1
- package/scripts/missing-assets.json +99101 -0
- package/scripts/update-icon-urls.js +86 -0
- package/scripts/validate-and-download-icons-fast.js +304 -0
- package/scripts/validate-and-download-icons.js +326 -0
|
@@ -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
|
+
});
|