Repository navigation
Expand file tree
/
Copy pathkaspa-mining.html
More file actions
2881 lines (2711 loc) · 161 KB
/
Copy pathkaspa-mining.html
File metadata and controls
2881 lines (2711 loc) · 161 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<script id="theme-init">
(function () {
var key = "kaspa-explained-theme";
var saved = null;
try { saved = localStorage.getItem(key); } catch (e) {}
var theme = new URLSearchParams(window.location.search).get("theme");
if (theme !== "light" && theme !== "dark") { theme = saved; }
document.documentElement.dataset.theme = theme === "light" ? "light" : "dark";
})();
</script>
<title>Kaspa Mining | Kaspa Explained</title>
<meta name="description" content="Why Kaspa price and hash rate move on different clocks, and how to point your own ASIC at your own node.">
<meta name="robots" content="index,follow,max-snippet:-1,max-image-preview:large">
<link rel="canonical" href="https://kaspaexplained.com/kaspa-mining">
<link rel="icon" href="kaspa-favicon.svg?v=20260512-real-k" type="image/svg+xml">
<link rel="icon" href="favicon.svg?v=20260512-k4" type="image/svg+xml">
<link rel="icon" href="favicon.ico" sizes="any">
<link rel="icon" href="favicon.png" type="image/png">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<link rel="manifest" href="site.webmanifest">
<meta name="application-name" content="Kaspa Explained">
<meta name="apple-mobile-web-app-title" content="Kaspa Explained">
<meta name="theme-color" content="#000000">
<meta property="og:title" content="Kaspa Mining | Kaspa Explained">
<meta property="og:description" content="Why Kaspa price and hash rate move on different clocks, and how to point your own ASIC at your own node.">
<meta property="og:type" content="article">
<meta property="og:url" content="https://kaspaexplained.com/kaspa-mining">
<meta property="og:image" content="https://kaspaexplained.com/og-kaspa-explained.png?v=20260829">
<meta property="og:image:type" content="image/png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Kaspa Explained - proof-of-work blockDAG guide">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Kaspa Mining | Kaspa Explained">
<meta name="twitter:description" content="Why Kaspa price and hash rate move on different clocks, and how to point your own ASIC at your own node.">
<meta name="twitter:image" content="https://kaspaexplained.com/og-kaspa-explained.png?v=20260829">
<meta name="dateModified" content="2026-10-06">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "Kaspa Mining",
"url": "https://kaspaexplained.com/kaspa-mining",
"dateModified": "2026-10-06",
"description": "Why Kaspa price and hash rate move on different clocks, and how to point your own ASIC at your own node.",
"about": ["Kaspa", "Mining", "Proof of Work", "ASIC", "Rusty Kaspa", "Stratum Bridge", "Toccata"]
}
</script>
<link rel="stylesheet" href="styles.css?v=20260915-apple27">
<script defer src="nav.js?v=20260915-apple27"></script>
</head>
<body>
<a class="skip-link" href="#top">Skip to content</a>
<header class="site-header">
<nav class="nav" aria-label="Primary">
<a class="brand" href="/" aria-label="Kaspa Explained home">
<span class="brand-mark" aria-hidden="true"></span>
Kaspa Explained
</a>
<button class="nav-menu-button" type="button" aria-expanded="false" aria-controls="primary-links">Menu</button>
<div id="primary-links" class="nav-links">
<a href="/what-is-kaspa">What is Kaspa</a>
<a href="/status">Live now</a>
<a href="/skeptical-case">Risks</a>
<a href="/build-on-kaspa">Build</a>
<a href="/demos">Try it</a>
</div>
<button class="theme-toggle" type="button" aria-label="Switch theme">Light</button>
</nav>
</header>
<main id="top" tabindex="-1" class="sources-page mining-cycle-page">
<section class="sources-hero section">
<p class="eyebrow">Proof-of-work mining</p>
<h1>Kaspa mining</h1>
<p class="lead">Price can fall while hash rate climbs. A coin market reprices in minutes; a machine market (ASIC orders, shipping, hosting) answers weeks or months later.</p>
<p class="fit-note">Toccata activated at <span class="term-def" tabindex="0" role="button" aria-describedby="daa-def-snapshot">DAA score<span class="term-def__panel" id="daa-def-snapshot">Difficulty-Adjusted Age: a running block count Kaspa consensus uses instead of wall-clock time, since many blocks land in parallel.</span></span> 474,165,565. Rusty Kaspa v2.1.0 is the current node release. Nothing here is a price call.</p>
</section>
<section class="section mining-data-panel">
<p class="eyebrow">Snapshot</p>
<h2>What the network is doing</h2>
<div class="summary-grid grid-cards">
<article><span>KAS price</span><p>Kaspa API: <strong id="snap-price">$0.04254906</strong> per KAS. All-time high: <strong>$0.2074</strong> (CoinGecko, 31 July 2024).</p></article>
<article><span>Network hash rate</span><p>Kaspa API: <strong id="snap-hash">330.0 PH/s</strong>, about <strong id="snap-hash-mult">660</strong>x<span class="info-affordance" id="hash-info"><button class="info-affordance__trigger" type="button" aria-describedby="hash-info-panel"><i class="info-affordance__glyph" aria-hidden="true">i</i><span class="sr-only">More about this comparison</span></button><span class="info-affordance__panel" id="hash-info-panel">Compared against the 500 TH/s the Kaspa wiki records for November 29, 2022.</span></span></p></article>
<article><span>Supply</span><p>Kaspa API: <strong id="snap-circ">27.741B KAS</strong> mined of a <strong>28.704B KAS</strong> maximum.</p></article>
<article><span>Block reward</span><p>Kaspa API: <strong id="snap-reward">2.06017223 KAS</strong> per block. The reward changes with DAA score.</p></article>
</div>
<p class="live-footnote" id="snap-stamp">Read from api.kaspa.org on October 6, 2026. These four numbers refresh from the same API when the page loads.</p>
<script>
// One cache in front of api.kaspa.org, because three separate widgets on this page
// want the same four endpoints and a reader should not pay for the same read twice.
window.KE_kaspaApi = (function () {
var cache = {};
return function (path) {
if (!cache[path]) {
cache[path] = fetch('https://api.kaspa.org/' + path, { cache: 'no-store' })
.then(function (r) { if (!r.ok) { throw new Error('http ' + r.status); } return r.json(); });
}
return cache[path];
};
})();
(function () {
if (!('fetch' in window)) { return; }
var BASELINE = 'Read from api.kaspa.org on October 6, 2026.';
var stamp = document.getElementById('snap-stamp');
function set(id, text) { var el = document.getElementById(id); if (el) { el.textContent = text; } }
Promise.all([
window.KE_kaspaApi('info/price'),
window.KE_kaspaApi('info/hashrate'),
window.KE_kaspaApi('info/coinsupply'),
window.KE_kaspaApi('info/blockreward')
]).then(function (r) {
var price = Number(r[0].price);
var thPerSec = Number(r[1].hashrate);
var circ = Number(r[2].circulatingSupply) / 1e8;
var reward = Number(r[3].blockreward);
if (!(price > 0 && thPerSec > 0 && circ > 0 && reward > 0)) { throw new Error('bad values'); }
set('snap-price', '$' + price.toFixed(8).replace(/0+$/, '').replace(/\.$/, '.0'));
set('snap-hash', (thPerSec / 1000).toFixed(1) + ' PH/s');
set('snap-hash-mult', Math.round(thPerSec / 500).toLocaleString('en-US'));
set('snap-circ', (circ / 1e9).toFixed(3) + 'B KAS');
set('snap-reward', reward + ' KAS');
stamp.textContent = 'Read from api.kaspa.org just now.';
}).catch(function () {
stamp.textContent = 'The live read from api.kaspa.org failed. ' + BASELINE;
});
})();
</script>
<div class="actions">
<a class="button primary" href="#two-markets">The two markets</a>
<a class="button" href="#cycle">The cycle</a>
<a class="button" href="#evidence">What to watch</a>
<a class="button" href="#solo-mining">Solo mining setup</a>
<a class="button" href="#sources">Sources</a>
</div>
</section>
<section id="two-markets" class="section">
<h2>The two markets</h2>
<div class="view-switch">
<input class="view-switch__input" type="radio" name="two-markets-view" id="two-markets-simple" checked>
<input class="view-switch__input" type="radio" name="two-markets-view" id="two-markets-full">
<div class="view-switch__tabs" role="radiogroup" aria-label="View">
<label class="view-switch__tab" for="two-markets-simple">Clocks</label>
<label class="view-switch__tab" for="two-markets-full">Full loop</label>
</div>
<div class="view-switch__panels">
<div class="view-switch__panel">
<div class="clock-diagram" aria-label="Coin market and machine market move at different speeds">
<div class="clock-lane fast-lane">
<div>
<span>Coin market</span>
<strong>minutes to days</strong>
<p>price, liquidity, sentiment</p>
</div>
<div class="signal-line" aria-hidden="true"><i></i></div>
</div>
<div class="clock-lane slow-lane">
<div>
<span>Machine market</span>
<strong>weeks to months</strong>
<p>ASIC orders, shipping, hosting, hash rate, difficulty</p>
</div>
<div class="signal-line" aria-hidden="true"><i></i></div>
</div>
</div>
</div>
<div class="view-switch__panel">
<div class="market-flow" aria-label="Kaspa proof-of-work market loop">
<article><span>Coin demand</span><strong>Moves first</strong><p>Reprices KAS within hours.</p></article>
<article><span>Miner revenue</span><strong>Sets the day's math</strong><p>Price and power cost decide if mining pays.</p></article>
<article><span>ASIC demand</span><strong>Capital follows margin</strong><p>Profit pulls capital into hardware orders.</p></article>
<article><span>Hash rate</span><strong>Arrives late</strong><p>Ordered machines arrive after the signal faded.</p></article>
<article><span>Difficulty</span><strong>Compresses margin</strong><p>More machines chase the same reward.</p></article>
<article><span>Used machines</span><strong>Sort the survivors</strong><p>The cold phase sorts who outlasts it.</p></article>
</div>
</div>
</div>
</div>
</section>
<section id="cycle" class="section">
<p class="eyebrow">Cycle model</p>
<h2>Coins and ASICs heat and cool at different times</h2>
<p class="lead">This framing is an outside opinion, not a sourced protocol fact: an X account, @Themooseisloos5, cast the coin-and-ASIC cycle as a four-phase Carnot-engine model.</p>
<details class="guide-detail" id="hot-cold-cycle">
<summary>
<span class="eyebrow">Four phases</span>
<h3>The hot/cold cycle and Kaspa's compressed history</h3>
<span class="guide-detail-preview">Phase-by-phase breakdown of coin and ASIC market timing.</span>
</summary>
<div class="guide-detail-body">
<div class="cycle-grid grid-cards">
<article>
<span>1</span>
<h3>Hot coins, cold ASICs</h3>
<p>Demand moves in before hardware can respond, so margins expand.</p>
</article>
<article>
<span>2</span>
<h3>Hot coins, hot ASICs</h3>
<p>New machines get ordered and hash rate climbs.</p>
</article>
<article>
<span>3</span>
<h3>Cold coins, hot ASICs</h3>
<p>Price cools while ordered machines keep arriving, crushing margins.</p>
</article>
<article>
<span>4</span>
<h3>Cold coins, cold ASICs</h3>
<p>Weak miners shut off, and the system compresses until it finds balance.</p>
</article>
</div>
<p>Kaspa compressed these phases into one short window instead of Bitcoin's years, which cuts both ways: bigger swings up and down.</p>
</div>
</details>
<p class="eyebrow">Try it</p>
<h3 id="emission-schedule">How fast the new supply shrinks</h3>
<div class="es-demo">
<div class="wrap">
<p class="lead">Every block pays a little less, in small steps, not a cliff. Zoom out to see Kaspa's whole issuance history, or zoom in to move around today.</p>
<div class="status" id="statusBar" role="status">
<span class="dot"></span>
<span id="statusText">Checking the live network…</span>
</div>
<div class="view-switch" role="group" aria-label="Time range">
<button type="button" id="viewToday" aria-pressed="true">Around today</button>
<button type="button" id="viewAll" aria-pressed="false">All time, since genesis</button>
</div>
<div class="primary-control">
<label for="timeSlider" id="sliderLabel">How far in the future: <span class="big-readout" id="timeReadout">today</span></label>
<input type="range" id="timeSlider" min="0" max="120" step="1" value="0" aria-label="Point in time">
<div class="marks" id="sliderMarks"><span style="left:0%">today</span><span class="tight" style="left:10%">1 year</span><span style="left:30%">3 years</span><span style="left:100%">10 years</span></div>
</div>
<p class="result-line" id="resultLine"></p>
<p class="supply-line" id="supplyLine"></p>
<div class="panel">
<svg id="curveSvg" viewBox="0 0 700 200" preserveAspectRatio="none" role="img" aria-labelledby="curveTitle">
<title id="curveTitle">Block reward over time, a smooth staircase down, no cliff</title>
</svg>
<p class="chart-caption" id="chartCaption">Each bar is one month's block reward.</p>
<p class="chart-caption">This schedule is written into the software every node runs. No vote, company, or founder can add coins to it.</p>
</div>
<details >
<summary>Why the reward falls 5.61% a month with no halving day, and the code that sets it</summary>
<p>The reward drops about 5.61% every month, forever, triggered by DAA score rather than the calendar, so there is no single halving day (rusty-kaspa's <code>coinbase.rs</code><span class="tuck">)</span>. Today's supply is live; other points on the slider are this model scaled to it.</p>
</details>
<p class="sr-only" id="liveFooter" aria-live="polite"></p>
</div>
</div>
<style>
/* Token audit 2026-08-31: literal token values below are deliberate divergences from styles.css (pinned palettes, demo-only colors, contrast variants); byte-identical copies were converted to var() references. */
.es-demo {
color-scheme: dark;
--bg:#100e0c; --card-bg:#322f2a;
--line-bright:#746e60;
--text:#f8f5ef; --muted:#b6b0a7; --faint:#9d988c;
--green:#66d1c1; --cyan:#2e9cff; --orange:#ff9f0a;
--control-bg:#2c2c2e; --control-hover:#3a3a3c;
--field-bg:#221f1c; --field-line:#4a453d;
--shadow-card: 0 1px 2px rgba(0,0,0,.4), 0 12px 32px rgba(0,0,0,.35);
}
[data-theme="light"] .es-demo {
color-scheme: light;
--bg:#fbfaf6; --card-bg:#fbfaf6;
--line-bright:#bcb6a8;
--text:#211e1a; --muted:#646057; --faint:#6b6659;
--green:#0c7a69; --cyan:#0068da; --orange:#b14f00;
--control-bg:#e8e8ed; --control-hover:#dededf;
--field-bg:#ffffff; --field-line:#d2d2d7;
--shadow-card: 0 1px 2px rgba(0,0,0,.05), 0 6px 16px rgba(0,0,0,.04);
}
.es-demo * {box-sizing:border-box;}
.es-demo {
margin:0; background:var(--bg); color:var(--text);
font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text","Segoe UI",Roboto,Helvetica,Arial,sans-serif;
font-size:16px; line-height:1.5; padding:20px 16px 48px;
}
.es-demo .wrap {max-width:720px; margin:0 auto;}
.es-demo h4 { font-weight:700; letter-spacing:-.014em; line-height:1.2; margin:0 0 6px; font-size:1.3rem; }
.es-demo .lead { color:var(--muted); font-weight:420; margin:0 0 22px; font-size:1.02rem; max-width:60ch; }
.es-demo .theme-toggle {
min-height:44px; min-width:44px; border:0; border-radius:980px; padding:0 14px;
background:var(--control-bg); color:var(--text); font-size:16px; font-weight:500; cursor:pointer; flex:none;
}
.es-demo .theme-toggle:hover {background:var(--control-hover);}
.es-demo .view-switch { display:flex; gap:6px; margin-bottom:16px; }
.es-demo .view-switch button {
flex:1; min-height:44px; border:1px solid var(--field-line); border-radius:980px;
background:var(--control-bg); color:var(--muted); font:inherit; font-size:16px; font-weight:650;
cursor:pointer; padding:0 12px;
}
.es-demo .view-switch button:hover { background:var(--control-hover); }
.es-demo .view-switch button[aria-pressed="true"] { background:var(--green); color:var(--field-bg); border-color:var(--green); }
.es-demo .primary-control { margin-bottom:6px; }
.es-demo .primary-control label { display:block; font-size:16px; color:var(--faint); margin-bottom:6px; }
.es-demo .primary-control .big-readout { font-size:1.5rem; font-weight:700; color:var(--text); font-variant-numeric:tabular-nums; }
.es-demo input[type=range] {
width:100%; height:44px; accent-color:var(--green); padding-block:14px;
}
.es-demo .marks { position:relative; height:1.1em; font-size:16px; color:var(--faint); margin-top:2px; }
.es-demo .marks span { position:absolute; transform:translateX(-50%); white-space:nowrap; }
.es-demo .marks span:first-child { transform:none; }
.es-demo .marks span:last-child { transform:translateX(-100%); }
/* "today" at 0% and "1 year" at 10% collide into "todayyear" whenever the panel this sits
in is narrow, which the 768px render check caught inside this page's own two-column grid
even though the viewport itself is wider than the old 600px cutoff. The readout above
always names the exact point, so the tightest tick steps out on a narrow screen. */
@media (max-width: 820px) { .es-demo .marks span.tight { display:none; } }
.es-demo .result-line { font-size:1.05rem; margin:20px 0 8px; }
.es-demo .result-line b { color:var(--green); }
.es-demo .supply-line { font-size:16px; color:var(--muted); margin:0 0 20px; }
.es-demo .supply-line b { color:var(--text); }
.es-demo .tag {
display:inline-block; font-size:.66rem; font-weight:700; letter-spacing:.03em; text-transform:uppercase;
padding:1px 6px; border-radius:5px; margin-left:4px; vertical-align:1px;
}
.es-demo .tag.measured { --status-color: var(--green); }
.es-demo .tag.projected { --status-color: var(--purple-pill, var(--purple)); }
.es-demo .panel {
background:var(--card-bg); border-radius:18px; padding:14px 16px 16px;
margin-bottom:16px; box-shadow:var(--shadow-card);
}
.es-demo svg {display:block; width:100%; height:auto;}
.es-demo .status {
display:flex; gap:8px; align-items:center; font-size:16px; color:var(--faint);
margin: 4px 0 18px;
}
.es-demo .status .dot {width:7px; height:7px; border-radius:50%; background:var(--green); flex:none;}
.es-demo .status.offline .dot {background:var(--orange);}
.es-demo .chart-caption { font-size:16px; color:var(--faint); margin:8px 0 0; }
.es-demo .live-footer {
font-size:1rem; margin:26px 0 6px; padding-top:16px; border-top:1px solid var(--field-line); color:var(--text);
}
.es-demo .live-footer b { color:var(--green); }
.es-demo details {
background:var(--card-bg); border-radius:14px; padding:10px 14px; margin-top:8px; box-shadow:var(--shadow-card);
}
.es-demo summary {cursor:pointer; font-size:16px; font-weight:600; color:var(--text);}
.es-demo summary::marker {color:var(--green);}
.es-demo details[open] summary::before {content:"\2212";}
.es-demo details p { color:var(--muted); font-size:16px; line-height:1.6; }
.es-demo code {
background:var(--field-bg); border:1px solid var(--field-line); border-radius:8px; padding:1px 5px; font-size:.85em;
}
.es-demo a {color:var(--cyan);}
.es-demo a:focus-visible, .es-demo button:focus-visible, .es-demo input:focus-visible, .es-demo summary:focus-visible {
outline:3px solid color-mix(in srgb, var(--cyan) 28%, transparent);
outline-offset:3px;
}
.es-demo .sr-only {position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0,0,0,0);}
</style>
<script>
(function(){
// ---- Sourced protocol constants (rusty-kaspa, see the details panel above) ----
var MONTH_SECONDS = 2629800; // SECONDS_PER_MONTH, coinbase.rs
var DEFLATIONARY_DAA = 15519600; // deflationary_phase_daa_score, params.rs
var PRE_DEFLATIONARY_KAS_PER_SEC = 500; // pre_deflationary_phase_base_subsidy, params.rs (1 BPS era)
var CRESCENDO_DAA = 110165000; // crescendo activation DAA score, v1.0.0 release notes
var DEFLATION_START_KAS_PER_SEC = 440; // SUBSIDY_BY_MONTH_TABLE[0] / 1e8, coinbase.rs
var GENESIS_MS = 1637609671037; // rusty-kaspa genesis.rs mainnet genesis timestamp
var T1_MONTHS = DEFLATIONARY_DAA / MONTH_SECONDS; // ~5.90 months
var CRESCENDO_MONTHS = CRESCENDO_DAA / MONTH_SECONDS; // ~41.9 months
var FALLBACK_DAA = 558513385; // api.kaspa.org/info/blockdag, 2026-10-06
var FALLBACK_CIRCULATING = 27740808149.258841; // KAS, api.kaspa.org/info/coinsupply, 2026-10-06
var FALLBACK_MAX_SUPPLY = 28704035605; // KAS, api.kaspa.org/info/coinsupply, 2026-10-06
var FALLBACK_ASOF = '2026-10-06';
var maxSupply = FALLBACK_MAX_SUPPLY;
var circulatingToday = FALLBACK_CIRCULATING;
var calibScale = 1;
// ---- Core schedule model, expressed as g = months since genesis ----
function secondsSinceGenesis(daa) {
return daa < CRESCENDO_DAA ? daa : CRESCENDO_DAA + (daa - CRESCENDO_DAA) / 10;
}
function gFromDaa(daa) { return secondsSinceGenesis(daa) / MONTH_SECONDS; }
function bpsAtG(g) { return g < CRESCENDO_MONTHS ? 1 : 10; }
// The protocol pays a flat reward for a whole month, then steps down: coinbase.rs
// indexes SUBSIDY_BY_MONTH_TABLE by a whole number of months since the deflationary
// phase began. Interpolating between steps looked smoother and read 2.21 KAS per
// block on 29 Aug 2026 while the network was paying 2.31246515
// (api.kaspa.org/info/blockreward), so the floor here is what makes the readout
// match the network and this page's own stated formula.
function rewardPerSecAtG(g) {
if (g < T1_MONTHS) return PRE_DEFLATIONARY_KAS_PER_SEC;
return DEFLATION_START_KAS_PER_SEC * Math.pow(2, -Math.floor(g - T1_MONTHS) / 12);
}
function rewardPerBlockAtG(g) { return rewardPerSecAtG(g) / bpsAtG(g); }
// Closed-form integral of rewardPerSecAtG from 0 to g, in raw (uncalibrated) KAS.
function rawCumulativeAtG(g) {
if (g <= T1_MONTHS) return PRE_DEFLATIONARY_KAS_PER_SEC * g * MONTH_SECONDS;
var base = PRE_DEFLATIONARY_KAS_PER_SEC * T1_MONTHS * MONTH_SECONDS;
var u = g - T1_MONTHS;
var tail = DEFLATION_START_KAS_PER_SEC * MONTH_SECONDS * (12 / Math.LN2) * (1 - Math.pow(2, -u / 12));
return base + tail;
}
function cumulativeAtG(g) { return rawCumulativeAtG(Math.max(0, g)) * calibScale; }
function dateAtG(g) {
// The month length used throughout this model is a fixed average (SECONDS_PER_MONTH,
// sourced from coinbase.rs), so its calendar-date output drifts a little from the real
// calendar over several years. Right at today's real, live-fetched mark, show "today"
// instead of the drifted model date; the drift only shows up away from that anchor.
if (Math.abs(g - gToday) < 0.5) return 'today';
var d = new Date(GENESIS_MS + g * MONTH_SECONDS * 1000);
return d.toLocaleDateString(undefined, {year:'numeric', month:'short', day:'numeric'});
}
var gToday = gFromDaa(FALLBACK_DAA);
var mode = 'today'; // 'today' or 'all'
var ALL_MAX_MONTHS = 240; // 20 years since genesis: covers the interesting part of the curve
function recalibrate() {
var raw = rawCumulativeAtG(gToday);
calibScale = raw > 0 ? (circulatingToday / raw) : 1;
}
recalibrate();
function fmtReward(v) {
if (v <= 0) return '0.00';
var d = 2;
while (d < 6 && parseFloat(v.toFixed(d)) === 0) d++;
return v.toFixed(d);
}
function fmtBillions(kas) { return (kas / 1e9).toFixed(2); }
function fmtPct(kas) { return Math.min(100, (kas / maxSupply) * 100).toFixed(1); }
function humanMonths(m){
if (m === 0) return 'today';
if (m < 12) return m === 1 ? '1 month from now' : (m + ' months from now');
var years = m / 12;
if (Math.abs(years - Math.round(years)) < 0.05) {
var yr = Math.round(years);
return yr === 1 ? '1 year from now' : (yr + ' years from now');
}
return years.toFixed(1) + ' years from now';
}
function yearsSinceGenesis(g) {
var y = g / 12;
return y.toFixed(1);
}
function paintCurve(gCenter, gMin, gMax) {
var svg = document.getElementById('curveSvg');
var W = 700, H = 200, pad = 10;
var count = Math.round(gMax - gMin) + 1;
var barW = (W - pad * 2) / count;
var maxR = rewardPerBlockAtG(gMin), minR = rewardPerBlockAtG(gMax);
if (maxR === minR) { maxR = minR + 0.0001; }
var bars = [];
for (var i = 0; i < count; i++) {
var g = gMin + i;
var r = rewardPerBlockAtG(g);
var h = (r - minR) / (maxR - minR) * (H - 30) + 10;
var x = pad + i * barW;
var y = H - h;
var isCurrent = Math.round(g) === Math.round(gCenter);
var isFuture = g > gToday + 0.5;
var fill = isCurrent ? 'var(--green)' : 'var(--cyan)';
var opacity = isCurrent ? 1 : (isFuture ? 0.28 : 0.55);
bars.push('<rect x="'+x.toFixed(1)+'" y="'+y.toFixed(1)+'" width="'+Math.max(1,barW-1).toFixed(1)+'" height="'+h.toFixed(1)+'" fill="'+fill+'" opacity="'+opacity+'" style="transition:opacity .2s ease"></rect>');
}
var markup = '<title>Block reward over time, a smooth staircase down, no cliff</title>' + bars.join('');
if (mode === 'all' && gToday > gMin && gToday < gMax) {
var todayX = pad + (gToday - gMin) * barW;
markup += '<line x1="'+todayX.toFixed(1)+'" x2="'+todayX.toFixed(1)+'" y1="0" y2="'+H+'" stroke="var(--faint)" stroke-width="1" stroke-dasharray="3 3"></line>';
markup += '<text x="'+(todayX+4).toFixed(1)+'" y="12" font-size="10" fill="var(--faint)">today</text>';
}
svg.innerHTML = markup;
}
function render(){
var slider = document.getElementById('timeSlider');
var val = parseInt(slider.value, 10);
var g, gMin, gMax;
if (mode === 'today') {
g = gToday + val;
gMin = gToday; gMax = gToday + 120;
document.getElementById('timeReadout').textContent = humanMonths(val);
} else {
g = val;
gMin = 0; gMax = ALL_MAX_MONTHS;
// The all-time slider steps in whole months, so its nearest notch to now sits up
// to half a month off today's live reading. Snapping it to today keeps the two
// views from printing different rewards for the same moment, and keeps the
// moment the network has already mined from being labeled a projection.
if (Math.abs(g - gToday) < 0.5) { g = gToday; }
document.getElementById('timeReadout').textContent =
(g < 0.05 ? 'genesis' : (g === gToday ? 'today' : yearsSinceGenesis(g) + ' years since genesis'));
}
var reward = rewardPerBlockAtG(g);
var supply = Math.min(cumulativeAtG(g), maxSupply);
var pct = fmtPct(supply);
var isFuture = g > gToday + 0.02;
var tag = isFuture ? '<span class="tag projected">Projected</span>' : '<span class="tag measured">Measured</span>';
var isNow = (g === gToday);
var isGenesis = (g < 0.05);
var line;
if (isNow) {
line = 'Right now each block pays <b>' + fmtReward(reward) + ' KAS</b>. ' + tag;
} else if (mode === 'today') {
var pctLess = (1 - reward / rewardPerBlockAtG(gToday)) * 100;
line = humanMonths(val).replace(/^./, function(c){return c.toUpperCase();}) +
' (around ' + dateAtG(g) + '), each block pays about <b>' + fmtReward(reward) + ' KAS</b>, ' +
Math.max(0, pctLess).toFixed(pctLess < 100 && pctLess >= 0 ? 2 : 0) + '% less than today. ' + tag;
} else {
line = (isGenesis ? 'At genesis' : ('On ' + dateAtG(g))) + ', each block ' + (isFuture ? 'will pay' : 'paid') +
' about <b>' + fmtReward(reward) + ' KAS</b>. ' + tag;
}
document.getElementById('resultLine').innerHTML = line;
document.getElementById('supplyLine').innerHTML = isGenesis
? 'At genesis none of it had been mined yet, out of the ' + fmtBillions(maxSupply) +
' billion KAS maximum. <span class="tag measured">Measured</span>'
: 'By ' + (isNow ? 'today' : dateAtG(g)) + ', about <b>' + fmtBillions(supply) + ' billion KAS</b> ' +
(isFuture ? 'will have been' : 'had been') + ' issued, roughly <b>' + pct + '%</b> of the ' +
fmtBillions(maxSupply) + ' billion KAS maximum. ' + tag;
document.getElementById('liveFooter').textContent =
(mode === 'all'
? (isGenesis ? 'Genesis' : (isNow ? 'Today' : yearsSinceGenesis(g) + ' years after genesis'))
: humanMonths(val).replace(/^./, function (c) { return c.toUpperCase(); })) +
': about ' + fmtReward(reward) + ' KAS per block, ' + pct + '% of all Kaspa issued by then.';
paintCurve(g, gMin, gMax);
}
function setStatus(ok, text){
var bar = document.getElementById('statusBar');
bar.classList.toggle('offline', !ok);
document.getElementById('statusText').textContent = text;
}
function fetchLive(){
if (!('fetch' in window)){
setStatus(false, 'No network access. Showing a reading from ' + FALLBACK_ASOF + '.');
return;
}
var timeout = setTimeout(function(){ setStatus(false, 'Network slow to answer. Showing a reading from ' + FALLBACK_ASOF + '.'); }, 6000);
Promise.all([
window.KE_kaspaApi('info/blockdag'),
window.KE_kaspaApi('info/coinsupply')
]).then(function(results){
clearTimeout(timeout);
var blockdag = results[0], supply = results[1];
var daa = parseInt(blockdag.virtualDaaScore, 10);
if (!isNaN(daa)) { gToday = gFromDaa(daa); }
var circ = parseFloat(supply.circulatingSupply) / 1e8;
var max = parseFloat(supply.maxSupply) / 1e8;
if (!isNaN(circ) && circ > 0) circulatingToday = circ;
if (!isNaN(max) && max > 0) maxSupply = max;
recalibrate();
setStatus(true, 'Live from the network.');
render();
}).catch(function(){
clearTimeout(timeout);
setStatus(false, 'Network unreachable. Showing a reading from ' + FALLBACK_ASOF + '.');
});
}
function setMode(next) {
mode = next;
var slider = document.getElementById('timeSlider');
var today = document.getElementById('viewToday');
var all = document.getElementById('viewAll');
today.setAttribute('aria-pressed', String(mode === 'today'));
all.setAttribute('aria-pressed', String(mode === 'all'));
if (mode === 'today') {
slider.min = 0; slider.max = 120; slider.value = 0;
document.getElementById('sliderLabel').firstChild.textContent = 'How far in the future: ';
document.getElementById('sliderMarks').innerHTML =
'<span style="left:0%">today</span><span class="tight" style="left:10%">1 year</span>' +
'<span style="left:30%">3 years</span><span style="left:100%">10 years</span>';
document.getElementById('chartCaption').textContent = "Each bar is one month's block reward.";
} else {
slider.min = 0; slider.max = ALL_MAX_MONTHS; slider.value = Math.round(gToday);
document.getElementById('sliderLabel').firstChild.textContent = 'Point in Kaspa\'s history: ';
document.getElementById('sliderMarks').innerHTML =
'<span style="left:0%">genesis</span>' +
'<span style="left:' + ((gToday / ALL_MAX_MONTHS) * 100).toFixed(1) + '%">' + Math.round(gToday / 12) + ' yrs (today)</span>' +
'<span style="left:100%">' + Math.round(ALL_MAX_MONTHS / 12) + ' yrs</span>';
document.getElementById('chartCaption').textContent = 'Each bar is one month\'s block reward since genesis. Past the "today" line the faint bars are projection, not a network reading.';
}
render();
}
document.getElementById('timeSlider').addEventListener('input', render);
document.getElementById('viewToday').addEventListener('click', function(){ setMode('today'); });
document.getElementById('viewAll').addEventListener('click', function(){ setMode('all'); });
render();
fetchLive();
})();
</script>
</section>
<section class="section">
<h2>ASICs buy security, with a bill attached</h2>
<p>Proof of work turns digital security into a physical bill; an attacker cannot rent cloud compute and be done.</p>
<p class="eyebrow">Try it</p>
<h3 id="attack-cost">What an attack on Kaspa would cost</h3>
<div class="ac-demo">
<p class="intro">Out-mining everyone else means owning most of the machines. Drag the duration and watch which half of the bill moves.</p>
<p class="live-status" id="liveStatus">Reading live hash rates and prices…</p>
<div class="primary-control" id="hoursControl">
<label for="ac-hoursSlider">How long the attack runs: <span class="big-readout" id="hoursOut">60 minutes</span></label>
<input type="range" id="ac-hoursSlider" min="0" max="1000" value="563" step="1" aria-label="Length of the attack, from about a minute to a day">
<div class="marks"><span>1 minute</span><span>1 day</span></div>
</div>
<details class="deep-dive">
<summary>Compare to another chain, or rent instead of buy</summary>
<div class="chain-picker" role="group" aria-label="Choose a chain">
<button type="button" data-chain="kaspa" aria-pressed="true">Kaspa</button>
<button type="button" data-chain="bitcoin" aria-pressed="false">Bitcoin</button>
<button type="button" data-chain="litecoin" aria-pressed="false">Litecoin</button>
<button type="button" data-chain="monero" aria-pressed="false">Monero</button>
<button type="button" data-chain="ethereum" aria-pressed="false">Ethereum</button>
<button type="button" data-chain="solana" aria-pressed="false">Solana</button>
</div>
<p class="chain-groups">Four proof-of-work chains and two proof-of-stake chains, priced the same way.</p>
<div class="mode-toggle" id="modeToggle" role="group" aria-label="How the attacker gets the power">
<button type="button" data-mode="buy" aria-pressed="true">Buy it outright</button>
<button type="button" data-mode="rent" aria-pressed="false">Rent the power</button>
</div>
</details>
<div class="compare-note " id="compareNote" style="display:none;">
<p id="compareNoteText"></p>
</div>
<div class="answer-panel">
<p class="lede" id="answerLede">Attacking Kaspa for 60 minutes would cost about</p>
<p class="amount" id="hourlyCost">reading…</p>
<p class="split" id="costSplit"> </p>
<p class="ratio" id="ratioNote"> </p>
<p class="ratio">That is the bill for rewriting recent history: undoing payments other people already received.</p>
<div class="ratio-bar-wrap">
<div class="ratio-bar"><div class="fill" id="ratioFill" style="width:0%"></div></div>
<div class="ratio-axis" aria-hidden="true"><span>0.001%</span><span>0.01%</span><span>0.1%</span><span>1%</span><span>10%</span><span>100%</span></div>
</div>
</div>
<details class="deep-dive" id="limitsDetails">
<summary>Limits of this number</summary>
<div class="limits ">
<p id="limitsNote"></p>
</div>
<div class="impact-note " id="impactNote" style="display:none;">
<p>Buying this much hash power or stake would push its own price up; the number shown is a floor, not the true cost.</p>
</div>
<div class="limits " id="permanencePanel">
<p id="permanenceNote"></p>
</div>
</details>
<details id="assumptions">
<summary>Assumptions, exact math, and sources</summary>
<div id="assumptionsBody"></div>
</details>
</div>
<p>An attacker needs kHeavyHash machines, electricity, and facilities, every step leaving a trail; that specialization concentrates control among manufacturers and large buyers.</p>
<style>
/* Token audit 2026-08-31: literal token values below are deliberate divergences from styles.css (pinned palettes, demo-only colors, contrast variants); byte-identical copies were converted to var() references. */
.ac-demo {
color-scheme: dark;
--bg: #100e0c;
--card-bg: #322f2a;
--line-bright: #746e60;
--text: #f8f5ef;
--muted: #b6b0a7;
--faint: #9d988c;
--green: #66d1c1;
--cyan: #2e9cff;
--orange: #ff9f0a;
--ink: #06201b;
--accent-soft: rgba(102, 209, 193, .12);
--control-bg: #2c2c2e;
--control-hover: #3a3a3c;
--field-bg: #221f1c;
--field-line: #4a453d;
--shadow-card: 0 1px 2px rgba(0,0,0,.4), 0 12px 32px rgba(0,0,0,.35);
}
[data-theme="light"] .ac-demo {
color-scheme: light;
--bg: #fbfaf6;
--card-bg: #fbfaf6;
--line-bright: #bcb6a8;
--text: #211e1a;
--muted: #646057;
--faint: #6b6659;
--green: #0c7a69;
--cyan: #0068da;
--orange: #b14f00;
--ink: #ffffff;
--accent-soft: rgba(14, 124, 107, .1);
--control-bg: #e8e8ed;
--control-hover: #dededf;
--field-bg: #ffffff;
--field-line: #d2d2d7;
--shadow-card: 0 1px 2px rgba(0,0,0,.05), 0 6px 16px rgba(0,0,0,.04);
}
.ac-demo * { box-sizing: border-box; }
.ac-demo {
margin: 0;
background: var(--bg);
color: var(--text);
font: 16px/1.5 -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.ac-demo { max-width: 900px; margin: 0 auto; padding: 20px 16px 48px; }
.ac-demo .top-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.ac-demo h4 { font-size: 1.3rem; font-weight: 700; letter-spacing: -.014em; line-height: 1.2; margin: 0 0 6px; }
.ac-demo .theme-toggle {
flex: none; min-height: 44px; min-width: 44px; border: 0; border-radius: 980px; padding: 0 14px;
background: var(--control-bg); color: var(--text); font-size: 16px; font-weight: 500; cursor: pointer;
}
.ac-demo .theme-toggle:hover { background: var(--control-hover); }
.ac-demo p.intro { color: var(--muted); font-weight: 420; max-width: 60ch; margin: 0 0 20px; font-size: 1.02rem; }
.ac-demo p.live-status { display: flex; align-items: center; gap: 7px; font-size: 16px; color: var(--faint); margin: -8px 0 18px; }
.ac-demo p.live-status .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--faint); flex: none; }
.ac-demo p.live-status[data-state="ok"] .dot { background: var(--green); }
.ac-demo p.live-status[data-state="error"] .dot { background: var(--orange); }
.ac-demo p.live-status[data-state="partial"] .dot { background: var(--cyan); }
.ac-demo .compare-note, .ac-demo .impact-note {
border-left: 3px solid var(--cyan);
border-radius: 0 14px 14px 0;
padding: 12px 16px;
margin-bottom: 16px;
background: color-mix(in srgb, var(--cyan) 7%, var(--card-bg));
font-size: 1rem;
}
.ac-demo .compare-note p, .ac-demo .impact-note p { margin: 0; color: var(--text); }
.ac-demo .compare-note strong, .ac-demo .impact-note strong { color: var(--text); }
.ac-demo .inline-detail { background: transparent; box-shadow: none; padding: 6px 0 0; margin-top: 6px; }
.ac-demo .inline-detail summary { font-size: 16px; color: var(--cyan); }
.ac-demo .inline-detail p { margin-top: 6px; }
.ac-demo .primary-control { margin-bottom: 20px; }
.ac-demo .primary-control label { display: block; font-size: 16px; color: var(--faint); margin-bottom: 6px; }
.ac-demo .primary-control .big-readout { font-size: 1.5rem; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.ac-demo input[type=range] {
width: 100%;
height: 44px;
margin: 0;
padding: 0;
background: transparent;
accent-color: var(--green);
-webkit-appearance: none;
appearance: none;
}
.ac-demo input[type=range]::-webkit-slider-runnable-track {
height: 4px;
border-radius: 2px;
background: var(--field-line);
}
.ac-demo input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--green);
margin-top: -9px;
cursor: pointer;
}
.ac-demo input[type=range]::-moz-range-track {
height: 4px;
border-radius: 2px;
background: var(--field-line);
}
.ac-demo input[type=range]::-moz-range-thumb {
width: 22px;
height: 22px;
border: 0;
border-radius: 50%;
background: var(--green);
cursor: pointer;
}
.ac-demo .marks { display: flex; justify-content: space-between; font-size: 16px; color: var(--faint); margin-top: 2px; }
.ac-demo .chain-picker { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 6px; }
.ac-demo .chain-picker button {
background: var(--control-bg); border: 0; color: var(--text); border-radius: 980px;
min-height: 44px; padding: 8px 16px; font-size: 16px; font-weight: 500; cursor: pointer;
}
.ac-demo .chain-picker button:hover { background: var(--control-hover); }
.ac-demo .chain-picker button[aria-pressed="true"] { background: var(--accent-soft); color: var(--green); font-weight: 600; }
.ac-demo .chain-groups { font-size: 1rem; color: var(--faint); margin: 0 0 20px; }
.ac-demo .mode-toggle {
display: inline-flex; background: var(--field-bg); border-radius: 980px; padding: 3px; margin-bottom: 22px;
opacity: .9;
}
.ac-demo .mode-toggle button {
background: transparent; border: none; color: var(--muted); min-height: 44px; padding: 6px 14px;
font-size: 16px; border-radius: 980px; cursor: pointer;
}
.ac-demo .mode-toggle button[aria-pressed="true"] { background: var(--control-bg); color: var(--text); font-weight: 600; }
.ac-demo .answer-panel {
background: var(--card-bg); border-radius: 18px; box-shadow: var(--shadow-card);
padding: 20px 20px 18px; margin-bottom: 16px; text-align: center;
}
.ac-demo .answer-panel .lede { font-size: 1rem; color: var(--faint); margin: 0 0 6px; }
.ac-demo .answer-panel .amount {
/* Display type steps down as its own content lengthens (see styles.css's
h1/h2 comment for the full rationale). This readout is JS-updated
("$3.38 million", "no market to price"), so --len is kept in sync by a
MutationObserver below rather than hardcoded, but the formula is the
same static calc: no measurement pass, no flash, no motion/theme
interaction. */
font-size: clamp(1.5rem, calc(clamp(2.1rem, 8vw, 3rem) - max(var(--len, 6) - 9, 0) * 0.09rem), 3rem);
font-weight: 700; letter-spacing: -.02em;
color: var(--green); font-variant-numeric: tabular-nums; margin: 0;
transition: opacity .15s ease;
}
.ac-demo .answer-panel .split { font-size: 16px; color: var(--text); margin: 10px 0 0; }
.ac-demo .answer-panel .ratio { font-size: 1rem; color: var(--muted); margin-top: 8px; }
.ac-demo .answer-panel .ratio b { color: var(--text); }
.ac-demo .ratio-bar-wrap { margin: 10px auto 0; max-width: 320px; }
.ac-demo .ratio-bar {
position: relative;
height: 6px; border-radius: 980px; background: var(--field-bg); border: 1px solid var(--field-line);
overflow: hidden;
}
.ac-demo .ratio-bar .fill { height: 100%; background: var(--green); transition: width .3s ease; }
.ac-demo .ratio-axis {
display: flex; justify-content: space-between;
margin-top: 4px; font-size: 16px; color: var(--faint);
font-variant-numeric: tabular-nums;
}
.ac-demo .ratio-axis span { flex: 0 0 auto; }
.ac-demo .limits {
border-left: 3px solid var(--orange);
border-radius: 0 14px 14px 0;
padding: 12px 16px;
margin-bottom: 16px;
background: color-mix(in srgb, var(--orange) 8%, var(--card-bg));
font-size: 1rem;
}
.ac-demo .limits p { margin: 0; color: var(--text); }
.ac-demo details {
background: var(--card-bg); border-radius: 14px; padding: 10px 14px; margin-top: 8px; box-shadow: var(--shadow-card);
}
.ac-demo summary { cursor: pointer; font-size: 16px; font-weight: 600; color: var(--text); }
.ac-demo summary::marker { color: var(--green); }
.ac-demo details[open] summary::before { content: "\2212"; }
.ac-demo details p, .ac-demo details li { color: var(--muted); font-size: 1rem; line-height: 1.6; }
.ac-demo details ul { padding-left: 1.2em; margin: 6px 0; }
.ac-demo code { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: .82em; color: var(--cyan); }
.ac-demo a:focus-visible, .ac-demo button:focus-visible, .ac-demo input:focus-visible, .ac-demo summary:focus-visible {
outline: 3px solid color-mix(in srgb, var(--cyan) 28%, transparent);
outline-offset: 3px;
}
.ac-demo .demo-footnote { font-size: 16px; color: var(--faint); margin-top: 18px; }
@media (prefers-reduced-motion: reduce) {.ac-demo * { transition: none !important; }}
</style>
<script>
(function(){
// Keep the headline figure's length-aware font-size (see the .amount CSS
// comment) in sync with its own live text, so "not priceable" or a longer
// formatted amount never renders at the size meant for "$0". Character
// count only, no layout measurement, so this can't cause a flash.
var amountEl = document.getElementById("hourlyCost");
if (amountEl) {
var syncAmountLen = function () {
amountEl.style.setProperty("--len", amountEl.textContent.trim().length);
};
syncAmountLen();
new MutationObserver(syncAmountLen).observe(amountEl, { characterData: true, childList: true, subtree: true });
}
// Hours of attack is the one primary control on the main surface (the
// "how long the attack runs" slider above), because it's the question the
// page's title actually asks. Hash-rate share, stake share, slippage,
// hardware price, electricity price, rental rate, and the amortization
// window are fixed at round, defensible defaults instead: each one is
// disclosed with its value and reasoning in the collapsible, not hidden.
var ATTACKER_HASH_SHARE = 0.51;
var STAKE_SHARE = 0.34;
var AMORTIZE_WEEKS = 4;
// No market-impact (slippage) model is applied to the proof-of-stake buy
// figure below. An earlier version of this page multiplied the spot cost
// by an unsourced, made-up 80% markup constant. Buying this much stake on
// the open market genuinely does move the price, but there is no sourced,
// defensible number for how much on these two chains, so the honest fix is
// to show the plain spot cost and say plainly, where the figure appears,
// that it is a floor because it ignores that real-world impact rather than
// quietly baking in a guessed multiplier.
// Dated baseline, gathered August 22, 2026. Live reads (see loadLiveData
// below) overwrite hashrateEH, valueSecuredUSD, priceUSD and
// circulatingSupply in place when a fetch succeeds; the flags below
// (_liveHash, _liveValue, _livePrice, _liveSupply) record which fields are
// currently showing a live number versus this dated fallback, and
// renderAssumptions() reads those flags to label each figure honestly.
// Fields with no flag (rental rates, hardware prices, Monero's hash rate)
// have no CORS-reachable live source found for this page and always show
// this dated figure; the reason is in that field's own *Source string.
var CHAINS = {
bitcoin: {
name: 'Bitcoin', security: 'pow',
hashrateEH: 900,
rentUnit: 'TH',
rentPerUnitPerHour: 0.00042,
valueSecuredUSD: 1.53e12,
datedPriceUSD: 77055,
hwPricePerTH: 20, hwEffJPerTH: 15, elecPerKWh: 0.06,
hwSource: 'Bitmain Antminer S21 Pro: 234 TH/s, 3,510 W (15 J/TH), street price around $20/TH (asicminervalue.com, miningnow.com, dated Aug 22, 2026; no live feed for ASIC street prices is fetched here).',
hashSource: 'Network hash rate reads live from mempool.space\'s public mining API (mempool.space/api/v1/mining/hashrate/3d, CORS-enabled, no key required). Dated fallback if that read fails: about 900 EH/s, Aug 22, 2026 (CoinWarz, Hashrate Index, minerstat).',
rentSource: 'NiceHash SHA-256 payrate, converted at roughly $95k to $100k per BTC (NiceHash payrate history, dated Aug 22, 2026). NiceHash\'s public API does not send CORS headers (checked directly), so a static page cannot read it live; this stays a dated figure.',
valueNote: 'Price reads live from Kraken\'s public ticker (api.kraken.com/0/public/Ticker, CORS-enabled, no key required); market capitalization is that live price times a dated circulating supply, so the price component updates live even though the supply component does not. Dated fallback if that read fails: roughly $77,055 times 19.9M circulating BTC, Aug 22, 2026 (data/l1-chains.json\'s CoinGecko snapshot). CoinGecko\'s market-cap endpoint was tried first and dropped: it returns intermittent 403s with no CORS header on the error response from this origin, which a static page cannot recover from.',
noRent: false
},
litecoin: {
name: 'Litecoin', security: 'pow', noBuy: true,
hashrateEH: 0.002479,
rentUnit: 'GH',
rentPerUnitPerHour: 0.53 / 24,
valueSecuredUSD: 6.5e9,
datedPriceUSD: 51.78,
valueNote: 'Price reads live from Kraken\'s public ticker (api.kraken.com/0/public/Ticker, CORS-enabled, no key required); market capitalization is that live price times a dated circulating supply, so the price component updates live even though the supply component does not. Dated fallback if that read fails: about $6.5 billion, Aug 22, 2026, matching data/l1-chains.json\'s snapshot.',
noBuyNote: 'Buying enough Scrypt ASIC hardware to match 51% of Litecoin outright is not priced here: this page has no verified, current street price for hydro-cooled Scrypt miners at that scale, and guessing one would be the same kind of invented number this page already removed once for market impact. Renting (shown here) reaches the same 51% figure without that gap.',
hashSource: 'Network hash rate reads live from litecoinspace.org\'s public mining API (same API shape as mempool.space, CORS-enabled, no key required). Dated fallback if that read fails: about 2.48 PH/s on Scrypt, Aug 22, 2026, matching data/l1-chains.json\'s hashrate_ehs.',
rentSource: 'About $0.53 per GH/s per day. Scrypt has a real rental market (NiceHash lists it directly, no cross-algorithm substitution needed), but NiceHash does not publish a plain dollar rate and its public API sends no CORS headers, so this figure is derived rather than read live: solved from minerstat\'s published NiceHash Scrypt profitability table for 4 listed ASIC models (hashrate, power draw, daily USD profit, e.g. Bitmain Antminer L11 Hyd 2U at 35 GH/s / 5,775 W / $4.82 a day), for the implied per-GH/s rental rate and the electricity price minerstat assumed. All 4 devices agreed on the same rate within rounding, which is why it is treated as sourced rather than guessed (minerstat.com/coin/NH-Scrypt/profitability, checked Aug 22, 2026). This is a derived, dated estimate, not a directly quoted or live price, and how much of Litecoin\'s hash rate is actually listed for rent at once is not verified here.'
},
monero: {
name: 'Monero', security: 'pow', noBuy: true, cpuMode: true,
hashrateEH: 5.7e-9,
cpuHashPerBoxHs: 19600,
cpuRentPerBoxPerHour: 0.1744,
valueSecuredUSD: 2.9e9,
datedPriceUSD: 426.74,
valueNote: 'Price reads live from Kraken\'s public ticker (api.kraken.com/0/public/Ticker, CORS-enabled, no key required); market capitalization is that live price times a dated circulating supply, so the price component updates live even though the supply component does not. Dated fallback if that read fails: about $2.9 billion, Aug 22, 2026, matching data/l1-chains.json\'s snapshot.',
noBuyNote: 'RandomX is deliberately ASIC-resistant, so there is no ASIC hardware market to buy into. Renting CPU capacity (shown here) is how this much hash power is acquired at all; "buying" would mean buying and racking commodity CPUs yourself, which is not priced separately from the equivalent rented server capacity above.',
hashSource: 'Network hash rate is about 5.7 GH/s on RandomX, dated Aug 22, 2026 (CoinWarz; matches data/l1-chains.json\'s hashrate_ehs). No CORS-enabled public API for Monero\'s live hash rate was found for this page (moneroblocks.info redirects, localmonero.co\'s block explorer API sends no Access-Control-Allow-Origin header, xmrchain.net did not resolve when checked), so this stays a dated figure rather than a guessed live one.',
rentSource: 'RandomX is deliberately CPU-only and ASIC-resistant, so this prices commodity CPU rental instead of a hash-power marketplace. A dedicated 16-core AMD Ryzen 9 7950X3D benchmarks 14,800 to 24,400 hashes per second on real submitted runs (xmrig.com/benchmark, checked Aug 2026); about 19,600 H/s, the midpoint, is used here. Hetzner rents that same CPU on a dedicated server (its AX102, 128 GB RAM) for €109 a month, about $0.17 an hour at today’s EUR/USD rate (hetzner.com, checked Aug 22, 2026), an ordinary hosting price, not a mining-specific deal or a live feed. A shared vCPU on a big public cloud would do much worse here: RandomX leans on a large per-thread cache that a shared vCPU does not get exclusively, so a dedicated-core host is the fair comparison.'
},
kaspa: {
name: 'Kaspa', security: 'pow',
hashrateEH: 0.33008,
valueSecuredUSD: 7.72e8,
hwPricePerTH: 20, hwEffJPerTH: 283, elecPerKWh: 0.06,
hwSource: 'IceRiver KS5L: 12 TH/s at 3,400 W (about 283 J/TH); around $20/TH used as a mid-market estimate, the least certain figure here (hashrate.no, asicminervalue.com, dated Aug 22, 2026; no live feed for ASIC street prices is fetched here).',
hashSource: 'Network hash rate reads live from api.kaspa.org/info/hashrate, the same public API this site\'s live-kaspa.js already uses elsewhere. Dated fallback if that read fails: 330.1 PH/s, read 29 Aug 2026, after a decline earlier in the year (Kaspalytics, 2Miners, minerstat).',
valueNote: 'Market capitalization reads live from api.kaspa.org/info/marketcap, the same public API this page already uses for hash rate (CORS-enabled, no key required). Dated fallback if that read fails: $772 million, the same api.kaspa.org read the snapshot at the top of this page uses, 29 Aug 2026.',
noRent: true,
noRentNote: 'Not priceable because there is no verified rental market at attack scale, not because KAS itself is hard to buy: NiceHash lists kHeavyHash, but nowhere near the order-book depth to rent 51% of Kaspa\'s network is confirmed, so this page will not substitute a guessed number for it.'
},
ethereum: {
name: 'Ethereum', security: 'pos',
priceUSD: 2450, circulatingSupply: 120.7e6,
valueSecuredUSD: 2450 * 120.7e6,
priceSource: 'Price reads live from Kraken\'s public ticker (api.kraken.com/0/public/Ticker, CORS-enabled, no key required). Dated fallback if that read fails: ETH around $2,450, Aug 22, 2026 (CoinGecko, CoinDesk).',
supplySource: 'No CORS-reachable live source for circulating supply was found for this page; always shown as a dated figure: around 120.7M ETH, Aug 22, 2026.'
},
solana: {
name: 'Solana', security: 'pos',
priceUSD: 89, circulatingSupply: 540e6,
valueSecuredUSD: 89 * 540e6,
priceSource: 'Price reads live from Kraken\'s public ticker (api.kraken.com/0/public/Ticker, CORS-enabled, no key required). Dated fallback if that read fails: SOL around $89, Aug 22, 2026 (CoinDesk, Bitget).',
supplySource: 'No CORS-reachable live source for circulating supply was found for this page; always shown as a dated figure: around 540M SOL, Aug 22, 2026.'
}
};
var state = { chain: 'kaspa', mode: 'buy', hours: 1, lastCost: null };
// LOG_MAX/sliderToMinutes are defined further down with fmtDuration; the
// slider's own default value (563) is chosen so that maps to 1 hour, and
// state.hours is corrected to the exact figure once that function exists,
// right before the first render call at the bottom of this script.
var chainButtons = document.querySelectorAll('.chain-picker button');
var modeToggle = document.getElementById('modeToggle');
var modeButtons = document.querySelectorAll('#modeToggle button');
var hoursControl = document.getElementById('hoursControl');
var hoursSlider = document.getElementById('ac-hoursSlider');
var hoursOut = document.getElementById('hoursOut');
var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// The slider position (0-1000) maps to minutes on a log scale spanning
// about a minute to a day (1,440 minutes), so one control reaches both the
// "one-minute attack" question and the "one-day attack" question without