Add transaction accelerator (prioritisetransaction UI + API)

Full-stack feature for boosting transactions via bitcoind's
prioritisetransaction RPC:

Backend:
- New accelerator package with Accelerate, Cancel, MaxFeerate, List,
  and background Cleanup goroutine (removes confirmed/dropped txs)
- RPC wrappers: GetMempoolEntry, PrioritiseTransaction,
  GetRawMempoolVerbose, IsRPCError helpers
- SQLite boosted_txs table for persistence across restarts
- Revenue impact measured via getblocktemplate before/after comparison
- Hard cap at 2000 sat/vB; MaxFeerate uses fees.base (not modified)
  to ignore our own prior priority adjustments

Frontend:
- Rocket icon in header with 10s jiggle animation
- AcceleratorPage with flame-gradient border, txid input, feerate
  input, "Prioritize above all" button with spinner, boost list with
  cancel buttons, and dismissible error/success messages
- Hash-based routing (#/accelerator)
- SharesBar font size bump
This commit is contained in:
satoshi
2026-05-11 03:54:35 +03:00
parent 72890d900b
commit dc7da6ab19
12 changed files with 1247 additions and 25 deletions
+17 -6
View File
@@ -3,6 +3,7 @@
// #/ -> dashboard
// #/user/<address> -> per-user page
// #/worker/<workername> -> per-worker page
// #/accelerator -> transaction accelerator
//
// selectUser / selectWorker / clearSelection just mutate the hash;
// a hashchange listener reads it back into reactive state so any
@@ -10,29 +11,34 @@
const USER_PREFIX = "#/user/";
const WORKER_PREFIX = "#/worker/";
const ACCELERATOR_HASH = "#/accelerator";
type Selection = { user: string | null; worker: string | null };
type Selection = { user: string | null; worker: string | null; page: string | null };
export const selection = $state<Selection>(readHash());
function readHash(): Selection {
if (typeof window === "undefined") return { user: null, worker: null };
if (typeof window === "undefined") return { user: null, worker: null, page: null };
const h = window.location.hash;
if (h === ACCELERATOR_HASH) {
return { user: null, worker: null, page: "accelerator" };
}
if (h.startsWith(WORKER_PREFIX)) {
const w = decodeURIComponent(h.slice(WORKER_PREFIX.length));
return { user: null, worker: w || null };
return { user: null, worker: w || null, page: null };
}
if (h.startsWith(USER_PREFIX)) {
const u = decodeURIComponent(h.slice(USER_PREFIX.length));
return { user: u || null, worker: null };
return { user: u || null, worker: null, page: null };
}
return { user: null, worker: null };
return { user: null, worker: null, page: null };
}
function syncFromHash(): void {
const parsed = readHash();
selection.user = parsed.user;
selection.worker = parsed.worker;
selection.page = parsed.page;
}
if (typeof window !== "undefined") {
@@ -47,11 +53,16 @@ export function selectWorker(workername: string): void {
window.location.hash = WORKER_PREFIX + encodeURIComponent(workername);
}
export function selectAccelerator(): void {
window.location.hash = ACCELERATOR_HASH;
}
export function clearSelection(): void {
if (
window.history.length > 1 &&
(window.location.hash.startsWith(USER_PREFIX) ||
window.location.hash.startsWith(WORKER_PREFIX))
window.location.hash.startsWith(WORKER_PREFIX) ||
window.location.hash === ACCELERATOR_HASH)
) {
window.history.back();
} else {