@pioneer-platform/pioneer-discovery 8.11.11 → 8.11.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/.turbo/turbo-build.log +1 -2
- package/CHANGELOG.md +12 -0
- package/data/coingecko-mapping.json +2 -2
- package/lib/generatedAssetData.json +5321 -42089
- package/package.json +1 -1
- package/scripts/.coingecko-failed.json +6754 -328
- package/scripts/.coingecko-progress.json +7888 -76
- package/scripts/.download-progress.json +65 -0
- package/scripts/.shapeshift-download-failed.json +30 -0
- package/scripts/.shapeshift-download-progress.json +4402 -0
- package/scripts/.shapeshift-download.pid +1 -0
- package/scripts/.upload-failed.json +3 -0
- package/scripts/README-S3-UPLOAD.md +373 -0
- package/scripts/coingecko-download.log +28263 -0
- package/scripts/download-shapeshift-icons.js +179 -0
- package/scripts/extract-shapeshift-icons.js +152 -0
- package/scripts/fix-s3-permissions.js +315 -0
- package/scripts/missing-assets.json +117 -36783
- package/scripts/shapeshift-download.log +5259 -0
- package/scripts/shapeshift-icon-mapping.json +57278 -0
- package/scripts/test-fix-permissions.js +156 -0
- package/scripts/upload-output.log +42954 -0
- package/scripts/upload-to-s3.js +363 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Upload Asset Icons to S3 (DigitalOcean Spaces)
|
|
5
|
+
*
|
|
6
|
+
* This script:
|
|
7
|
+
* 1. Reads generatedAssetData.json
|
|
8
|
+
* 2. For each asset, checks if local icon exists
|
|
9
|
+
* 3. Uploads to S3 with PUBLIC-READ ACL
|
|
10
|
+
* 4. Verifies upload is accessible (not 403)
|
|
11
|
+
* 5. Tracks progress and failures
|
|
12
|
+
*
|
|
13
|
+
* CRITICAL: Sets ACL to 'public-read' on each file to avoid 403 errors
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { execSync } = require('child_process');
|
|
19
|
+
const https = require('https');
|
|
20
|
+
|
|
21
|
+
// Configuration
|
|
22
|
+
const S3_ENDPOINT = 'https://sfo3.digitaloceanspaces.com';
|
|
23
|
+
const S3_BUCKET = 'keepkey';
|
|
24
|
+
const S3_REGION = 'sfo3';
|
|
25
|
+
const CDN_URL = 'https://keepkey.sfo3.cdn.digitaloceanspaces.com';
|
|
26
|
+
|
|
27
|
+
// Paths
|
|
28
|
+
const DATA_FILE = path.join(__dirname, '..', 'src', 'generatedAssetData.json');
|
|
29
|
+
const LOCAL_COINS_DIR = path.join(__dirname, '..', '..', '..', '..', '..', 'services', 'pioneer-server', 'public', 'coins');
|
|
30
|
+
const PROGRESS_FILE = path.join(__dirname, '.upload-progress.json');
|
|
31
|
+
const FAILED_FILE = path.join(__dirname, '.upload-failed.json');
|
|
32
|
+
|
|
33
|
+
// AWS credentials from environment
|
|
34
|
+
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || 'DO00FXP8KK64LCXYAEZP';
|
|
35
|
+
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || 'Uyw/cq63rrQmFV9yy1HbovTSMNhLkEwImqPa88N/E/s';
|
|
36
|
+
|
|
37
|
+
if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) {
|
|
38
|
+
console.error('❌ Missing AWS credentials');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Convert assetId to base64-encoded format
|
|
44
|
+
*/
|
|
45
|
+
function encodeAssetId(assetId) {
|
|
46
|
+
return Buffer.from(assetId).toString('base64');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if URL is accessible (returns 200)
|
|
51
|
+
*/
|
|
52
|
+
async function urlExists(url, timeout = 5000) {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const req = https.request(url, { method: 'HEAD', timeout }, (res) => {
|
|
55
|
+
resolve(res.statusCode === 200);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
req.on('error', () => resolve(false));
|
|
59
|
+
req.on('timeout', () => {
|
|
60
|
+
req.destroy();
|
|
61
|
+
resolve(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
req.end();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Upload file to S3 with public-read ACL
|
|
70
|
+
*/
|
|
71
|
+
function uploadToS3(localPath, s3Key) {
|
|
72
|
+
try {
|
|
73
|
+
const cmd = `AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} aws s3 cp "${localPath}" "s3://${S3_BUCKET}/${s3Key}" --endpoint-url=${S3_ENDPOINT} --acl public-read --region ${S3_REGION}`;
|
|
74
|
+
|
|
75
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
76
|
+
return true;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(` ❌ Upload error: ${error.message}`);
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Set ACL to public-read for existing file
|
|
85
|
+
*/
|
|
86
|
+
function setPublicAcl(s3Key) {
|
|
87
|
+
try {
|
|
88
|
+
const cmd = `AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} aws s3api put-object-acl --bucket ${S3_BUCKET} --key "${s3Key}" --acl public-read --endpoint-url=${S3_ENDPOINT}`;
|
|
89
|
+
|
|
90
|
+
execSync(cmd, { stdio: 'pipe' });
|
|
91
|
+
return true;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(` ❌ ACL set error: ${error.message}`);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Wait for specified milliseconds
|
|
100
|
+
*/
|
|
101
|
+
function sleep(ms) {
|
|
102
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Load progress file
|
|
107
|
+
*/
|
|
108
|
+
function loadProgress() {
|
|
109
|
+
if (fs.existsSync(PROGRESS_FILE)) {
|
|
110
|
+
return JSON.parse(fs.readFileSync(PROGRESS_FILE, 'utf8'));
|
|
111
|
+
}
|
|
112
|
+
return { processed: [], uploaded: 0, verified: 0, failed: 0 };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Save progress file
|
|
117
|
+
*/
|
|
118
|
+
function saveProgress(progress) {
|
|
119
|
+
fs.writeFileSync(PROGRESS_FILE, JSON.stringify(progress, null, 2));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Load or initialize failed tracking
|
|
124
|
+
*/
|
|
125
|
+
function loadFailed() {
|
|
126
|
+
if (fs.existsSync(FAILED_FILE)) {
|
|
127
|
+
return JSON.parse(fs.readFileSync(FAILED_FILE, 'utf8'));
|
|
128
|
+
}
|
|
129
|
+
return { assets: [] };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Save failed assets
|
|
134
|
+
*/
|
|
135
|
+
function saveFailed(failed) {
|
|
136
|
+
fs.writeFileSync(FAILED_FILE, JSON.stringify(failed, null, 2));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Process a single asset
|
|
141
|
+
*/
|
|
142
|
+
async function processAsset(assetId, assetData, progress, failed, stats) {
|
|
143
|
+
const encoded = encodeAssetId(assetId);
|
|
144
|
+
const localPath = path.join(LOCAL_COINS_DIR, `${encoded}.png`);
|
|
145
|
+
const s3Key = `coins/${encoded}.png`;
|
|
146
|
+
const cdnUrl = `${CDN_URL}/coins/${encoded}.png`;
|
|
147
|
+
|
|
148
|
+
stats.total++;
|
|
149
|
+
|
|
150
|
+
// Skip if already processed
|
|
151
|
+
if (progress.processed.includes(assetId)) {
|
|
152
|
+
console.log(`⏭️ [${stats.total}] Skipping (already processed): ${assetData.symbol}`);
|
|
153
|
+
stats.skipped++;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(`\n🔄 [${stats.total}] Processing: ${assetData.symbol} (${assetId.substring(0, 50)}...)`);
|
|
158
|
+
|
|
159
|
+
// Check if local file exists
|
|
160
|
+
if (!fs.existsSync(localPath)) {
|
|
161
|
+
console.log(` ⚠️ No local file - skipping upload`);
|
|
162
|
+
progress.processed.push(assetId);
|
|
163
|
+
stats.noLocal++;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const fileSize = fs.statSync(localPath).size;
|
|
168
|
+
console.log(` 📦 Local file size: ${(fileSize / 1024).toFixed(2)} KB`);
|
|
169
|
+
|
|
170
|
+
// Upload to S3 with public-read ACL
|
|
171
|
+
console.log(` 📤 Uploading to S3...`);
|
|
172
|
+
const uploaded = uploadToS3(localPath, s3Key);
|
|
173
|
+
|
|
174
|
+
if (!uploaded) {
|
|
175
|
+
console.log(` ❌ Upload failed`);
|
|
176
|
+
failed.assets.push({
|
|
177
|
+
assetId,
|
|
178
|
+
symbol: assetData.symbol,
|
|
179
|
+
reason: 'upload_failed',
|
|
180
|
+
localPath,
|
|
181
|
+
s3Key,
|
|
182
|
+
});
|
|
183
|
+
progress.processed.push(assetId);
|
|
184
|
+
progress.failed++;
|
|
185
|
+
stats.uploadFailed++;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log(` ✅ Uploaded successfully`);
|
|
190
|
+
progress.uploaded++;
|
|
191
|
+
stats.uploaded++;
|
|
192
|
+
|
|
193
|
+
// Wait a bit for S3 to propagate
|
|
194
|
+
await sleep(500);
|
|
195
|
+
|
|
196
|
+
// Verify accessibility via CDN
|
|
197
|
+
console.log(` 🔍 Verifying CDN access: ${cdnUrl.substring(0, 80)}...`);
|
|
198
|
+
const accessible = await urlExists(cdnUrl);
|
|
199
|
+
|
|
200
|
+
if (!accessible) {
|
|
201
|
+
console.log(` ⚠️ Not accessible (403?) - trying to set ACL...`);
|
|
202
|
+
|
|
203
|
+
const aclSet = setPublicAcl(s3Key);
|
|
204
|
+
if (aclSet) {
|
|
205
|
+
await sleep(1000); // Wait for ACL to propagate
|
|
206
|
+
const retryAccessible = await urlExists(cdnUrl);
|
|
207
|
+
|
|
208
|
+
if (retryAccessible) {
|
|
209
|
+
console.log(` ✅ Now accessible after ACL fix!`);
|
|
210
|
+
progress.verified++;
|
|
211
|
+
stats.verified++;
|
|
212
|
+
} else {
|
|
213
|
+
console.log(` ❌ Still not accessible after ACL fix`);
|
|
214
|
+
failed.assets.push({
|
|
215
|
+
assetId,
|
|
216
|
+
symbol: assetData.symbol,
|
|
217
|
+
reason: 'not_accessible_after_acl',
|
|
218
|
+
cdnUrl,
|
|
219
|
+
s3Key,
|
|
220
|
+
});
|
|
221
|
+
progress.failed++;
|
|
222
|
+
stats.verifyFailed++;
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
console.log(` ❌ Failed to set ACL`);
|
|
226
|
+
failed.assets.push({
|
|
227
|
+
assetId,
|
|
228
|
+
symbol: assetData.symbol,
|
|
229
|
+
reason: 'acl_set_failed',
|
|
230
|
+
cdnUrl,
|
|
231
|
+
s3Key,
|
|
232
|
+
});
|
|
233
|
+
progress.failed++;
|
|
234
|
+
stats.verifyFailed++;
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
console.log(` ✅ Verified accessible!`);
|
|
238
|
+
progress.verified++;
|
|
239
|
+
stats.verified++;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
progress.processed.push(assetId);
|
|
243
|
+
|
|
244
|
+
// Save progress every 10 assets
|
|
245
|
+
if (stats.total % 10 === 0) {
|
|
246
|
+
saveProgress(progress);
|
|
247
|
+
saveFailed(failed);
|
|
248
|
+
console.log(`\n📊 Progress saved - Total: ${stats.total}, Uploaded: ${stats.uploaded}, Verified: ${stats.verified}, Failed: ${stats.uploadFailed + stats.verifyFailed}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Main execution
|
|
254
|
+
*/
|
|
255
|
+
async function main() {
|
|
256
|
+
console.log('🚀 Starting S3 upload process...\n');
|
|
257
|
+
console.log(`📂 Local coins directory: ${LOCAL_COINS_DIR}`);
|
|
258
|
+
console.log(`☁️ S3 bucket: ${S3_BUCKET}`);
|
|
259
|
+
console.log(`🌐 CDN URL: ${CDN_URL}\n`);
|
|
260
|
+
|
|
261
|
+
// Check local directory exists
|
|
262
|
+
if (!fs.existsSync(LOCAL_COINS_DIR)) {
|
|
263
|
+
console.error(`❌ Local coins directory not found: ${LOCAL_COINS_DIR}`);
|
|
264
|
+
console.log('💡 Run the download script first to get icons locally');
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Load data and progress
|
|
269
|
+
const data = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
|
|
270
|
+
const entries = Object.entries(data);
|
|
271
|
+
const progress = loadProgress();
|
|
272
|
+
const failed = loadFailed();
|
|
273
|
+
|
|
274
|
+
const stats = {
|
|
275
|
+
total: 0,
|
|
276
|
+
skipped: 0,
|
|
277
|
+
noLocal: 0,
|
|
278
|
+
uploaded: 0,
|
|
279
|
+
verified: 0,
|
|
280
|
+
uploadFailed: 0,
|
|
281
|
+
verifyFailed: 0,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
console.log(`📊 Total assets in database: ${entries.length}`);
|
|
285
|
+
console.log(`📊 Already processed: ${progress.processed.length}`);
|
|
286
|
+
console.log(`📊 Remaining: ${entries.length - progress.processed.length}\n`);
|
|
287
|
+
|
|
288
|
+
if (progress.processed.length > 0) {
|
|
289
|
+
const answer = require('readline').createInterface({
|
|
290
|
+
input: process.stdin,
|
|
291
|
+
output: process.stdout
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const response = await new Promise(resolve => {
|
|
295
|
+
answer.question('Resume from previous progress? (y/n): ', resolve);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (response.toLowerCase() !== 'y') {
|
|
299
|
+
console.log('Starting fresh...');
|
|
300
|
+
progress.processed = [];
|
|
301
|
+
progress.uploaded = 0;
|
|
302
|
+
progress.verified = 0;
|
|
303
|
+
progress.failed = 0;
|
|
304
|
+
failed.assets = [];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
answer.close();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Process assets sequentially (to respect rate limits)
|
|
311
|
+
for (const [assetId, assetData] of entries) {
|
|
312
|
+
await processAsset(assetId, assetData, progress, failed, stats);
|
|
313
|
+
|
|
314
|
+
// Small delay between uploads to be nice to S3
|
|
315
|
+
await sleep(100);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Final save
|
|
319
|
+
saveProgress(progress);
|
|
320
|
+
saveFailed(failed);
|
|
321
|
+
|
|
322
|
+
// Cleanup progress file if complete
|
|
323
|
+
if (progress.processed.length === entries.length && failed.assets.length === 0) {
|
|
324
|
+
fs.unlinkSync(PROGRESS_FILE);
|
|
325
|
+
console.log('\n✅ All assets processed successfully - progress file cleaned up');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Print summary
|
|
329
|
+
console.log('\n' + '='.repeat(80));
|
|
330
|
+
console.log('📊 FINAL SUMMARY');
|
|
331
|
+
console.log('='.repeat(80));
|
|
332
|
+
console.log(`Total assets: ${entries.length}`);
|
|
333
|
+
console.log(`Already processed (skipped): ${stats.skipped}`);
|
|
334
|
+
console.log(`No local file: ${stats.noLocal}`);
|
|
335
|
+
console.log(`Uploaded successfully: ${stats.uploaded}`);
|
|
336
|
+
console.log(`Verified accessible: ${stats.verified}`);
|
|
337
|
+
console.log(`Upload failed: ${stats.uploadFailed}`);
|
|
338
|
+
console.log(`Verification failed: ${stats.verifyFailed}`);
|
|
339
|
+
console.log(`Total failed: ${stats.uploadFailed + stats.verifyFailed}`);
|
|
340
|
+
console.log('='.repeat(80));
|
|
341
|
+
|
|
342
|
+
if (failed.assets.length > 0) {
|
|
343
|
+
console.log(`\n⚠️ ${failed.assets.length} assets failed - see ${FAILED_FILE}`);
|
|
344
|
+
|
|
345
|
+
// Group by failure reason
|
|
346
|
+
const byReason = {};
|
|
347
|
+
failed.assets.forEach(asset => {
|
|
348
|
+
byReason[asset.reason] = (byReason[asset.reason] || 0) + 1;
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
console.log('\nFailure reasons:');
|
|
352
|
+
Object.entries(byReason).forEach(([reason, count]) => {
|
|
353
|
+
console.log(` ${reason}: ${count}`);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
console.log('\n✅ Upload process complete!\n');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
main().catch(err => {
|
|
361
|
+
console.error('❌ Fatal error:', err);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
});
|