Page MenuHomePhabricator

No OneTemporary

diff --git a/web/explorer/explorer-server/src/templating/filters.rs b/web/explorer/explorer-server/src/templating/filters.rs
index 38b17ef9e..ae48b3bf9 100644
--- a/web/explorer/explorer-server/src/templating/filters.rs
+++ b/web/explorer/explorer-server/src/templating/filters.rs
@@ -1,494 +1,480 @@
use std::collections::HashMap;
use bitcoinsuite_chronik_client::proto::{
GenesisInfo, OutPoint, Token, TokenInfo,
};
use bitcoinsuite_core::Script;
use chrono::DateTime;
use chrono_humanize::HumanTime;
use humansize::{file_size_opts as options, FileSize};
use maud::{html, PreEscaped};
use num_format::{Locale, ToFormattedString};
use regex::bytes::Regex;
use crate::{blockchain, templating::TokenEntryTemplate};
fn render_integer_with_small_flag(
int: i128,
smallify: bool,
) -> askama::Result<String> {
let string = int.to_formatted_string(&Locale::en);
let parts = string.split(',').collect::<Vec<_>>();
let output = html! {
@for (idx, part) in parts.iter().enumerate() {
@if idx >= 2 && smallify {
small.digit-sep[idx < parts.len() - 1] { (part) }
} @else {
span.digit-sep[idx < parts.len() - 1] { (part) }
}
}
};
Ok(output.into_string())
}
pub fn max(value: &i64, maximum: &i64) -> askama::Result<i64> {
Ok(*value.max(maximum))
}
pub fn unwrap<T>(value: &Option<T>) -> askama::Result<&T> {
Ok(value.as_ref().unwrap())
}
pub fn check_is_coinbase(outpoint: &OutPoint) -> askama::Result<bool> {
Ok(outpoint.txid == [0; 32] && outpoint.out_idx == 0xffff_ffff)
}
-pub fn destination_from_script<'a>(
+pub fn cashaddr_from_script<'a>(
script: &'a [u8],
- is_token: &bool,
- sats_addr_prefix: &'a str,
- tokens_addr_prefix: &'a str,
+ prefix: &'a str,
) -> askama::Result<blockchain::Destination<'a>> {
- let prefix = if *is_token {
- tokens_addr_prefix
- } else {
- sats_addr_prefix
- };
Ok(blockchain::destination_from_script(prefix, script))
}
pub fn get_script(signature_script: &[u8]) -> askama::Result<String> {
let script = Script::from_slice(signature_script);
Ok(script.to_string())
}
pub fn find_token_entry<'a>(
token: &Option<Token>,
entries: &'a [TokenEntryTemplate<'a>],
) -> askama::Result<Option<&'a TokenEntryTemplate<'a>>> {
let Some(token) = token else { return Ok(None) };
Ok(entries
.iter()
.find(|entry| entry.entry.token_id == token.token_id))
}
pub fn token_entry_genesis_info<'a>(
entry: &Option<&'a TokenEntryTemplate<'a>>,
) -> askama::Result<Option<&'a GenesisInfo>> {
Ok(entry.and_then(|entry| entry.genesis_info.as_ref()))
}
-pub fn check_is_token(token: &Option<Token>) -> askama::Result<bool> {
- Ok(token
- .as_ref()
- .map(|slp| slp.amount > 0 || slp.is_mint_baton)
- .unwrap_or(false))
-}
-
pub fn human_time(
timestamp: &DateTime<chrono::Utc>,
) -> askama::Result<HumanTime> {
Ok(HumanTime::from(*timestamp))
}
pub fn render_integer(int: &i128) -> askama::Result<String> {
render_integer_with_small_flag(*int, false)
}
pub fn render_integer_smallify(int: &i128) -> askama::Result<String> {
render_integer_with_small_flag(*int, true)
}
pub fn render_human_size(value: &u64) -> askama::Result<String> {
Ok(value.file_size(options::CONVENTIONAL).unwrap())
}
pub fn render_difficulty(difficulty: &f64) -> askama::Result<String> {
let est_hashrate = difficulty * (0xffffffffu64 as f64) / 600.0;
let hashrate = if est_hashrate < 1e12 {
html! { (format!("{:.2} GH/s", est_hashrate / 1e9)) }
} else if est_hashrate < 1e15 {
html! { (format!("{:.2} TH/s", est_hashrate / 1e12)) }
} else if est_hashrate < 1e18 {
html! { (format!("{:.2} PH/s", est_hashrate / 1e15)) }
} else {
html! { (format!("{:.2} EH/s", est_hashrate / 1e18)) }
};
let num_digits = difficulty.log10().floor();
let exponent = (num_digits / 3.0) as u32;
let difficulty = match exponent {
0 => html! { (format!("{:.0}", difficulty)) },
1 => html! { (format!("{:.2}", difficulty / 1e3)) " ×10" sup { "3" } },
2 => html! { (format!("{:.2}", difficulty / 1e6)) " ×10" sup { "6" } },
3 => html! { (format!("{:.2}", difficulty / 1e9)) " ×10" sup { "9" } },
4 => {
html! { (format!("{:.2}", difficulty / 1e12)) " ×10" sup { "12" } }
}
_ => {
html! { (format!("{:.2}", difficulty / 1e15)) " ×10" sup { "15" } }
}
};
let output = html! {
(difficulty)
small {
" (10 min. blocks = "
(hashrate)
")"
}
};
Ok(output.into_string())
}
pub fn render_miner(coinbase_data: &[u8]) -> askama::Result<String> {
// Miners identified exactly by utf8 string in coinbase data
let self_identified_miners = [
"Mining-Dutch",
"ZULUPooL",
"zpool.ca",
"molepool.com",
"CoinMinerz.com",
"zergpool.com",
"solopool.org",
"p2p-spb.xyz",
"Cminors-Pools",
"with Om Power",
"nodeStratum",
];
for &str_to_match in &self_identified_miners {
if contains_subslice(coinbase_data, str_to_match.as_bytes()) {
return Ok(str_to_match.to_string());
}
}
// Miners with identifying coinbase data substring that must be clarified
// for the user
let partial_string_miners = [("Hath", "Hathor-MM")];
for &(str_to_match, str_to_show) in &partial_string_miners {
if contains_subslice(coinbase_data, str_to_match.as_bytes()) {
return Ok(str_to_show.to_string());
}
}
// Pools with identifiable miners
// ViaBTC
let reg_viabtc =
Regex::new(r"ViaBTC(.*/Mined by (?P<mined_by>\w+)/)?").unwrap();
if let Some(captures) = reg_viabtc.captures(coinbase_data) {
if let Some(mined_by) = captures.name("mined_by") {
return Ok(format!(
"ViaBTC | Mined by {}",
String::from_utf8_lossy(mined_by.as_bytes())
));
};
return Ok("ViaBTC".to_string());
};
// CK Pool
// Note: CK Pool software is used by solo miners
// Parse as solo miners unless CK Pool is the only identifier
let reg_ckpool =
Regex::new(r"ckpool(.*/mined by (?P<mined_by>\w+)/)?").unwrap();
if let Some(captures) = reg_ckpool.captures(coinbase_data) {
if let Some(mined_by) = captures.name("mined_by") {
return Ok(format!(
"{}",
String::from_utf8_lossy(mined_by.as_bytes())
));
};
return Ok("CK Pool".to_string());
};
// Miner not recognized, return "Unknown"
return Ok("Unknown".to_string());
}
pub fn contains_subslice(haystack: &[u8], needle: &[u8]) -> bool {
haystack
.windows(needle.len())
.any(|window| window == needle)
}
pub fn render_integer_with_commas(int: &u64) -> askama::Result<String> {
let string = int.to_formatted_string(&Locale::en);
let parts = string.split(',').collect::<Vec<_>>();
let output = html! {
@for (idx, part) in parts.iter().enumerate() {
@if idx != 0 {
span.non-selectable { "," }
}
span { (part) }
}
};
Ok(output.into_string())
}
pub fn render_sats(sats: &i64) -> askama::Result<String> {
let coins = *sats as f64 / 100.0;
let fmt = format!("{:.2}", coins);
let mut parts = fmt.split('.');
let integer_part: u64 = parts.next().unwrap().parse().unwrap();
let fract_part = parts.next().unwrap();
let output = {
let output = html! {
(PreEscaped(render_integer_with_commas(&integer_part)?))
"."
small {
(fract_part)
}
};
output.into_string()
};
Ok(output)
}
pub fn hexify_u8_vector(value: &[u8]) -> askama::Result<String> {
Ok(hex::encode(value))
}
pub fn string_from_lossy_utf8(value: &[u8]) -> askama::Result<String> {
Ok(String::from_utf8_lossy(value).to_string())
}
pub fn to_le_hex(slice: &[u8]) -> askama::Result<String> {
Ok(blockchain::to_be_hex(slice))
}
pub fn u32_to_u64(value: &u32) -> askama::Result<u64> {
Ok(*value as u64)
}
pub fn to_i128<T: Into<i128> + Copy>(value: &T) -> askama::Result<i128> {
Ok((*value).into())
}
pub fn string_to_i128(value: &String) -> askama::Result<i128> {
Ok(value.parse::<i128>().unwrap())
}
pub fn render_token_amount(
base_amount: &i128,
decimals: &u32,
) -> askama::Result<String> {
let decimals = *decimals as usize;
if decimals == 0 {
return render_integer(base_amount);
}
let base_amount_str =
format!("{:0digits$}", base_amount, digits = decimals + 1);
let decimal_idx = base_amount_str.len() - decimals;
let integer_part: i128 = base_amount_str[..decimal_idx].parse().unwrap();
let fract_part = &base_amount_str[decimal_idx..];
let num_fract_sections = (decimals as usize + 2) / 3;
let mut all_zeros = true;
let mut rendered = html! {};
for section_idx in (0..num_fract_sections).rev() {
let offset = section_idx * 3;
let section = &fract_part[offset..fract_part.len().min(offset + 3)];
if !section.chars().all(|c| c == '0') {
all_zeros = false;
}
rendered = html! {
small.zeros[all_zeros].digit-sep[
section_idx != num_fract_sections - 1] {
(section)
}
(rendered)
};
}
let output =
html! { (PreEscaped(render_integer(&integer_part)?)) "." (rendered) };
Ok(output.into_string())
}
pub fn get_token<'a>(
tokens: &'a HashMap<String, TokenInfo>,
token_id: &str,
) -> askama::Result<Option<&'a TokenInfo>> {
Ok(tokens.get(token_id))
}
#[cfg(test)]
mod tests {
use crate::templating::filters::{contains_subslice, render_miner};
#[test]
fn test_contains_subslice() {
// Returns true if substring is present
assert_eq!(contains_subslice(b"abcdefViaBTCghijk", b"ViaBTC"), true);
// Returns false if substring is not present
assert_eq!(
contains_subslice(b"abcdefViaBTCghijk", b"Mining-Dutch"),
false
);
// Returns true if substring matches whole string
assert_eq!(
contains_subslice(b"abcdefViaBTCghijk", b"abcdefViaBTCghijk"),
true
);
// Returns true if substring occurs more than once
assert_eq!(contains_subslice(b"abcabcabc", b"abc"), true);
}
#[test]
fn test_render_miner() {
// ViaBTC 791160 (mined by 260786)
// Note: To build mocks using coinbase hex as bytes:
// 1 - In python, run bytes.fromhex("<coinbase_hex_of_block>")
// 2 - Use double quotes to wrap output, not single quotes ("" not '')
// 3 - Manually format across multiple lines by adding '\' before
// line breaks
// 4 - Any double quotes inside the bytes string must be escaped with
// '\'
let via_coinbase_hex = b"\x03x\x12\x0c\x18/ViaBTC/Mined by \
260786/\x10;o\xa2\x0f\xf3d\x8ai\xac\xc3\x1e\xd9\xb4\x94l\x00";
assert_eq!(
render_miner(via_coinbase_hex).unwrap(),
"ViaBTC | Mined by 260786"
);
// ViaBTC 852373 (mined by zuberjawan)
let via_zuberjawan_coinbase_hex = b"\x03\x95\x01\r\x1c/ViaBTC\
/Mined by zuberjawan/\x10\xbf\xde\xb8\x0bV\xe4=\xed\xd0\x03PN\
\xa2\xaa\x0f\x00";
assert_eq!(
render_miner(via_zuberjawan_coinbase_hex).unwrap(),
"ViaBTC | Mined by zuberjawan"
);
// Mining-Dutch 854964
let md_coinbase_hex = b"\x03\xb4\x0b\r\x04\xca\xa4\xa3f\x08\xfa\
\xbemmU\xa9\x8co\xaa\xf7\xa7ta\xd5\xe7/\x05d'\x8akl\x94\xe8x\
\x8bJy\x1a\x01\x90\xc3\xbc\x8e\x04\x8c\x00\x01\x00\x00\x00\x00\
\x00\x00\x04\xe2H\xcb]A\x00\x00Z\x00\x12/Mining-Dutch/-114";
assert_eq!(render_miner(md_coinbase_hex).unwrap(), "Mining-Dutch");
// Hathor-MM 823276
let hathor_coinbase_hex = b"\x03\xec\x8f\x0cHath=\xec\";v5D\xf2\
\x88\x19\xdf\x8b\xa9W\x94K\xba\xc2\xe3\\\xb8\xde\x15\x1b\x03\
\x0e\xde\xe8\x8a\x90\x13Ps\xe0/\x19\x1d\x00\x00\x00";
assert_eq!(render_miner(hathor_coinbase_hex).unwrap(), "Hathor-MM");
// Zulu Pool 785677
// A block with both Hathor and Zulu hex strings is returned as Zulu
let zulu_coinbase_hex = b"\x03\r\xfd\x0bHath\xa8\x81\xa5K_\xbc(\
\xb2~\xb3\xedY\xfcI$\xa3\xb9\x91\x03?\xeezx\xb9\x19\x17\n\x92\xd9\
\xb7\xbe\xafZULUPooL-XEC\x00\x00\x11\xd8\xe9\xbb\x1b\x00";
assert_eq!(render_miner(zulu_coinbase_hex).unwrap(), "ZULUPooL");
// CK Pool specified miner TinyChipHub
// 854770
let ck_tinychiphub_coinbase_hex = b"\x03\xf2\n\r\x00\x04\xfa\xe1\
\xa1f\x04Z\x8c\xe5\x02\x0c,^\x9ffX\xe1o>\x86\xee^\x00\nckpool\
\x16/mined by TinyChipHub/";
assert_eq!(
render_miner(ck_tinychiphub_coinbase_hex).unwrap(),
"TinyChipHub"
);
// CK Pool unspecified miner
// 788631
let ck_unspecified_coinbase_hex = b"\x03\x97\x08\x0c\x04\x18\
\x16x\xa1\x04d\x98Ad\x04\xbbg\xca\r\x0c6\x92Adwc\x01\x00\x00\
\x00\x00\x00\nckpool";
assert_eq!(
render_miner(ck_unspecified_coinbase_hex).unwrap(),
"CK Pool"
);
// zpool 790863
let zpool_coinbase_hex = b"\x03O\x11\x0c\x04`*Wd\x08B\x00\x07\
\x90wB+\x01zpool.ca\x00\xfa\xbemm\xa8!)D&e.\xbd\x8c\xff\x8d\xf5\
\xe0/\xfc\xbb\xdc\x1b\x1d\x9e\x90\"\x83*\xcfM\x07\x1e\x9b\xfa-\
\x95 \x00\x00\x00\x00\x00\x00\x00";
assert_eq!(render_miner(zpool_coinbase_hex).unwrap(), "zpool.ca");
// molepool.com 796646
let molepool_coinbase_hex = b"\x03\xe6'\x0c\x04}%\x8cd\x00\x18\
\x96 \xe6\xfe\xe4\xe2\x17\x0e/molepool.com/";
assert_eq!(
render_miner(molepool_coinbase_hex).unwrap(),
"molepool.com"
);
// CoinMinerz.com 787515
let coinminerz_coinbase_hex = b"\x03;\x04\x0c\x04\x92\x9a7d\
\x08b\xc9\xc3\x13\x19\x1b\x1e\x00\x10/CoinMinerz.com/";
assert_eq!(
render_miner(coinminerz_coinbase_hex).unwrap(),
"CoinMinerz.com"
);
// zergpool.com 806676
let zergpool_coinbase_hex = b"\x03\x14O\x0c\x04\xb0\x1e\xe7d\
\x08\x81\x06\t\x08-\xc3\x06\x00zergpool.com\x00\xfa\xbemm'@\
\x1f=\xb7\xd7\xaf\x86Z\xfe\xba\xb8\r|\xb55~}\xf5\xaa\xe8b\
\x1eF\xc2\xaa|\x90\xbb\xfc\xdb\xca\x02\x00\x00\x00\x00\x00\
\x00\x00";
assert_eq!(
render_miner(zergpool_coinbase_hex).unwrap(),
"zergpool.com"
);
// solopool.org 806713
let solopool_coinbase_hex = b"\x039O\x0c\x04R\x9e\xe7d\x08\
\xf5\x1e\xec\xb6Q\xd0I\x04\x0csolopool.org";
assert_eq!(
render_miner(solopool_coinbase_hex).unwrap(),
"solopool.org"
);
// p2p-spb 821556
let p2pspb_coinbase_hex = b"\x034\x89\x0c,\xfa\xbemm*5\x7f\
\xe8\xc5f\x8c\x1e\xddX\xa4.\xaa\xa1\x81\xf4\x9a\xfc\xf3\x97\
\xcdj\xe2GS\x93\xec\x06\xcf\xcab\xf0\x10\x00\x00\x00\x00\
\x00\x00\x00p2p-spb.xyz";
assert_eq!(render_miner(p2pspb_coinbase_hex).unwrap(), "p2p-spb.xyz");
// Cminors-Pools 827550
let cminors_coinbase_hex = b"\x03\x9e\xa0\x0c\x04)\xc1\xa7e\
\x08\x81\x00\x00\x1f\xa1Zh\x00Cminors-Pools\x00\xfa\xbemm\
\xf3o<\xed\x97\xaa\xc3c\xe4\xf5 .K9\xd59EM\x9d!\xc4\x08\xdf\
\x8a\xc4\xbe\x8e\xb96`B\x0b\x01\x00\x00\x00\x00\x00\x00\x00";
assert_eq!(
render_miner(cminors_coinbase_hex).unwrap(),
"Cminors-Pools"
);
// AnandrajSingh Pool 840619
let anandra_coinbase_hex = b"\x03\xab\xd3\x0c\x00\x04j6!f\x04\
i\x94\xe2\x10\x0c`\x1f!f\x88hD\x00\x00\x00\x00\
\x00 Mined by with Om Power /AnandrajSingh Pool/\r \xf0\x9f\
\x8f\x86\xf0\x9f\x8f\x86\xf0\x9f\x8f\x86";
assert_eq!(
render_miner(anandra_coinbase_hex).unwrap(),
"with Om Power"
);
// nodeStratum 856227
let nodestratum_coinbase_hex = b"\x03\xa3\x10\r\x04\xabF\xaef\x08\
\xfa\xbemm\x1dYZ\xdbWv\x91,y\xed)d\xf6<\x8a\xdd\x8b7\xdb\x9e=\
pR\xaf>+PIpW\xfe\xb5\x04\x00\x00\x00\x00\x00\x00\x00\xb1V\xdbd`\
\x14\x00\x00\r/nodeStratum/";
assert_eq!(
render_miner(nodestratum_coinbase_hex).unwrap(),
"nodeStratum"
);
// Unknown miner
// genesis block 0
let unknown_coinbase_hex = b"\x04\xff\xff\x00\x1d\x01\x04\
EThe Times 03/Jan/2009 Chancellor on brink of second \
bailout for banks";
assert_eq!(render_miner(unknown_coinbase_hex).unwrap(), "Unknown");
}
}
diff --git a/web/explorer/explorer-server/templates/components/input.html b/web/explorer/explorer-server/templates/components/input.html
index c75ea0ca2..274502d61 100644
--- a/web/explorer/explorer-server/templates/components/input.html
+++ b/web/explorer/explorer-server/templates/components/input.html
@@ -1,87 +1,86 @@
{% macro render(
index,
input,
tx,
sats_addr_prefix,
tokens_addr_prefix,
token_icon_url) %}
{% let token_entry = input.token|find_token_entry(token_entries) %}
{% let genesis_info = token_entry|token_entry_genesis_info %}
- {% let is_token = input.token|check_is_token %}
- {% let destination = input.output_script |
- destination_from_script(is_token, sats_addr_prefix, tokens_addr_prefix) %}
+ {% let
+ destination = input.output_script|cashaddr_from_script(sats_addr_prefix) %}
{% let decoded_input_script = input.input_script|get_script %}
{% let prev_out = input.prev_out|unwrap %}
<div>
{% if prev_out|check_is_coinbase %}
<div class="input-row coinbase-row">
<div class="ui green horizontal label">Coinbase</div>
</div>
{% else %}
<div class="input-row">
<div class="input-row-section1">
<div>{{ index }}</div>
<a href="/tx/{{ prev_out.txid|to_le_hex }}">
<i class="horizontally flipped icon sign out"></i>
</a>
</div>
<div class="input-row-section2">
{% match destination %}
{% when Destination::Address with (address) %}
<div class="address-and-copy">
<div class="copy-btn" onclick="copyString('{{ address.as_str() }}')">
<span class="tooltiptext">Copy to clipboard</span>
</div>
<div class="input-hex input-hex-address">
<a href="/address/{{ address.as_str() }}">{{ address.as_str() }}</a>
{% when Destination::P2PK with (pubkey) %}
Pubkey: {{ pubkey|hexify_u8_vector }}
{% when Destination::Unknown with (_bytes) %}
Unknown
{% when Destination::Nulldata with (_ops) %}
Unreachable
{% endmatch %}
</div>
</div>
<div class="input-hex">
{% match input.token %}
{% when Some with (slp_token) %}
{% match genesis_info %}
{% when Some with (genesis_info) %}
{% if slp_token.amount > 0 || slp_token.is_mint_baton %}
{% if slp_token.is_mint_baton %}
<div class="ui green horizontal label">Mint baton</div>
{% else %}
{{ slp_token.amount |
to_i128 |
render_token_amount(genesis_info.decimals) |
safe }}
{{ genesis_info.token_ticker|string_from_lossy_utf8 }}
{% endif %}
{% endif %}
{% match token_entry %}
{% when Some with (token_entry) %}
<img src="{{ token_icon_url }}/32/{{ token_entry.token_hex }}.png" />
{% when None %}
{% endmatch %}
<div>
<small>{{ input.value|render_sats|safe }} XEC</small>
</div>
{% when None %}
{{ input.value|render_sats|safe }} XEC
{% when None %}
{% endmatch %}
{% when None %} {{ input.value|render_sats|safe }} XEC
{% endmatch %}
</div>
</div>
</div>
{% endif %}
<div class="tx-transaction__script-data display-none">
<h4>Script Hex</h4>
<div class="hex">{{ input.input_script|hexify_u8_vector }}</div>
<h4 class="margintopscript">Script Decoded</h4>
<div class="hex">{{ decoded_input_script }}</div>
</div>
</div>
{% endmacro %}
diff --git a/web/explorer/explorer-server/templates/components/output.html b/web/explorer/explorer-server/templates/components/output.html
index 5864ed95e..aa8c25dc0 100644
--- a/web/explorer/explorer-server/templates/components/output.html
+++ b/web/explorer/explorer-server/templates/components/output.html
@@ -1,84 +1,83 @@
{% macro render(
index,
output,
tx,
sats_addr_prefix,
tokens_addr_prefix,
token_icon_url) %}
{% let token_entry = output.token|find_token_entry(token_entries) %}
{% let genesis_info = token_entry|token_entry_genesis_info %}
- {% let is_token = output.token|check_is_token %}
- {% let destination = output.output_script |
- destination_from_script(is_token, sats_addr_prefix, tokens_addr_prefix) %}
+ {% let
+ destination = output.output_script|cashaddr_from_script(sats_addr_prefix) %}
{% let decoded_output_script = output.output_script|get_script %}
<div>
<div class="output-row">
<div class="output-row-section1">
{% match destination %}
{% when Destination::Address with (address) %}
<div class="address-and-copy">
<div class="input-hex input-hex-address">
<a href="/address/{{ address.as_str() }}">{{ address.as_str() }}</a>
</div>
<div class="copy-btn" onclick="copyString('{{ address.as_str() }}')">
<span class="tooltiptext">Copy to clipboard</span>
</div>
</div>
{% when Destination::Nulldata with (_ops) %}
OP_RETURN data
{% when Destination::P2PK with (pubkey) %}
Pubkey: {{ pubkey|hexify_u8_vector }}
{% when Destination::Unknown with (_bytes) %}
Unknown
{% endmatch %}
<div class="input-hex">
{% match output.token %}
{% when Some with (slp_token) %}
{% match genesis_info %}
{% when Some with (genesis_info) %}
{% if slp_token.amount > 0 || slp_token.is_mint_baton %}
{% if slp_token.is_mint_baton %}
<div class="ui green horizontal label">Mint baton</div>
{% else %}
{{ slp_token.amount|to_i128|render_token_amount(genesis_info.decimals) |safe }}
{{ genesis_info.token_ticker|string_from_lossy_utf8 }}
{% endif %}
{% endif %}
{% match token_entry %}
{% when Some with (token_entry) %}
<img src="{{ token_icon_url }}/32/{{ token_entry.token_hex }}.png" />
{% when None %}
{% endmatch %}
<div>
<small>{{ output.value|render_sats|safe }} XEC</small>
</div>
{% when None %}
{{ output.value|render_sats|safe }} XEC
{% endmatch %}
{% when None %}
{{ output.value|render_sats|safe }} XEC
{% endmatch %}
</div>
</div>
<div class="output-row-section2">
<div>{{ index }}</div>
<div>
{% match output.spent_by %}
{% when Some with (outpoint) %}
<a href="/tx/{{ outpoint.txid|to_le_hex }}">
<i class="icon sign out"></i>
</a>
{% when None %}
<i class="disabled icon sign out"></i>
{% endmatch %}
</div>
</div>
</div>
<div class="tx-transaction__script-data display-none output-script-text">
<h4>Script Hex</h4>
<div class="hex">{{ output.output_script|hexify_u8_vector }}</div>
<h4 class="margintopscript">Script Decoded</h4>
<div class="hex">{{ decoded_output_script }}</div>
</div>
</div>
{% endmacro %}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Mar 2, 08:56 (20 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5177486
Default Alt Text
(26 KB)

Event Timeline