Simple block halving countdown website
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. /*
  2. * SPDX-License-Identifier: AGPL-3.0-or-later
  3. */
  4. const API_PREFIX = "https://blockstream.info/api";
  5. const IRON_BLOCK = "https://gamepedia.cursecdn.com/minecraft_gamepedia/7/7e/Block_of_Iron_JE4_BE3.png?version=692673bafa1e94785ab6012d7a3c8dc4";
  6. const GOLD_BLOCK = "https://gamepedia.cursecdn.com/minecraft_gamepedia/7/72/Block_of_Gold_JE6_BE3.png?version=9d1a63c717df80feffa9ec93ebceb014";
  7. const DIAMOND_BLOCK = "https://gamepedia.cursecdn.com/minecraft_gamepedia/6/6b/Block_of_Diamond_JE6_BE3.png?version=117daf438c2ddfff59bab05d5715f6d2";
  8. var nextHalvingHeight = 630000;
  9. var curTipBlock = null;
  10. var recentBlocks = null;
  11. function doApiReq(endpoint, cb) {
  12. let xhr = new XMLHttpRequest();
  13. xhr.timeout = 5000;
  14. xhr.onreadystatechange = function() {
  15. if (xhr.readyState == 4) {
  16. if (xhr.status == 200) {
  17. let json = JSON.parse(xhr.responseText);
  18. cb(json);
  19. } else {
  20. // TODO Make this better?
  21. cb(null);
  22. }
  23. }
  24. };
  25. xhr.open("GET", API_PREFIX + endpoint);
  26. xhr.send()
  27. }
  28. function doGetRecentBlocks(cb) {
  29. doApiReq("/blocks/tip", cb);
  30. }
  31. function doGetBlockCoinBaseTx(blockhash, cb) {
  32. doApiReq("/block/" + blockhash + "/txs", function(resp) {
  33. if (resp != null) {
  34. cb(resp[0]);
  35. } else {
  36. cb(null);
  37. }
  38. });
  39. }
  40. function calcRenderedSatQty(sats, decimals) {
  41. let subBtc = sats % 100000000;
  42. let btc = (sats - subBtc) / 100000000;
  43. let maskModulus = 10 ** (8 - decimals);
  44. let subDecimal = (subBtc - (subBtc % maskModulus)) / maskModulus;
  45. let decimal = "0".repeat(decimals) + subDecimal.toString();
  46. if (decimal != 0) {
  47. return btc.toString() + "." + decimal.substr(decimals * -1);
  48. } else {
  49. return btc.toString();
  50. }
  51. }
  52. const HALVING_PERIOD = 210000;
  53. function calcRewardAtHeight(height) {
  54. let epoch = Math.floor(height / HALVING_PERIOD);
  55. return 5000000000 / (2 ** epoch);
  56. }
  57. function calcRewardBreakdown(height, sats) {
  58. let subsidy = calcRewardAtHeight(height);
  59. let fees = sats - subsidy;
  60. let rSubsidy = calcRenderedSatQty(subsidy, 2);
  61. let rFees = calcRenderedSatQty(fees, 3);
  62. return rSubsidy + " subsidy + " + rFees + " fees"
  63. }
  64. function toggleElemSelected(elem) {
  65. if (elem.classList.contains("blbselected")) {
  66. elem.classList.remove("blbselected");
  67. } else {
  68. elem.classList.add("blbselected");
  69. }
  70. }
  71. function formatDate(date) {
  72. let year = date.getUTCFullYear();
  73. let month = "0" + (1 + date.getUTCMonth());
  74. let day = "0" + date.getUTCDate();
  75. let hours = "0" + date.getUTCHours();
  76. let minutes = "0" + date.getUTCMinutes();
  77. let seconds = "0" + date.getUTCSeconds();
  78. let datePart = year + "-" + month.substr(-2) + "-" + day.substr(-2);
  79. let timePart = hours.substr(-2) + ":" + minutes.substr(-2) + ":" + seconds.substr(-2);
  80. return datePart + " " + timePart;
  81. }
  82. function makeBlockElem(block, prevBlock) {
  83. let entry = document.createElement("div");
  84. let innerElem = document.createElement("div");
  85. innerElem.classList.add("entryinner");
  86. entry.appendChild(innerElem);
  87. if (block.height % 2 == 0) {
  88. innerElem.classList.add("blocklisteven");
  89. } else {
  90. innerElem.classList.add("blocklistodd");
  91. }
  92. /* ===== Top row data ===== */
  93. let topElem = document.createElement("div");
  94. topElem.classList.add("entrytop");
  95. innerElem.appendChild(topElem);
  96. // Make the icon.
  97. let iconCtr = document.createElement("div");
  98. iconCtr.classList.add("blbiconctr");
  99. topElem.appendChild(iconCtr);
  100. let iconElem = document.createElement("img");
  101. iconElem.classList.add("blbicon");
  102. iconCtr.appendChild(iconElem);
  103. if (block.height % HALVING_PERIOD == 0) {
  104. iconElem.src = DIAMOND_BLOCK;
  105. } else {
  106. iconElem.src = GOLD_BLOCK;
  107. }
  108. // Element for all the data.
  109. let dataElem = document.createElement("div");
  110. dataElem.classList.add("blbdata");
  111. topElem.appendChild(dataElem);
  112. // Make the height element.
  113. let heightElem = document.createElement("span");
  114. heightElem.classList.add("blbheight");
  115. heightElem.innerHTML = block.height.toString();
  116. dataElem.appendChild(heightElem);
  117. // this makes it look a lot nicer
  118. dataElem.append("—");
  119. // Make the timestamp element.
  120. let timestampElem = document.createElement("span");
  121. let timestampDate = new Date(block.timestamp * 1000);
  122. timestampElem.innerHTML = formatDate(timestampDate) + " UTC";
  123. dataElem.appendChild(timestampElem);
  124. // this makes it look a lot nicer
  125. dataElem.append("—");
  126. // Make the reward breakdown element.
  127. let rewardElem = document.createElement("span");
  128. rewardElem.innerHTML = "Reward: ? sat";
  129. dataElem.appendChild(rewardElem);
  130. doGetBlockCoinBaseTx(block.id, function(resp) {
  131. let reward = resp.vout[0].value;
  132. let rewardStr = calcRewardBreakdown(block.height, reward);
  133. rewardElem.innerHTML = rewardStr;
  134. });
  135. /* ==== Detail row data ===== */
  136. // Details
  137. let detailElem = document.createElement("div");
  138. detailElem.classList.add("entrydetail");
  139. // Make the hash element.
  140. let hashElem = document.createElement("div");
  141. hashElem.innerHTML = block.id;
  142. hashElem.classList.add("blbhash");
  143. detailElem.appendChild(hashElem);
  144. // Weight and height
  145. let sizeElem = document.createElement("div");
  146. detailElem.appendChild(sizeElem);
  147. let blockKB = Math.floor(block.size / 1024);
  148. let blockkWU = Math.floor(block.weight / 1024);
  149. sizeElem.innerHTML = "Size/Weight: " + blockKB + " KiB / " + blockkWU + " kSipa";
  150. // Number of transactions
  151. let txsElem = document.createElement("div");
  152. detailElem.appendChild(txsElem);
  153. txsElem.innerHTML = "Tx count: " + block.tx_count;
  154. // Make the duration element.
  155. if (prevBlock != null) {
  156. let timeElem = document.createElement("div");
  157. let blocktime = block.timestamp - prevBlock.timestamp;
  158. if (blocktime < 0) {
  159. blocktime = 0; // ehhhhhhhh
  160. }
  161. timeElem.innerHTML = "Since last block: " + blocktime.toString() + " sec";
  162. detailElem.appendChild(timeElem);
  163. } else {
  164. let oopsElem = document.createElement("div");
  165. oopsElem.innerHTML = "<br/>(I don't feel like making the request to get the previous block to find the blocktime)";
  166. detailElem.appendChild(oopsElem);
  167. }
  168. let linksElem = document.createElement("div");
  169. linksElem.classList.add("detaillinks");
  170. detailElem.appendChild(linksElem);
  171. // Link to the block on Blockstream's explorer.
  172. let blockstreamLink = document.createElement("a");
  173. blockstreamLink.href = "https://blockstream.info/block/"+ block.id;
  174. let blockstreamLogo = document.createElement("img");
  175. blockstreamLogo.src = "https://blockstream.info/img/icons/blockstream-logo.png";
  176. blockstreamLink.appendChild(blockstreamLogo);
  177. let viewonElem = document.createElement("span");
  178. viewonElem.innerHTML = "View on blockstream.info";
  179. blockstreamLink.appendChild(viewonElem);
  180. linksElem.appendChild(blockstreamLink);
  181. // Thing to close the details.
  182. //let closeElem = document.createElement("div");
  183. //closeElem.classList.add("closebtn");
  184. //closeElem.innerHTML = "close";
  185. linksElem.onclick = function() {
  186. console.log("foobar");
  187. // This is such a hack but it works. We have to do this because the
  188. // onclick for the entry we're removing it from here *still gets run*
  189. // when we pick up the click here. This just defers removing it until
  190. // we're about to render the next frame, so we're sure we remove it.
  191. window.requestAnimationFrame(function() {
  192. entry.classList.add("blbselected");
  193. });
  194. };
  195. //detailElem.appendChild(closeElem);
  196. /* ===== Finalize ===== */
  197. // Put it together.
  198. innerElem.appendChild(detailElem);
  199. entry.appendChild(innerElem);
  200. entry.classList.add("blocklistentry");
  201. entry.onclick = function() {
  202. toggleElemSelected(entry);
  203. };
  204. return entry;
  205. }
  206. var blocksLeftElem = null;
  207. var timeLeftElem = null;
  208. var halfTimeElem = null;
  209. function updateRemainingCount(block) {
  210. if (blocksLeftElem == null) {
  211. blocksLeftElem = document.getElementById("blocksleft");
  212. }
  213. if (timeLeftElem == null) {
  214. timeLeftElem = document.getElementById("timeleft");
  215. }
  216. if (halfTimeElem == null) {
  217. halfTimeElem = document.getElementById("halftime");
  218. }
  219. let blocksLeft = nextHalvingHeight - curTipBlock.height;
  220. blocksLeftElem.innerHTML = blocksLeft.toString()
  221. if (blocksLeft < 1) {
  222. timeLeftElem.innerHTML = "Halving imminent!";
  223. // Hide the expected time.
  224. let timectr = document.getElementById("halftimectr");
  225. timectr.style.display = "none";
  226. } else {
  227. let nowUnix = Date.now() / 1000; // wtf???
  228. let sinceLastBlock = nowUnix - block.timestamp;
  229. let secsLeft = ((blocksLeft * 10 * 60) - sinceLastBlock)|0;
  230. let secsPart = secsLeft % 60;
  231. let minLeft = (secsLeft - secsPart) / 60;
  232. let minPart = minLeft % 60;
  233. let hrsLeft = (minLeft - minPart) / 60;
  234. let acc = secsPart + "s";
  235. if (minLeft > 0) {
  236. acc = (minPart + "m ") + acc;
  237. }
  238. if (hrsLeft > 0) {
  239. acc = (hrsLeft + "h ") + acc;
  240. }
  241. timeLeftElem.innerHTML = "roughly " + acc + " remaining";
  242. // Reward halving date.
  243. let localNow = new Date();
  244. let tzOff = (new Date()).getTimezoneOffset() * 60 * 1000;
  245. let etaDate = new Date(localNow.getTime() + (secsLeft * 1000) - tzOff);
  246. let formattedEtaDate = formatDate(etaDate);
  247. halfTimeElem.innerHTML = "around " + formattedEtaDate + " local time";
  248. }
  249. }
  250. function addNewBlock(block) {
  251. let prev = recentBlocks[0];
  252. recentBlocks = [block].concat(recentBlocks);
  253. let elem = makeBlockElem(block, prev);
  254. elem.classList.add("newblock");
  255. setTimeout(function() {
  256. elem.classList.remove("newblock");
  257. }, 2000);
  258. let blocklist = document.getElementById("blocklist");
  259. blocklist.prepend(elem);
  260. }
  261. function populateRecentBlocks(blocks) {
  262. let blocklist = document.getElementById("blocklist");
  263. let loadingElem = document.getElementById("blocklistloading");
  264. curTipBlock = blocks[0];
  265. updateRemainingCount(curTipBlock.height);
  266. for (let i = 0; i < blocks.length; i++) {
  267. let block = blocks[i];
  268. let prevBlock = null;
  269. if (i < blocks.length - 1) {
  270. prevBlock = blocks[i + 1];
  271. }
  272. let elem = makeBlockElem(block, prevBlock);
  273. if (i == 0) {
  274. // Adding the new block so we give it the class to make it colored,
  275. // but set a timer so it goes away eventually.
  276. elem.classList.add("newblock");
  277. setTimeout(function() {
  278. elem.classList.remove("newblock");
  279. }, 2000);
  280. }
  281. blocklist.appendChild(elem);
  282. }
  283. loadingElem.style.display = "none";
  284. updateRemainingCount(curTipBlock);
  285. }
  286. function getPollDelayAtHeight(height) {
  287. let blocksUntilHalving = HALVING_PERIOD - (height % HALVING_PERIOD);
  288. if (blocksUntilHalving == 1) {
  289. return 1000;
  290. } else if (blocksUntilHalving == 2) {
  291. return 2500;
  292. } else {
  293. return 10000;
  294. }
  295. }
  296. function checkNewBlocks() {
  297. console.log("Polling for new blocks...");
  298. doGetRecentBlocks(function(blocks) {
  299. if (blocks == null) {
  300. alert("Error polling for new bocks, please refresh the page.");
  301. return;
  302. }
  303. // Find the index of the newest known block.
  304. let knownHeight = recentBlocks[0].height;
  305. let newestKnownIndex = -1;
  306. for (let i = 0; i < blocks.length; i++) {
  307. if (blocks[i].height == knownHeight) {
  308. newestKnownIndex = i;
  309. break;
  310. }
  311. }
  312. // If there's no new blocks, exit.
  313. if (newestKnownIndex == 0) {
  314. console.log("No new blocks found.");
  315. } else if (newestKnownIndex == -1) {
  316. console.log("wtf");
  317. }
  318. // If there are new blocks, add them.
  319. for (let i = newestKnownIndex - 1; i >= 0; i--) {
  320. let newBlock = blocks[i];
  321. console.log("Adding new block at height " + newBlock.height + " (" + newBlock.id + ")");
  322. addNewBlock(newBlock);
  323. }
  324. // Set up to call this again.
  325. setTimeout(checkNewBlocks, getPollDelayAtHeight(recentBlocks[0].height));
  326. });
  327. }
  328. function init() {
  329. console.log("Hello!");
  330. doGetRecentBlocks(function(blocks) {
  331. if (blocks == null) {
  332. alert("Error loading blocks, please refresh the page.");
  333. return;
  334. }
  335. recentBlocks = blocks;
  336. populateRecentBlocks(blocks);
  337. setTimeout(checkNewBlocks, 5000);
  338. setInterval(function() {
  339. updateRemainingCount(recentBlocks[0]);
  340. }, 1000);
  341. });
  342. }