Coinbase Transaction Exporter

Export your Coinbase transaction history to CSV or JSON format

Size

16.8 KB

Version

1.0.0

Created

Nov 27, 2025

Updated

15 days ago

1// ==UserScript==
2// @name		Coinbase Transaction Exporter
3// @description		Export your Coinbase transaction history to CSV or JSON format
4// @version		1.0.0
5// @match		https://*.coinbase.com/*
6// @icon		https://www.coinbase.com/img/favicon/favicon-32.png
7// @grant		GM.setClipboard
8// ==/UserScript==
9(function() {
10    'use strict';
11
12    console.log('Coinbase Transaction Exporter loaded');
13
14    // Utility function to debounce
15    function debounce(func, wait) {
16        let timeout;
17        return function executedFunction(...args) {
18            const later = () => {
19                clearTimeout(timeout);
20                func(...args);
21            };
22            clearTimeout(timeout);
23            timeout = setTimeout(later, wait);
24        };
25    }
26
27    // Extract transaction data from the page
28    function extractTransactionData() {
29        console.log('Extracting transaction data...');
30        const transactions = [];
31        
32        // Find all transaction rows
33        const transactionRows = document.querySelectorAll('tr[data-testid="transaction-history-row"]');
34        console.log(`Found ${transactionRows.length} transaction rows`);
35        
36        transactionRows.forEach((row, index) => {
37            try {
38                const ariaLabel = row.getAttribute('aria-label') || '';
39                
40                // Extract transaction type and amount from aria-label
41                // Format: "Sent USDC -5.07 USDC" or "Received BTC +0.001 BTC"
42                const labelParts = ariaLabel.split(' ');
43                
44                // Get all cells in the row
45                const cells = row.querySelectorAll('td');
46                
47                // Extract transaction details
48                const transaction = {
49                    type: '',
50                    asset: '',
51                    amount: '',
52                    value: '',
53                    date: '',
54                    status: ''
55                };
56                
57                // Parse aria-label for type and amount
58                if (labelParts.length >= 3) {
59                    transaction.type = labelParts[0]; // "Sent" or "Received"
60                    transaction.asset = labelParts[1]; // "USDC", "BTC", etc.
61                    transaction.amount = labelParts.slice(2).join(' '); // "-5.07 USDC"
62                }
63                
64                // Extract additional details from cells
65                cells.forEach((cell, cellIndex) => {
66                    const cellText = cell.textContent.trim();
67                    
68                    // First cell usually contains type and asset info
69                    if (cellIndex === 0) {
70                        const typeElement = cell.querySelector('p[class*="headline"]');
71                        if (typeElement) {
72                            transaction.type = typeElement.textContent.trim();
73                        }
74                    }
75                    
76                    // Look for value (currency amount like €5.07)
77                    if (cellText.match(/[€$£¥]\d+/)) {
78                        transaction.value = cellText;
79                    }
80                    
81                    // Look for date patterns
82                    if (cellText.match(/\d{1,2}\/\d{1,2}\/\d{2,4}/) || 
83                        cellText.match(/Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec/)) {
84                        transaction.date = cellText;
85                    }
86                });
87                
88                // Only add if we have meaningful data
89                if (transaction.type || transaction.amount) {
90                    transactions.push(transaction);
91                    console.log(`Transaction ${index + 1}:`, transaction);
92                }
93            } catch (error) {
94                console.error(`Error extracting transaction ${index}:`, error);
95            }
96        });
97        
98        return transactions;
99    }
100
101    // Convert transactions to CSV format
102    function convertToCSV(transactions) {
103        if (transactions.length === 0) {
104            return 'No transactions found';
105        }
106        
107        // Get all unique keys from transactions
108        const headers = ['Type', 'Asset', 'Amount', 'Value', 'Date', 'Status'];
109        
110        // Create CSV header
111        let csv = headers.join(',') + '\n';
112        
113        // Add data rows
114        transactions.forEach(transaction => {
115            const row = [
116                escapeCSV(transaction.type || ''),
117                escapeCSV(transaction.asset || ''),
118                escapeCSV(transaction.amount || ''),
119                escapeCSV(transaction.value || ''),
120                escapeCSV(transaction.date || ''),
121                escapeCSV(transaction.status || '')
122            ];
123            csv += row.join(',') + '\n';
124        });
125        
126        return csv;
127    }
128
129    // Escape CSV values
130    function escapeCSV(value) {
131        if (typeof value !== 'string') {
132            value = String(value);
133        }
134        // Escape quotes and wrap in quotes if contains comma, quote, or newline
135        if (value.includes(',') || value.includes('"') || value.includes('\n')) {
136            return '"' + value.replace(/"/g, '""') + '"';
137        }
138        return value;
139    }
140
141    // Download file
142    function downloadFile(content, filename, mimeType) {
143        const blob = new Blob([content], { type: mimeType });
144        const url = URL.createObjectURL(blob);
145        const link = document.createElement('a');
146        link.href = url;
147        link.download = filename;
148        document.body.appendChild(link);
149        link.click();
150        document.body.removeChild(link);
151        URL.revokeObjectURL(url);
152        console.log(`Downloaded ${filename}`);
153    }
154
155    // Export to CSV
156    async function exportToCSV() {
157        console.log('Exporting to CSV...');
158        const transactions = extractTransactionData();
159        
160        if (transactions.length === 0) {
161            alert('No transactions found on this page. Please make sure you are on the transactions page.');
162            return;
163        }
164        
165        const csv = convertToCSV(transactions);
166        const timestamp = new Date().toISOString().split('T')[0];
167        downloadFile(csv, `coinbase-transactions-${timestamp}.csv`, 'text/csv');
168        
169        // Show success message
170        showNotification(`Exported ${transactions.length} transactions to CSV`, 'success');
171    }
172
173    // Export to JSON
174    async function exportToJSON() {
175        console.log('Exporting to JSON...');
176        const transactions = extractTransactionData();
177        
178        if (transactions.length === 0) {
179            alert('No transactions found on this page. Please make sure you are on the transactions page.');
180            return;
181        }
182        
183        const json = JSON.stringify(transactions, null, 2);
184        const timestamp = new Date().toISOString().split('T')[0];
185        downloadFile(json, `coinbase-transactions-${timestamp}.json`, 'application/json');
186        
187        // Show success message
188        showNotification(`Exported ${transactions.length} transactions to JSON`, 'success');
189    }
190
191    // Copy to clipboard
192    async function copyToClipboard() {
193        console.log('Copying to clipboard...');
194        const transactions = extractTransactionData();
195        
196        if (transactions.length === 0) {
197            alert('No transactions found on this page. Please make sure you are on the transactions page.');
198            return;
199        }
200        
201        const csv = convertToCSV(transactions);
202        
203        try {
204            await GM.setClipboard(csv);
205            showNotification(`Copied ${transactions.length} transactions to clipboard`, 'success');
206        } catch (error) {
207            console.error('Error copying to clipboard:', error);
208            showNotification('Failed to copy to clipboard', 'error');
209        }
210    }
211
212    // Show notification
213    function showNotification(message, type = 'info') {
214        const notification = document.createElement('div');
215        notification.textContent = message;
216        notification.style.cssText = `
217            position: fixed;
218            top: 20px;
219            right: 20px;
220            background: ${type === 'success' ? '#0052FF' : type === 'error' ? '#FF4444' : '#333'};
221            color: white;
222            padding: 16px 24px;
223            border-radius: 8px;
224            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
225            font-size: 14px;
226            font-weight: 500;
227            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
228            z-index: 999999;
229            animation: slideIn 0.3s ease-out;
230        `;
231        
232        document.body.appendChild(notification);
233        
234        setTimeout(() => {
235            notification.style.animation = 'slideOut 0.3s ease-out';
236            setTimeout(() => {
237                if (notification.parentNode) {
238                    notification.parentNode.removeChild(notification);
239                }
240            }, 300);
241        }, 3000);
242    }
243
244    // Add CSS animations
245    TM_addStyle(`
246        @keyframes slideIn {
247            from {
248                transform: translateX(400px);
249                opacity: 0;
250            }
251            to {
252                transform: translateX(0);
253                opacity: 1;
254            }
255        }
256        
257        @keyframes slideOut {
258            from {
259                transform: translateX(0);
260                opacity: 1;
261            }
262            to {
263                transform: translateX(400px);
264                opacity: 0;
265            }
266        }
267        
268        .coinbase-export-button {
269            display: flex;
270            align-items: center;
271            justify-content: center;
272            gap: 8px;
273            background: #0052FF;
274            color: white;
275            border: none;
276            border-radius: 8px;
277            padding: 12px 20px;
278            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
279            font-size: 14px;
280            font-weight: 600;
281            cursor: pointer;
282            transition: all 0.2s ease;
283            box-shadow: 0 2px 4px rgba(0, 82, 255, 0.2);
284        }
285        
286        .coinbase-export-button:hover {
287            background: #0045DD;
288            box-shadow: 0 4px 8px rgba(0, 82, 255, 0.3);
289            transform: translateY(-1px);
290        }
291        
292        .coinbase-export-button:active {
293            transform: translateY(0);
294            box-shadow: 0 2px 4px rgba(0, 82, 255, 0.2);
295        }
296        
297        .coinbase-export-dropdown {
298            position: absolute;
299            top: 100%;
300            right: 0;
301            margin-top: 8px;
302            background: white;
303            border-radius: 8px;
304            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
305            overflow: hidden;
306            z-index: 999999;
307            min-width: 200px;
308        }
309        
310        .coinbase-export-dropdown-item {
311            display: flex;
312            align-items: center;
313            gap: 12px;
314            padding: 12px 16px;
315            background: white;
316            color: #050F19;
317            border: none;
318            width: 100%;
319            text-align: left;
320            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
321            font-size: 14px;
322            font-weight: 500;
323            cursor: pointer;
324            transition: background 0.2s ease;
325        }
326        
327        .coinbase-export-dropdown-item:hover {
328            background: #F5F8FF;
329        }
330        
331        .coinbase-export-container {
332            position: relative;
333            display: inline-block;
334        }
335    `);
336
337    // Create export button
338    function createExportButton() {
339        console.log('Creating export button...');
340        
341        // Check if button already exists
342        if (document.getElementById('coinbase-export-container')) {
343            console.log('Export button already exists');
344            return;
345        }
346        
347        // Find the filter section to place the button
348        const filterSection = document.querySelector('div[class*="sticky"][class*="zIndex"]');
349        
350        if (!filterSection) {
351            console.log('Filter section not found, will retry...');
352            return;
353        }
354        
355        // Create container
356        const container = document.createElement('div');
357        container.id = 'coinbase-export-container';
358        container.className = 'coinbase-export-container';
359        container.style.cssText = 'margin-left: auto; padding: 0 16px;';
360        
361        // Create main button
362        const button = document.createElement('button');
363        button.className = 'coinbase-export-button';
364        button.innerHTML = `
365            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
366                <path d="M8 11L4 7H7V2H9V7H12L8 11Z" fill="currentColor"/>
367                <path d="M2 13H14V15H2V13Z" fill="currentColor"/>
368            </svg>
369            Export
370        `;
371        
372        // Create dropdown
373        const dropdown = document.createElement('div');
374        dropdown.className = 'coinbase-export-dropdown';
375        dropdown.style.display = 'none';
376        
377        // CSV option
378        const csvOption = document.createElement('button');
379        csvOption.className = 'coinbase-export-dropdown-item';
380        csvOption.innerHTML = `
381            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
382                <path d="M3 2H13V14H3V2Z" stroke="currentColor" stroke-width="1.5" fill="none"/>
383                <path d="M5 5H11M5 8H11M5 11H9" stroke="currentColor" stroke-width="1.5"/>
384            </svg>
385            Export as CSV
386        `;
387        csvOption.addEventListener('click', () => {
388            dropdown.style.display = 'none';
389            exportToCSV();
390        });
391        
392        // JSON option
393        const jsonOption = document.createElement('button');
394        jsonOption.className = 'coinbase-export-dropdown-item';
395        jsonOption.innerHTML = `
396            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
397                <path d="M4 2H12V14H4V2Z" stroke="currentColor" stroke-width="1.5" fill="none"/>
398                <path d="M6 5H10M6 8H10M6 11H8" stroke="currentColor" stroke-width="1.5"/>
399            </svg>
400            Export as JSON
401        `;
402        jsonOption.addEventListener('click', () => {
403            dropdown.style.display = 'none';
404            exportToJSON();
405        });
406        
407        // Copy option
408        const copyOption = document.createElement('button');
409        copyOption.className = 'coinbase-export-dropdown-item';
410        copyOption.innerHTML = `
411            <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
412                <path d="M5 5V3H13V11H11" stroke="currentColor" stroke-width="1.5" fill="none"/>
413                <path d="M3 5H11V13H3V5Z" stroke="currentColor" stroke-width="1.5" fill="none"/>
414            </svg>
415            Copy to Clipboard
416        `;
417        copyOption.addEventListener('click', () => {
418            dropdown.style.display = 'none';
419            copyToClipboard();
420        });
421        
422        dropdown.appendChild(csvOption);
423        dropdown.appendChild(jsonOption);
424        dropdown.appendChild(copyOption);
425        
426        // Toggle dropdown
427        button.addEventListener('click', (e) => {
428            e.stopPropagation();
429            dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
430        });
431        
432        // Close dropdown when clicking outside
433        document.addEventListener('click', () => {
434            dropdown.style.display = 'none';
435        });
436        
437        container.appendChild(button);
438        container.appendChild(dropdown);
439        
440        // Find the flex container that holds the filters
441        const flexContainer = filterSection.querySelector('div[class*="flex"][class*="row"]');
442        if (flexContainer) {
443            flexContainer.appendChild(container);
444            console.log('Export button added successfully');
445        } else {
446            console.log('Flex container not found');
447        }
448    }
449
450    // Initialize the extension
451    function init() {
452        console.log('Initializing Coinbase Transaction Exporter...');
453        
454        // Check if we're on the transactions page
455        if (window.location.pathname.includes('/transactions')) {
456            // Wait for the page to load
457            setTimeout(() => {
458                createExportButton();
459            }, 2000);
460            
461            // Observe DOM changes to re-add button if needed
462            const observer = new MutationObserver(debounce(() => {
463                if (!document.getElementById('coinbase-export-container')) {
464                    createExportButton();
465                }
466            }, 1000));
467            
468            observer.observe(document.body, {
469                childList: true,
470                subtree: true
471            });
472        }
473    }
474
475    // Run when body is ready
476    TM_runBody(init);
477})();
Coinbase Transaction Exporter | Robomonkey