mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-12-22 14:28:07 +00:00
fix(core): rebase on drawlib
This commit is contained in:
parent
1ee36baea8
commit
11808a6f14
@ -170,7 +170,11 @@ STATIC const mp_rom_map_elem_t mod_trezorui_Display_locals_dict_table[] = {
|
|||||||
{MP_ROM_QSTR(MP_QSTR_FONT_NORMAL), MP_ROM_INT(FONT_NORMAL)},
|
{MP_ROM_QSTR(MP_QSTR_FONT_NORMAL), MP_ROM_INT(FONT_NORMAL)},
|
||||||
{MP_ROM_QSTR(MP_QSTR_FONT_DEMIBOLD), MP_ROM_INT(FONT_DEMIBOLD)},
|
{MP_ROM_QSTR(MP_QSTR_FONT_DEMIBOLD), MP_ROM_INT(FONT_DEMIBOLD)},
|
||||||
{MP_ROM_QSTR(MP_QSTR_FONT_MONO), MP_ROM_INT(FONT_MONO)},
|
{MP_ROM_QSTR(MP_QSTR_FONT_MONO), MP_ROM_INT(FONT_MONO)},
|
||||||
|
#ifdef FONT_BOLD_UPPER
|
||||||
{MP_ROM_QSTR(MP_QSTR_FONT_BOLD_UPPER), MP_ROM_INT(FONT_BOLD_UPPER)},
|
{MP_ROM_QSTR(MP_QSTR_FONT_BOLD_UPPER), MP_ROM_INT(FONT_BOLD_UPPER)},
|
||||||
|
#else
|
||||||
|
{MP_ROM_QSTR(MP_QSTR_FONT_BOLD_UPPER), MP_ROM_INT(FONT_BOLD)},
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
STATIC MP_DEFINE_CONST_DICT(mod_trezorui_Display_locals_dict,
|
STATIC MP_DEFINE_CONST_DICT(mod_trezorui_Display_locals_dict,
|
||||||
mod_trezorui_Display_locals_dict_table);
|
mod_trezorui_Display_locals_dict_table);
|
||||||
|
@ -1268,10 +1268,9 @@ pub enum TranslatedString {
|
|||||||
instructions__continue_in_app = 867, // "Continue in the app"
|
instructions__continue_in_app = 867, // "Continue in the app"
|
||||||
words__cancel_and_exit = 868, // "Cancel and exit"
|
words__cancel_and_exit = 868, // "Cancel and exit"
|
||||||
address__confirmed = 869, // "Receive address confirmed"
|
address__confirmed = 869, // "Receive address confirmed"
|
||||||
reset__title_shamir_backup = 870, // "Multi-share backup"
|
pin__cancel_description = 870, // "Continue without PIN"
|
||||||
pin__cancel_description = 871, // "Continue without PIN"
|
pin__cancel_info = 871, // "Without a PIN, anyone can access this device."
|
||||||
pin__cancel_info = 872, // "Without a PIN, anyone can access this device."
|
pin__cancel_setup = 872, // "Cancel PIN setup"
|
||||||
pin__cancel_setup = 873, // "Cancel PIN setup"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TranslatedString {
|
impl TranslatedString {
|
||||||
@ -2535,7 +2534,6 @@ impl TranslatedString {
|
|||||||
Self::instructions__continue_in_app => "Continue in the app",
|
Self::instructions__continue_in_app => "Continue in the app",
|
||||||
Self::words__cancel_and_exit => "Cancel and exit",
|
Self::words__cancel_and_exit => "Cancel and exit",
|
||||||
Self::address__confirmed => "Receive address confirmed",
|
Self::address__confirmed => "Receive address confirmed",
|
||||||
Self::reset__title_shamir_backup => "Multi-share backup",
|
|
||||||
Self::pin__cancel_description => "Continue without PIN",
|
Self::pin__cancel_description => "Continue without PIN",
|
||||||
Self::pin__cancel_info => "Without a PIN, anyone can access this device.",
|
Self::pin__cancel_info => "Without a PIN, anyone can access this device.",
|
||||||
Self::pin__cancel_setup => "Cancel PIN setup",
|
Self::pin__cancel_setup => "Cancel PIN setup",
|
||||||
@ -3803,7 +3801,6 @@ impl TranslatedString {
|
|||||||
Qstr::MP_QSTR_instructions__continue_in_app => Some(Self::instructions__continue_in_app),
|
Qstr::MP_QSTR_instructions__continue_in_app => Some(Self::instructions__continue_in_app),
|
||||||
Qstr::MP_QSTR_words__cancel_and_exit => Some(Self::words__cancel_and_exit),
|
Qstr::MP_QSTR_words__cancel_and_exit => Some(Self::words__cancel_and_exit),
|
||||||
Qstr::MP_QSTR_address__confirmed => Some(Self::address__confirmed),
|
Qstr::MP_QSTR_address__confirmed => Some(Self::address__confirmed),
|
||||||
Qstr::MP_QSTR_reset__title_shamir_backup => Some(Self::reset__title_shamir_backup),
|
|
||||||
Qstr::MP_QSTR_pin__cancel_description => Some(Self::pin__cancel_description),
|
Qstr::MP_QSTR_pin__cancel_description => Some(Self::pin__cancel_description),
|
||||||
Qstr::MP_QSTR_pin__cancel_info => Some(Self::pin__cancel_info),
|
Qstr::MP_QSTR_pin__cancel_info => Some(Self::pin__cancel_info),
|
||||||
Qstr::MP_QSTR_pin__cancel_setup => Some(Self::pin__cancel_setup),
|
Qstr::MP_QSTR_pin__cancel_setup => Some(Self::pin__cancel_setup),
|
||||||
|
@ -1,12 +1,18 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
micropython::gc::Gc,
|
io::BinaryData,
|
||||||
strutil::TString,
|
strutil::TString,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
translations::TR,
|
translations::TR,
|
||||||
trezorhal::usb::usb_configured,
|
trezorhal::usb::usb_configured,
|
||||||
ui::{
|
ui::{
|
||||||
component::{Component, Event, EventCtx, TimerToken},
|
component::{Component, Event, EventCtx, TimerToken},
|
||||||
display::{tjpgd::jpeg_info, toif::Icon, Color, Font},
|
display::{
|
||||||
|
self,
|
||||||
|
image::{ImageInfo, ToifFormat},
|
||||||
|
tjpgd::jpeg_info,
|
||||||
|
toif::{Icon, Toif},
|
||||||
|
Color, Font,
|
||||||
|
},
|
||||||
event::{TouchEvent, USBEvent},
|
event::{TouchEvent, USBEvent},
|
||||||
geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect},
|
geometry::{Alignment, Alignment2D, Insets, Offset, Point, Rect},
|
||||||
layout::util::get_user_custom_image,
|
layout::util::get_user_custom_image,
|
||||||
@ -37,6 +43,7 @@ const LOADER_DURATION: Duration = Duration::from_millis(2000);
|
|||||||
|
|
||||||
pub const HOMESCREEN_IMAGE_WIDTH: i16 = WIDTH;
|
pub const HOMESCREEN_IMAGE_WIDTH: i16 = WIDTH;
|
||||||
pub const HOMESCREEN_IMAGE_HEIGHT: i16 = HEIGHT;
|
pub const HOMESCREEN_IMAGE_HEIGHT: i16 = HEIGHT;
|
||||||
|
pub const HOMESCREEN_TOIF_SIZE: i16 = 144;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct HomescreenNotification {
|
pub struct HomescreenNotification {
|
||||||
@ -48,7 +55,7 @@ pub struct HomescreenNotification {
|
|||||||
pub struct Homescreen {
|
pub struct Homescreen {
|
||||||
label: TString<'static>,
|
label: TString<'static>,
|
||||||
notification: Option<(TString<'static>, u8)>,
|
notification: Option<(TString<'static>, u8)>,
|
||||||
custom_image: Option<Gc<[u8]>>,
|
image: BinaryData<'static>,
|
||||||
hold_to_lock: bool,
|
hold_to_lock: bool,
|
||||||
loader: Loader,
|
loader: Loader,
|
||||||
delay: Option<TimerToken>,
|
delay: Option<TimerToken>,
|
||||||
@ -67,7 +74,7 @@ impl Homescreen {
|
|||||||
Self {
|
Self {
|
||||||
label,
|
label,
|
||||||
notification,
|
notification,
|
||||||
custom_image: get_user_custom_image().ok(),
|
image: get_homescreen_image(),
|
||||||
hold_to_lock,
|
hold_to_lock,
|
||||||
loader: Loader::with_lock_icon().with_durations(LOADER_DURATION, LOADER_DURATION / 3),
|
loader: Loader::with_lock_icon().with_durations(LOADER_DURATION, LOADER_DURATION / 3),
|
||||||
delay: None,
|
delay: None,
|
||||||
@ -185,15 +192,14 @@ impl Component for Homescreen {
|
|||||||
if self.loader.is_animating() || self.loader.is_completely_grown(Instant::now()) {
|
if self.loader.is_animating() || self.loader.is_completely_grown(Instant::now()) {
|
||||||
self.render_loader(target);
|
self.render_loader(target);
|
||||||
} else {
|
} else {
|
||||||
let img_data = match self.custom_image {
|
match ImageInfo::parse(self.image) {
|
||||||
Some(ref img) => img.as_ref(),
|
ImageInfo::Jpeg(_) => shape::JpegImage::new_image(AREA.center(), self.image)
|
||||||
None => IMAGE_HOMESCREEN,
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_image_jpeg(img_data) {
|
|
||||||
shape::JpegImage::new(AREA.center(), img_data)
|
|
||||||
.with_align(Alignment2D::CENTER)
|
.with_align(Alignment2D::CENTER)
|
||||||
.render(target);
|
.render(target),
|
||||||
|
ImageInfo::Toif(_) => shape::ToifImage::new_image(AREA.center(), self.image)
|
||||||
|
.with_align(Alignment2D::CENTER)
|
||||||
|
.render(target),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.label.map(|t| {
|
self.label.map(|t| {
|
||||||
@ -271,7 +277,7 @@ impl crate::trace::Trace for Homescreen {
|
|||||||
|
|
||||||
pub struct Lockscreen<'a> {
|
pub struct Lockscreen<'a> {
|
||||||
label: TString<'a>,
|
label: TString<'a>,
|
||||||
custom_image: Option<Gc<[u8]>>,
|
image: BinaryData<'static>,
|
||||||
bootscreen: bool,
|
bootscreen: bool,
|
||||||
coinjoin_authorized: bool,
|
coinjoin_authorized: bool,
|
||||||
}
|
}
|
||||||
@ -280,7 +286,7 @@ impl<'a> Lockscreen<'a> {
|
|||||||
pub fn new(label: TString<'a>, bootscreen: bool, coinjoin_authorized: bool) -> Self {
|
pub fn new(label: TString<'a>, bootscreen: bool, coinjoin_authorized: bool) -> Self {
|
||||||
Lockscreen {
|
Lockscreen {
|
||||||
label,
|
label,
|
||||||
custom_image: get_user_custom_image().ok(),
|
image: get_homescreen_image(),
|
||||||
bootscreen,
|
bootscreen,
|
||||||
coinjoin_authorized,
|
coinjoin_authorized,
|
||||||
}
|
}
|
||||||
@ -306,19 +312,19 @@ impl Component for Lockscreen<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||||
let img_data = match self.custom_image {
|
let center = AREA.center();
|
||||||
Some(ref img) => img.as_ref(),
|
|
||||||
None => IMAGE_HOMESCREEN,
|
|
||||||
};
|
|
||||||
|
|
||||||
let center = constant::screen().center();
|
match ImageInfo::parse(self.image) {
|
||||||
|
ImageInfo::Jpeg(_) => shape::JpegImage::new_image(center, self.image)
|
||||||
if is_image_jpeg(img_data) {
|
|
||||||
shape::JpegImage::new(center, img_data)
|
|
||||||
.with_align(Alignment2D::CENTER)
|
.with_align(Alignment2D::CENTER)
|
||||||
.with_blur(4)
|
.with_blur(4)
|
||||||
.with_dim(102)
|
.with_dim(102)
|
||||||
.render(target);
|
.render(target),
|
||||||
|
ImageInfo::Toif(_) => shape::ToifImage::new_image(center, self.image)
|
||||||
|
.with_align(Alignment2D::CENTER)
|
||||||
|
//.with_blur(5)
|
||||||
|
.render(target),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
shape::ToifImage::new(center, ICON_LOCKSCREEN_FILTER.toif)
|
shape::ToifImage::new(center, ICON_LOCKSCREEN_FILTER.toif)
|
||||||
@ -394,24 +400,30 @@ impl Component for Lockscreen<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_homescreen_format(buffer: &[u8]) -> bool {
|
pub fn check_homescreen_format(image: BinaryData, accept_toif: bool) -> bool {
|
||||||
#[cfg(not(feature = "new_rendering"))]
|
match ImageInfo::parse(image) {
|
||||||
let result = is_image_jpeg(buffer) && crate::ui::display::tjpgd::jpeg_test(buffer);
|
ImageInfo::Jpeg(info) => {
|
||||||
#[cfg(feature = "new_rendering")]
|
info.width() == HOMESCREEN_IMAGE_WIDTH
|
||||||
let result = is_image_jpeg(buffer); // !@# TODO: test like if `new_rendering` is off
|
&& info.height() == HOMESCREEN_IMAGE_HEIGHT
|
||||||
|
&& info.mcu_height() <= 16
|
||||||
result
|
}
|
||||||
|
ImageInfo::Toif(info) => {
|
||||||
|
accept_toif
|
||||||
|
&& info.width() == HOMESCREEN_TOIF_SIZE
|
||||||
|
&& info.height() == HOMESCREEN_TOIF_SIZE
|
||||||
|
&& info.format() == ToifFormat::FullColorBE
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_image_jpeg(buffer: &[u8]) -> bool {
|
fn get_homescreen_image() -> BinaryData<'static> {
|
||||||
let jpeg = jpeg_info(buffer);
|
if let Ok(image) = get_user_custom_image() {
|
||||||
if let Some((size, mcu_height)) = jpeg {
|
if check_homescreen_format(image, true) {
|
||||||
if size.x == HOMESCREEN_IMAGE_WIDTH && size.y == HOMESCREEN_IMAGE_HEIGHT && mcu_height <= 16
|
return image;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
false
|
IMAGE_HOMESCREEN.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ui_debug")]
|
#[cfg(feature = "ui_debug")]
|
||||||
|
@ -2,9 +2,9 @@ use core::{cmp::Ordering, convert::TryInto};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::Error,
|
error::Error,
|
||||||
|
io::BinaryData,
|
||||||
micropython::{
|
micropython::{
|
||||||
buffer::get_buffer, gc::Gc, iter::IterBuf, list::List, map::Map, module::Module, obj::Obj,
|
gc::Gc, iter::IterBuf, list::List, map::Map, module::Module, obj::Obj, qstr::Qstr, util,
|
||||||
qstr::Qstr, util,
|
|
||||||
},
|
},
|
||||||
strutil::TString,
|
strutil::TString,
|
||||||
translations::TR,
|
translations::TR,
|
||||||
@ -14,7 +14,7 @@ use crate::{
|
|||||||
base::ComponentExt,
|
base::ComponentExt,
|
||||||
connect::Connect,
|
connect::Connect,
|
||||||
image::BlendedImage,
|
image::BlendedImage,
|
||||||
jpeg::{ImageBuffer, Jpeg},
|
jpeg::Jpeg,
|
||||||
paginated::{PageMsg, Paginate},
|
paginated::{PageMsg, Paginate},
|
||||||
text::{
|
text::{
|
||||||
op::OpTextLayout,
|
op::OpTextLayout,
|
||||||
@ -26,7 +26,6 @@ use crate::{
|
|||||||
},
|
},
|
||||||
Border, Component, Empty, FormattedText, Label, Never, Timeout,
|
Border, Component, Empty, FormattedText, Label, Never, Timeout,
|
||||||
},
|
},
|
||||||
display::tjpgd::jpeg_info,
|
|
||||||
geometry,
|
geometry,
|
||||||
layout::{
|
layout::{
|
||||||
obj::{ComponentMsgObj, LayoutObj},
|
obj::{ComponentMsgObj, LayoutObj},
|
||||||
@ -561,15 +560,15 @@ extern "C" fn new_confirm_homescreen(n_args: usize, args: *const Obj, kwargs: *m
|
|||||||
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
let title: TString = kwargs.get(Qstr::MP_QSTR_title)?.try_into()?;
|
||||||
let image: Obj = kwargs.get(Qstr::MP_QSTR_image)?;
|
let image: Obj = kwargs.get(Qstr::MP_QSTR_image)?;
|
||||||
|
|
||||||
let mut jpeg = unwrap!(ImageBuffer::from_object(image));
|
let mut jpeg: BinaryData = image.try_into()?;
|
||||||
|
|
||||||
if jpeg.is_empty() {
|
if jpeg.is_empty() {
|
||||||
// Incoming data may be empty, meaning we should
|
// Incoming data may be empty, meaning we should
|
||||||
// display default homescreen image.
|
// display default homescreen image.
|
||||||
jpeg = ImageBuffer::from_slice(theme::IMAGE_HOMESCREEN);
|
jpeg = theme::IMAGE_HOMESCREEN.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
if jpeg_info(jpeg.data()).is_none() {
|
if !check_homescreen_format(jpeg, false) {
|
||||||
return Err(value_error!("Invalid image."));
|
return Err(value_error!("Invalid image."));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1417,9 +1416,8 @@ extern "C" fn new_show_lockscreen(n_args: usize, args: *const Obj, kwargs: *mut
|
|||||||
|
|
||||||
pub extern "C" fn upy_check_homescreen_format(data: Obj) -> Obj {
|
pub extern "C" fn upy_check_homescreen_format(data: Obj) -> Obj {
|
||||||
let block = || {
|
let block = || {
|
||||||
let buffer = unsafe { get_buffer(data) }?;
|
let buffer = data.try_into()?;
|
||||||
|
Ok(check_homescreen_format(buffer, false).into())
|
||||||
Ok(check_homescreen_format(buffer).into())
|
|
||||||
};
|
};
|
||||||
|
|
||||||
unsafe { util::try_or_raise(block) }
|
unsafe { util::try_or_raise(block) }
|
||||||
|
@ -88,7 +88,7 @@ impl CornerHighlight {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Shape<'_> for CornerHighlight {
|
impl Shape<'_> for CornerHighlight {
|
||||||
fn bounds(&self, _cache: &DrawingCache) -> Rect {
|
fn bounds(&self) -> Rect {
|
||||||
Rect::snap(
|
Rect::snap(
|
||||||
self.pos_rect,
|
self.pos_rect,
|
||||||
Offset::uniform(self.length),
|
Offset::uniform(self.length),
|
||||||
@ -103,7 +103,7 @@ impl Shape<'_> for CornerHighlight {
|
|||||||
let circle_center = self.pos + Offset::uniform(self.r_outer).rotate(self.corner);
|
let circle_center = self.pos + Offset::uniform(self.r_outer).rotate(self.corner);
|
||||||
let circle_visible_part = Rect::snap(self.pos_rect, Offset::uniform(self.r_outer), align);
|
let circle_visible_part = Rect::snap(self.pos_rect, Offset::uniform(self.r_outer), align);
|
||||||
in_clip(canvas, circle_visible_part, &|can| {
|
in_clip(canvas, circle_visible_part, &|can| {
|
||||||
can.fill_circle(circle_center, self.r_outer, self.color);
|
can.fill_circle(circle_center, self.r_outer, self.color, self.alpha);
|
||||||
});
|
});
|
||||||
|
|
||||||
// rectangles (rounded) tailing from a corner
|
// rectangles (rounded) tailing from a corner
|
||||||
@ -146,7 +146,7 @@ impl Shape<'_> for CornerHighlight {
|
|||||||
self.pos + Offset::uniform(self.thickness + self.r_inner).rotate(self.corner);
|
self.pos + Offset::uniform(self.thickness + self.r_inner).rotate(self.corner);
|
||||||
in_clip(canvas, rect_outer_fill, &|can| {
|
in_clip(canvas, rect_outer_fill, &|can| {
|
||||||
can.fill_rect(rect_outer_fill, self.color, self.alpha);
|
can.fill_rect(rect_outer_fill, self.color, self.alpha);
|
||||||
can.fill_circle(circle_cover_center, self.r_inner, self.bg_color);
|
can.fill_circle(circle_cover_center, self.r_inner, self.bg_color, self.alpha);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,9 +154,9 @@ impl Shape<'_> for CornerHighlight {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> ShapeClone<'s> for CornerHighlight {
|
impl<'s> ShapeClone<'s> for CornerHighlight {
|
||||||
fn clone_at_bump<'alloc, T>(self, bump: &'alloc T) -> Option<&'alloc mut dyn Shape<'s>>
|
fn clone_at_bump<T>(self, bump: &'s T) -> Option<&'s mut dyn Shape<'s>>
|
||||||
where
|
where
|
||||||
T: LocalAllocLeakExt<'alloc>,
|
T: LocalAllocLeakExt<'s>,
|
||||||
{
|
{
|
||||||
let clone = bump.alloc_t::<CornerHighlight>()?;
|
let clone = bump.alloc_t::<CornerHighlight>()?;
|
||||||
Some(clone.uninit.init(CornerHighlight { ..self }))
|
Some(clone.uninit.init(CornerHighlight { ..self }))
|
||||||
|
@ -300,7 +300,7 @@ async def slip39_advanced_prompt_group_threshold(num_of_groups: int) -> int:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def show_warning_backup(slip39: bool) -> None:
|
async def show_warning_backup() -> None:
|
||||||
result = await interact(
|
result = await interact(
|
||||||
RustLayout(
|
RustLayout(
|
||||||
trezorui2.show_warning(
|
trezorui2.show_warning(
|
||||||
|
@ -869,8 +869,7 @@
|
|||||||
"867": "instructions__continue_in_app",
|
"867": "instructions__continue_in_app",
|
||||||
"868": "words__cancel_and_exit",
|
"868": "words__cancel_and_exit",
|
||||||
"869": "address__confirmed",
|
"869": "address__confirmed",
|
||||||
"870": "reset__title_shamir_backup",
|
"870": "pin__cancel_description",
|
||||||
"871": "pin__cancel_description",
|
"871": "pin__cancel_info",
|
||||||
"872": "pin__cancel_info",
|
"872": "pin__cancel_setup"
|
||||||
"873": "pin__cancel_setup"
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"current": {
|
"current": {
|
||||||
"merkle_root": "9c620dab3212f47d952020e2badc61a9443778fbca180208db622f3d0ebcbe5c",
|
"merkle_root": "928438526b993d433d52359d86099848d570c13fbe6aac72a2f5a29a4e8e94c5",
|
||||||
"datetime": "2024-05-17T10:46:08.576451",
|
"datetime": "2024-05-17T10:55:04.124405",
|
||||||
"commit": "cce30d1364fb62e3ed51509fefe7d48c86b45a5c"
|
"commit": "1409ed27df07827a2a3e3756420db1b41fe108e5"
|
||||||
},
|
},
|
||||||
"history": [
|
"history": [
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user