') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - optout21/icepeek: Watch-only bitcoin onchain balance checker, private and secure, based on compact block filters (BIP147/148) · GitHub
Skip to content

Repository files navigation

Icepeek

Update Dec'2025

The concept is somwhat weakened by the time taken to download all the filter. Nonetheless, Kyoto is improving. Update to v0.15 is ongoing (https://github.com/optout21/icepeek/tree/kyoto-15).

Description

Watch-only bitcoin onchain balance checker, private and secure. Can be used for checking cold wallet balances ("peek under the ice").

WARNING: Don't ever enter any seedphrase or private key in this project!

Warning: This software is prototype with no warranty whatsoever.

Based on compact block filters (BIP-147/148), peer-to-peer, not using servers. Written in Rust, using kyoto-cbf lib.

Friendly Advice: Don't forget to regularly check your keys!

Variants

  • icepeek-iced: desktop version with UI, based on iced library (Rust)

  • icepeek-cli: simple command-line based version (mostly for testing)

History

The first version of Icepeek was based on Nakamoto CBF library, but later (Oct2024) it was switched to the more recent kyoto-cbf.

How to run

The (simple) command-line variant:

cargo r -p icepeek-cli

Wait and watch the output!

The desktop UI variant:

cargo r -p icepeek-iced
  • Optionally change the xpub & parameters

  • Press Continue

  • Wait and watch the info

TODO

  • show state (state Connecting, FastSync, ChilledSync; show number of peers)
  • more user-friendly starting block supoprt (no hash needed, custom height, start filters from there)
  • initial DNS-based peer discovery doesn't work with VPN (kyoto?)
  • Address discovery (gap limit, add new addresses)
  • download filters in reverse order

Kyoto Qs

  • report more progress events (peer connect/disconnect, etc.)

Nakamoto Qs (obsolete)

  • latest version is not released (can only be used with local build)
  • filters are always retrieved again, even if they are cached (first they are loaded, then retrieved from the beginning)
  • load never returns, scan cannot be started only after load
  • separation of load and retrieve (wallet-independent), and scan (wallet dependent)

About

Watch-only bitcoin onchain balance checker, private and secure, based on compact block filters (BIP147/148)

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages